fix(compiler,harness): the wave countdown is a liveness census, not a kill tally (task #178) - #268
Merged
Conversation
…his 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
… 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
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.
The divergence
The owner played the staged
hollow-vigilbuild (engineworker/spec-0026-valley@ 649a164, content @ ce82515) on 2026-08-05 and hit a hard softlock: after the first zombie wave, the campaign never advances and there is nowhere to obtain the barrow key. The #157 ladder on a byte-identical build was all green —delvec buildclean, PackTest 19/19, botpassed: truewith both encounters census-cleared.Both were telling the truth about different games.
The delve's rule.
#<wave> dw.wavewas a tally of PLAYER KILL CREDITS:spawn_<wave>seeded it with the wave size, and only theplayer_killed_entityadvancement decremented it. The party's rule. Akillobjective asks for the wave to be down — and a mob has many ways to die that credit nobody. This campaign's opening beat had its roof and two walls carved off for the open-air rule, the world is pinned attime set noon, and the wave stands one door from the yard. The footmen chased the party out and burned.Reproduced live on the shipped datapack (rcon, worker-isolated server):
both outside the carved north wall. Terminal state:
obj/purgeflag/halls-quietquest/the-barrow-keyarmedtrial_keyWhy every rung was green
verb_killkills the wave and then callsk_reward_<wave>once per mob by hand — it supplies the player-kill credits itself. It asserts the countdown arithmetic, never the causal link "the mob died ⇒ the countdown moved".MineflayerExecutor.killwas the one objective-bearing step that never calledrequireObjective.talk-to,reach,collect,interactall end on the delve's own[dw:complete …]marker;killended on MOB-side evidence — confirmed kills, every engaged mob down, the server's live wave census — and returned.critical-path.jsonformat 2 already documented the marker contract forkill; the harness simply did not implement it.So the machine proved: the room can be emptied. The player needed: the campaign accepts that the room was emptied.
What changes
Emission — one line per wave a
killobjective adjudicates, intick, ahead of every completion check: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: beforespawn_<wave>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 arespawns_on_restre-seat (whosekill+spawnare atomic) re-arms cleanly. The advancement decrement STAYS as a fast path — a mob the player just killed is matched by@ethrough its ~20-tick death animation, so without it the final blow would land a second before the objective noticed.Harness —
killnow ends onrequireObjective(step.objective)like every other step. The census logic is untouched and still decides when to stop swinging; it no longer decides whether the step is proven.Ladder — new generated PackTest
kill_uncredited:verb_killminus the hand-fedk_rewardcalls. The same wave, wiped the same way, with nobody credited for it — the objective must complete anyway. Atomic (one tick, noawait), so the batch model needs no new reasoning about it.What CI now proves
datapack/+ this branch'spacktest-datapack/:1 required tests failed :( - hollow-vigil:kill_uncredited: Expected #party dw.o_warden to match 1, but got 0(19/20).All 20 required tests passed :).critical path 'hollow-vigil' PASSED (9 steps, 2 advisory finding(s)).docs/reference/compiler.mdrecords both halves (thekill/spawn-waverow and thecritical-path.jsonformat-2 note).Not fixed here
The wave can still be won by the sun rather than the party — a difficulty defect, not a completability one. The DSL already carries the sanctioned remedy (
equipment.head, "neverset-time"); the content fix isdelvewright-campaigns#worker/hollow-vigil-daylight-helm. The compile-time version of it — a diagnostic for daylight-burning undead whose arena is walk-reachable from sky-open cells, buildable on the existingcompiler::navBFS andlight.rs::sky_open— needs an owner-approved spec and is proposed, not built.Merge gate
Engine emission + campaign content, so the owner's own playtest is the gate (batched, never per-PR). The machine red→green above admits it to her batch; it does not replace her.
Branch is based on
worker/spec-0026-valley@ 649a164 so it stacks on the build she will replay; it carries a pick of the already-merged #263 so the bot tier can run 0.9.0 from this base.🤖 Generated with Claude Code
https://claude.ai/code/session_01RnrGdWS7ADsjCoJfiJc6GC