feat: schem→nbt converter (delve-schem) — Sponge v2/v3, safety strip, oversize split - #32
Merged
Merged
Conversation
New `delvewright-schem` crate + `delve-schem` binary: converts Sponge schematics (v2 and v3) into deterministic 1.21.11 structure templates (DataVersion 4671) for the prefab admission pipeline. - Parses both Sponge layouts (flat v2 root; v3 `Schematic`/`Blocks` wrapper), palette block-states with properties, LEB128 varint block data, and position-rebased block entities. - Safety strip (community-contract audit hook): unconditionally removes command, structure, and jigsaw blocks plus the spawner family (mob_spawner / trial_spawner / vault) and any block entity carrying an embedded command or spawner definition, emitting a per-strip DW0700 warning. `--palette-report` prints the admission allowlist feed. - Oversize splitting past the 48-cube cap into `<base>.xIyJzK.nbt` parts plus a `<base>.split.json` reassembly manifest. - Determinism (ADR-0006): BTreeMap-backed NBT value type (fastnbt's Value is a HashMap), sorted palette, fixed block iteration, gzip mtime 0 — proven by a double-convert byte-identity test. - In-code v2/v3 reference fixtures (no network); tests cover block-for-block round-trips, v2/v3 convergence, strip + chest survival, byte-identity, and lossless split reassembly, plus end-to-end CLI tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
stellarfeline
added a commit
that referenced
this pull request
Jul 31, 2026
Harden the gap-8 transport wait so the critical-path bot reliably survives a compiler-emitted cross-area `teleport @s` (areas sit ~256 blocks apart across void). The teleport itself always registers via mineflayer's `forcedMove`; the race was the harness racing ahead of it into the next step's pathfinding. - Reset the pathfinder on a large server-forced position jump (`forcedMove` handler), so a path computed in the OLD area cannot survive the teleport and strand the next step with a spurious "No path to the goal!". - After the jump lands, wait for the destination chunk to load and the bot to rest on solid footing (`onGround` + `blockAt != null`) before the next step pathfinds — closing the race where A* starts against an unloaded chunk. - Factor the transport/footing waits through one bounded, death-aware `waitFor` helper, so nothing can hang the run and a mid-transport death still fails fast (PR #50 death fast-fail exit 3 preserved; no death/select-class changes). Navigation/assertions only. New unit tests cover jump-wait, footing-hold, pathfinder reset on the jump, death-abort, and the large-jump threshold. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
stellarfeline
added a commit
that referenced
this pull request
Jul 31, 2026
…59) Harden the gap-8 transport wait so the critical-path bot reliably survives a compiler-emitted cross-area `teleport @s` (areas sit ~256 blocks apart across void). The teleport itself always registers via mineflayer's `forcedMove`; the race was the harness racing ahead of it into the next step's pathfinding. - Reset the pathfinder on a large server-forced position jump (`forcedMove` handler), so a path computed in the OLD area cannot survive the teleport and strand the next step with a spurious "No path to the goal!". - After the jump lands, wait for the destination chunk to load and the bot to rest on solid footing (`onGround` + `blockAt != null`) before the next step pathfinds — closing the race where A* starts against an unloaded chunk. - Factor the transport/footing waits through one bounded, death-aware `waitFor` helper, so nothing can hang the run and a mid-transport death still fails fast (PR #50 death fast-fail exit 3 preserved; no death/select-class changes). Navigation/assertions only. New unit tests cover jump-wait, footing-hold, pathfinder reset on the jump, death-abort, and the large-jump threshold. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
stellarfeline
added a commit
that referenced
this pull request
Aug 4, 2026
…e-leg is success (#134) (#235) A step can complete MID-WALK: the tide-mill `obj/wheelpit` reach fires its distance check as the bot lands a timed-gate crossing, and the objective's emission teleports it to the next area over a physically one-way transport. The leg's remaining hops then fail on the position discontinuity, and the harness read that as the gate blocking a leg the bot had already walked: three gate 'attempts', then a re-center toward a cell the one-way transport makes unreachable — six red runs of a campaign whose server state said dw.o_wheelpit = 1 the whole time. The walk's failure paths now consult a completion oracle (LegSettled) built from the two authoritative signals the harness already consumes: the step objective's anchored [dw:complete] marker, and the bot standing at the step's compiler-exported transport destination (the same arrival predicate awaitTransport uses). Either one ends the leg as SUCCEEDED — no window wait, standoff, or recovery may path the bot back across the transport — and the leg cursor already sits at the next area's leg, so navigation resumes there. Failure paths only: a healthy hop is never shortcut, and a genuinely blocked leg with an unsettled step fails exactly as before. No compiler change needed: critical-path.json already exports per-step transport destinations (gap 8, task #32); the harness had only consulted them AFTER a step succeeded, never while its walk was still being judged. Co-authored-by: Claude Fable 5 <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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed (design level)
New workspace crate
crates/schem(spec-0007 shared machinery item 1):delve-schemconverts Sponge schematics (v2 and v3, auto-detected) into vanilla structure.nbtpinned to MC 1.21.11 (DataVersion 4671).Command/spawn data are stripped conservatively.--palette-reportfeeds the future admission allowlist audit.--splittiles >48³ inputs into deterministic grid parts + a reassembly manifest.What CI now proves
11 new tests: round-trips, v2/v3 convergence, byte-identity, strip policy, lossless split reassembly, CLI exit codes. Workspace fmt/clippy/test green. No existing crate touched.
Mechanical-class PR → auto-merge on green.
🤖 Generated with Claude Code