Gododin Gododin Documentation

Example project

Cavern Lab (Native)

examples/cavern_lab/README.md

View original Markdown · Download · All examples

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:

sh
/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_lab

Project 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.toml commits authority_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):

sh
python3 examples/cavern_lab/verify_classify.py \
  --cli /absolute/checkout/build/gododin \
  --godot /absolute/locked/Godot \
  --evidence-dir /absolute/checkout/build/new-classify-evidence

It 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:

sh
python3 examples/cavern_lab/verify_mask.py \
  --cli /absolute/checkout/build/gododin \
  --godot /absolute/locked/Godot \
  --evidence-dir /absolute/checkout/build/new-mask-evidence

It 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:

sh
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-evidence

It 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.