Skip to content

Feature: Toroid vertical slice — first live flying enemy (slice 8) - #110

Merged
StarshipSuperjam merged 12 commits into
mainfrom
claude/xevious-toroid-slice8
Sep 2, 2026
Merged

Feature: Toroid vertical slice — first live flying enemy (slice 8)#110
StarshipSuperjam merged 12 commits into
mainfrom
claude/xevious-toroid-slice8

Conversation

@StarshipSuperjam

@StarshipSuperjam StarshipSuperjam commented Sep 2, 2026

Copy link
Copy Markdown
Owner

Closes #65
Closes #66
Closes #57
Closes #58
Closes #59
Closes #61
Closes #62
Closes #63
Closes #64

A green mechanical check below shows this change conforms to the engine's rules — not that it is correct. What covers correctness is the behavioural steps in Review and your own read of the change; a green check is never a substitute for that. Your merge is the binding gate.

About those checks: only the one that runs when the change is proposed for merge can stop a risky merge — a check that ran while the change was still being written is early advice. Each check is itself proven against a deliberately broken example it must catch, so a passing check can't be one that quietly did nothing — but that proves the check works, not that this change is right. And a check that could not run leaves its area unverified.

Purpose

The first complete enemy, end to end — the Toroid vertical slice — turning the dormant entity/RNG/formation foundations into live combat.

  • Formation spawn → aim-approach → lateral swing → flap → single aimed shot (shooting variant) → enemy bullets → blaster-to-air hit (+30, explosion) → player-collision death → cleanup.
  • First live consumer of the shared RNG (record 004) and of the slice-7 formation state; retires the debug D/G death keys and the S scoring fixture that stood in for real combat since slice 4.

Impact: area 1 now fights back — the first live enemy the player can be killed by and can destroy for score.

Scope

Toroid (AIR-01) plus the eight integration leaves that ride on it — the live dispatch walk, collision, RNG draw, air-hit award, and air-collision death.

  • AIR-01 Toroid (record 024) — spawns from the live formation, aim-approaches (1.5 px/frame), swings laterally by reversing its course (arcade toroid_toggle_dir bounce) when it draws level, flaps; culls off any field edge keeping its scroll position.
  • AIR-12 standard bullets (record 026) — the shooting variant (type 0x0B) fires one aimed bullet at its swing commit; the bullet flies straight and culls off any edge.
  • WPN-02 / SYS-03 / ECO-01 (record 025) — a shot overlapping an active Toroid resolves once, awards 30, and the enemy explodes and is freed.
  • PLY-02 — a Toroid or an enemy bullet touching the craft raises the hit; the walk's death check (gated player hit == 1 AND invuln == 0) spends a craft and respawns or ends the game.
  • SYS-02 / SYS-04 — the index-order dispatch walk and live occupants; AREA-02 / FORM-01 / DIF-01 — the formation now spawns live waves.

Change profile — the shape of this pull request at a glance:

  • Size: 33 files changed, +32906 / −12287 lines (the bulk is the regenerated src/xevious/project.json, a single generated artifact whose block IDs re-flow on every build).
  • Kinds of thing touched: generated game logic, tests, headless harness, mechanics records, two locked specs.
  • Where: docs, harness, tests, tools, src, .engine.
  • Shape: one slice of the roadmap; Part of #60 (see Out of scope).

Impact: the entity/formation/RNG scaffolding built dormant in slices 1–7 becomes a live, playable enemy loop.

Behaviors

The capabilities this change delivers, each with the check that exercises it.

  • A live Toroid advances under its own velocity each tick — harness toroid-wave-spawns-and-moves.
  • A drawing-level Toroid reverses its lateral course (bounce, not homing) — harness toroid-swing-reverses-away.
  • A player shot destroys an overlapping Toroid and scores 30 once — harness blaster-kills-toroid-and-scores.
  • The shot hit box spans the rendered Toroid width but no further — harness air-shot-hit-column-bounded.
  • The craft dies only on a Toroid's exact cell, not the row/column near it — harness craft-collision-is-single-cell.
  • A fired shot cannot step over a Toroid between frames (no collision tunneling) — Python invariant B8-no-tunnel.
  • A Toroid or bullet touching the craft runs death → respawn / game-over — harness death-respawn, death-game-over.

Out of scope

Everything past the Toroid loop — recorded, deliberate deferrals.

  • Fire-permission mask consumption (DIF-03.play) — the shooting Toroid fires once without consuming any mask; Part of #60, reassigned to slice 10 (mask-consuming families).
  • The add_object flying branch (Torkan/Kapi/Terrazi, slice 10); radiating bullets + Bacura-vs-craft (slice 11).
  • Dedicated enemy/bullet crops and the bullet colour pulse (a later art pass — the bullet renders as a small stand-in and the kill explosion reuses the player death-burst frames); the flying-enemy hit sound (slice 20 audio).

Impact: these are named follow-ups with their own slices, not silent gaps — the Toroid loop is complete on its own.

Risk

Two locked product specs are amended (needs guardrail-ack), and the collision windows deviate from the reference for playability.

  • Locked-spec amendments — needs guardrail-ack. docs/spec/core-game-systems.md (removes the temporary D/G debug death keys now that live combat is lethal) and docs/spec/player-craft-and-weapons.md (records the shot-vs-flying hit window and its doubling). The fail-closed product-lock-integrity check names both and blocks merge until the single global guardrail-ack label is applied.
  • Shot hit window doubled from the reference (16,32,8,16) to (32,64,16,32) — a recorded deviation that (a) prevents collision tunneling (the shot's 2.5-cell/frame step exceeded the 2-cell reference window) and (b) matches the 36-px rendered Toroid. The craft hurtbox is left tight on purpose (forgiving offence, precise defence). Guarded by the B8-no-tunnel invariant.
  • Timing/feel is not machine-verifiable. Nothing in the repo runs a real Scratch VM, and the headless harness runs threads to settling (no per-frame stepping) — so tunneling, swing feel, and hit feel rest on the operator playtest, not a check.

Impact: the guardrail-weakening is fenced to two named spec edits under your explicit label; the deviations are documented in records 025/026 and bounded by the invariant test.

Validation

Full automated suite green; feel confirmed by the operator playtest.

  • 193 Python tests, 37 headless harness scenarios (each with a biting negative), both generator check()s, mechanics-record and roadmap validators — all green.
  • Two builds byte-identical (build_hash 0c7c82db…); build→import round-trip holds.
  • CI: functional checks (runtime-harness, CodeQL/Analyze, engine-guard, secret-scan, actionlint, overlay-disclosure) pass; the two red checks are the consent gates (engine-ciguardrail-ack, xevious-projectplaytest-approved + owner playtest comment).

Impact: an approver can rely on the logic layer being exercised and deterministic; the on-screen layer is the playtest's, recorded below.

Review

A Standard pre-submission review ran cold (five passes) at 515db05; the operator playtest then caught three defects the review missed, each fixed and re-verified — the merged build differs materially from the reviewed one.

  • Depth: Standard. Passes that ran (plain checks): spec-conformance, an adversarial divergence hunt against the locked specs, technical-integrity, security-governance, and usability, plus a scoped re-audit. Findings were dispositioned: re-anchored an ENGINE-TODO marker; a generate-time formation/slot-capacity assert; a bullet-dispatch structural clause; disclosure of the two interim art stand-ins; stale-comment/estimate fixes. No blocking security/governance defect (the aiming.json extraction is instruction-derived; invuln is test-only with zero writers; D/G/S fully removed).

  • Material change after review — re-verified. Between reviewed 515db05 and submitted 299dc37, 10 files changed, 5477 insertions / 3698 deletions (net +1779 — the large figure is the regenerated project.json; the source/logic change is the swing revert plus two collision fixes plus their tests/docs). The operator playtest (this project's terminal gate for feel, since nothing runs a real VM) drove three fixes the cold review got wrong or could not see: (1) the Toroid swing was flagged inverted by the review and "fixed" to home; the playtest showed the arcade Toroid reverses — reverted and grounded against the pinned disassembly (toroid_toggle_dir), guard flipped to toroid-swing-reverses-away. (2) both collision windows reused one axis-delta reporter across their two bounds, and a Scratch reporter attaches to only one parent, so the second bound stole it — the box degraded to a quadrant/row; rebuilt each delta fresh per compare, added off-target MISS scenarios. (3) the shot tunneled through Toroids (2.5-cell/frame step > 2-cell window); window doubled and guarded by B8-no-tunnel. Each fix re-ran the full suite green. These are validated by the playtest, not by a re-run of the cold review.

  • Spec-derived acceptance steps (from spec_referent.py review-steps --doc docs/spec/aerial-enemies.md) — only the shared-rules and Toroid paragraphs are live this slice; the other families' steps are for their slices:

    Things you can confirm yourself

    • Each family plays its recorded pattern — approach, trigger, fire rule, exit: Play scheduled waves of each family in the built .sb3 against this document's descriptions
    • A Zakato that fires its shot self-destructs scoring nothing; one killed first scores: Play: let one fire, kill one early
    • A Garu Zakato left alone rings 16 bullets and releases four Brag Sparios: Play (or seeded fixture) the detonation
    • Sheonites cannot be killed and track the craft in the recorded pincer-and-dock pattern: Play the boss approach

    Things I checked for you

    • Every family's speeds, fuses, windows, and per-variant differences in the build's data match this document: Data-table comparison fixture over the build's generated family constants
    • All families share one blaster hit window and one explosion; Giddo Spario's short variant excepted: Structural fixture over the build's collision and animation data
    • Fire timing draws from the shared stream under the family mask (seeded waves repeat exactly): Seeded fixture: identical seeds reproduce identical wave behavior

Impact: the engine's account is that the logic conforms and was reviewed; the swing/collision/tunneling corrections rest on the operator playtest — your merge is the binding gate.

Demonstration

Open the delivered .sb3 in Scratch 3 / TurboWarp and play area 1 — the behaviors fail visibly if broken.

  • Toroid waves appear after the first formation record and keep refilling until a reset row; a Toroid draws level and bounces away (not into you); the shooting variant fires one aimed bullet at its swing.
  • Shoot a Toroid you're lined up under → it explodes and the score rises by exactly 30; held fire mows down a column of Toroids (the fix that this PR's last commit addressed — before it, shots passed through).
  • Touch a Toroid or its bullet → death → respawn (craft remain) or GAME OVER (last craft), immediately vulnerable.
  • Stop and green-flag with a wave on screen → no orphan clones survive.
  • Interim art (note, don't fail): the enemy bullet is a small stand-in costume; the kill explosion reuses the player death-burst frames.

Impact: the operator can watch every claimed behavior work or fail; the full sweep is docs/PLAYTEST_CHECKLIST.md.

Files of interest

The generator, the two locked specs, the tests, and the four mechanics records.

  • tools/game_director.py (spawn/aim/swing/cull, both collision windows, the walk) and the regenerated src/xevious/project.json.
  • docs/spec/aerial-enemies.md (settled this slice), docs/spec/core-game-systems.md + docs/spec/player-craft-and-weapons.md (locked amendments — guardrail-ack).
  • tests/test_scratch_project.py (structural contracts, B8-no-tunnel, pinned build_hash), harness/lib/catalog.js (live scenarios).
  • docs/mechanics/023–026-*.md.

Impact: the generator and the two locked specs most determine whether this merge is sound.

AI involvement

Claude Code (Opus) authored the slice end to end under the operator's direction and playtest gate.

  • AI drove: the block-DSL generation, the reference-grounded behavior port (aim/swing/cull/collision), the tests and harness scenarios, and the diagnosis of the three playtest defects (swing direction, reporter single-parent steal, collision tunneling).
  • Operator-driven: the plan approval and review depth, the playtest verdicts (the authority on feel that overturned the review's swing finding), and every consent gate — the guardrail-ack and playtest-approved labels and the merge itself.

Impact: AI judgment is load-bearing on the reference port and the collision fixes; the operator's playtest and labels are the binding checks over it.

🤖 Generated with Claude Code

StarshipSuperjam and others added 8 commits September 2, 2026 07:48
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Correct the Toroid and shared-rules paragraphs against the pinned reference
(continuous formation refill with inherited scroll position; the swing is a
lateral acceleration triggered by a lateral-column window; the spawn column is a
rejected-and-redrawn stream draw) and settle the document so its slice-8 leaves
can proceed. Record per-family verification status inside the doc: only the
shared rules and Toroid are verified line-by-line this slice; the other eleven
families are drafted-pending-verification until their own slices.

Roadmap: air parent -> locked; air.toroid and air.standard-bullets ->
planned; difficulty.live-pressure (#60) -> slice 10 (kept atomic — DIF-03.play
needs a fire-permission-mask family, which Toroid is not). Regenerate the
settled-acceptance-criteria matrix.

Part of #65, #66. Part of #18.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extract the 32-direction aim tables (the four angle_dX_dY tables and the octant
quantizer) into docs/spec/data/aiming.json via the deterministic extractor, and
bake the octant table plus the two tiers Toroid uses (24 approach, 32 bullet)
into read-only Stage lists. Add the eight per-slot position/motion fields
(slot x/y/dx/dy/timer/code/pts/flag) as parallel 64-entry lists beside slot
type/state, zeroed at generation and re-zeroed by an extended clear slots.

Dormant this commit: no entity aims, moves, or occupies a slot — the aim
procedure, the formation spawner, and the Toroid updater land next. Model
fixtures prove the angle tables against their sine/cosine model and the baked
lists against the committed data; structural fixtures prove every slot field is
zeroed and that clear slots covers every slot list. Re-pin build_hash. Record
023.

Part of #57, #65. Part of #18.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The first live flying enemy. The formation wave spawns Toroids into the six
flying slots; the ordered walk dispatches each to `update toroid`, which aims at
the craft on the 24-magnitude homing tier, approaches, swings laterally toward
the craft's side when nearly level, animates its flap, and culls off the play
field keeping its scroll-axis position (the coded inherited-scroll refill). This
is the first live consumer of the shared RNG (SYS-04) and the slice-7 formation
state, and lights the AREA-02 air path (formations, not add_object, spawn live
flying enemies). Six persistent `toroid` clones render the pool.

Also fixes op_round: `round` is the standalone operator_round block, not an
operator_mathop function — the mathop form silently returned 0, so the player
cell read as (0,0) and aim was wrong. With the fix the craft reads (row 30,
col 16) and Toroids aim and move correctly.

Evidence:
- structural `_air01_failures` + per-clause negatives (lifecycle procs warp,
  spawn/dispatch driven, cull inherits position, bounded draw)
- model fixture `ToroidSpawnDraw` over the committed RNG seeds (valid,
  craft-avoiding columns; bounded draw; ~1.3% exhaustion deviation)
- harness `toroid-wave-spawns-and-moves` and `rng-draw-order`, each with a
  biting negative (new `neutralizeProc` mutation empties a warp proc's body)
- roadmap-evidence markers for AIR-01, SYS-04, AREA-02
- mechanics record 024; cross-ref updates to records 004, 016, 020

Also reconciles two roadmap tests (test_roadmap.py) to the commit-0 spec-settle:
`air` is now a settled spec, so the negative/executable fixtures repoint to the
still-draft `ground` spec. These belong with the commit-0 settle; they are folded
here because this environment has no interactive rebase.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The first working combat. A player shot overlapping an active Toroid resolves
through the single-hit path: the walk's `check air shot hit` detector (called per
active flying enemy) tests the shot slots with the reference's shadow-MSB window
(HIT_WINDOW_SHOT_FLYING, from check_shot_hit_flying_enemy $19A6), and on an
overlap sets `award value` from the enemy's `slot pts`, calls `resolve hit` (the
one score path), starts the enemy's explosion, and marks the shot spent. The
blaster clone mirrors its live position into its shot slot each travel iteration
(inverse render map, floored) and self-destructs when its slot leaves ACTIVE, so
the clone still frees its own slot and the 3-shot cap can't desync. A struck
Toroid runs `explode toroid tick` (drifts, 20 frames, size-doubling phase) then
frees; the renderer shows the referenced solv_death explosion frames as a
recorded stand-in.

The debug S scoring fixture is retired: a real kill now produces `award value`.

Locked spec amended (needs operator guardrail-ack): the shot-vs-air hit window is
recorded in player-craft-and-weapons.md beside the player and Bacura windows.

Evidence:
- structural `_wpn02_failures` + per-clause negatives; the retired-fixture check;
  extended SYS-03 tests still green with a live detector driving them
- harness `blaster-kills-toroid-and-scores` (+ biting negative) and the
  kill-driven `score-digits-render`
- roadmap-evidence markers for WPN-02, SYS-03, ECO-01
- mechanics record 025; cross-ref updates to records 006 and 009

Deviations (record 025): no shadow-byte wrap (no phantom long-range hits);
one-tick shot-position lag; explosion visual is the solv_death stand-in (dedicated
Toroid-burst crops + the frame-8 recentre deferred to a later art pass).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… (AIR-12, PLY-02)

The fourth combat commit of slice 8 brings the enemy's side of the fight
online and retires the last debug fixtures.

- AIR-12: the shooting Toroid (type 0x0B) fires exactly one aimed enemy
  bullet at its swing commit (allocating from the 19-slot bullet pool,
  aimed on the generic 32-magnitude tier); the bullet flies straight and
  culls off any edge. A 19-clone `enemy_bullet` renderer shows the pool.
- PLY-02: a Toroid or an enemy bullet touching the craft raises
  `player hit`; the walk's terminal death check, gated on
  `player hit` = 1 AND `invuln` = 0, spends a craft, clears the flag, and
  runs the one player-dead transition. The lives-driven respawn/game-over
  decision (slice 4) is now driven by real contact.
- The debug D (respawn) and G (terminal death) key fixtures are removed.

Tests: `_ply02_failures`, `_enemy_bullet_pool_failures`, and `_air12_failures`
rewritten/added on the live path with biting negatives; build_hash re-pinned;
AIR-12 and PLY-02 roadmap-evidence markers added; harness `death-respawn`,
`death-game-over`, and `enemy-bullet-fires` run it live. Record 026 written;
records 006/008/013 cross-referenced. 193 Python tests and 33 harness
scenarios green; both generators' check() pass; two builds byte-identical.

GOVERNANCE: this commit amends the LOCKED spec docs/spec/core-game-systems.md
to remove the temporary D/G keyboard death fixtures now that live combat deals
real lethal contact. Per the plan-review disposition, this locked-spec edit
requires the operator to apply the `guardrail-ack` label on the PR.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Post-build documentation for the Toroid vertical slice.

- MECHANICS_CATALOG: SYS-02/SYS-03/SYS-04, AIR-01, WPN-02, and PLY-02 move to
  present (live combat participants); ECO-01 records the real in-play awarder;
  AIR-12/AREA-02/FORM-01/DIF-01/DIF-03 rows note what is now live and what is
  deferred (DIF-03.play / #60 to slice 10; radiating bullets and Bacura to
  slice 11).
- PLAYTEST_CHECKLIST: the D/G/S debug keys are gone; a new step 4a walks the
  Toroid combat (waves spawn and refill, swing, single aimed shot, shoot-to-
  score-30, no orphan clones on reload), and steps 5/6 exercise death and
  scoring through real combat instead of the retired fixtures.
- BUILD_PLAN: a delivered-annotation row for slice 8 listing what it delivered,
  the leaves it closes, and the recorded deferrals.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pre-submission cold review (5 passes: spec-conformance, divergence-hunter,
technical-integrity, security-governance, usability) surfaced one blocking
defect and several minor items; dispositioned here.

BLOCKING (spec-conformance + divergence-hunter, both independently):
- The Toroid's lateral swing accelerated AWAY from the craft, not toward it —
  the opposite of the locked aerial-enemies.md / record 024 behavior. A
  craft-on-the-right (offset >= 0 -> SWING_RIGHT) did `slot dy -= accel`, which
  the render map (stage_x increases with slot y) turns into leftward motion.
  Fixed: SWING_RIGHT now does `slot dy += accel` (toward higher columns / the
  craft), SWING_LEFT mirrors it; the animation keeps its per-direction order.
  No committed test bound the swing SIGN, so it passed every check — added the
  live harness guard `toroid-swings-toward-craft` (asserts dy > 0 on a right
  swing; the exact sign the bug inverted) with a biting negative.

Also addressed:
- tech-integrity: re-anchored the ENGINE-TODO at the walk dispatch (was mid-
  sentence, fell out of engine_todo.py enumeration); added a generate-time
  assert that the max formation count fits FLYING_SLOTS (fails loud on a future
  data regen instead of silently under-spawning).
- divergence-hunter: `_air12_failures` now checks the bullet update is actually
  DISPATCHED from the walk (not just that its body is correct), with a negative.
- spec-conformance / divergence-hunter minor: refreshed three stale comments
  (the removed debug S key; the "DORMANT" enemy-bullet allocator, now live).
- usability: the playtest checklist now discloses the two interim art stand-ins
  (bullet costume, reused explosion frames) as "note, don't fail", and corrects
  the stale time estimate.

Regenerated project.json (swing swap only, 4 lines), re-pinned build_hash to
f09da295. 193 Python tests, 35 harness scenarios, both generator checks, and the
engine CI suite all green; two builds byte-identical.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown

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 docs/spec/), not the file itself:

  • .engine/product-spec-matrix.json

This is a heads-up only — it does not block your merge, and your merge is the decision.

Operator playtest caught that Toroids home straight into the craft instead of
swinging away like the arcade. Grounded against the pinned disassembly
(jotd666/xevious@71473685, `toroid_toggle_dir` / `toroid_swing_right`
`subq #1,_dY` / `toroid_swing_left` `addq #1,_dY`): the Toroid spawns AIMED at
the craft, and the swing nudges that lateral velocity AGAINST the approach each
frame, so it decelerates, reverses, and peels away — a bounce, not a dive.

This reverts the swing-sign change from the pre-submission fix commit (13e6e68),
which had been made on the strength of the locked spec's wording ("swing toward
the craft's side"). That wording was wrong and it fooled all five cold review
passes plus the scoped re-audit — only the operator playtest, and then the
reference itself, caught it. `SWING_RIGHT` is back to `slot dy -= accel`,
`SWING_LEFT` to `slot dy += accel`; build_hash returns to f2fe851b.

Root cause of all three reported symptoms is this one homing bug: homing Toroids
funnel into the craft's column, so they (2) die in the held-fire shot stream
"regardless of alignment" and (3) crash into the craft after speeding up. The
bounce should resolve 2 and 3 as well (they are rendered-gameplay effects the
headless harness can't fully exercise — operator re-playtest confirms).

- Flipped the harness guard `toroid-swing-reverses-away` (was
  `toroid-swings-toward-craft`) to assert the reversal (`slot dy < 0` on a right
  swing, the toggle_dir bounce) with a biting negative — so the sign cannot
  silently re-invert into a homing dive.
- Corrected the swing description in the locked-at-settling `aerial-enemies.md`
  (Toroid paragraph, draft at PR base so not lock-gated), record 024, and the
  playtest checklist step 4a; all now describe the reversal/bounce and cite
  `toroid_toggle_dir`.

193 Python tests, 35 harness scenarios, both generator checks, mechanics-record
check, and engine CI all green; two builds byte-identical (f2fe851b).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
StarshipSuperjam and others added 2 commits September 2, 2026 14:17
Follow-up to the swing reversal, from continued operator playtest ("flees away
now, but slides down the side, then my ship explodes; dies every time they go
off screen"). Two grounded fixes:

- LEFT-EDGE CULL. The Toroid cull checked bottom/top/right but not the left.
  The reference culls a left exit via its 8-bit column wrapping past the right
  threshold; this signed-column port needs an explicit `col <= -2` cull. Without
  it a Toroid the (now-correct) swing sends off the left never freed its slot and
  slid down the edge. Bullets already had this; Toroids now match.

- TOP-ENTRY SPAWN. `init toroid` now resets the scroll row to the top before
  aiming. The reference spawner sets only the object TYPE and relies on the world
  scroll to carry flying enemies down from the top; this self-propelled port has
  no enemy world-scroll, so a refilled slot was inheriting the previous occupant's
  mid-field scroll row (the swing culls Toroids at the sides, mid-field) and aiming
  a steep short-range dive that clipped a stationary craft. Top-entry restores the
  arcade's "stream in from the top" and gives the swing room to divert.

Note: an aimed Toroid still clips a *stationary* craft — it is aimed at you,
spawns >=8 columns away (arcade rule), and the swing only triggers on lateral
alignment (near arrival); a non-dodging craft gets hit, which appears inherent to
the faithful design. The headless harness (craft can't dodge) reflects this; the
active-play feel is the operator's to judge.

build_hash -> ee6f42c6; 193 Python tests + 35 harness scenarios green; both
generator checks pass; two builds byte-identical. Record 024 updated (top-entry
deviation, any-edge cull). Also fixes the earlier silent no-op where generate()
refused on a dirty project.json, so the left-cull/top-spawn are actually built now.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The craft-overlap and shot-vs-air hit windows each built one delta block
and reused it across both the `<` (lower) and `>` (upper) compares. A
Scratch reporter attaches to only one parent, so the `>` stole the delta
from the `<`, leaving the lower bound reading an empty operand (always in
range). Both boxes degraded to a quadrant/whole-row:

- craft died whenever it crossed the row OR column above/beside a
  not-yet-fleeing Toroid (operator: "I die if I cross the vertical/
  horizontal column above the Toroid")
- a held shot destroyed any Toroid in its row regardless of column
  (operator: "the fire button just explodes them at random")

Rebuild each axis delta as its own reporter subtree (a lambda called
once per compare) so both bounds read live operands. The craft box
collapses to the single cell (0,0); the shot box to a single column
within one row ahead — verified by headless hit-box maps.

Add two discriminating harness scenarios the original tests lacked (they
only ever seeded on-target, so the dead lower bound was invisible):
  - air-shot-hit-is-single-column (off-column shot must NOT score)
  - craft-collision-is-single-cell (off-row/off-column Toroid must NOT hit)

Re-pin build_hash; records 025/026 document the fix. 193 Python tests,
39 harness scenarios, both generator checks green; builds byte-identical.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
StarshipSuperjam added a commit that referenced this pull request Sep 2, 2026
Correct the citations the resolver cannot resolve at the pinned commit — locators
only, no gameplay-meaning change:

- Nonexistent labels -> their real names at the pin: sub_2_fn_8/_22 gain their
  __fire_freq_mask_* suffixes (difficulty-and-formations.md, mechanics 022),
  sub_2_fn_3 gains __inc_enemy_AI_and_flying_enemies (mechanics 020).
- Missing file in scope -> declared: player-craft-and-weapons.md and
  difficulty-and-formations.md each declare their default source file
  (main and sub respectively); core-game-systems.md and andor-genesis.md gain an
  explicit file token on the one citation that leaves the surrounding default.
- Approximate reference -> exact: area-progression-and-terrain.md's `~514`
  becomes `main_gameplay_loop` 514-521 (the area-advance / 16->7 loop).

Add tests/test_reference_corpus.py: three network-free guards (every reference
provenance line names the index pin; every spec citation has a file in scope; no
approximate line reference survives), which run in the ordinary test job.

The settled-criteria matrix is unchanged (edits fall outside acceptance-criteria
cells, verified by regenerating it). Deferred, deliberately: the bare-range ->
labelled-citation conversions, which cluster in the draft enemy docs that PR #110
and the upcoming settling slices rewrite; they land there under the new fidelity
review rather than conflicting with in-flight work.

Touches four settled specs (player-craft-and-weapons, difficulty-and-formations,
core-game-systems, area-progression-and-terrain) -> needs guardrail-ack.

Part of #111.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The player found shots passing through Toroids ("many rounds into a
group and nothing happens") — far harder to kill than the arcade. Two
compounding causes, both port artifacts the reference never faced:

1. TUNNELING (the real bug). The blaster shot travels changeyby 20 =
   20 stage-px/frame ÷ RENDER_ROW_STAGE(8) = 2.5 cells/frame, but the
   shot-vs-air hit window was only 2 cells tall. The per-frame step
   overshoots the window, so a fired shot steps clean over a Toroid
   between collision samples. Every shot in a held stream starts at the
   craft's row and so shares one sampling phase — a Toroid in a gap is
   immune to the entire stream. DY=20 was a preserved-baseline the
   movement slice never reconciled to the collision window.
2. The window covered only ~40% of the 36-px rendered Toroid, so
   bullets visibly on the sprite missed.

Fix: double HIT_WINDOW_SHOT_FLYING (16,32,8,16) -> (32,64,16,32). The
4-cell height exceeds the 2.5-cell step (with margin for enemy closing
motion) so every crossing is sampled; the ±1-cell width matches the
rendered sprite. The craft HURTBOX (HIT_WINDOW_BULLET_FLYING) is left
tight on purpose: forgiving offence, precise defence. A deliberate,
recorded deviation from the reference; locked spec updated (guardrail-ack).

The headless harness cannot reproduce per-frame timing (threads run to
settling), so the guard for tunneling is a numeric invariant, B8-no-tunnel:
shot cell-step <= window height - 1 cell. Also reworked the shot-window
harness scenario to seed a real detector slot (37) with the enemy off the
craft column — the old one seeded slot 36, which the detector never reads
(SHOT_SLOTS = 37-39), so it only passed via the real tapped shot.

Re-pin build_hash. 193 Python tests, 39 harness scenarios, both
generator checks green; builds byte-identical (0c7c82db).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@StarshipSuperjam
StarshipSuperjam marked this pull request as ready for review September 2, 2026 22:43
@StarshipSuperjam

StarshipSuperjam commented Sep 2, 2026

Copy link
Copy Markdown
Owner Author

Playtest record — approved

Tested head commit: 299dc376363bbbbe19ed20ec910fc092229fcadd (299dc37), built to dist/Xevious.sb3 sha256 0c7c82db73b670f15695af5e9b6a7568506b4a42cb19e58bdaa8f13f6bf8f519.

Played area 1 in Scratch 3 across the slice-8 behavior list (spawn → aim → swing → flap → shot → blaster-to-air kill/+30 → collision death → cleanup). After the three playtest-driven fixes (swing reversal, single-cell/bounded collision boxes, and the anti-tunneling shot-window widening) the Toroids behave and destroy correctly — not pixel-exact to the arcade, but a good faithful port. Approved to merge; further hit-feel tuning can follow as more mechanics come online.

Interim art stand-ins (enemy bullet costume, reused explosion frames) noted, not blocking — deferred to the art pass per the PR's Out of scope.

@StarshipSuperjam

Copy link
Copy Markdown
Owner Author

Follow-up: the reported seedAirKill "seeds inert slot 36" item — not a bug, closing

A work item was raised during this slice claiming harness/lib/catalog.js's seedAirKill seeds the player shot into slot 36 = SOLVALOU_SLOT (the craft's own slot), making the seed inert — so blaster-kills-toroid-and-scores would be scoring only via a stray shot rather than the controlled seed it claims to test. I checked this against both the director source and the running VM, and the premise doesn't hold up.

Root cause of the confusion: Scratch 1-based lists vs. JS 0-based arrays

tools/game_director.py numbers slots as Scratch item numbers (Scratch lists are 1-based): SOLVALOU_SLOT = 36, SHOT_SLOTS = (37, 39). The check air shot hit detector reads list_item("slot type", …, number(s)) for s in range(37, 40) — Scratch items 37/38/39.

readVar(...) in the harness returns a plain 0-based JS array, so Scratch item N lands at JS index N − 1:

  • Detector shot slots (Scratch items 37/38/39) → JS indices 36/37/38
  • SOLVALOU_SLOT (Scratch item 36) → JS index 35

So seedAirKill's default shotSlot = 36 is a JS index, i.e. the first real detector slot (Scratch item 37) — not the craft, and not inert.

Evidence (running VM: clear leftover shots, seed enemy + one controlled shot in each JS slot, step once)

seeded shot JS index score delta shot consumed?
enemy only (no shot) 0
35 (craft, Scratch item 36) 0 no
36 (Scratch item 37) 30 yes
37 (Scratch item 38) 30 yes
38 (Scratch item 39) 30 yes
39 (Scratch item 40) 0 no

In the scenario as it runs today, after the step it is slot 36's state that flips 1→0 while 37/38 stay active — the seeded shot is the one that resolves the hit. blaster-kills-toroid-and-scores scores via its controlled seed, for the right reason, and the neutralizeProc('check air shot hit') negative still bites (enemy-only → 0 delta). Two incidental corrections: reachPlaying does not deliberately fire a shot (it taps start precisely to avoid a held-space shot), and air-shot-hit-column-bounded's use of JS slot 37 is also a valid detector slot (Scratch item 38) — correct, just not because "37 is the first real detector slot."

Changing the default from JS 36 → 37 would swap one valid detector slot for another: it fixes nothing and would force a matching edit to the scenario's slot-state[36] read. No change made; no build_hash change.

One real (minor, deferred) weakness

There is a genuine fragility nearby, just misdiagnosed: seedAirKill does not clear reachPlaying's three stray active shots (JS 36/37/38), and it parks the enemy at an arbitrary absolute cell (5000, 4000). Those stray shots (on the craft column) happen not to overlap that cell today, so the delta is exactly 30 — but that's coincidental. A robust hardening (clear JS shot slots 36/37/38 before seeding + park the enemy a few columns off the craft column) would guarantee exactly one controlled shot with no contamination. That's an enhancement to a currently-correct test, not a bug fix — noted here for later, not done in this slice.

Resolution: closing as not-a-bug.

@StarshipSuperjam StarshipSuperjam added guardrail-ack You add this to approve a change the engine flagged as weakening a built-in safety protection. playtest-approved Operator approved the playable build at the recorded commit labels Sep 2, 2026
@StarshipSuperjam
StarshipSuperjam merged commit 09174c1 into main Sep 2, 2026
30 of 42 checks passed
@StarshipSuperjam
StarshipSuperjam deleted the claude/xevious-toroid-slice8 branch September 2, 2026 23:02
StarshipSuperjam added a commit that referenced this pull request Sep 2, 2026
Correct the citations the resolver cannot resolve at the pinned commit — locators
only, no gameplay-meaning change:

- Nonexistent labels -> their real names at the pin: sub_2_fn_8/_22 gain their
  __fire_freq_mask_* suffixes (difficulty-and-formations.md, mechanics 022),
  sub_2_fn_3 gains __inc_enemy_AI_and_flying_enemies (mechanics 020).
- Missing file in scope -> declared: player-craft-and-weapons.md and
  difficulty-and-formations.md each declare their default source file
  (main and sub respectively); core-game-systems.md and andor-genesis.md gain an
  explicit file token on the one citation that leaves the surrounding default.
- Approximate reference -> exact: area-progression-and-terrain.md's `~514`
  becomes `main_gameplay_loop` 514-521 (the area-advance / 16->7 loop).

Add tests/test_reference_corpus.py: three network-free guards (every reference
provenance line names the index pin; every spec citation has a file in scope; no
approximate line reference survives), which run in the ordinary test job.

The settled-criteria matrix is unchanged (edits fall outside acceptance-criteria
cells, verified by regenerating it). Deferred, deliberately: the bare-range ->
labelled-citation conversions, which cluster in the draft enemy docs that PR #110
and the upcoming settling slices rewrite; they land there under the new fidelity
review rather than conflicting with in-flight work.

Touches four settled specs (player-craft-and-weapons, difficulty-and-formations,
core-game-systems, area-progression-and-terrain) -> needs guardrail-ack.

Part of #111.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
StarshipSuperjam added a commit that referenced this pull request Sep 2, 2026
…refine guard (issue #111)

Rebasing onto main (slice 8, PR #110) surfaced two things the fidelity tooling
now covers:

- docs/mechanics/023-aiming-and-slot-positions.md cited `cpy_dY_dX_to_obj` by its
  routine address ($3383) where the convention is a line number; the resolver
  flagged it. Corrected to the line (5135) so it resolves — the gate catching a
  real citation slip in freshly-merged work.
- The approximate-line guard mis-flagged value suffixes new in slice 8 records
  (`~128 half-px`, `~40%`). Refined: flag a bare `~NNN` only when it is not
  followed by a unit word or `%`, so a genuine `~line)` reference is still caught
  while counts, distances, and percentages are left alone.

Resolver: 107 citations checked, 0 unresolved. Full suite (229 tests) green.

Part of #111.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
StarshipSuperjam added a commit that referenced this pull request Sep 3, 2026
Flip the ten already-merged leaves from status:"planned" to
status:"history" with their delivering PR: difficulty.models-live-state
(#56, PR #34) and the nine slice-8 leaves #57-66 except #60 (PR #110).
#60 (difficulty.live-pressure) stays planned — it is Part of, deferred
to slice 10. Produced with `roadmap.py deliver --pr 34` / `--pr 110`.

This clears the reconcile drift for those leaves and removes the hazard
that a future `apply` would reopen the merged issues. proof and titles
are left unchanged (truthful — these were playtested; validation only
requires delivered_by on a history leaf).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

guardrail-ack You add this to approve a change the engine flagged as weakening a built-in safety protection. playtest-approved Operator approved the playable build at the recorded commit

Projects

None yet

1 participant