Example project
Cavern Lab (Native)
examples/cavern_lab/README.md
Explore a cave generated by Odin, collect markers, and reach the exit. Cavern
Lab shows how a native component can supply a substantial part of a Godot game:
Odin generates and refines the cave, while Godot owns the player, tile collision,
scene, UI and presentation. A native CavernGenerator Node connects the game to
an engine-independent Odin algorithm.
Try it
- Walk with WASD or the arrow keys. Collect the markers to open the exit.
- Press R to generate from the saved settings, C to cancel, or Esc to quit.
- Turn Shape on, paint walls with the left mouse button and floor with the right, then choose Refine mask to use your edits.
- Apply settings reads the assigned settings Resource. Save inputs saves your mask and candidate positions for the next session; Undo or Ctrl+Z undoes a mask edit, candidate move or grid install.
- Load baked grid plays the committed asset. Bake displayed grid creates a session asset while the cave remains playable. Open Lab opens the algorithm comparison and diagnostics.
Start reading with the native class and settings, the pure generation algorithm, and the Godot expedition. For the separate editor workflow, follow the bake designer flow.
Build and run
Use a configured copy of this example with its attached SDK. The separate project setup guide covers tools and SDK connection. From any directory, use your SDK and project paths:
/absolute/sdk/bin/gododin build --project-root /absolute/gododin/examples/cavern_lab --profile debug
/absolute/sdk/bin/gododin run --project-root /absolute/gododin/examples/cavern_labProject layout
declarations/is the single authoring source: the native class, its lifecycle hooks, and its coarse operation with real bodies.subsystem/owns the engine-independent algorithm and tests.live/project/owns the Godot UI and exact GDScript reference algorithm.generated/is compiler-owned output; never edit it by hand.gododin.tomlcommitsauthority_kind = "installed"; the SDK location is machine-local state in ignored.gododin/authority.toml.
The GDScript reference uses the same flat byte layout, integer operations, xorshift32 stream, neighbor rules, culling order, and connection order. The verification process requires byte-for-byte equality, not merely a similar image. Generation and Godot presentation time are recorded separately.
Native generation and the Godot boundary
The subsystem in subsystem/ imports only Odin's core memory and core math
packages. It uses a
deterministic flat-byte cellular algorithm: seeded fill, smoothing, four-way
flood labelling, small-region culling, scan-order region connection, and final
border/connectivity validation. One workspace is allocated when the Node is
constructed and reused by every generation call.
generate(...) returns one callback-scoped borrowed []u8 through the
Host/image ABI. The stable Host validates the span, constructs one Godot
PackedByteArray, resizes it once, copies the cells once, and destroys its
temporary owner after either ptrcall or Variant return conversion. There are no
per-cell foreign calls and no String, Variant, or caller-side staging buffer
between the pure subsystem and the packed array.
process_mask(mask, width, height, smoothing_steps, minimum_region_size) accepts
a Godot-owned PackedByteArray in row-major order, with 0 for floor and 255
for wall. It closes the border, opens the center, then applies the same smoothing,
culling, connection, and validation pipeline as generate. Dimensions must be
8–1024, the length must equal width * height, smoothing steps must be 0–12, and
the minimum region size must be 1–width * height. An empty/wrong-sized mask
returns an empty array and error 8; an invalid cell returns error 9. Settings keep
their existing error codes. Validation happens before workspace mutation.
The Host borrows the input's engine storage through const reads during the
callback. The kernel copies into its existing workspace because the algorithm
mutates its working grid; it neither changes nor retains the caller's input.
The output is a new engine-owned array and survives subsequent calls and Node
destruction. GDScript assignment aliases packed arrays; use duplicate() for an
independently editable copy. ADR 0059
owns the native borrow and copy-on-write contract.
Refine shown grid processes the displayed grid using its own saved dimensions and the current smoothing/minimum-region settings, and compares it with the GDScript oracle. Rejected settings keep the displayed grid. The sidebar scrolls so all diagnostics remain accessible in a smaller window.
classify_positions(positions, radii, origin, cell_size) classifies candidate
placements against the processed grid and returns one borrowed []u8 flag span
through ABI 4.2's packed-byte result — 255 marks placeable, 0 marks blocked —
valid until the next classification. It is the spatial batch operation the
MultiMesh workload builds on. Its inputs are ADR 0060's typed forms: a
PackedVector3Array of candidate centers, a PackedFloat32Array of radii, one
scalar Vector3 origin, and an f64 cell size. Every packed input is a
callback-scoped read-only borrow of the caller's own engine storage — one const
size read plus one const element-0 pointer, no copy, no allocation, no
copy-on-write detach — and the scalar Vector3 borrows the caller's argument
frame on the ptrcall path. World-to-grid mapping floors in f64 exactly like
the GDScript oracle, so both compute identical flags. Up to 16384 candidates;
a size mismatch returns error 10, capacity overflow 11, an invalid cell size
12, and classification before generation 13. y is ignored because the grid is
the cave floor plane.
The focused checkout evidence for this operation is recorded in
classify-evidence.json (repository: examples/cavern_lab/classify-evidence.json) with the boundary observer
archive classify-2026-09-06.tar.xz (repository: examples/cavern_lab/classify-2026-09-06.tar.xz):
python3 examples/cavern_lab/verify_classify.py \
--cli /absolute/checkout/build/gododin \
--godot /absolute/locked/Godot \
--evidence-dir /absolute/checkout/build/new-classify-evidenceIt runs the seeded oracle and the classification workload on exact 4.8-dev4 through both invocation forms — 70 ptrcalls and 5 Variant calls in the frozen receipt — with byte-identical oracle flags, unchanged inputs, y-invariance, all four rejections, repeated use on one warm workspace, results surviving owner destruction, and zero owners at shutdown. A test-only observer around the accepted Host verifies the actual engine calls: one size read plus one const element-0 read per packed input aliasing the original backing store, no mutable acquisition, no detach of caller storage, zero conversions on the ptrcall path, and exactly three type-check/convert rounds per Variant call (the engine registers no Vector3 destructor, so the Host frees its scalar temporary directly). This harness does not publish an SDK or establish a speedup claim.
Background generation
background.gd — the Phase 13 workload cavern-background-generation — runs
the same seeded kernel on one background worker per CavernGenerator. The pure
owner (subsystem/background.odin) freezes the workload's budgets: one active
computation plus one replaceable pending request, a bounded handoff of exactly
one owned record where a newer completion displaces an unconsumed older one,
worker-local storage under a tracking allocator, cooperative cancellation
observed at the kernel's loop boundaries, and a destroy that cancels, joins,
drains, and reports whether every tracked byte was freed. The worker creates
its pthread through core:sys/posix with default attributes because the engine
process rejects core:thread's scheduler-attribute setup.
The installed harness drives it from a windowed frame loop: submit, poll from
the marked methods (the only allowed publication phase), verify byte equality
against the synchronous kernel, then sustain 120 submit-cancel rounds and a
coalescing burst, and quit with an outstanding request so the exit teardown
drains a live worker. Every campaign wait is bounded by wall clock, never by
frame counts — windowed frame pacing depends on display state, and an
occluded or locked display lets the same process pump thousands of frames per
second. The frozen receipt is
background-evidence.json (repository: examples/cavern_lab/background-evidence.json) with the log archive
background-2026-09-08.tar.xz (repository: examples/cavern_lab/background-2026-09-08.tar.xz): 40 byte-identical
generations, cancellation acknowledged at a 6 us median (14 us max), main-thread
submit/poll blocking of at most 194 us, peak
worker storage 20,988,088 bytes, and zero owners in every process.
The harness also runs background_lifecycle.gd, the headless Phase 13 C3
lifecycle campaign, behind lifecycle=true in the installed sentinel. It
retires an instance mid-computation (the retired record never surfaces and the
replacement publishes its own kernel-identical result), destroys an instance
holding an unconsumed completed record, tears down partially initialized
instances, refuses a worker whose own workspace creation failed (it reports the
workspace error and rejects every later submit; a replacement instance
recovers), and publishes a computation error as a failed record under its
request id with the consumer's last valid output preserved and a clean
recovery. The two failure scenarios arm a per-worker, mutex-guarded,
fixture-only injection through the documented debug_inject_failure
admission. The frozen receipt is
background-lifecycle-evidence.json (repository: examples/cavern_lab/background-lifecycle-evidence.json) with
the log archive
background-lifecycle-2026-09-08.tar.xz (repository: examples/cavern_lab/background-lifecycle-2026-09-08.tar.xz),
normalized by
verify_background_lifecycle.py (repository: examples/cavern_lab/verify_background_lifecycle.py).
Editor baking
The bake designer flow turns an edited occupancy-mask
Resource into a baked floor asset through one supervised headless child: the
shell saves the request and computes its SHA-256 digest, the child loads the
accepted image through the standard runtime contract, validates the kernel's
process_mask result, and publishes atomically. One outstanding bake per
request; cancellation, supersession, and failure all preserve the last valid
asset. The installed harness's bake campaign exercises the process contract —
oracle-identical output, deterministic repeat, mid-bake cancel, supersession,
the no-image refusal, an invalid mask, freshness badges, and two consumers
sharing one request — behind bake=true in the
GODODIN_CAVERN_INSTALLED_PASS sentinel, with the normalized report written
by verify_bake.py (repository: examples/cavern_lab/verify_bake.py). The real-editor exercises run through
the bake_probe plugin
(live/project/editor/bake_probe/ (repository: examples/cavern_lab/live/project/editor/bake_probe/), a no-op
outside the exercise mode): a no-image editor drives the real shell into the
refusal, and an accepted-image editor proves deterministic repeat, mid-bake
cancellation, native-process failure, the stale reimport, and two consumers
sharing one request, behind bake_editor=true. The committed designer asset
(bake/cave_bake_request.tres (repository: examples/cavern_lab/live/project/bake/cave_bake_request.tres) and
bake/cave_bake_data.tres (repository: examples/cavern_lab/live/project/bake/cave_bake_data.tres)) was produced
by the real shell through its authoring driver, and both export profiles
consume it in a clean closure — no compiler, OLS, or editor helper in the
bundle — behind exports=2 bake_consume=true, normalized by
verify_bake_export.py (repository: examples/cavern_lab/verify_bake_export.py). The editor-reload run
(reload-2026-09-08.tar.xz (repository: examples/cavern_lab/reload-2026-09-08.tar.xz), the reload_probe
plugin) arms an editor-held generator with continuously in-flight background
work and a supervised bake child in flight when the addon's reload lands, and
proves reconstruction, kernel parity, and destination preservation behind
reload=true; the shared C3 checkpoint closed with it (the SDK/user docs and
the affected release-matrix rows updated).
Recorded development and evidence
This directory extends the completed Phase 7 example on Phase 8's single-source
authoring form. Phase 10 adds an existing-data operation, process_mask, with
the Refine shown grid control, and the spatial batch operation
classify_positions on ADR 0060's typed vector/packed inputs. Their focused
checkout evidence is recorded in mask-evidence.json (repository: examples/cavern_lab/mask-evidence.json) and
classify-evidence.json (repository: examples/cavern_lab/classify-evidence.json). The shared C1 publication and
installed developer journey subsequently closed. The
qualification audit tracks those receipts,
later SDK reseats and remaining release qualification. Background generation and
native baking also have recorded C3 evidence. CL-1, the playable slice, CL-2,
the integration loop, and CL-3, durable authoring, from the
example integration plan, are
delivered (2026-09-28/29, Windows journeys) and live in
live/project/expedition.tscn; CL-4 closed the integrated result
(2026-09-29, Windows journey) on the same scene.
The playable expedition (CL-1)
live/project/expedition.tscn is the project's main scene: a top-down cave
expedition where Godot owns everything a player touches and Odin owns the
cave. The scene-owned native CavernGenerator retains an authored
CaveGenerationSettings Resource (cave_settings.tres — the six seeded
generation parameters, editable in the Inspector and saved with the scene).
Running the project submits that request to the generator's background worker;
completion arrives through the native generation_completed /
generation_failed signals (scalar request identity only), the game takes the
bytes through request-checked published_cells outside the emission frame,
installs them into a disabled pending TileMapLayer in bounded batches, and
swaps terrain and collision at a safe physics boundary with the player
suspended. Tiles, HUD identity line, and spawn share one cave identity. A
failed or cancelled request (C) leaves the last committed cave playable.
Controls: WASD or arrow keys to walk, R resubmits the saved settings
(quick presses exercise supersession — the newest result wins), C cancels,
Esc quits. The headless campaign
(test.sh's expedition mode, res://verify_expedition.gd) drives the same
scene: published bytes oracle-equal against the GDScript kernel, a
blocked-and-free walk through the real tile collision, same-frame supersession,
cancellation, and an injected failure — GODODIN_CAVERN_EXPEDITION_PASS with a
zero-owner GODODIN_RUN_PASS. The committed generated bundle and the
recording-host harness refreeze over these declarations are the standing
residual; the executed journey is recorded in
platform-migration.md.
CL-2 completes the integration loop on the same scene. Five scene-authored
candidate markers sit under Candidates; after every install commit the game
accepts exactly the displayed bytes through the native accept_grid (a
monotonic accepted-grid identity with an independently checkable content
hash), and classify_positions decides the scene content against those
bytes: the first placeable candidate hosts the exit scene and the next ones
host up to three collectable marker pickups — collect the markers to open
the exit, reach it to complete the expedition. The HUD names the accepted
grid (grid #2 refined, grid #3 baked, …). Shape: on seeds a mask
from the displayed cave — left-click paints a wall cell, right-click
paints floor — and Refine mask hands it to process_mask, installing
and accepting the refined grid; Load baked grid installs the committed
baked asset (bake/cave_bake_data.tres, its own 256×192 identity). The
campaign grew to caves=4: markers collected and the opened exit reached on
the fixed preset, classification flag-identical to the GDScript oracle on
background, refined, and baked bytes, a moved candidate changing and
restoring placement and scene content, a painted wall block surviving
refinement and blocking its candidate, and the baked asset classifying
against its own bytes — still with a zero-owner GODODIN_RUN_PASS.
CL-3 completes durable authoring on the same scene, with no native-surface
change. The settings Resource's changed notification refreshes the HUD
line deliberately and Apply settings re-reads the assigned Resource
into a new request — an Inspector edit never restarts generation on its
own. Save inputs writes the Shape-mode mask and the candidate marker
positions to cave_inputs.tres; a reopened expedition restores them before
its first request, so authored work survives restart. Mask strokes,
candidate moves, and grid installs are undoable (Ctrl+Z or Undo) —
undoing a refinement reinstalls the previous cave through the same bounded
install path. Two scene instances receive the same assigned Resource and
observe each other's edits, while resource_local_to_scene gives a
deliberate per-instance copy that leaks nothing back. Bake displayed
grid supervises the committed bake shell out of frame — the cave stays
playable while the child runs — and a published session asset
(bake/cave_bake_session.tres, machine-local, never the committed designer
pair) installs through the same playable consumer; Cancel bake and a
kernel-rejected request each leave the last valid baked asset in place.
The campaign grew to caves=9 (apply_resubmitted, inputs_reopened,
undo_restored, share_obeyed, unique_obeyed, bake_responsive,
bake_published_played, bake_cancelled_kept, bake_failed_kept,
committed_asset_untouched, all true), and the executed debug export ran
its own gates — GODODIN_CAVERN_BAKE_CONSUME_PASS (freshness FRESH,
oracle-equal, zero owners) and the new GODODIN_CAVERN_BAKE_PLAY_PASS:
exported play consumes the baked asset and runs this scene's own
placement, collection, and exit loop
(GODODIN_BAKE_PLAY=1, gameplay=expedition_loop).
CL-4 closes the integrated result on the same scene, with no
native-surface change. Shape mode shows a translucent overlay over exactly
the cells where the authored mask differs from the displayed cave, and the
candidates view draws every scene-authored marker in the running game —
green for placeable, red for blocked — so native classification is visible
while playing. HUD buttons take no keyboard focus, and Open Lab keeps
the byte-compare and classification surfaces (and benchmark access through
the Lab) reachable in-game. Save inputs records the chosen scenario:
a restarted expedition reconstructs it explicitly — a saved baked scenario
reloads the committed asset (request #0 · grid #1 baked) instead of
generating. The focused qualification over this source: the campaign grew
scenario_reconstructed, overlay_drawn, and candidates_drawn (debug
and release, zero-owner launcher runs), the executed debug and release
exports each ran the bake-consume and bake-play gates in the exported
applications, the native editor reload journey preserved unsaved Godot
work across an in-place reload
(GODODIN_CAVERN_RELOAD_STAGE_PASS … scene_hold=unsaved_marker_and_settings … per_cycle_teardown=zero_owners), and the windowed session drove
author → generate → explore → refine → bake without code edits, closed
clean, and the relaunch visibly reconstructed the saved scenario.
The dev6 format/SDK reseat passed the installed harness again on 2026-09-19:
dated Xcode 27 evidence (repository: examples/cavern_lab/cavern-evidence-2026-09-19-xcode27.json) records generation, packed
input, the two kernel workloads, background work, native editor reload, baking
and both executed exports on the macOS recording host. Earlier receipts retain
their original identities; this does not qualify another host or renderer.
The accepted developer journey
(cavern-native-authoring) extends this example into existing-game adoption and
persistent native component authoring. It closed on 2026-09-07 on macOS arm64:
journey/journey.py (repository: examples/cavern_lab/journey/journey.py) authors a standalone GDScript cave
game outside the checkout, adopts it through the installed SDK, and drives all
twelve stages — 64 live native components plus 64 native settings Resources,
cell-for-cell parity with the game's GDScript oracle, editor persistence,
broken-revision repair and automatic same-process reload, scenario
stop/restart, SDK relocation, and executed debug/release exports. The receipt
is journey-evidence.json (repository: examples/cavern_lab/journey-evidence.json) with the log archive
journey-2026-09-07.tar.xz (repository: examples/cavern_lab/journey-2026-09-07.tar.xz). The pure kernel and
GDScript oracle here stay untouched; the compositor remains the separate
Render/ownership fixture. See
ADR 0053.
Verification harnesses
The 2026-10-19 installed-SDK receipt (repository: examples/cavern_lab/cavern-evidence-2026-10-19-sdk5.json) is the
current beta1 qualification. Its retained trace (repository: examples/cavern_lab/cavern-trace-2026-10-19-sdk5.tar.xz)
contains executed logs, reports, publication manifests, authored input snapshots,
the exact harnesses and an inspected rendered frame. It records oracle and
packed-input equality, the expedition, background/lifecycle work, editor baking,
native reload with outstanding generation and baking, both release benchmark
sizes, and both exported applications consuming the baked asset with zero-owner
shutdown. Earlier receipts retain their original identities.
The cold editor reload session passes without preparatory import using the
reader fix in the current SDK. Both exported apps execute without native image
launcher variables. The receipt binds the production SDK archive, its
byte-identical second pack and its validated installation. The no-image editor
test removes native discovery explicitly, because ordinary startup now loads
the adjacent image. The interrupted attempt with the old harness assumption is
retained separately; the successful campaign reran all stages from a fresh
workspace. The first beta1 receipt (repository: examples/cavern_lab/cavern-evidence-2026-10-19-beta1.json) remains
historical.
contract.sh checks the current lock, catalog, source inputs and toolchain
against this receipt and verifies the retained trace. Stale or missing evidence
fails both routine tiers; there is no evidence-pending skip. The trace also
retains the positive control and ten refusal checks for stale or damaged
evidence. These results qualify the named workload on the recording macOS host;
clean-machine and other-platform qualification remain separate.
Use the project's configured installed SDK and its matching lock. The qualification audit records historical SDK reseats; the version policy owns the current engine and checkout integration state. The focused harnesses below require matching source, CLI and engine inputs; an older receipt's SDK identity is not an instruction to restore its superseded target:
python3 examples/cavern_lab/verify_mask.py \
--cli /absolute/checkout/build/gododin \
--godot /absolute/locked/Godot \
--evidence-dir /absolute/checkout/build/new-mask-evidenceIt builds a separate checkout-authority consumer, runs the existing seeded oracle
and the input workload, then observes the unchanged native binaries through a
test-only public-interface adapter. The observer checks actual ptrcall/Variant
entry points, original backing-store identity, constant input-read counts, native
copy-on-write, empty inputs, and wrong-type rejection. --resume reuses unchanged
accepted inputs and successful plain runs after a harness failure. This harness
does not publish an SDK or establish a new speedup claim.
The exact qualification harness takes the SDK, compiler, engine, and a fresh evidence directory:
examples/cavern_lab/test.sh \
--sdk /absolute/extracted/<release>-macos-arm64-sdk \
--odin /absolute/locked/odin \
--godot /absolute/Godot.app/Contents/MacOS/Godot \
--evidence-dir /absolute/new-cavern-evidenceIt validates and attaches the SDK, runs first, doctor, generate, check,
and configured debug/release builds, executes headless correctness and normal
visual runs, and measures three release samples at 512x512 and 1024x1024. A
performance claim is allowed only when every measured result is byte-identical
and the GDScript/native generation-time median ratio is at least 2x at both
sizes. The receipt records the actual medians; it does not generalize beyond
this named workload and recording host.