- 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.