Skip to content

fix(harness): dsl_version allowlist covers 0.9.0 + compiler-sync CI gate (task #157) - #263

Merged
stellarfeline merged 1 commit into
mainfrom
fix/harness-dsl-0.9.0-allowlist
Aug 4, 2026
Merged

fix(harness): dsl_version allowlist covers 0.9.0 + compiler-sync CI gate (task #157)#263
stellarfeline merged 1 commit into
mainfrom
fix/harness-dsl-0.9.0-allowlist

Conversation

@stellarfeline

Copy link
Copy Markdown
Owner

Summary

  • The hollow-vigil ladder run (task fix(compiler,tools): stealth onset survivability proof — DW0355; one-code-one-rule gate #157) found the bot tier refusing every dsl_version 0.9.0 campaign at the version gate: server boots, bot connects, then critical-path/version unsupported version "0.9.0"; harness supports 0.2.0 … 0.8.0. harness/src/critical-path.ts's SUPPORTED_DSL_VERSIONS allowlist lagged spec-0026's compiler bump to 0.9.0.
  • Extended the allowlist to 0.9.0. Checked for version-conditional harness behavior first: there is none — every consumer (critical-path.ts, waypoints.ts, combat.ts) gates on membership in this one array, and v0.9 (spec-0026) only adds stage-1 world-generation surface (horizon object form, new base/shorthand names) the harness never parses. No other harness file needed a change.
  • Confirmed on main the compiler's own SUPPORTED_DSL_VERSION is still 0.8.0 (spec-0026's compiler-side bump to 0.9.0 lives on the still-open worker/spec-0026-foundation / PR feat(dsl,compiler): spec-0026 foundation — horizon library surface, per-area walk_y datum, DW0364/DW0366/DW0367 (dsl 0.9.0) #260) — this PR only moves the harness ceiling ahead of that landing, per the version-adoption discipline (CLAUDE.md: adoption is scheduled within the milestone, never left to lag).

Durable guard (debug doctrine: automate the pitfall out of existence)

Added tools/check-harness-dsl-version.py, wired into the docs (local link check) CI job next to tools/check-dw-codes.py (same class: deterministic, offline, stdlib-only Python 3 scanning source text — no build required). It asserts the compiler's single SUPPORTED_DSL_VERSION identity constant (crates/dsl/src/envelope.rs) is a member of the harness's SUPPORTED_DSL_VERSIONS allowlist (harness/src/critical-path.ts).

Design choice and why: the compiler already names its own ceiling in exactly one place (SUPPORTED_DSL_VERSION, distinct from the full accepted-set SUPPORTED_DSL_VERSIONS list) — that's the authoritative source of truth, no compiler change needed to expose it. The harness already has one array every JSON-artifact consumer gates on. A text-scanning script comparing the two, run in the existing offline docs job, is the smallest mechanically-enforced form: no new plumbing, no artifact the compiler would need to start emitting, no cross-job dependency. It checks membership of the ceiling, not set equality, so the harness may still carry older versions if the compiler ever drops one from its own list.

Red -> green proof

Added a harness unit test (accepts the 0.7.0, 0.8.0, and 0.9.0 dsl versions) before the fix — it reproduced the exact production error:

✖ accepts the 0.7.0, 0.8.0, and 0.9.0 dsl versions (additive; same path contract)
  Error [CriticalPathParseError]: critical-path/version unsupported version "0.9.0"; harness supports 0.2.0, 0.3.0, 0.4.0, 0.5.0, 0.6.0, 0.7.0, 0.8.0

After adding "0.9.0" to SUPPORTED_DSL_VERSIONS: full harness suite green (320/320), tsc --noEmit clean.

The sync guard itself is proven both ways by tools/tests/test_check_harness_dsl_version.py (test_harness_lagging_the_compiler_ceiling_fails reproduces the task #157 shape over a synthetic source pair — red — test_harness_covering_the_compiler_ceiling_passes — green), and against the live repo: python3 tools/check-harness-dsl-version.py passes today (compiler 0.8.0 ⊆ harness {…, 0.9.0}).

Scope

Validator-only: no crates/ change, player-facing output is byte-identical. Files touched: harness/src/critical-path.ts, harness/test/critical-path.test.ts, tools/check-harness-dsl-version.py (new), tools/tests/test_check_harness_dsl_version.py (new), .github/workflows/ci.yml, docs/reference/tools.md.

Test plan

  • node --test test/critical-path.test.ts — red before, green after (see above)
  • npm run test (harness, full suite) — 320/320 green
  • npm run typecheck — clean
  • python3 -m pytest -q tools/tests — 92/92 green (incl. new sync-gate tests)
  • python3 tools/check-harness-dsl-version.py, check-dw-codes.py, check-doc-dupes.py, check-worker-override.py — all green locally
  • CI green (watching)

…ate (task #157)

The hollow-vigil ladder run found the bot tier refusing every dsl_version
0.9.0 campaign at the version gate — server booted, bot connected, then
`critical-path/version unsupported version "0.9.0"` — because
harness/src/critical-path.ts's SUPPORTED_DSL_VERSIONS allowlist still ended
at 0.8.0 while spec-0026 raises the compiler's ceiling to 0.9.0. v0.9 only
adds stage-1 (world-generation) surface the harness never parses, so every
consumer gated on this one array (waypoints, combat plan) needed no other
change.

Durable guard: tools/check-harness-dsl-version.py asserts the compiler's
SUPPORTED_DSL_VERSION (crates/dsl/src/envelope.rs) is a member of the
harness's SUPPORTED_DSL_VERSIONS, wired into the docs CI job next to
check-dw-codes.py. Nothing else in the repo relates these two files, so a
future compiler version bump now fails loud instead of silently stranding
the bot tier.

Validator-only: no crates/ change, player-facing output is byte-identical.
@stellarfeline
stellarfeline merged commit d722f3d into main Aug 4, 2026
9 checks passed
@stellarfeline
stellarfeline deleted the fix/harness-dsl-0.9.0-allowlist branch August 4, 2026 08:59
stellarfeline added a commit that referenced this pull request Aug 4, 2026
…ene-rect moat fill (task #157 round 3)

1. SNAPSHOT PALETTE. block_color had no cherry entries, so 2044 cherry
   cells painted oak-green through the `_leaves` suffix family — the whole
   point of cherry-valley is that these read pink in every render. Explicit
   1.21.11 texture-average entries: cherry_leaves [229,158,187], cherry_log
   [92,58,58] (bark-dominant), pink_petals [224,168,190] (was falling to
   the magenta UNKNOWN fallback). Sweep: those three were the only gaps in
   the surround emission id set; a test pins the pinks against the suffix
   families and the whole emission palette off-fallback.

2. VISTA FOV (planner ruling). A spawn eye 40 blocks from a 48-high rim
   subtends ~52 degrees — no fixed FOV frames floor + crest + sky. The
   vista camera now aims at MID-rim on the nearest crest line and carries a
   per-shot vertical FOV derived from the geometry: frame bottom <= -15
   degrees elevation (gap floor in frame), top >= crest-top + 8 degrees of
   sky, clamped <= 110. Plumbing verified end to end: render_plan emits
   camera.fov (the existing pov_camera channel) and delve-render's Chunky
   scene emission honors it (scene.rs, default 70 when absent) — a new
   scene.rs test proves the field is never silently dropped, and a surround
   test asserts the 94x27 spawn-eye case frames floor AND crest-top
   arithmetically.

3. SCENE-RECT MOAT (planner design ruling). The #157 walls-down probe
   found DW0322 void exposure INSIDE the scene rect: the rect is the keep
   blob's bounding box, and columns outside every piece footprint were
   void. Scene-rect columns with ZERO piece-authored blocks now receive
   ambient gap-floor ground at floor_top_y with the gap floor's own surface
   dapple (world-coordinate noise - the pattern runs seamlessly from the
   annulus through the interior); columns with any authored block are
   untouched (pieces own their columns; authored overhangs/voids are
   intentional). Emitted as row-strip tiles (horizon/valley/m<n>) so no
   tile ever carries an air cell over a piece column; attached at the
   read_structures choke point (the authored-column set needs the piece
   bytes), so build, snapshot, blocking and the render plan all see the
   same completed surround; the un-climbability flood seeds on moat cells
   too. Fixtures with full plates emit zero moat tiles - existing
   emissions byte-identical.

Proofs (all green): snapshot palette test; scene.rs per-shot fov test;
vista_fov_frames_floor_and_crest_on_hollow_proportions;
the_moat_fills_only_unauthored_scene_columns (L-blob, authored columns
untouched, every unauthored column surfaced, byte-deterministic);
v09_blob_scene_with_void_moat_builds_green (walls-down irregular blob in a
94x27 rect - no DW0322 at the rect edge or interior). Spec-0026 amendment
text (short) + compiler.md same PR.

Note: the bot tier on this branch is stale-red only because the branch
predates merged #263; the batch integration branch carries the fix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
stellarfeline added a commit that referenced this pull request Aug 5, 2026
… kill tally (task #178) (#268)

* chore(harness): pick #263 (dsl_version allowlist covers 0.9.0) onto this base

worker/spec-0026-valley predates the fix, so the bot tier refuses every
0.9.0 campaign from this base and no ladder run is possible here. Identical
patch to the one already on main; it drops out on rebase/merge.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RnrGdWS7ADsjCoJfiJc6GC

* fix(compiler,harness): the wave countdown is a liveness census, not a kill tally (task #178)

A `kill` objective asks for the wave to be DOWN. The emitted countdown asked
for something else — one player-credited kill per mob — and a mob has many
ways to die that credit nobody: burning in daylight, falling out of the world,
drowning, another mob. Every such death took a mob out of the world and left
the countdown above zero, asking the party to kill something that no longer
existed. No rest, no retry and no route recovers from that.

hollow-vigil shipped it (owner playtest 2026-08-05). Its opening beat's roof
and two walls come off for the open-air rule, the world is pinned at
`time set noon`, and the first wave walked out through the carved wall and
burned — `空洞步卒 burned to death`, reproduced live on the shipped datapack.
Countdown stuck at 3 with nothing alive, so `obj/purge` never completed,
`flag/halls-quiet` never set, `quest/the-barrow-key` never armed, and the
barrow key's chest was never placed anywhere in the world.

Design change, in two halves.

**Emission.** `tick` now reconciles the countdown against the world, once per
wave a `kill` objective adjudicates:

    execute if score #<w> dw.wave matches 1.. store result score #<w> dw.wave \
        if entity @e[tag=dw_wave_<w>]

Vanilla's own answer to "how many are left", so no new primitive and no
bookkeeping to keep honest. `matches 1..` does both guard jobs: before the
spawn the counter is unset, so an empty selector can never read as "cleared";
after a legitimate zero the line stops firing, so a finished wave stays
finished and a `respawns_on_rest` re-seat re-arms it cleanly. The
`player_killed_entity` decrement stays as a fast path — a mob the player just
killed is still matched by `@e` through its death animation.

**Harness.** `kill` was the one objective-bearing step that never called
`requireObjective`; it graded itself on mob-side evidence and returned. That
is why an all-green ladder shipped a softlock: `passed: true`, both encounters
census-cleared, and the delve considered the objective open forever. The
census still decides when to stop swinging; it no longer decides whether the
step is proven. `critical-path.json` format 2 already DOCUMENTED this contract
— `kill` simply did not implement it.

Ladder proof (same world both sides, `hollow-vigil` @ ce82515):
  * generated PackTest `kill_uncredited` — `verb_kill` minus the hand-fed
    `k_reward_<wave>` calls. Shipped datapack: 19/20, `Expected #party
    dw.o_warden to match 1, but got 0`. Fixed datapack: 20/20.
  * bot tier on the fixed build: `critical path 'hollow-vigil' PASSED`.
  * live rcon on both builds: countdown 3→3→3 (stuck, 0 alive, no chest) vs
    3→1→0 tracking the burn exactly.

Determinism: two builds byte-identical; the datapack diff against the staged
build is exactly the two census lines.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RnrGdWS7ADsjCoJfiJc6GC

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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