diff --git a/docs/endless-runner.md b/docs/endless-runner.md index 7d8b2d4f..3129430a 100644 --- a/docs/endless-runner.md +++ b/docs/endless-runner.md @@ -78,9 +78,9 @@ Paths beginning with `tests/` or `scenes/` are relative to `hoppys-adventure-arc | A3: Hazards + projectiles | Shared relief atlas, forged meshes, pit-depth and exposure guards | Maximum 11 / 5,968 per asset; 3 shared 512² maps + 256² border | `--script res://tests/assets/a3_check.gd`; `ASSET A3: PASS (158393 checks)` | `scenes/runner/hazards/dev/capture_a3.gd` | `docs/previews/assets/a3-*.png` | [A3](assets/a3.md) | | A4: Power-ups + tools | Upward beacons, soft rings, distance-aware glyphs, bounded body emission and grab-vs-avoid affordances | 7 idle, 8 peak each / up to 2,196 peak triangles / shared relief maps and per-kind glyph textures | `--script res://tests/assets/a4_check.gd`; `ASSET A4: PASS (6035 checks)` | `scenes/runner/powerups/dev/capture_a4.gd` | `docs/previews/assets/a4-*.png` | [A4](assets/a4.md) | | A5: Bosses + fight FX | Forged reactor, articulated Hydra, cached metal maps, pooled telegraphs/tracers/numbers | Swarm 11 / 22,080; Hydra 18 / 21,396; Monolith 14 / 6,970; FX pool 22 / 7,716; whole fight 40 draws / 4 shared 256² textures | `--script res://tests/assets/a5_check.gd`; `ASSET A5: PASS (60339 checks)` | `scenes/runner/boss/dev/capture_a5.gd` | `docs/previews/assets/a5-*.png` | [A5](assets/a5.md) | -| A6: Pursuer | Bevelled MultiMesh PR cards, Sobel normals, routed cables, threat-driven effects | 13 / 7,144 / 3 × 256² | `--script res://tests/assets/a6_check.gd`; `ASSET A6: PASS (102 checks)` | `scenes/runner/pursuer/dev/capture_a6.gd` | `docs/previews/assets/a6-*.png` | [A6](assets/a6.md) | +| A6: Pursuer | Robotic QUARANTINE hunter: two claws, receivers, lift rotors and a red light; the catch leaps past Hoppy, clamps at 1.6 s and drops a containment field | 12 / 3,840 / none | `--script res://tests/assets/a6_check.gd`; `ASSET A6: PASS (209 checks)` | `scenes/runner/pursuer/dev/capture_a6.gd` | `docs/previews/assets/a6-*.png` | [A6](assets/a6.md) | | A7: UI | GET READY and menu grab-vs-avoid legend, first-event toasts, SPEED handoff, vector panels/icons and portrait viewport | 0 3D draws / 0 mesh tris / 0 procedural maps; 217 CanvasItems and existing 256² portrait viewport (not zero UI GPU cost) | `--script res://tests/assets/a7_check.gd`; `ASSET A7: PASS (1245 checks)`; `HUD LEGIBILITY: PASS (60 labels checked)` in visual smoke | `scenes/runner/ui/dev/capture_a7.gd` | `docs/previews/assets/a7-*.png` | [A7](assets/a7.md) | -| A8: Audio | Deterministic layered PCM, FM transients, filtered noise, family envelopes, leased reverb/compressor/delay buses | 0 / 0 / 0; 32 recipes, 3,901,938 checked PCM bytes, peak 0.789972 | `--script res://tests/assets/a8_check.gd`; `ASSET A8: PASS (2323 checks)` | `scenes/runner/audio/dev/capture_a8.gd` | `docs/previews/assets/a8-*.png` | [A8](assets/a8.md) | +| A8: Audio | Deterministic layered PCM, FM transients, filtered noise, family envelopes, leased reverb/compressor/delay buses | 0 / 0 / 0; 32 recipes, 3,901,938 checked PCM bytes, peak 0.789972 | `--script res://tests/assets/a8_check.gd`; `ASSET A8: PASS (2333 checks)` | `scenes/runner/audio/dev/capture_a8.gd` | `docs/previews/assets/a8-*.png` | [A8](assets/a8.md) | | A9: Post-processing | Single screen-reading shader: grading, grain, vignette, hit aberration, letterbox, speed streaks, Nitro lift | 1 fullscreen pass / 2 / 0 generated textures; implicit screen backbuffer copy is additional storage/bandwidth | `--script res://tests/assets/a9_check.gd`; `ASSET A9: PASS (147 checks)` | `scenes/runner/ui/dev/capture_a9.gd` | `docs/previews/assets/a9-*.png` | [A9](assets/a9.md) | A4 peak census includes the collection burst; code_review has the highest triangle count. A5 whole-fight draws are a concurrent budget, not the sum of every boss. A9's current 147-check verification supersedes both the earlier 126- and 134-check transcripts. diff --git a/hoppys-adventure-arcade/tests/assets/a6_check.gd b/hoppys-adventure-arcade/tests/assets/a6_check.gd index 359984c5..bc5e93c0 100644 --- a/hoppys-adventure-arcade/tests/assets/a6_check.gd +++ b/hoppys-adventure-arcade/tests/assets/a6_check.gd @@ -8,10 +8,13 @@ func check(ok: bool, message: String) -> void: failures += 1 push_error(message) func _initialize() -> void: call_deferred("run") +## The hunter rig's bounds. The quarantine field is a separate world-space effect dropped around +## Hoppy during a catch, so it is not part of the hunter's envelope. func bounds(node: Node3D) -> AABB: var result := AABB() var first := true for mesh: MeshInstance3D in node.find_children("*","MeshInstance3D",true,false): + if mesh == node.get("_field"): continue var box: AABB = node.global_transform.affine_inverse()*mesh.global_transform*mesh.mesh.get_aabb() result = box if first else result.merge(box) first = false @@ -46,13 +49,22 @@ func run() -> void: visual._process(0.1) check(not visual._fans[0].rotation.is_equal_approx(rotor),"Visible propulsion rotates") var count := visual.find_children("*","",true,false).size() + var start_z: float = visual._wall.global_position.z visual.play_catch() - visual._process(0.4) - check(absf(visual._arms[0].rotation.x) > 0.3,"Capture deploys claws") + visual._process(visual.CLAMP_AT) + check(absf(visual._arms[0].rotation.x) > 0.3,"Capture clamps the claws at CLAMP_AT") + check(visual._field.visible,"Quarantine field drops once the claws shut") visual.play_catch() - check(is_equal_approx(visual._reaction_time,0.4),"Catch idempotent") + check(is_equal_approx(visual._reaction_time,visual.CLAMP_AT),"Catch idempotent") visual._process(1.0) - check(visual._wall.scale.is_equal_approx(Vector3.ONE) and visual._wall.position.z > 2,"Catch lunges without giant growth") + var held: Transform3D = visual._wall.global_transform + var unscaled := held.basis.get_scale().is_equal_approx(Vector3.ONE) + check(unscaled and held.origin.z < start_z - 4.0,"Catch leaps past Hoppy without giant growth") + visual.set_threat(1) + check(visual._reaction == &"catch","Catch holds while the threat stays at 1") + visual.set_threat(0) + check(visual._reaction == &"idle" and not visual._field.visible, + "Run reset ends the catch and lifts the field") visual.set_threat(1) var pose: Transform3D = visual._wall.transform visual.set_threat(0) diff --git a/hoppys-adventure-arcade/tests/assets/a8_check.gd b/hoppys-adventure-arcade/tests/assets/a8_check.gd index 9d838354..fe9c7bc7 100644 --- a/hoppys-adventure-arcade/tests/assets/a8_check.gd +++ b/hoppys-adventure-arcade/tests/assets/a8_check.gd @@ -170,8 +170,11 @@ func run() -> void: audio.play_ui_click() var heard := {} audio.sound_played.connect(func(kind: StringName, _stream: AudioStream) -> void: heard[kind] = true) - # Existing mock timeline exercises all 32 signal-triggered recipes. - for i in 405: + # The mock timeline exercises all 32 signal-triggered recipes. Run it until it finishes, so the + # Game Over sting still fires when config.caught_sequence_seconds changes (#61 made it 3.0 s). + var ticks := 0 + while mock.state != &"finished" and ticks < 600: + ticks += 1 audio.one_shots.stop() audio.one_shots.play() audio._playback = audio.one_shots.get_stream_playback() @@ -180,6 +183,7 @@ func run() -> void: mock.advance_demo(0.1) audio._process(0.1) check(audio.active_voice_count() <= 10, "voice cap") + check(mock.state == &"finished", "demo timeline reaches Game Over (%d ticks)" % ticks) for kind: StringName in Synth.RECIPES: check(heard.has(kind), "trigger exercised: " + kind) audio._stop_all() audio.one_shots.play()