Feature: difficulty director and normal flying formations, live - #34
Conversation
The locked difficulty-and-formations spec described the set-formation table index as "that offset plus the current AI level, doubled". The reference it cites does not add the AI level on that path: - sub_2_fn_2__set_flying_enemies indexes by the record's signed offset alone (sign-extended, doubled to address the 2-byte entries). - sub_2_fn_3 (the raise record) indexes the SAME table by the raised, folded AI level instead — with no record offset. The spec fused the two paths into one wrong "offset + level" formula. Under that reading the real committed schedules drive the index to 233, far past the table's -32..127 domain; under the reference reading, set offsets (-26..126) and the folded AI level (0..127) both land in range. The committed formations.json (domain -32..127, sized to the offset range) independently corroborates the reference, so only the prose was wrong — no data changes. Correcting a status:locked spec — carries guardrail-ack at merge. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…1, FORM-01) Wire the first behaviour into slice 6's empty schedule-dispatch seam, to the reference-faithful model the spec correction (prior commit) restored: - DIF-01: an `ai level` accumulates live from `raise_ai_level_and_set_formation` records (cabinet increment [2,0,6,16] at a project-defined DIP index 0 = +2), folding back below 0x80. Its own `difficulty_reset` receiver zeroes it on a world reset; it persists across a life. - FORM-01: the incoming wave's `formation count` + `formation type offset` are selected from the normal formation table baked as two parallel 160-lists (index -32..127). set-formation indexes by the record offset; raise by the folded AI level; reset zeroes. A two-sided guard skips an out-of-domain index (Scratch item-of-list returns "" out of range); a range fixture proves the guard is a dead defensive branch under this slice's raise/set dynamics. - A 4th parallel schedule column `schedule arg` carries the one runtime-readable scalar each dispatched record needs (Scratch can't parse the JSON payload). Verification: model fixtures over the committed data (baked-table golden, full lookup reproduction, the in-domain range proof, the fold-back math) in test_spec_docs; the extended round-trip golden covers the new column; the `area-scheduler` structural checks track the now-wired dispatch; a live scratch-vm harness scenario (difficulty-and-formations) with a biting negative; build_hash re-pinned; two builds byte-identical + round-trip. Records 019/020; catalog DIF-01/FORM-01 -> present; playtest step 4 gains the difficulty watchers. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wire the adjust_ai_level_from_score handler into the schedule dispatch: when an adjust record fires it adds floor(floor(score/1000) / craft), capped at 16, to the AI level, guarded on craft > 0. Reserve is the live `craft` count (the reference divides by solvalou_number with no subtraction). Unlike the raise, the reference does not fold this add back. - `ai adjust` transient working register (machinery). - Model fixture (test_score_retune_rule) reproduces the rule: sub-1000 floor, the cap at 16, the craft-zero guard. - Fixed the PLY-02 death-decision negative fixture to target the death `craft > threshold` specifically (a control_if_else condition), not DIF-02's new `craft > 0` re-tune guard. Record 021; catalog DIF-02 -> present; playtest step 4 notes the score-adjust jump. build_hash re-pinned; two builds byte-identical + round-trip; harness green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wire the eight fire_mask_<family> handlers and ground_stop_firing_row into the schedule dispatch: each sets its Stage variable from the schedule arg column as its record fires. The eight branches + variables are generated from one family table (FIRE_MASK_FAMILIES) so a family cannot be half-wired; all reset to 0 on a world reset via difficulty_reset. Held as eight named variables (not one indexed list) because the slice-8 consumers are per-family sprites reading one mask each. The per-family FIRING that consumes these masks is the enemy slices' (8+). Verification: the mask bytes round-trip via the schedule arg golden; a live scratch-vm scenario (fire-permission-masks) confirms the masks are set to their scheduled values, with a biting negative on the logram branch. Record 022; catalog DIF-03 -> present. Also adds the roadmap closure-evidence markers for leaf #56 (DIF-01/02/03/FORM-01 model obligations) to the difficulty model-fixture class, per the dependency-aware roadmap contract merged (#36) while this slice was in flight. build_hash re-pinned; two builds byte-identical + round-trip; full suite + harness green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
cd68e27 to
e29f461
Compare
Post-review fixes (no block-graph change — build hash unchanged, dfd51ede): - Prove, don't assert, the formation-domain guard: the range fixture now models DIF-02's un-folded score adjust at its worst-case +16 over many loop cycles, so the "guard is dead" claim holds under the slice's FULL live dynamics, not just raises/sets (flagged by three review passes). Record 021 tightened to match. - Cover all nine DIF-03 targets live: the harness fire-permission-masks scenario now reaches area 4 and asserts fire_mask_andor_genesis and ground_stop_firing_row are set (previously unexercised). - Correct stale "later commit" comments in game_director.py and record 020 — DIF-02/DIF-03 ship in this same PR, not later. - Add the DIF-03 fire-mask / ground-stop watcher to the playtest checklist. - Align MECHANICS_CATALOG DIF-01/02/03/FORM-01 to "partial" (foundation). - Regenerate .engine/product-spec-matrix.json (the derived settled-acceptance record) after the locked spec's formation acceptance-criterion was corrected — the engine's settled-criteria check requires it to match the spec. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fa1d42d to
e8d284f
Compare
|
These engine-internal files are regenerated by the update. The engine rebuilds them from your project's own current state on each update. A change here that reflects a real change in your project — you edited what the file is generated from and regenerated it — is reproduced, not lost. But content typed directly into one of these files, with no matching change to the source it comes from, will not survive: it is rebuilt away on the next update. So if you meant to change what one of these records, edit the source it is generated from (for the settled-criteria record, your
This is a heads-up only — it does not block your merge, and your merge is the decision. |
|
Operator playtest approved on head commit Verified on the Scratch variable watchers (no enemies this slice): the AI level accumulates and folds back below 0x80, formations select a valid wave (count 1–6) with a type-offset, and the per-family fire masks + ground-stop row take their scheduled values as the area scrolls. |
Closes #56
Purpose
Make enemy pressure real: the adaptive difficulty and the normal flying formations now run live in the game, the last foundation before enemies can be scheduled to attack.
Impact: enemy behaviour in slices 8+ now has a live difficulty number and a selected formation to read; nothing stochastic is added (difficulty/formations are deterministic, so the shared RNG stays dormant one more slice — first consumer is enemy behaviour).
Scope
A live wiring of difficulty + formations into slice 6's dispatch seam, plus a faithful correction to the locked difficulty spec — no data re-extraction.
_consume_schedulegains handler-keyed branches: raise (AI level += DIP increment[2,0,6,16], fold back at0x80, re-select formation from the folded level), adjust (score re-tune), set/reset formation, the 8fire_mask_<family>setters, and ground-stop-firing. A 4th parallel schedule columnschedule argcarries the one runtime-readable scalar each record needs (Scratch can't parse the JSON payload).ai level,formation count,formation type offset, the 8 fire masks,ground stop firing row(all reset on a world reset via a dedicateddifficulty_resetreceiver); plus machinery registersformation index,ai adjust. The formation table (formations.json, index −32..127) is baked as two parallel logical lists.docs/spec/difficulty-and-formations.md(astatus: lockeddocument) — prose only, no data change.Change profile — the shape of this pull request at a glance:
Part of #Nline below says it is one slice of a larger effort.This is a description, not a gate — it never blocks a merge.
Part of #18.
Impact: the +7201 lines are almost entirely the regenerated
src/xevious/project.json+runtime_identifiers.json(the baked tables and dispatch); the executable change is the dispatch,_select_formation, and the schedule loaders intools/game_director.py.Behaviors
Difficulty and formation state are computed live from the real schedules, and the formation index is proven to stay in its table domain.
0x80—DifficultyAndFormations.test_ai_level_fold_back; live in thedifficulty-and-formationsscratch-vm scenario.test_formation_lookup_reproduces_committed_table.test_formation_index_in_domain_over_committed_schedules(so the two-sided guard is proven-dead, not asserted).min(16, floor(floor(score/1000)/craft))forcraft>0—test_score_retune_rule.fire-permission-masksscenario (exercises Andor Genesis in area 4 and the ground-stop row); theschedule argcolumn round-trips —test_all_area_schedules_round_trip_from_json.test_full_repository_verification(build hashdfd51ede…).Out of scope
Everything that needs enemies on screen.
Impact: these are dependencies not yet built, recorded as such; nothing here is a silent stub.
Risk
Low behaviourally — the one real hazard (an out-of-domain formation index) is proven unreachable and fail-safe; the notable governance item is the locked-spec edit.
item N of listreturns""out of range); an out-of-domain index leaves the prior formation, and the range fixture proves it is never reached under raises + sets + DIF-02's worst-case adjust. Residual: exact in-game index fidelity (that the emitted blocks compute the right slot) rests on the deferred operator playtest, per the spec's engine/operator split.craftcount with no subtraction (matching the reference'ssolvalou_number); whether that is off-by-one against a strict "in reserve" reading is recorded as residual uncertainty in record 021.status: lockedspec. That is a change to settled ground and needs yourguardrail-acklabel on this PR — a deliberate confirmation separate from the merge click. No automated gate enforces it (the weakening guard's floor does not coverdocs/spec/), so it rests on your applying the label; the edit itself is a faithful, prose-only correction verified against the cited reference.Impact: a wrong formation slice fails the range fixture or shows in playtest; it cannot ship silently. The locked-spec change is disclosed for your guardrail-ack.
Validation
All mechanical checks green; the build is byte-deterministic; the reviewed artifact is byte-identical to the submitted one.
unittest discover -s tests), including 24 new roadmap-closure tests carried in frommain.node --testinharness/), including the two new live scenarios and their biting negatives.tools/scratch_project.py verify, sha256dfd51ede…); generator at its fixpoint (game_director.py check).selftest.py, exit 0) — this change touches no engine surface, so the engine's own index files are unchanged.Impact: an approver can rely on the difficulty/formation state being faithfully computed and the build reproducible; the live gameplay effect is the operator's playtest.
Review
Standard-depth review: four cold plan-review passes before building and four cold pre-submission passes after — no blocking findings; the serious findings were test-coverage/accuracy gaps, all fixed in line.
main; nothing committed changed) and independently simulated the difficulty dynamics over the committed data. Spec-conformance independently re-read the reference assembly and confirmed the correction is faithful. Technical-integrity mechanically confirmed zero reporter block-id reuse anywhere in the project and the determinism. No blocking findings.craftoff-by-one uncertainty. Escalated to you: the locked-specguardrail-ack(Risk), and an optional engine hardening (adddocs/spec/to the guarded-paths floor) I can log as a tracked issue..engine/product-spec-matrix.json) that the spec correction required — no block-graph change, so the shipped artifact is byte-identical (build hashdfd51ede…unchanged). Between reviewede29f461and submittede8d284f, 51 lines were added and 28 deleted or modified, a net change of +23 lines. Given the tiny size and byte-identical output, no re-audit was warranted; the full suite, harness, and determinism were re-checked green.Things you can confirm yourself
Things I checked for you
(these ran on the engine's side — listed so you know what was checked; nothing for you to do)
python3 tools/reference_extract.py --verify --checkout <clone>with a fresh clone at the pin (clone recipe in the index); the run passes or names the failing tableImpact: this is the engine's own account of the review; your merge is the binding gate.
Demonstration
Run the model fixtures, and watch the difficulty/formation state live on the variable monitors.
python3 -m unittest tests.test_spec_docs.DifficultyAndFormations -v— the model fixtures over the committed data (baked tables, formation lookup, fold-back, score re-tune, and the in-domain proof over the full dynamics). They fail on a wrong table, a wrong index rule, or an out-of-domain index; they pass here.ai level,formation count,formation type offset, afire mask *, andground stop firing rowwatchers and fly —ai levelclimbs and stays below 128, the formation values change to valid waves (count 1–6), and the masks take their scheduled bytes. Read the growth rate as placeholder-driven (the DIP is a project default), not fidelity — only the mechanism is confirmed; no enemies spawn from it yet.Impact: you can watch the difficulty director and formation selector run live against the real schedules, and run one command to see the models proven against the committed data.
Files of interest
The dispatch, the formation selector, and the four records.
tools/game_director.py—_consume_schedule(the handler dispatch),_select_formation(the two-sided guard),_schedule_arg/_load_formation_tables, and thedifficulty_resetreceiver.tests/test_spec_docs.py— theDifficultyAndFormationsmodel fixtures (incl. the full-dynamics range proof) and the roadmap-evidence markers.harness/lib/catalog.js— thedifficulty-and-formationsandfire-permission-maskslive scenarios.docs/spec/difficulty-and-formations.md— the locked-spec formation-index correction.docs/mechanics/019–022— the DIF-01/02/03 + FORM-01 records.Impact: the dispatch and the range fixture most determine whether the difficulty/formation state is faithful.
AI involvement
Claude Code (Opus 4.8) authored the change end to end under the engine's Build workflow; the operator chose the review depth, approved the plan and the live-state scope, and directed the locked-spec correction.
guardrail-ack, and merge are the binding gates.Impact: the mechanical checks and the operator's playtest / guardrail-ack / merge, not the AI's account, are what gate the change.