Skip to content

gate: don't strand the terrain generator; bound the splash's waits - #104

Open
dsrw wants to merge 1 commit into
mainfrom
fix/spawn-gate-stranded-generator
Open

gate: don't strand the terrain generator; bound the splash's waits#104
dsrw wants to merge 1 commit into
mainfrom
fix/spawn-gate-stranded-generator

Conversation

@dsrw

@dsrw dsrw commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Loading a level from inside another one (clicking a sign link that evals load_level("welcome")) held the splash for the full 30s spawn-gate timeout, then revealed with spawn gate: deadline exceeded. The level had finished loading well before that — the process was completely idle for the whole wait (snapshots_delta=0, no meshing), with build_water the only unit still reporting pending work.

Root cause

build_node drops the terrain's generator and stream on RESETTING.added and restores them on RESETTING.removed. But Build.reset() adds and removes RESETTING inside one worker tick, and a build's node is created in the middle of that window — its script runs right after the thing joins state.things. The node can see the added, as its watch replaying a flag that is already set, while the removed that preceded its registration never arrives as an edge.

The terrain is then left with no generator and no stream at all, which turns streaming off wholesale in VoxelTerrain::_process (stream_enabled false): no blocks are ever requested, has_stream_started() never becomes true, and the unit publishes the not-started floor of 1 forever with no work behind it. The spawn gate waits on that until it times out.

keep_generator_in_sync now derives the generator from the flag each frame instead of trusting its edges.

Evidence: booting tutorial-3 stranded three units deterministically — build_3npxfmddql9kx, build_mlsxf11t2oaz6, build_wevpat48mxjhe each logged "stream never started" on both pre-fix runs. After the fix, none, across every run since.

Bounding the gate

So no single unit can hold the splash again:

  • The not-started floor expires after 3s. It exists to stop a terrain reading "done" before its required set is computed — a frame or two, measured well under a second even for the 6528-chunk terrain. Past that nothing is coming, and it logs the unit it dropped plus whether it still had a generator.
  • The warm-up lifts after 1s even if get_frames_drawn() never advances. It waits for two real draws behind the splash, which needs the engine to actually be drawing; a minimized or occluded window (every MCP-managed and test instance) freezes that counter, and the splash never lifted at all, with nothing logged. This was a second, independent way to hang the splash forever.
  • Settle timeout 30s → 10s, and the deadline error names the blocking units with their counts (blocking=["build_water=1"]) instead of just the whole gate set.

Testing

nim test_all passes. Switch timings for tutorial-3 → welcome: 628ms with a drawing window, 1.58s minimized (previously: 30s timeout, or never).

🤖 Generated with Claude Code

Loading a level from inside another one (a sign link running
load_level) left the splash up for the full spawn-gate timeout, with
the level fully loaded behind it, one unit reporting work that never
arrived.

The node drops the terrain's generator and stream on RESETTING and
restores them on its removal, but a reset adds and removes the flag
inside one worker tick, and the node is created in the middle of that
window. It can see the `added` as its watch replaying an already-set
flag while the `removed` that preceded registration never arrives as
an edge, leaving the terrain with no generator at all. That turns
streaming off wholesale in VoxelTerrain::_process: nothing is
requested, has_stream_started() stays false, and the unit publishes
the not-started floor forever. Derive it from the flag instead.

Then bound both halves of the gate, so no single unit can hold the
splash again:

- the not-started floor expires after 3s (pairing takes a frame or
  two) and logs the unit it dropped, with whether it had a generator
- the warm-up lifts after 1s even if frames_drawn never advances --
  a minimized or occluded window never draws, and the splash used to
  stay up forever with nothing logged
- the settle timeout drops to 10s and the error names the units that
  were still pending, not just the whole gate set
@dsrw
dsrw force-pushed the fix/spawn-gate-stranded-generator branch from cb821ea to ccdbbd7 Compare July 27, 2026 21:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant