Skip to content

feat(dsl,compiler): spec-0026 foundation — horizon library surface, per-area walk_y datum, DW0364/DW0366/DW0367 (dsl 0.9.0) - #260

Open
stellarfeline wants to merge 10 commits into
mainfrom
worker/spec-0026-foundation
Open

feat(dsl,compiler): spec-0026 foundation — horizon library surface, per-area walk_y datum, DW0364/DW0366/DW0367 (dsl 0.9.0)#260
stellarfeline wants to merge 10 commits into
mainfrom
worker/spec-0026-foundation

Conversation

@stellarfeline

@stellarfeline stellarfeline commented Aug 4, 2026

Copy link
Copy Markdown
Owner

spec-0026 foundation (task #155 slice W-A): horizon library surface, per-area walk_y datum, DW0364/DW0366/DW0367

Merge gate: owner playtest batch (engine emission = player-experienceable). Do not merge on green. Machine red→green on the motivating scenario is below and admits this into the batch.

Pairs with content PR stellarfeline/delvewright-campaigns#21 (worker/spec-0026-walk-y-backfill) — versions.toml re-pins to that branch's SHA (a63d1a0); re-pin to content main tip after the pair merges.

What changed (design level)

  • DSL 0.9.0 (stage 1): horizon accepts the existing strings (byte-identical for "void"/"ocean") OR the object form {base, …params} (spec-0026 §1). All six bases parse with their param schemas; "cherry-valley" desugars exactly to {base:"valley", flora:"cherry", palette:"stone-petal"} (parameter row, not a base — machine-asserted). Fences: the new surface is DW0141 below 0.9.0; valley/cherry-valley/summit/sky are additionally reserved at 0.9.0 (DW0141, the npc vendor/boss precedent) until the W-B/W-C surround slices land — never silently mis-emitted. DW0320 generalizes per §5: every non-void base requires boundary.
  • Flatland lands end-to-end at the ambient level (§1/§3): pinned bedrock/dirt/grass superflat (grass top y=63 = walk plane − 1 by datum equation), minecraft:plains biome, bare by design; Ambient::Flatland branch in the DW0322 boundary model (vacuously safe: ground everywhere, flush). The §3 seam material-dither band + acceptance criterion 4 belong to the surround-generator slices.
  • Per-area placement datum (§2), superseding plan::OCEAN_BASE_Y (deleted): area base y = walk_ref_y − walk_y; walk_y is required prefab metadata for every piece placed in a non-void horizon (DW0367, build/exit 3; pools additionally require entry-role agreement). Island walk_y=3 → base 60 — the datum change does not move the island (asserted against the real content island-beach-camp).
  • DW0364 (build/exit 3): empirical every-piece walk-cell-vs-flood-level proof over the assembled (or stage-7-edited) model, no waterline_y exemption — closes the DW0344 gap; DW0344 retained for waterline-declaring shore pieces (sea-mating), message rewritten to name the datum-equation term. Supersedes interim task feat(dsl,compiler): map editor L2 massing verbs — swap/insert/remove/rewire/reseed-piece (spec-0017 PR 3) #149.
  • DW0366 (validation/exit 1): horizon params out of range (ratio 2..=3, min_drop ≥ 100, build-range bounds on plateau_y/float_y) and params foreign to the declared base.
  • New module compiler::horizon: the one place horizon → physical facts (walk_ref_y, flood_level, generator-settings literals, ambient, slice status). docs/reference/compiler.md updated in this PR (rows DW0364/DW0366/DW0367 added; DW0141/DW0320/DW0322/DW0344 rows + the world-output/stage-1 sections updated); tools/check-dw-codes.py green (190 codes, all test-covered).

Plug-in contract for W-B (valley/cherry surround)

Stated in crates/compiler/src/horizon.rs module docs. A surround generator plugs in with three one-line seams outside its own new module:

  1. implement the generator in its own module (e.g. compiler::valley), consuming delvewright_dsl::ResolvedHorizon + the campaign seed, emitting placed prefab tiles like any other piece;
  2. flip that base's arm in horizon::base_implemented to true, and delete the DSL-layer reserved-base arm for it in dsl::validate::horizon_rules (same PR);
  3. fill the base's horizon::walk_ref_y / generator_settings / nav::Ambient arms with its ambient facts.

Everything else (per-area datum, DW0364/DW0367, server-properties plumbing, check-world-settings.sh parity — which asserts derivation, not values, so it is already horizon-agnostic) is base-generic. ResolvedHorizon carries ratio/rim_height/flora/palette with pinned defaults; HorizonFlora/HorizonPalette are typed enums.

Machine red→green: the #149 tide-mill scenario

Permanent fixture spec0026_horizon::flooded_interior_is_dw0364_and_corrected_datum_is_green_and_dry, plus the live demonstration:

run result
delvec @ origin/main (8a544d8), interior piece (walk plane local 1, no waterline_y) on horizon: ocean exit 0, silently GREEN — placed at base 60, walk plane world 61, one block under sea level; floods on first boot while every proof derives from a dry model
this branch, same content with the folklore datum stated honestly (walk_y: 3) exit 3, DW0364 — "74 standable cell(s) … at or below the ambient flood level (y=62)"
this branch, corrected datum (walk_y: 1, what the piece really authors) exit 0, placed at 0 62 0 (asserted), walk plane 63 — dry

Fixture verdicts (acceptance criteria 1/2/3)

  • Criterion 1: double-build byte-identity for flatland (new fixture) + ocean (existing v0.6 fixture, still green); {base:"ocean"} object form emits byte-identical to "ocean" (new fixture). Old-engine vs new-engine on the void hello-world: datapack/, server/, critical-path.json byte-identical; only manifest.json (content-pin provenance) and the unshipped creator-datapack/layout.json (version: 0.8.0→0.9.0, the standard consequence of every SUPPORTED_DSL_VERSION bump) differ — the shipped delve image (Dockerfile.delve COPY list) carries neither beyond the manifest pin.
  • Criterion 2: negative fixtures assert DW0364, DW0366 (5 range + 3 foreign-param cases), DW0367 by code; the feat(dsl,compiler): map editor L2 massing verbs — swap/insert/remove/rewire/reseed-piece (spec-0017 PR 3) #149-shaped fixture above is red with DW0364. (DW0365 belongs to the sky slice.)
  • Criterion 3: island_walk_y_3_keeps_base_60 — real content island-beach-camp (walk_y: 3) plans at [0, 60, 0], exactly the retired global datum.
  • The drowned-bell (owner's local WIP content, tidal-keep pool, entry walk_y: 3 → base 60): old and new engines produce identical diagnostic sets on it (DW0331 ×5 / DW0465 / DW0476 — pre-existing version-adoption debt, red on origin/main too); the datum change alters nothing about it.
  • One fixture adapted, check unweakened: edit_ocean_breach_strands_the_player_dw0322 relied on the mis-datumed placement (walk plane under the sea) that this PR fixes — its breach is now a canonical beach, correctly green. The stranding fixture now uses a genuinely lipped tileset (walk_y: 0, walk plane two over the sea) and still proves DW0322 fires.
  • Full workspace: 1206 tests green; fmt + clippy -D warnings clean; check-dw-codes / check-doc-dupes / check-versions / check-world-settings green.

Content pairing & adoption notes

  • Content branch backfills walk_y across all 36 GitHub-main prefabs (keep 1, cave 2, island 3, hello-room/hero 1–2), verified empirically from the .nbt geometry (standable-cell histogram + socket-floor probe), and regenerating the keep/cave tilesets from the updated generators reproduces the backfilled files byte-identically. All five generators now emit walk_y.
  • The in-flight tidal-keep/drowned-bell content (local, unpushed) needs its walk_y adoption when it lands: shore pieces 3, plinth pieces 11 (the tk generator in this PR already derives them from its door list) — same-milestone adoption round per the version-adoption discipline.
  • The pin also advances over content docs: spec-0005 — infra images on GHCR + version manifest (owner review requested) #11/docs: M1 complete — owner QA findings (lighting rule, environment sealing) #12 (island set-pieces/terrain), which the old pin (f5766f7) predated.

Ambiguities → reported, not invented

  1. vista_radius vs its own default: spec says DW0366 on vista_radius < view-distance×16, but summit ships view-distance 12 (§6) → floor 192 > the default 176. Contradiction left unenforced (summit is reserved anyway); needs a ruling in the summit slice.
  2. blend_width range: none stated; unvalidated (criterion 4's no-material-wall assertion is the real gate, W-B).
  3. Sky backdrop/placement params: deliberately not on HorizonSpec yet — the schema rejects the fields rather than freezing a wire shape before the sky slice picks one (§4 states no wire syntax).
  4. cave-shore authors an ankle-deep beach (walks flush at its own waterline); under its declared walk_y: 2 its walk plane lands dry at 63 but its authored pond would sit one block above the world sea. No tracked ocean campaign binds it; documented in cave-tileset.md.
  5. Pre-existing island-generator drift vs committed content (merged beach camp, waterline_y) is untouched.

🤖 Generated with Claude Code


Addendum 2026-08-06 — merged main (66 commits) into this branch

The branch sat while main moved 66 commits; six files conflicted. Resolved by
merge (no rebase, no force-push — PR #259 is based on this branch). Everything
below is either conflict resolution or an adaptation forced by what main
landed meanwhile. No player-facing behaviour of this slice changed:
dsl::validate::horizon_rules, horizon_defaults, compiler::horizon,
compiler::plan's datum and DW0364/DW0366/DW0367 are byte-identical to
370a45b.

The two dangerous conflicts

crates/dsl/src/envelope.rsmain had independently taken dsl 0.9.0.
Both sides bumped SUPPORTED_DSL_VERSION to 0.9.0 and both added is_v09,
for different features: main for task #179's declared drops (stage 5), this
branch for the spec-0026 horizon library (stage 1). Resolved as one version
carrying both surfaces
— the established precedent (v0.8 carries spec-0025
and spec-0016 §1), and correct because the fences are per stage: raising
quests to 0.9.0 opens drops and still DW0141s the horizon object form, and
vice versa. SUPPORTED_DSL_VERSIONS and the ordinal arm were identical on
both sides and auto-merged; only the doc contract needed writing, and it now
names both halves and the per-stage consequence.

versions.toml — the content pin. main had advanced [content].sha to
87a8ade (island storybook marker), and content main has since moved to
885fd5d. This branch's pin (a63d1a0) was a content-branch commit sitting on
4992b4f, four commits behind — reinstating it would have rolled back the
storybook marker, the v1.0.0 engine re-pin and both release fixes. Taking
main's pin was equally impossible: content main carries walk_y on zero
of its 36 prefabs
, and this engine's DW0367 hard-errors (exit 3) on any piece
placed in a non-void horizon without it.

Resolved by bringing the paired content branch forward instead: content
worker/spec-0026-walk-y-backfill (PR #21) now merges content main 885fd5d,
and the pin is that merge, 5a23d90. The merge result is exactly content
main plus 36 one-line additions
— the same shape as the original backfill,
re-derived over main's regenerated island prefabs (which changed geometry —
island-beach-camp 21×8×17 → 21×15×44 — and switched to 1-space indent). The
island values were re-derived, not copied: each regenerated file still declares
waterline_y: 2, and the island convention is walk_y = waterline_y + 1 = 3.
5a23d90 is a strict descendant of both 87a8ade and 885fd5d, so nothing is
rolled back. Still a branch sha, still re-pinned to content main the moment
content #21 merges.

Semantic collisions the conflict markers never flagged

  1. crates/compiler/src/render_plan.rs (new on main, PR feat(render): first-class panorama emission + Chunky adoption (task #29) #295) matched the
    old Horizon enum shape
    and stopped compiling against this branch's
    Horizon::Name/Horizon::Spec. Rewritten onto horizon::base_of, exhaustive
    on HorizonBase. flatland deliberately emits no horizon key: the
    render side's Horizon is an externally-tagged enum that knows only ocean,
    so a {"kind":"flatland"} invented here would be an unparseable plan rather
    than a better picture. The wire shape belongs to spec-0026 §6's render work,
    which lands with the surround slices. Ocean and void are byte-identical.
  2. main amended spec-0026 itself (PR docs(specs): spec-0026 amendment — vista_radius definition + blend_width range #261, 2026-08-04), resolving the two
    ambiguities this PR had reported rather than invented — vista_radius
    measured from the scene bounding box, floor 192, default 208 (the old 176
    sat below its own floor); blend_width 1..=16. The DW0366 doc row's claim
    that the spec "contradicts its own default" was therefore false and is
    corrected. Enforcement is deliberately not added here: both params
    configure surrounds this slice does not emit, and PR feat(dsl,compiler): valley + cherry-valley horizon — surround generator, biome layer, tree layer (spec-0026 W-B, task #155) #259 (based on this
    branch) already implements both verbatim. Implementing them twice only put a
    duplicate in the dependent branch's path — it was written, then backed out for
    exactly that reason, and the row now says who owes the check.
  3. Per-row rather than per-file docs resolution: pass-order row 6 is main's
    (compiler::pool/DW0498), row 7 + the new 7b are this branch's (walk_y
    datum, DW0364). The waterline_y bullet keeps this branch's general
    walk_y − 1 statement and main's concrete island fact, which agree
    (walk_y 3 → waterline 2). The DW0141 row is main's fuller pre-0.8
    enumeration plus a pre-0.9 clause naming both v0.9 surfaces.
  4. crates/compiler/tests/cli.rs asserted delvec 0.1.0; main made
    DELVEC_VERSION derive from CARGO_PKG_VERSION (PR fix(compiler): DELVEC_VERSION derives from the crate manifest, not a hand-typed copy #293) and the crate is
    1.0.0. Took main's assertion — the branch's literal is stale, not a design
    disagreement.

Verification

  • 1662 workspace tests green (was 1206 pre-merge); cargo fmt + clippy -D warnings clean.
  • All five spec-0026 fixtures still green over the regenerated content,
    including island_walk_y_3_keeps_base_60 — the datum survives main's island
    regen — and the feat(dsl,compiler): map editor L2 massing verbs — swap/insert/remove/rewire/reseed-piece (spec-0017 PR 3) #149 red→green pair.
  • check-versions / check-world-settings / check-dw-codes (210 codes, all
    test-covered, 1 allowlisted) / check-doc-dupes (68 files) /
    check-required-contexts (10 ci.yml jobs ↔ 10 required contexts) /
    check-harness-dsl-version (0.9.0 in the allowlist) / check-skill-version /
    check-storybook-version (1 campaign, 1 allowlisted) /
    check-shell-pipe-shortcircuit (15 scripts) / check-compose-isolation (4
    services) — all green. No job renamed, so .github/required-status-checks.txt
    is untouched.

Effect on PR #259

main alone conflicts with #259 in 8 files. Against this updated branch it
conflicts in 3: crates/compiler/src/main.rs (main's DW0498 advisory
changed read_structures from &mut plan to &plan), docs/reference/tools.md
(main deleted check-worker-override.py and added five checker rows), and
docs/reference/compiler.md (5 blocks — four are #259's task-#178 wave-countdown
content vs main's container-adoption/drops content, plus the DW0141 row,
which conflicts against main regardless). Exactly one block is attributable
to this PR: the DW0366 doc row, whose resolution is to take #259's side, since
#259 enforces what that row describes.

stellarfeline and others added 2 commits August 4, 2026 00:32
…er-area walk_y datum, DW0364/DW0366/DW0367 (WIP: tests/docs follow)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…er-area walk_y datum, DW0364/DW0366/DW0367 (dsl 0.9.0)

Stage-1 horizon generalizes into the spec-0026 composition: string shorthands
(byte-identical for void/ocean) OR the object form {base, ...params}, all six
bases parsed and param-validated; flatland lands end-to-end (grass superflat
ambient one block under the scene walk plane, plains biome, bare);
valley/cherry-valley/summit/sky are reserved (DW0141) until their surround
slices. Placement moves to the per-area datum walk_ref_y - walk_y (DW0367),
retiring plan::OCEAN_BASE_Y; DW0364 proves every placed piece's standable
cells sit above the flood level empirically, with no waterline_y exemption —
the #149 flooded-interior class is now impossible by construction.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
stellarfeline and others added 4 commits August 6, 2026 01:57
…dation

# Conflicts:
#	crates/compiler/src/lib.rs
#	crates/compiler/tests/cli.rs
#	crates/dsl/src/envelope.rs
#	crates/dsl/src/lib.rs
#	docs/reference/compiler.md
#	versions.toml
…am, not by this slice

The DW0366 row claimed `summit.vista_radius`'s floor was unenforceable because
the spec contradicted itself (floor 192 vs default 176). That was true when the
foundation slice reported it and stopped being true on 2026-08-04: the spec-0026
amendment (PR #261, on `main` for 66 commits now) resolved both open params —
`vista_radius` measured from the scene bounding box, floor 192, default 208; and
`blend_width` 1..=16.

Enforcement is deliberately NOT added here. Both params configure surrounds this
slice does not emit, and the valley slice (task #155 W-B, PR #259, based on this
branch) already carries the checks and the corrected default verbatim.
Implementing them twice would put a duplicate in the dependent branch's path for
no behaviour gained. The row now says what is true: the ambiguity is resolved,
the enforcement is owed, and which slice owes it.

No behaviour change: `dsl::validate::horizon_rules` and `horizon_defaults` are
byte-identical to this branch's pre-merge state.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AjQ5p1Kv5MrkGPumi7yXWL
…compiler proves (task #shallows)

`DW0364` refuses standable ground at or below the horizon's flood line with no
exemption, deliberately: that exemption is what made #149 invisible. Applied to
the released `nobodys-cave-island` it fires on 26 cells at world y=62 — the
shoreline the two bank-falls batches exist to make. Owner ruling 2026-08-06: the
sculpted shoreline stays, and it needs a first-class declaration.

The declaration is NOT a suppression switch. `flood` admits the horizon's
ambient sea into a declared envelope; the compiler computes which cells the sea
actually reaches and materializes them as water, in the model AND in the emitted
world. A cell stops being a `DW0364` violation only by ceasing to be ground.

Three properties do the safety work, and none of them is "skip the check":
- a sealed room under the sea is unreachable by the sea, so declaring `flood`
  over the #149 fixture is `DW0394`, never green — it only changes which red;
- over-declaring costs you your own map (a wider envelope wets more of it, and
  an envelope that swallows a corridor turns nav red), so it silences nothing;
- under-declaring is `DW0395`: water that runs on past the envelope into an
  undeclared air cell of a placed piece means the shoreline is not where the
  author said it is. Re-proved after every later batch.

`DW0364` keeps its exact rule, tier and domain. A campaign that declares no
`flood` emits byte-for-byte what it emitted before (`DW0141` fences the verb
below world-edits 0.9.0).

Spec (proposal) in `docs/specs/spec-0029-deliberate-shorelines.md`; the proof is
`crates/compiler/tests/spec0029_flood.rs` plus the island build, which goes
green on exactly this declaration with 25 wetted cells while `DW0364` goes on
refusing the one sealed pocket the envelope also covers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AjQ5p1Kv5MrkGPumi7yXWL
…ken on an unmerged branch

Two workers picked the next free spec number on the same day from
`main`, where the highest is 0028. Neither could see the other's
branch: `docs/spec-0029-i18n-v2` already holds
`spec-0029-i18n-v2-client-selected-language.md`.

Text-only rename plus the test-file name; no behaviour change.
`spec0030_flood` 6/6 green.
stellarfeline added a commit that referenced this pull request Aug 7, 2026
…he board is red (task #44) (#322)

Every gate in CI built a FIXTURE. Nothing ever built a real campaign, so the
product could stop compiling with the whole board green — PR #260 reached 10/10
while stopping the flagship released campaign `nobodys-cave-island` from
building at all (26 x DW0364 on standable cells at the ocean line).

New required status check `campaign builds (every campaign in the content repo)`
builds every campaign the pinned content checkout carries, in every language its
world.json declares, on every push.

Campaigns are DISCOVERED, never listed, so the next content re-pin gates a new
campaign with nobody remembering. A campaign that cannot build today is recorded
in .github/campaign-build-exclusions.toml, which INVERTS the assertion instead of
removing it: still built, must still fail, and must fail with exactly the
recorded codes — an extra code is a new break that was hiding behind the
exclusion, and a success is an expired exclusion. Both red. One entry today:
hollow-vigil, DW0331 (task #34).

Validator-only: no crate, no emission path and no prefab is touched, so
player-facing output is byte-identical.


Claude-Session: https://claude.ai/code/session_01AjQ5p1Kv5MrkGPumi7yXWL

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
stellarfeline added a commit that referenced this pull request Aug 8, 2026
87a8ade -> e4c58c0f78f2e0e949c625697c3f5b04df125aff

Carries content #29 (the flock walks out of the pen), #32 (three actor
nameplates the widened l10n inventory now requires) and #33 (untrack the
absolute-path symlink).

Ordering, established by measurement rather than assumed: this must land
BEFORE #317. A sidecar row the engine does not inventory is tolerated (built
green against today's main), but an inventory key with no sidecar row is
DW0180 and hard — so new-engine-with-old-content reds, and old-engine-with-
new-content does not. The pin therefore leads the engine here. Note this is
the OPPOSITE of the #260/content#21 ordering, where PrefabMeta's
deny_unknown_fields made engine-first the only safe order: the direction is a
property of the change, never a rule of the repo.
stellarfeline added a commit that referenced this pull request Aug 8, 2026
…on (spec-0029) (#317)

* feat(compiler): two holes where player-visible text escapes translation (spec-0029)

The owner played the island in Chinese and read English. Two independent leaks,
both closed here, plus the general form of each so neither class can recur.

**Hole 1 — a body's name was not in the inventory.** `dsl::l10n::each_string`
inventoried an actor's DROPS but never the actor's own `name`, so every
`actors[].name` shipped as a literal. That is one hole with two signatures on the
island: the giant is `npc/polyphemus` (translated, correct when he stands still)
AND four actor puppets all named `Polyphemus` (untranslated, English the moment a
cutscene stages one) — while the eight sheep have no NPC identity at all and were
English in every frame. 16 actors, 16 carrying a name, 0 inventoried.

Inventorying the field alone would have made it worse: a translator asked for
`Polyphemus` five times can answer five ways, and the giant's name would change as
he walked into a cutscene — an authored defect rather than an accidental one. So
**an entity display name is keyed by its canonical English text, not by its
declaration site**: the first site in the traversal (NPCs before actors) owns the
key and every later site carrying the identical name emits that same key. The
translator is asked once; two bodies a player reads as one character cannot render
as two. Scoped to `npc.*.name` + `actor.*.name` — prose stays per-site, and
merging `wave.*.mob.*.name` would retire keys live campaigns already translate,
which is an owner call, not this PR's.

**The general form.** `DW0185` proves a string the inventory KNOWS reaches a
component; it is blind to one the inventory never met, which ships English in
silence — how this survived twenty playtest rounds. `crates/dsl/tests/
l10n_surface.rs` closes that half: it enumerates every string-valued property of
the seven stage schemas (derived from the Rust types, so complete by construction
— 78 today) and requires each to be classified Inventoried / Reference / Machine /
NotPlayerVisible(<why>), in both directions. A new `String` anywhere in the DSL is
red until somebody records whether a player reads it. A test rather than a DW
code because the defect is in the compiler: no campaign input can produce it.

**Hole 2 — the compiler's own chrome was untranslatable.** Thirteen strings the
compiler writes itself had no key and no override. They are now compiler-owned end
to end (`dsl::chrome`): reserved `delvewright.ui.…` keys, English canonical,
translations shipped with the engine, zero campaign authoring. Eight are product
chrome (`New objective: `, `Delve Complete`, `Choose your class`, …) — no author
wants to write those, which is why the answer is not an override; five are the
diegetic defaults whose authored overrides already exist, are unchanged, and still
win.

Chrome rides the existing road rather than a parallel one: it enters emission as a
translation tag, lowers through `emit::tr`/`snbt_component`, and a site that fails
to is `DW0185` like any authored string.

Three of the eight were CONCATENATIONS, which freeze English word order into every
language. They are now one key with `%s` carried by the component's `with` —
`"%s — complete."`, `"New objective: %s"`, `"Waiting for the party — %s / %s"` —
vanilla's own primitive for the job. A unit test requires every language's
placeholder count to equal the English's.

`DW0186` reserves the namespace: a sidecar may not define a chrome key, which
would otherwise be written into the language file and replace product chrome.

**Language coverage.** `dsl::mclang::CLIENT_LANGS` is the pinned client's own
language set — 143 stems DERIVED from Mojang's 1.21.11 asset index, with the
digests recorded and `tools/derive-client-langs.py` to re-derive; it replaces the
hand-maintained 25-entry table and is a strict superset, so nothing that compiled
stops compiling. Chrome tables: 30, covering 47 locales; every other language
reads English — absent from its lang file, never faked. Chrome is emitted only
into the files a delve already ships, so a French client never gets French chrome
around English story.

The translations are machine-produced and unreviewed; that is stated in the
module header, not glossed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AjQ5p1Kv5MrkGPumi7yXWL

* feat(compiler,skill): translation provenance, and the naming rule that prevents divergence

Three follow-ups on the review of #317.

**1. A sidecar row can go stale silently — now it cannot (`DW0187`).** Coverage
validation compares key SETS: rewrite an authored line and its translation is
present, applied and WRONG, with no key moved and every check green. The text-owned
key scheme this PR introduced adds a second, nastier way in — rename ONE body and
the key migrates to ANOTHER, so the row that goes stale is not the row the author
edited, and `DW0180` points somewhere else entirely.

The sidecar now records the English each row was translated FROM (`source`), so the
compiler compares instead of a human auditing. It is deliberately the GENERAL fix,
not a migration-only guard: any edited line is caught, whatever kind of string it
is.

`source` is additive — an older sidecar parses unchanged — so `DW0188` counts its
unguarded rows on every run and names the deprecation window. An unadopted sidecar
is a reported number, never a silence that reads like a pass. `i18n-translate.py`
writes `source`, so adoption is a re-run that retranslates nothing.

**2. Wave-mob names stay per-site keyed** (owner ruling, 2026-08-06). Live
campaigns keep the keys they already translate.

**3. The prevention moves to authoring time, where she put it.** Declining the
merge leaves the divergence real, so the rule goes where an author can act on it:
`SKILL.md` *Writing craft* §D — byte-identical entity names collapse to one name
and one translation, wave-mob names do not, and what to do in each direction.
Written as a rule applied while naming, for an authoring agent; the key-scheme
mechanics stay in `compiler.md`, for a compiler maintainer.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AjQ5p1Kv5MrkGPumi7yXWL

* chore(audit): two baked-string findings are closed by this PR — drop their ledger entries

The capability-ownership checker refuses a stale exemption by name: an entry in
BAKED_STRINGS whose constant no longer exists is a FAIL, because "a stale
exemption hides the next one". This PR moves SEAL_HINT_DEFAULT and
BOUNDARY_DEFAULT_MESSAGE behind `dsl::chrome`, where they are inventoried and
translatable — so both stop being baked literals, and the guard fired on its
first real occasion.

Ledger entries dropped; the audit doc's row 23 and its check-B binding count
(5 -> 3) follow, and the demonstrated-firing transcript is marked historical for
B with a surviving entry named for reproducing it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AjQ5p1Kv5MrkGPumi7yXWL

* chore: re-pin content onto the island v1.1 batch

87a8ade -> e4c58c0f78f2e0e949c625697c3f5b04df125aff

Carries content #29 (the flock walks out of the pen), #32 (three actor
nameplates the widened l10n inventory now requires) and #33 (untrack the
absolute-path symlink).

Ordering, established by measurement rather than assumed: this must land
BEFORE #317. A sidecar row the engine does not inventory is tolerated (built
green against today's main), but an inventory key with no sidecar row is
DW0180 and hard — so new-engine-with-old-content reds, and old-engine-with-
new-content does not. The pin therefore leads the engine here. Note this is
the OPPOSITE of the #260/content#21 ordering, where PrefabMeta's
deny_unknown_fields made engine-first the only safe order: the direction is a
property of the change, never a rule of the repo.

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
versions.toml conflict: main re-pinned content to its own tip (e4c58c0f);
this branch pins the walk_y backfill BRANCH. Resolved by re-syncing the
content branch onto e4c58c0f and pinning the new sha — the pin stays a
branch pin because the change is atomic across two repos and has no valid
merge order (deny_unknown_fields on prefab metadata makes content-first
impossible; a walk_y-less library makes engine-first impossible).
These cannot merge separately. The foundation's per-area walk_y datum makes
DW0364 see the island's banks, and DW0364 fires on 26 standable cells at
y=62 — the ocean line — in ALREADY RELEASED content. Owner ruled those
banks intended (2026-08-06), and the answer to an intended shoreline is
spec-0030's `flood`: the author names an envelope, the compiler works out
what the sea reaches inside it and builds that water. DW0364 is untouched;
a cell stops being a violation only by ceasing to be ground.

So the foundation alone breaks a released campaign, and the flood verb
alone has nothing to reason against. One unit.

Conflict in edit.rs was a spliced doc comment, and resolving it fixes a
real defect on the unmerged branch: `Tide` had been inserted INSIDE
`write_cell`'s doc comment, so on `docs/spec-shallows` the first three
lines of `struct Tide`'s documentation are about clearing open-gate
markings and `fn write_cell` has no documentation at all. `Tide` now keeps
only its own doc and write_cell's rejoins write_cell — in main's newer
form, which carries the island-round-21 `open=true` paragraph.
…ed pair

The pin now names `content/island-tideline`, which CONTAINS the walk_y
backfill branch — one content branch carrying both halves this engine
needs, because the pair is atomic across two repositories and has no
valid merge order in either direction.

Capability check C flagged HorizonSpec/ResolvedHorizon as structural
twins. Ledgered ACCEPTED, with the reason stated as a re-checkable
structural test rather than an assurance: one is every field Option +
deny_unknown_fields + serde (what an author may write), the other is
every field concrete with no serde (what downstream reads). Sharing the
type would delete DW0366 — rejecting an `ocean` that declares
`blend_width` is only possible while *absent* and *defaulted* are
distinct states. The entry says to generalise the CHECK on that test if a
third such pair ever appears, rather than grow a third exemption.

Island builds green on this engine, both languages: DW0364 0, DW0394 0,
DW0395 0, 0 errors. Before this merge it was 1 error, DW0364, on already
released content.
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