Lapis for Crystal provides high-performance Crystal bindings and a bidirectional runtime integration for Godot Engine 4.8+ using LibGodot and GDExtension. It empowers developers to build Godot games with native LLVM machine speed, compile-time static type safety, and Ruby-like syntax elegance.
📖 Official Documentation: https://sol-vin.github.io/lapis/
📊 Interactive Benchmarks: https://sol-vin.github.io/lapis/benchmarks.html
📑 Interactive Slide Deck: https://sol-vin.github.io/lapis_slides/
| Dimension | Lapis (Crystal) | GDScript | C# (.NET) | C++ (GDExtension) |
|---|---|---|---|---|
| Execution Speed | Native LLVM machine code (-O3 auto-vectorization) |
Interpreted bytecode / VM stack overhead | JIT compilation, GC pause overhead | Native C++ machine code |
| Type Safety | Exhaustive compile-time typing & nil-safety | Optional gradual typing (runtime checks) | Static typing with nullable annotations | Static typing, manual memory hazards |
| Memory Model | Boehm GC + 64-bit ObjectDB dead-pointer guards | Engine RefCounted / manual free | CLR Garbage Collector + interop wrappers | Manual allocation, dangling pointers / UB |
| Syntax Elegance | Ruby-like expressive DSL, blocks, macros, union types | Python-like concise scripting | Verbose C-style OOP boilerplate | Verbose header/source separation & macros |
| Hot Reloading | Shadow DLL loading (bypasses Windows file locks on F5) | Native script reload | Domain reload (requires editor restart on edge cases) | File-locked on Windows without custom loader |
| Native Debugger | In-editor radare2: pseudo-C decompilation (pdc) & gutter sync |
Engine GDScript debugger | External IDE debugger (VS Code / Rider) | External native debugger (GDB / MSVC / LLDB) |
| Editor Automation | Action Driver: DOM inspection, REPL, and Set-of-Marks AI Vision | EditorScript (limited headless execution) | None | Custom C++ editor modules |
- Architecture: Dual-Paradigm Integration
- Production Gameplay DSL & Ergonomics
- In-Editor Action Driver ("Selenium for Godot Editor")
- Native In-Editor radare2 Debugger & Decompiler
- Interactive Opal TUI Hub (
lapis cli) - Memory Safety & Dead-Pointer Defense
- Performance & Benchmarks
- Unified Multi-Tier Test Suite
- Lapis CLI Toolchain Reference
- Comprehensive In-Code Documentation (
Lapis::Docs) - Prerequisites & Installation
- Repository Structure
- License
Lapis provides two execution paradigms designed for both rapid in-editor iteration and lean standalone production shipping:
graph TD
subgraph Mode A: GDExtension In-Editor Workflow
GE[Godot Editor 4.8] -->|Loads| GDX[addons/crystal_integration/crystal.gdextension]
GDX -->|Loads| CB[bin/crystal_bridge.dll]
CB -->|1. Initializes Boehm GC| CRT[Crystal Runtime]
CB -->|2. Shadow loads| GDL[bin/game_loaded_pid_ts.dll]
GDL -->|3. crystal_godot_init| REG[ClassDB & EditorHelp]
REG -->|4. Exposes Nodes & Inspector| GE
GE -->|F5 / Build Hook| EB[EditorPlugin._build]
EB -->|Recompiles & Hot-Reloads| GDL
end
subgraph Mode B: Standalone LibGodot Host Paradigm
EXE[bin/game.exe] -->|1. Owns main Entry Point| CRTM[Crystal Boehm GC]
EXE -->|2. In-Memory Boot| LGD[bin/libgodot.dll]
LGD -->|3. libgodot_create_godot_instance| GINST[Godot Engine Instance]
GINST -->|4. Passes GDExtension C-API Table| EXE
EXE -->|5. Steps Main Loop & Dispatches| WIN[Game Window & Audio]
end
| Paradigm | Mode A: GDExtension In-Editor | Mode B: Standalone LibGodot Host |
|---|---|---|
| Host Binary | Godot Engine (godot.exe) |
Crystal Executable (bin/game.exe) |
| Primary Binaries | bin/crystal_bridge.dll + bin/game.dll |
bin/libgodot.dll (or .so) |
| GC Bootstrapping | Initialized by C++ loader bridge (GC_init()) |
Initialized natively by Crystal CRT |
| Hot Reloading | Timestamped shadow DLL loading on F5 / F6 | Recompilation of standalone executable required |
| Primary Use Case | Rapid development, tool scripts, level design | Production shipping, headless CI, embedded runners |
On Windows, dynamic libraries loaded via LoadLibraryA are locked by the OS kernel, preventing compilers from overwriting game.dll. In development mode, crystal_bridge.cpp copies game.dll to a timestamped shadow file (game_loaded_<PID>_<TIMESTAMP>.dll) and loads the shadow copy. bin/game.dll remains completely unlocked so Crystal can recompile freely while the Godot Editor stays open.
Here is an idiomatic Lapis character node demonstrating property exports, @onready path unrolling, signals, tweening, pattern matching, direct space physics queries, and dead-pointer safety:
require "lapis"
# Player character with physics movement, combat raycasting, and animations
node Player < CharacterBody3D do
# Scene unique node references resolved and cached during _ready
onready visual_mesh : MeshInstance3D = ~"%VisualMesh"
onready collider : CollisionShape3D = ~"CollisionShape3D"
# Inspector export system with ranges, steps, and tooltips
@[Export(range: 1.0_f32..25.0_f32, step: 0.5_f32)]
property move_speed : Float32 = 8.0_f32
@[Export(range: 1.0_f32..30.0_f32, step: 0.5_f32)]
property jump_velocity : Float32 = 9.5_f32
@[Export(range: 10..500, step: 10)]
property max_health : Int32 = 100
# First-class type-safe signals
signal health_changed(current : Int32, max_health : Int32)
signal died
@current_health : Int32 = 100
def _ready : Void
@current_health = @max_health
Godot.print("Player initialized: #{name}")
end
def _physics_process(delta : Float64) : Void
vel = velocity
# Apply gravity when airborne
unless is_on_floor
vel.y -= 24.0_f32 * delta.to_f32
end
# Handle jump impulse
if Input.is_action_just_pressed("jump") && is_on_floor
vel.y = @jump_velocity
end
# Vectorized movement direction
input_dir = Input.get_vector("move_left", "move_right", "move_forward", "move_back")
dir = (transform.basis * Vector3.new(input_dir.x, 0.0_f32, input_dir.y)).normalized
if dir.length > 0.001_f32
vel.x = dir.x * @move_speed
vel.z = dir.z * @move_speed
else
vel.x = Math.move_toward(vel.x, 0.0_f32, @move_speed * delta.to_f32)
vel.z = Math.move_toward(vel.z, 0.0_f32, @move_speed * delta.to_f32)
end
self.velocity = vel
move_and_slide
# Fire weapon raycast on attack action
if Input.is_action_just_pressed("fire")
fire_weapon
end
end
# Performs a direct space physics raycast
private def fire_weapon : Void
start_pos = global_position + Vector3.new(0.0_f32, 1.2_f32, 0.0_f32)
target_pos = start_pos + (-transform.basis.z * 50.0_f32)
# One-liner direct space query returning typed PhysicsHit3D
if hit = raycast_to(target_pos, from: start_pos)
# Expression pattern matching with dead-pointer safe downcasting
match hit.collider do
is Enemy do |enemy|
enemy.take_damage(25)
flash_hit_marker
end
default do
Godot.print("Hit scenery at #{hit.position}")
end
end
end
end
# Statement-based tween orchestration with auto call-peeling and typed identifiers
private def flash_hit_marker : Void
tween(self) do
animate(visual_mesh.scale, to: Vector3.new(1.15_f32, 1.15_f32, 1.15_f32), in: 0.08.seconds)
trans(Trans.Back)
ease(Ease.Out)
chain()
animate(visual_mesh.scale, to: Vector3.new(1.0_f32, 1.0_f32, 1.0_f32), in: 0.12.seconds)
trans(Trans.Cubic)
ease(Ease.In)
end
end
# Inflicts damage, emits signals, and handles death
def take_damage(amount : Int32) : Void
return if @current_health <= 0
@current_health = Math.max(0, @current_health - amount)
emit_health_changed(@current_health, @max_health)
if @current_health <= 0
emit_died
queue_free
end
end
endLapis introduces expressive pipeline and query operators that eliminate boilerplate typecasts:
# 1. Preload Pipeline (>): Preload PackedScene, instantiate, and cast to concrete node
enemy = "res://scenes/enemy.tscn" > Enemy do |e|
e.position = Vector3.new(10.0, 0.0, 5.0)
end
# 2. Dynamic Load Pipeline (>>): Load dynamically at runtime without caching
boss = "res://scenes/boss.tscn" >> Boss
# 3. Symmetrical Upward Ancestor Resolution (<<): Find nearest parent in hierarchy
player = hitbox << Player # Strict: returns Player or raises NodeNotFoundError
boss = hitbox << Boss? # Nilable: returns Boss? or nil if not found
# 4. Wildcard Scene Glob Queries (*):
all_enemies = self * "Enemies/*" # Array(Node)
all_lights = self * "**/Light*" # Recursive globstar
all_areas = self * {"HitBoxes/*", Area3D} # Typed: Array(Area3D)
regex_boxes = self * /^HitBox_\d+$/ # Regex: Array(Node)
hitbox = self["Enemies/*/Hitbox", Area2D]? # Nilable: returns nil if empty!
# 5. Zero-Allocation Streaming Iteration:
each_node("Enemies/*", Enemy) do |enemy|
enemy.take_damage(50)
end
# 6. Fluent Group Queries (group):
group(:enemies).each(as: Enemy) do |enemy|
enemy.alert!(player.global_position)
end
group(:enemies).call("wake_up")
alive_count = group(:enemies).sizeThe tween macro peels apart block statements into a fluent pipeline, strictly verifying typed property identifiers at compile time:
# Statement-based tween pipeline with auto call-peeling, easing curves, and typed identifiers
tw = tween(hero) do
animate(position, to: Vector2.new(120, 80), in: 4.seconds)
chain()
animate(modulate, from: Color::RED, to: Color::BLUE, in: 0.3.seconds)
parallel()
ease(Ease.Out)
trans(Trans.Cubic)
animate(scale, to: Vector2.new(1.2, 1.2), in: 0.3.seconds)
chain()
animate(modulate.a, to: 0.0, in: 0.25.seconds)
end
# Non-blocking cooperative await on the tween's completion
await(tw.finished)Game development frequently deals with polymorphic nodes, dynamic Variant payloads, and input dictionaries. The match macro handles these with zero-allocation inlined execution:
match event do
# Variant unboxing & dead-pointer safe downcasting
is Player do |player|
Godot.print("Player HP: #{player.max_health}")
end
# Guard clauses with if: condition
is Enemy, if: event.level > 50 do |boss|
trigger_boss_music(boss)
end
# Symbol / String equivalence
is :action_jump, :action_vault do
player.execute_aerial_move
end
# Tuple destructuring
is {state, on_floor?} do
is :jumping, false do apply_air_control end
is :jumping, true do trigger_landing_dust end
end
default do
handle_unhandled_event
end
end# 1. Zero-Allocation Typed Union FSM
struct Idle; end
struct Walking; getter speed : Float32 = 6.0_f32; end
struct Attacking; getter combo : Int32 = 1; end
alias State = Idle | Walking | Attacking
node CombatUnit < CharacterBody3D do
fsm State, initial: Idle.new do
on_enter Walking do |w|
Godot.print("Walking at speed #{w.speed}")
end
on_exit Attacking do
reset_combo_timer
end
end
end
# 2. Type-Safe Decoupled Signal Bus
signal_bus GameEvents do
signal score_changed(new_score : Int32)
signal player_died
end
# Emitter:
GameEvents.instance.score_changed.emit(100)
# Receiver:
GameEvents.instance.score_changed.connect do |score|
ui_label.text = "Score: #{score}"
end
# 3. High-Performance Object Pool
class Arena < Node2D
node_pool bullets : Bullet, capacity: 64
def fire_volley(at_pos : Vector2) : Void
bullet = bullets.acquire do |b|
b.position = at_pos
b.velocity = Vector2.new(500.0, 0.0)
end
end
endLapis includes the Action Driver, a programmatic UI inspection, automation, and testing framework for the Godot Editor. It provides DOM traversal, synthetic inputs, state preservation across reloads, and Set-of-Marks AI Vision generation:
[ Lapis Action Driver ] ──TCP:9095──> [ Godot Editor 4.8 ]
│ │
├─ click "Build" ├─ Finds Control via DOM predicate
├─ type "%SearchEdit" "Player" ├─ Synthesizes InputEventKey / Mouse
├─ open-scene "res://test.tscn" ├─ EditorInterface scene dispatch
└─ vision --output reports/ai └─ Generates Set-of-Marks AI Manifest
# Click a button in the Godot Editor UI
lapis driver click "Build"
# Type text into a LineEdit or search filter
lapis driver type "%FilterEdit" "Player"
# Switch main editor screens (2D, 3D, Script, Crystal)
lapis driver screen 3D
# Generate Set-of-Marks AI Vision manifest & cropped widget images
lapis driver vision --output reports/ai_vision
# Dump the live Godot Editor DOM tree hierarchy
lapis driver dom 8
# Launch interactive Action Driver REPL
lapis driver replPowered by sol-vin/cradare2, Lapis embeds a native multi-threaded debugger directly inside the Godot Editor:
- Gutter Breakpoint Sync: Set breakpoints directly in Godot's Script Editor gutter (
F9). Breakpoints are synchronized with radare2 via source line matching (dbl) instantly. - Dedicated In-Editor Tab: Docked Crystal Debugger tab featuring Continue (
F5), Step Over (F10), Step Into (F11), Step Out (Shift+F11), call stack frame navigation, and CPU register inspection (RAX,RBX,RIP,RSP). - Live Pseudo-C Decompiler (
pdc): Built-in side-by-side view with native pseudo-C (pdc) decompilation and annotated assembly (pdf/pdca) with zero external decompiler dependencies. - Multiplayer Lockstep Break: When any peer hits a breakpoint, all other active instances are automatically paused via cooperative interrupt (
DebugBreakProcess/SIGINT) to eliminate network heartbeat timeouts (ENet / WebRTC). - Dead-Pointer Forensics: Automatically inspects faulting memory addresses, classifies loaded modules, and checks 64-bit ObjectDB IDs to diagnose dead pointers.
# Launch test project under native radare2 debugger
lapis run -d
# Launch Godot Editor under native radare2 debugger
lapis editor -d
# Decompile symbol directly from terminal
lapis decompile bin/game.dll "*Player*physics_process*" --side-by-sideLapis includes an interactive full-screen terminal command center powered by Opal (sol-vin/opal). Launch it with lapis cli (or lapis -i):
┌── Lapis Engine Command Hub (v0.1.1) ─────────────────────────── [Godot 4.8-beta1] ──┐
│ │
│ [+] New Project / Addon Wizard Interactive folder & template scaffold │
│ [#] Launch Godot Editor Persistent launcher with log watching │
│ [A] Action Driver Controller DOM automation REPL & AI vision capturer │
│ [B] Packaging & Export Center Multi-target builds & release packaging │
│ [D] Radare2 Native Debugger Registers, disassembly & hex memory view │
│ [L] Diagnostic Log Viewer Real-time log tailing & channel filters │
│ [#] Benchmark Visualizer Cartesian charts, percentiles & memory delta │
│ [~] Run Game (Performance Monitor) Live FPS/memory charts with Ctrl+K stop │
│ [?] Test Suites Dashboard Multi-phase test runner with split-screen │
│ [+] Toolchain Doctor Environment & dependency health check │
│ [~] Synchronize Multi-Targets Sync bridge DLLs and manifests across targets│
│ [x] Clean Build Artifacts Prune binaries & unlock shadow DLLs │
│ [=] Generate Documentation Build static offline HTML documentation site │
│ │
├────────────────────────────────────────────────────────────────────────────────────┤
│ [↑/↓] Navigate │ [Enter] Launch │ [Ctrl+P] Command Palette │ [Q] Exit │
└────────────────────────────────────────────────────────────────────────────────────┘
Press Ctrl+P from any screen to open the global floating Command Palette for instant fuzzy search across all 35+ CLI tools and actions.
Embedding a garbage-collected language (Crystal Boehm GC) inside a native C++ engine (Godot ObjectDB) introduces a critical hazard: when Godot frees an object (e.g. via queue_free()), standard C-API bindings retain a raw pointer to dead memory. Dereferencing it triggers an unrecoverable segmentation fault (ACCESS_VIOLATION / SIGSEGV).
[ Crystal Runtime ] [ Godot Engine / GDScript ]
enemy = get_node("Enemy")
enemy.@pointer = 0x7FFE_1234 ────────────> Node instance at 0x7FFE_1234
│
│ GDScript: enemy.queue_free()
▼
ObjectDB destroys Node & frees memory!
0x7FFE_1234 is now DEAD / UNMAPPED!
enemy.position = Vector3.new(...)
│
▼
[ Lapis check_alive! ]
│
├───── Query ObjectDB for monotonic 64-bit instance ID: ID is INVALID!
├───── Marks wrapper dead (@pointer = null)
└───── Raises Godot::DisposedObjectError (Clean, catchable Crystal exception!)
[ ZERO NATIVE CRASHES ]
- Monotonic 64-bit Instance ID Tracking: Every
Godot::Objectwrapper tracks its engine-assigned ID. Godot's ObjectDB monotonic IDs never collide with recycled heap addresses. - Pre-Dispatch Liveness Check (
#check_alive!): Before dispatches or reflection calls, Lapis validates liveness in O(1) time (Bridge.is_instance_valid(instance_id)). - Graceful
DisposedObjectError: If an object is freed, Lapis marks the pointer null and raisesGodot::DisposedObjectErrorinstead of crashing. - Defensive Inspection: Check
node.alive?ornode.destroyed?before interacting with transient entities. - Main-Thread SceneTree Affinity: Scene graph mutations (
add_child,remove_child,queue_free) enforce main-thread execution (assert_main_thread!) with automatic thread-safe deferral (call_deferred/Godot.on_main_thread). - Dead-Pointer Armor (
try?&if_alive): Safely invoke methods on nilable or transient objects with@heal_sfx.try?(&.play)or@target.try?(&.take_damage(spell.power)).try?automatically queries Godot's ObjectDB monotonic ID table and returnsnilsafely if the object was freed ornil, eliminating C#'s.NET ?.ObjectDisposedExceptiontrap.
Lapis features an automated 1-to-1 cross-language benchmark suite comparing native compiled Crystal (GDExtension) against GDScript (Godot 4 Bytecode):
📊 Explore the Live Dashboard: https://sol-vin.github.io/lapis/benchmarks.html
| Benchmark Category | Included Benchmarks | Speedup vs GDScript | Key Architectural Advantage |
|---|---|---|---|
| Algorithmic & Compute | Matmul, Primes, Brainfuck, Base64, JSON, NBody, BinaryTrees, Mandelbrot, TransformMath |
3.1x – 227.6x faster | LLVM -O3 auto-vectorization, native unboxed arithmetic, zero Variant boxing. |
| Godot Engine Core | NodeLifecycle, MaterialResources, Signals |
1.1x – 307.7x faster | Direct GDExtension C-API dispatch, low GC pause times, fast resource allocation. |
# Run all benchmarks and generate HTML/SVG reports
lapis benchmarks [ITERATIONS=3]
# Run specific categories or filters
crystal run benchmarks/runner.cr -- -c compute
crystal run benchmarks/runner.cr -- -c engine
crystal run benchmarks/runner.cr -- --filter=signalsLapis enforces quality through an automated test harness covering language bindings, in-editor tool execution, and standalone runtimes:
- Engine Core Specs (
spec/): Headless unit specs covering GC object retention, dynamic scaling, Variant type round-trips, and Vector math (spec/core_spec.cr,spec/math_spec.cr,spec/safety_spec.cr). - Lapis CLI Specs (
tools/lapis/spec/): CLI argument parsing, standalone execution, and packaging tests (tools/lapis/spec/cli_spec.cr). - 85+ Modular Runtime Suites (
spec/suites/): Comprehensive engine verification covering 2D/3D nodes, navigation, physics, multiplayer, shaders, audio servers, FSM, and memory lifecycle. - Quantitative Zero-Leak Verification:
Lapis::Test.assert_no_leakleverages Godot'sPerformancemonitors (OBJECT_COUNT,OBJECT_NODE_COUNT,MEMORY_STATIC) and Crystal'sGC.collectto mathematically verify zero object or memory leaks.
# Run the complete test suite (interactive TUI or streaming)
make test
lapis test
# Run headless specs only
make specThe lapis CLI (bin/lapis / bin/lapis.exe) provides a unified developer toolchain:
| Command | Category | Description |
|---|---|---|
lapis cli (or lapis -i) |
Interactive Hub | Launches the 14-workspace Opal TUI dashboard with floating Command Palette (Ctrl+P). |
lapis driver [cmd] |
Editor Automation | In-Editor Action Driver controller, interactive REPL, and Set-of-Marks AI Vision generation. |
lapis doctor |
Diagnostics | Diagnoses environment health (Crystal, Godot, C++ compiler, radare2, packaging dependencies). |
lapis build [target] |
Build & Sync | Compiles Crystal targets (game, plugin, tests, bench) with automatic flag resolution. |
lapis sync |
Build & Sync | Synchronizes compiled binaries, runtime DLLs (GC, iconv, PCRE2, LibGodot), and manifests across targets. |
lapis editor |
Editor Launcher | Launches Godot Editor with log monitoring, build hooks, and radare2 debugger support. |
lapis run |
Runner | Runs Godot project standalone with live FPS and memory metrics. |
lapis test [options] |
Testi A80C ng | Unified multi-tier test runner with interactive TUI or non-TTY streaming logs. |
lapis new <game|addon|example> [name] |
Scaffolding | Scaffolds a new game, redistributable GDExtension addon, or showcase example from templates. |
lapis init [path] |
Project Management | Initializes Crystal/Lapis integration into an existing Godot project. |
lapis bind <engine|project> |
Code Generation | Generates typed Crystal API bindings for Godot classes or custom GDScript project nodes. |
lapis package [target] |
Packaging | Packages playable standalone game executables or distribution zip archives. |
lapis decompile <bin> <sym> |
Debugging | Decompiles functions to pseudo-C (pdc) or disassembles binary using native radare2. |
lapis analyze <bin> |
Debugging | Performs deep static and dynamic radare2 binary and security analysis. |
lapis log [options] |
Logging | Views, tails, searches, exports, and filters diagnostic logs. |
lapis shard <cmd> |
Dependencies | Manages Crystal shard dependencies in the Godot project (list, install, uninstall, prune). |
lapis addon <cmd> |
Dependencies | Installs, uninstalls, or manages Godot GDExtension addons. |
lapis ide [editor] |
Tooling | Configures VS Code, Cursor, Zed, or Neovim with Crystalline LSP and radare2 debugging. |
lapis template <cmd> |
Templates | Manages global project templates (save, list, remove, clean, export, import). |
lapis clean [options] |
Maintenance | Prunes compiled binaries, intermediate objects, and shadow DLLs. |
lapis docs |
Documentation | Generates offline HTML API documentation via crystal docs. |
lapis setup |
Setup | Downloads and configures targeted Godot engine binary and dumps GDExtension API. |
lapis install |
System | Installs Lapis CLI globally into system/user PATH. |
Lapis includes an extensive 10-track technical documentation system compiled directly into the code via Jasper (sol-vin/jasper):
| Learning Track | Submodule | Topics Covered |
|---|---|---|
| 1. Getting Started | Docs::A_GETTING_STARTED |
Installation, source compilation, First Game Tutorial (Zero to Hero), Crystal syntax for Godot. |
| 2. CLI & Toolchain | Docs::B_LAPIS_CLI_AND_TOOLCHAIN |
CLI architecture, doctor diagnostics, scaffolding, build & sync, testing, clean, addons, shards, IDE. |
| 3. Gameplay DSL | Docs::C_GAMEPLAY_AND_DECLARATIVE_DSL |
Node DSL, exports, signals, input, scene tree, mixins (gmodule), multiplayer, GDScript migration. |
| 4. Concurrency & Fibers | Docs::D_CONCURRENCY_AND_FIBERS |
Fibers, cooperative signal awaiting, OS background threads, actor channels, main thread dispatch. |
| 5. Memory Safety | Docs::E_MEMORY_SAFETY_AND_ENGINE_INTERNALS |
Godot ObjectDB monotonic IDs, dead-pointer defense, Boehm GC vs Godot reference counting. |
| 6. Scripting & Interop | Docs::F_SCRIPTING_AND_INTEROPERABILITY |
First-class .cr script editing in Godot, GDScript bindings, compile-time doc comment harvesting. |
| 7. Debugging & Diagnostics | Docs::G_DEBUGGING_AND_DIAGNOSTICS |
Native radare2 debugging, gutter breakpoints, pseudo-C decompilation, crash forensics, watchpoints. |
| 8. Testing & Benchmarks | Docs::H_TESTING_AND_BENCHMARKS |
Testing framework, zero memory leak validation, performance profiling, benchmark architecture. |
| 9. Architecture & Extensions | Docs::I_ARCHITECTURE_AND_EXTENSIONS |
Dual-paradigm execution model, C++ loader bridge, multi-addon isolation, engine upgrade guide. |
| 10. Modern CLI & TUI | Docs::J_MODERN_CLI_AND_TUI |
Interactive terminal command center, Opal TUI hub, differential cell rendering, domain workspaces. |
Build and view documentation locally:
make docs
# Open docs/index.html in your browser- Crystal Compiler: 1.20+
- radare2: 5.8+ (native debugger, disassembler, and decompiler)
- Godot Engine: 4.8-beta1+ (Standard build, 64-bit)
- C++ Compiler: GCC (
g++) or Clang (for compiling the GDExtension loader bridge) - GNU Make: Workspace build & synchronization automation
- Git: Shards dependency resolution & version control
| Platform | One-Liner Command to Install Dependencies |
|---|---|
| Windows (Scoop) | scoop install crystal radare2 make git |
| Windows (Winget) | winget install CrystalLang.Crystal radare2.radare2 ezwinports.make Git.Git |
| Ubuntu / Debian | sudo apt update && sudo apt install -y crystal radare2 make git g++ |
| Arch Linux | sudo pacman -S crystal radare2 make git gcc |
| macOS (Homebrew) | brew install crystal radare2 make git |
- Windows Installer (
lapis-setup-windows-x86_64.exe): Installs Lapis CLI, bundlescrystalline.exe(LSP) andradare2.exe(native debugger), configures userPATH, and registers PowerShell completions. - Debian Package (
lapis_amd64.deb): Installs Lapis globally with automatic package dependency resolution (Depends: crystal, radare2).
dependencies:
lapis:
github: sol-vin/lapis
branch: 4.8-beta1Run shards install to install dependencies.
lapis/
├── src/ # Reusable Lapis library & root host application
│ ├── libgodot.cr # Library root entry point (require "libgodot")
│ ├── lapis.cr # Lapis prelude & engine extensions
│ ├── main.cr # Root host game entry point & test runner panel
│ ├── bridge/crystal_bridge.cpp # C++ GDExtension loader bridge
│ └── libgodot/ # Core engine C-API, macros, and generated bindings
├── tools/ # Built-in CLI toolchain
│ └── lapis/ # Compiled native CLI (bin/lapis)
├── spec/ # Unified Crystal specifications & test suites
│ ├── spec_helper.cr # Common spec helper and test nodes
│ ├── editor_driver_spec.cr # In-editor @tool and runtime tests via EditorDriver
│ ├── suites/ # 85+ modular engine test suites
│ └── fixtures/ # Spec test targets & fixtures
├── scenes/ # Root Godot host project scenes (main_test_runner.tscn, etc.)
├── scripts/ # GDScript test fixtures and interop nodes
├── project.godot # Root Godot host project configuration
├── addons/ # GDExtensions & Editor Plugins
│ ├── crystal_integration/ # Official GDExtension manifest & editor build hook
│ └── dummy_*/ # Isolated test addons for multi-addon stress tests
├── examples/ # Independent consumer showcase examples
├── template/ # Clean starter template for new games
├── template-addon/ # Starter template for redistributable addons
├── bin/ # Output binaries, bridge DLL, and dependencies
└── AGENTS.md # Agent development guidelines
Distributed under the MIT License. Copyright (c) 2026 Ian and contributors.