- Waves.js: pure wave runner (rolling/fixed) + CombatSystem.startWaves
for quest-step combat; named scripts (probe/skirmish/raid) ship in
combat.json, driven from the console until the quest hook lands
- EnemyTypes.js: named hostiles (raider/bomber) deep-merged onto the
base craft, with per-type textures preloaded via enemySheetList()
- Hostiles now materialize off-screen (camera edge + game.spawnLead)
and fly in on their chase AI instead of popping into view mid-fight
- Wave ships are run-owned: no self-respawn, kills feed rolling
replacements, and the combat bar stays up through the lead and gaps
A shared readability layer (js/combat/CombatHud.js) for both combat
contexts — the training range and the main game's ?combat:
- Target/lock indicator: rotating brackets on the current target (the
auto-fire target — one rule, one target, js/combat/Combat.js#pickTarget).
- Threat cues: pulsing markers above each live hostile.
- Off-screen arrows: edge arrows toward hostiles outside the camera
(zoom-aware via a context-supplied worldToScreen).
- Kill feed: newest-first fading entries, top-right.
- Gauge flash: the combat bar's shields/hull gauge flashes on the hit
it took (CombatBar.gaugeRect feeds the shared CombatHud.flash).
Pure rules stay in Combat.js (pickTarget, feedPush, offscreenArrow),
covered by dev/combat.test.mjs. The main game re-anchors the pack
around the ship once its start position settles (CombatSystem
.resyncAroundShip + a short settle window in GameScene), so the demo
pack sits in auto-fire range instead of kilometers off.
All combat rules tests pass (0 failures); CDP-verified in both
/combat and index.html?combat.
All from the one shared Fx path (js/combat/Fx.js) — both contexts call
the same cues, so the read never drifts:
- Fx.damage(): floating damage numbers on every hit, cyan when the
shields took it / orange when the armor did (both enemy + player).
- Hit flashes are now color-coded by the layer that took the hit
(FX_COLORS.shield / .hull) instead of a flat white.
- Fx.explode() (the kill burst) gains a second delayed shockwave ring
+ a denser spark burst.
- Fx.down(): the player-DOWN cue — hot-red burst + big DOWN hold.
- Fx.respawn(): the REFORMED cue — clean cyan bloom + pop.
- Fx.text() is a graceful no-op without scene.add.text (the Node test
stubs), and canvas text gets a CSS string (toCss) per the v4 Giedi
fillStyle quirk.
CombatScene + CombatSystem wire every hit/kill/down/respawn site to the
shared cues. dev/combat.test.mjs stays 0 failures; CDP screenshots
(/tmp/vfx-cues-*.png) show the damage numbers, kill burst, DOWN and
REFORMED cues live in both /combat and index.html?combat.
- 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.
- Add CombatSystem: owns the hostile pack, tracers, player armor/energy,
auto-fire (nearest-in-range), swept-hit collisions, rams, respawn — all
driven by data/combat.json `game.*` / `energy` sections
- Add CombatBar (Diablo 4 style): vertical shields + hull gauges on the
left, energy gauge on the right, eight skill seats (keys 1–8) in the
middle; shown in place of the action deck while combat is live
- `?combat` now boots the normal game with the pack spawning when the
player starts (mouse steers, cannon auto-fires); the standalone WASD
range remains at `/combat` / `combat.html`
- GameScene wires the system into its update loop, solid constraints, and
click-through gate; exposes `window.__ORBIT_COMBAT__` for DevTools
- Extend combat tests to cover CombatSystem lifecycle (spawn, auto-fire,
hits, rams, armor, respawn, cast stub, destroy)