# Cavern Lab (Native)

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](declarations/cavern_lab.odin),
[the pure generation algorithm](subsystem/cavern_lab.odin), and
[the Godot expedition](live/project/expedition.gd). For the separate editor
workflow, follow [the bake designer flow](bake/README.md).

## Build and run

Use a configured copy of this example with its attached SDK. The separate
[project setup guide](../../docs/setup.md) 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](../../docs/versioning.md#generated-interface-compatibility). 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](../../docs/decisions/0059-authored-value-admission-matrix-and-packed-byte-inputs.md)
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`](classify-evidence.json) with the boundary observer
archive [`classify-2026-09-06.tar.xz`](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`](background-evidence.json) with the log archive
[`background-2026-09-08.tar.xz`](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`](background-lifecycle-evidence.json) with
the log archive
[`background-lifecycle-2026-09-08.tar.xz`](background-lifecycle-2026-09-08.tar.xz),
normalized by
[`verify_background_lifecycle.py`](verify_background_lifecycle.py).

## Editor baking

The [bake designer flow](bake/README.md) 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`](verify_bake.py). The real-editor exercises run through
the `bake_probe` plugin
([live/project/editor/bake_probe/](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](live/project/bake/cave_bake_request.tres) and
[bake/cave_bake_data.tres](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`](verify_bake_export.py). The editor-reload run
([reload-2026-09-08.tar.xz](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`](mask-evidence.json) and
[`classify-evidence.json`](classify-evidence.json). The shared C1 publication and
installed developer journey subsequently closed. The
[qualification audit](../../docs/qualification-audit.md) 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](../../docs/example-integration-plan.md), 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](../../docs/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](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](../../docs/implementation-plan.md#developer-acceptance-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`](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](journey-evidence.json) with the log archive
[journey-2026-09-07.tar.xz](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](../../docs/decisions/0053-native-authoring-without-fixture-limits.md).

## Verification harnesses

The [2026-10-19 installed-SDK receipt](cavern-evidence-2026-10-19-sdk5.json) is the
current beta1 qualification. Its [retained trace](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](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](../../docs/qualification-audit.md) records historical SDK
reseats; the [version policy](../../docs/versioning.md#active-target-and-historical-evidence)
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.
