Skip to content

fix(view): a showcase picture shows the story's moment, its cast facing the lens, to the horizon - #1337

Merged
stellarfeline merged 11 commits into
mainfrom
fix/frames-show-the-story
Nov 11, 2026
Merged

stellarfeline merged 11 commits into
mainfrom
fix/frames-show-the-story

Conversation

@stellarfeline

@stellarfeline stellarfeline commented Nov 10, 2026 •

Copy link
Copy Markdown
Owner

What this fixes

A release-quality render round on the cheerful town campaign found six engine defects that stop its showcase pictures being right. Each is fixed where its cause is.

  1. A story moment holds the cast the cast ledger stations there. A camera's world after a step stood every NPC at its load position: move-npc effects never reached it, a deferred spawn-npc fired only by a flag-gated effect was unforced in the critical path's ledger, and a branch's world laid only the setup rows. Now every NPC the cast ledger declares stands where cast::station puts it at that moment of that path (the quest of the next step, the flags the path's flow journal holds), resolved as a talk-to step resolves it (plan::body_station, BodyScope::Beat) — moved, summoned from its ledger rows, or taken out while declared absent; a body a planned walk leaves on the mark faces its last step (view::moment::cast_at, bodies_of).

  2. DW0721 refuses only a moment whose blocks, cast and sky are all load's. A step after which only NPCs move (or only the sky changes) is a moment; the refusal and its remedy now say "no block, no member of the cast and no sky differs". The binding line states <m> of the cast moved, sky <as at load | set by the story>.

  3. A moment's world is the load world with every write fired on the way laid, in firing order, and the count is the bytes'. Two causes: (a) region writes were read under the proofs' causal ancestry, so a write at a step that is no causal ancestor of the next step vanished from later moments (candles lit, then unlit, then lit again); a picture is of one play of the path, so PathModel::at_moment reads the writes in the path's order. (b) The ledger's block rows were laid after the configuration's writes, so a setup row (a candle placed unlit at world init) overwrote the beat that lit it — the reason a written world could hash equal to load while "3 cells moved" was printed. Every write now goes through view::moment::Laid: setup rows, then per step its region writes (Configuration::laid_writes carries each write's firing step) and its ledger rows, laid by the one writer nav::lay_writes (which RegionState::blocks_over now calls too). The printed count is the count of cells the written bytes differ from load's. A branch's moment fires the ledger along the branch's own steps (placed::Ledger::on_path, firings_of taking the path).

  4. A far frame loads the declared horizon. Where the horizon's base lays ground past the build (an ocean's sea), a frame whose rays meet that surface past the build's extent loads those chunks out to where its fog closes (frames::horizon_cover), and the fog stays where the build's extent puts it. The yellow rectangle in the sea of the plan frame is the sun's reflection in the water: the sun at the picture's tick (daytime 10500, altitude 34.9° on the game's eased curve, due west) reflected in a flat sea is seen 239 blocks west of the lens's nadir, which projects to x ≈ 347 of the 1600-pixel frame at the lens's own z (the frame's middle row); the rectangle stands at ≈ (345, 450), and the shape is the game's square sun texture. It is the path tracer's specular water, not a body.

  5. A body the renderer draws nothing of, in frame, refuses the approved picture (DW1100), and only that. delvec cameras names every body of the frame's world whose kind the renderer's own statistics for that frame report skipped and that stands in frame, nearer than the fog and in sight of the lens; an hq frame is not written (exit 5), a preview is drawn and names it. The engine keeps no list of kinds: the refusal reads what Lumenwright reports skipped, so on Lumenwright 0.4.0, which draws the happy ghast (and the allay, boats, minecarts, sheep wool, the warden, the firework rocket), only what 0.4.0 still skips (a bat among them) is refused.

  6. NPCs face the lens. In play a body that watches turns to the nearest player within its reach; a picture's lens stands where a player stands for it. This is a picture-time reading of the body's own declared watch (spec-0101), not a new rest facing: the engine already declares the rest facing (the anchor's), and the watch is what decides how the body faces a player. A watching body within its reach of the lens turns to it by the game's look-at bearing; the frame stands in a copy of its world with its bodies turned (world::copy_with_entities). A body declared not to watch, or watching a class, keeps its rest facing.

  7. Lumenwright 0.4.0, and the sea plane over an ocean. The renderer moves to =0.4.0 (crates/delvec/Cargo.toml, versions.toml [render], frames::RENDERER_VERSION, Cargo.lock, the acknowledgements row). An hq frame whose world's horizon is an ocean states the renderer's sea plane at the level above the topmost generator water block (frames::sea_plane_at / sea_plane, one rule read by the showcase cameras, the review frames, the panorama and the single frame); every other horizon states none. The sea then reaches the horizon where the fog-less hq frame used to end it as a band.

DW1100 is the one code allocated (handed: DW1100, DW1101; DW1101 unused).

Proof

Defect Test Red on main Green here
cast at a moment (items 1, 2), watch at the lens (item 6) it::story_moment::a_moment_holds_the_cast_the_ledger_stations_and_a_watcher_faces_the_lens main's delvec cameras refuses the camera after obj/look with DW0721 ("block for block"), exit 2 the keeper at [3.5, 65, 4.5] after obj/look, [5.5, 65, 4.5] facing north at load; each facing copy's keeper at the bearing to the lens
firing order and the count (item 3) it::story_moment::a_moment_lays_every_write_its_path_fired_and_counts_what_it_wrote; it::post_beat_camera::a_beat_s_write_lands_over_the_setup_s; view::moment::tests main writes no gold at 1,65,1 after obj/talk and prints 6 cells moved the gold is there, 7 cells moved, equal to the written worlds' difference
horizon (item 4) view::frames::tests::a_frame_looking_past_the_build_loads_the_horizon_to_its_fog — (new function) the cover past the build, bounded by the fog, none for a frame that sees nothing past it
renderer (item 5) it::story_moment::a_frame_of_a_body_the_renderer_cannot_draw_is_refused (a bat keeper); it::story_moment::a_happy_ghast_is_drawn_in_hq_and_a_bat_is_still_refused on 0.3.0 the happy ghast is skipped and refused exit 5, DW1100 naming minecraft:bat dw_npc_keeper at [5.5, 65.0, 4.5], no picture; a preview is drawn and names it; in a world holding both, the renderer reports the ghast drawn and the bat skipped
sea plane (item 7) view::frames::tests::the_sea_plane_is_stated_for_an_ocean_horizon_only; it::story_moment::the_sea_plane_changes_an_hq_far_frame_over_a_sea — (new) the rule states sea_level + 1 for an ocean and nothing otherwise; the same hq far frame over a sea differs in pixels when the plane is stated

The town, redrawn (preview, fixed engine; content campaign/town-rpg @ 3655d61f, no content commit)

  • The moments: after Lady Agathe's talk 1 of the cast moved (Agathe to the square); after the loaf 4 (Agathe, Sanna, Oskar, Runa on the square); the ending on chorus-sings+oven-loaf+nest-moved 8; on …+dumpling-tail the deferred npc/dumpling-square stands over the square and npc/dumpling is gone. The candle steps' worlds hold the lit candles and agree byte for byte (after-obj-find-the-wings and after-obj-climb-to-the-tail share one sha256).
  • Runa (forge-near), Fenna (tavern-near) and four more frames' watchers face the lens.
  • sea, plan, from-the-castle and castle-far load the sea to their fog; in hq the sea plane carries it to the horizon: the sea draft shows the sea to the horizon with no band under it.
  • castle-far frames the happy ghast at the castle gate: on 0.4.0 its hq picture is drawn (the ghast is a white box-shaped body at the gate at draft size), no longer refused.

Checks (head 3128edb99; the town renders on the local Mac, the gallery baseline on the remote build machine)

  • fmt (tools/ci/fmt-workspaces.sh): 2 of 2 workspaces clean; clippy --workspace --all-targets -D warnings: clean.
  • cargo test -p delvec --lib: 1417 passed, 0 failed.
  • cargo test -p delvec --test it over the render and frame tests (frames, story_moment, post_beat_camera, view_cli, camera_sky, review_frame_sky, codes, panorama, cameras): 115 passed, 0 failed, of 3078 in the binary; the full it runs in CI.
  • Gallery baseline (tools/ci/gallery-baseline.py, built from source, compared with the merge base): REPORT — 10 builds each side, 10711 emitted paths, 0 added, 0 removed, 0 changed; 10 recorded values moved (the dw_codes lists and the renderer version); warning rows 283 against the ledger's 283. No row to regenerate.
  • Docs-job checks of ci.yml (17 listed, the fetch step aside) and validation/check-versions.sh: all exit 0.

Fixes #1326 #1327 #1328 #1329 #1330

Later commits

  • Lumenwright's pin registry entry moves to 0.4.0 with its sites.
  • The gallery probe a-beat-that-changes-nothing is removed. Under the corrected moment rule every step the gallery's critical path carries once leaves load in a block, the cast or the sky, so no single camera edit of the gallery reaches DW0721's "equal to load" cause any more. That is a loosening of the gallery's demonstration of that one cause; the refusal itself is unchanged and the cause stays asserted by the compiler's own test (it::post_beat_camera), which is where a refusal of the compiler's output is proven. DW0721's other three gallery probes are unchanged.

🤖 Generated with Claude Code

stellarfeline and others added 9 commits November 9, 2026 23:40
A camera taken after a step now stands in the world its path leaves once
every step up to it is played, in the path's order:

- the blocks: every region write and every placements-ledger block row
  fired on the way, laid in firing order over the load bytes (the setup's
  writes first), so a cell the setup lays and a later beat rewrites holds
  the beat's block; a branch's path fires the ledger along its own steps;
  the moved-cell count is the count of cells the written bytes differ in;
- the cast: every NPC the cast ledger declares stands where the ledger
  stations it at that moment of that path, moved, spawned or taken out;
- the sky: the latest set-time and set-weather the path fired.

DW0721 now refuses only a moment whose blocks, cast and sky are all load's.
A body that watches the nearest player turns to the lens when the lens is
within its reach. A frame that looks out past the build loads the horizon's
ground out to where its fog closes. A showcase frame that frames a body the
renderer draws nothing of names it under DW1100; an hq frame is not written.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019qiQFhKtTpTbYnn9NZBbSd
…the horizon and DW1100

- story_moment (it): the keeper a step moves is stood where the cast
  ledger stations him and the moment is pictured, not refused; a watcher
  turns to the lens; a write the path fired before the moment is laid
  though its step is no causal ancestor, and the printed moved count is
  the written worlds' difference; an allay in frame refuses the hq
  picture under DW1100 and is named on a preview.
- post_beat_camera: a beat's fill lands over a setup row on one cell; the
  one writer of region writes is nav::lay_writes.
- unit: horizon_cover, Laid's firing order and count, facing_the_lens.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019qiQFhKtTpTbYnn9NZBbSd
… watch, the horizon and DW1100

compiler.md §7 `delvec cameras` states the moment a camera stands in (its
writes in firing order, the cast where the cast ledger stations it, the
sky the path set), DW0721's distinctness over all three, the binding
line's new fields, the horizon a frame loads, the watch at the lens and
DW1100; tools.md's `cameras` section and the after row say the same to
the agent placing cameras; DW1100 has its catalogue row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019qiQFhKtTpTbYnn9NZBbSd
… reads the cover per axis

A preview's `undrawn:` line counted only refused hq frames, so a preview
naming a body under DW1100 still said 0; it now counts every frame that
names one and says an hq picture of them is refused. The horizon test held
the cover's bounding-box corner to the fog's radius, which a disc's box
exceeds by a factor of the square root of two; it holds each axis.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019qiQFhKtTpTbYnn9NZBbSd
The binding line now says how many of the cast moved and whose sky a
moment holds, and DW0721's refusal says no block, no member of the cast
and no sky differs; the tests that read them say so too, as strictly as
before. The one-writer test counts the walks over a state's laid writes
with their blocks, the world writer's walk over the regions it marks
being no such walk.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019qiQFhKtTpTbYnn9NZBbSd
The hello-world keeper watches, and the fixture's lens stands within his
reach, so the door frame stands in its world's copy with him turned to the
lens; its manifest names that copy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019qiQFhKtTpTbYnn9NZBbSd
… DW1100 reads the renderer

Moves the renderer to 0.4.0 (manifest, versions.toml, frames::RENDERER_VERSION,
lock, acknowledgements). An hq frame whose world's horizon is an ocean states
the sea plane at the level above the topmost water block, in one rule
(frames::sea_plane_at / sea_plane) read by the showcase cameras, the review
frames, the panorama and the single frame; no other horizon states it.

DW1100 reads the kinds the renderer reports skipped, so it refuses only what
0.4.0 still skips: the story-moment refusal test now uses a bat (the allay is
drawn), and a world holding a happy ghast and a bat is drawn in hq with the
ghast reported drawn and the bat skipped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019qiQFhKtTpTbYnn9NZBbSd
… skip statistics

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019qiQFhKtTpTbYnn9NZBbSd
@stellarfeline
stellarfeline marked this pull request as ready for review November 10, 2026 22:49
stellarfeline and others added 2 commits November 11, 2026 06:50
…21 probe the cast rule made unreachable

The pin registry records the renderer at 0.4.0 beside its three sites.

The probe `a-beat-that-changes-nothing` moved the eighth camera to the first
step of the critical path, whose world was load's block for block. A moment is
now distinct by block, cast or sky, and after that step the cast ledger stations
the marshal and a block is rewritten (measured: 1 cell and 1 member of the cast
moved), so the build accepts it. Run over every step the path carries once, each
leaves load in a block, the cast or the sky, so no camera edit of the gallery
reaches the refusal. The probe is removed rather than weakened; the refusal of a
moment equal to load in all three is still asserted by
it::post_beat_camera on its fixture, and the gallery's other DW0721 probes
(a candidate id, a step out of the path, a step out of sight) are unchanged. This
loosens the gallery's committed demonstration of one DW0721 cause, nothing in
DW0721 itself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019qiQFhKtTpTbYnn9NZBbSd
@stellarfeline
stellarfeline merged commit 32705b8 into main Nov 11, 2026
39 checks passed
@stellarfeline
stellarfeline deleted the fix/frames-show-the-story branch November 11, 2026 03:41
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.

A story-moment frame world puts every NPC at its load position

1 participant