- New hostiles frames 1–7 (bomber → warden) with distinct stats and
monotonic difficulty ramp in data/combat.json
- Shared js/combat/Weapons.js dispatch for lance, spray, burst, rapid,
ring, missile, and combo patterns used by both combat contexts
- New dodgeable heat-seeking Missile (capped turn-rate homing) plus pure
geometry helpers in Combat.js
- Rewire ?combat boot to startWaves('combat_demo'): 7 rolling waves, one
type each, 6 ships per wave, 3 concurrent, replacement per kill
- Add missile_fire SFX voice and extend tests/CDP demo checks for all
patterns and the full wave run
- Split the shared `fire` event into `cannon_fire`, `cannon_hit`, and `fire` so each weapon gets its own distinct voice
- Add three new sound assets (combat-mass-cannon, combat-mass-cannon-hit, combat-laser) to data/sfx.json and load them in GameScene
- Remove the 0.45 volume multiplier on enemy fire; the laser now plays at full volume since it has its own voice
- Update CombatSfx event map, both combat contexts (CombatScene, CombatSystem), dev tests, and README to reflect the ten-event SFX set
- Kills grant the hostile type's own `xp` (the roster sets XP-per-threat;
the curve lives in data/progression.json, 100 x n^2, capped at level 80)
- Each crossed level fires one `levelup` event: +3 hull / +2 shields with
a full heal, cyan burst + a distinct voice, and a kill-feed line — the
placeholder toughness the skill layer will subscribe to later
- Combat bar gains an XP row below the orbs (level, live gold fill,
banked / next cost; hidden when the snapshot carries no xp, so the
range bar keeps its old face)
- Kills show a gold "+N" reward floater; the level-up's SFX is the new
8th combat event (solar_flare_01)
- XpManager is pure and tested headless (node dev/xp.test.mjs); the
ledger saves as { level, xp } and old saves load fresh at level 0
- dev hooks (xp/grantXp), sfxcheck (range seven, main eight), shot
captures, and README all follow along
- js/combat/CombatSfx.js (new): ONE event→voice map + playback path for
both contexts, using ONLY existing data/sfx.json files:
fire=scan · impact=ui_click · shield=solar_buzz · hull=mining_split
kill=thunder_01..04 (one at random) · down=deconstruct ·
respawn=discovery
plus combatSfxEnemyHit() (shield/hull decision in one place: a hit
that breaks through to the hull plays both — the shield-pop read),
loadCombatSfx() for the range's preload (the main game already loads
all of sfx.json), and a capped dev log on window.__COMBAT_SFX__ so
the wiring is verifiable headless (no audio manager) in both.
- js/combat/CombatScene.js: fire/enemy-fire/wall-clip/ram/lance/kill/
down/respawn sites call the shared path; the player-hit voices
(impact + shield/hull from applyDamage) funnel through damagePlayer.
- js/combat/CombatSystem.js: the same seven call sites, identical
wiring (no per-context copies).
- dev/combat.test.mjs: sfx block — seven events mapped, voices exist in
data/sfx.json, pairwise distinct, vol scaling, thunder pick, the
shield/hull decision, the dev log, the master switch (0 failures).
- dev/combat.sfxcheck.mjs (new): CDP check that drives real events in
/combat AND index.html?combat and asserts all seven events pass
through the shared path — PASS in both.