lapis
Version, currently 4.8-beta25 versions
- 4.8-dev7latestOct 16, 2026
- 4.8-dev6Sep 26, 2026
- 4.8-beta2not indexedNov 4, 2026
- 4.8-beta1not indexedOct 19, 2026
- latestSep 25, 2026
github.com/sol-vin/lapis
Lapis: High-performance Crystal bindings and 2-way host integration for Godot Engine 4.8+ using LibGodot and GDExtension.
Nothing has been indexed for 4.8-beta2 yet. The tag is recorded, its shard.yml has not been read, so the manifest and dependency list below are empty because they are unknown rather than because they are absent.
Installation
# Add this to your shard.yml
dependencies:
lapis:
github: sol-vin/lapis
version: ~> 4.8-beta2Then run:
shards installshard.yml
No shard.yml has been indexed for 4.8-beta2. You can read it on the repository.
Dependencies
Unknown: the shard.yml for this version has not been read yet.
README
This README is the one indexed from the repository at its latest ref, not from the tag for this version.
Lapis for Crystal
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.
βΆοΈ Watch the Video Tour: https://youtu.be/EKMw_zQjovc
π 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/
Why Lapis?
Table of Contents
- 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
Architecture: Dual-Paradigm Integration
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
Windows Shadow Loading
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.
Production Gameplay DSL & Ergonomics
Showcase Quickstart Node
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
end
Typed Scene Pipeline & Queries
Lapis 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).size
Type-Safe Tween Orchestration
The 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)
Pattern Matching (match)
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
Gameplay Patterns: FSM, Signal Bus & Pooling
# 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
end
In-Editor Action Driver ("Selenium for Godot Editor")
Lapis 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
CLI Commands:
# 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 repl
Native In-Editor radare2 Debugger & Decompiler
Powered 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-side
Interactive Opal TUI Hub (lapis cli)
Lapis 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.0.264) βββββββββββββββββββββββββ [Godot 4.8-dev7] βββ
β β
β [+] 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.
Memory Safety & Dead-Pointer Defense
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 ]
Safety Invariants:
- 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.
Performance & Benchmarks
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
Running Benchmarks Locally:
# 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=signals
Unified Multi-Tier Test Suite
Lapis 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 spec
Lapis CLI Toolchain Reference
The lapis CLI (bin/lapis / bin/lapis.exe) provides a unified developer toolchain:
Comprehensive In-Code Documentation (Lapis::Docs)
Lapis includes an extensive 10-track technical documentation system compiled directly into the code via Jasper (sol-vin/jasper):
Build and view documentation locally:
make docs
# Open docs/index.html in your browser
Prerequisites & Installation
System Dependencies
- Crystal Compiler: 1.20+
- radare2: 5.8+ (native debugger, disassembler, and decompiler)
- Godot Engine: 4.8-dev7+ (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
Quick Install by Platform
Official Installers
- 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).
Adding to an Existing Project (shard.yml):
dependencies:
lapis:
github: sol-vin/lapis
branch: 4.8-dev7
Run shards install to install dependencies.
Repository Structure
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
License
Distributed under the MIT License. Copyright (c) 2026 Ian and contributors.
Documentation
Built from the current release. The first visit to a release nobody has asked for starts its build.
Links
This release
- Version
4.8-beta2- Tagged
- Nov 4, 2026
- Commit
56c198f42da4- Indexed
- not yet
Dependents
Repository
github.com/sol-vin/lapis
Metadata
- Created
- Sep 21, 2026
- Updated
- Nov 6, 2026
- Synced
- Nov 6, 2026
- Versions
- 5