Flight (js/flight/): tube-mode controller in spline space (s,x,y) — flow-locked forward motion, throttle as a spring-back lean, boost + i-frames, banking chase cam on the parallel-transport frame, arcade wall response (shove + graze damage, never a hard stop). Twin-stick input: WASD/left-stick moves the ship in the disc, mouse/right-stick aims, Shift/Ctrl is throttle. Gamepad supported. Combat (js/combat/): lysozyme cannon (pooled, instanced, heat-limited with hysteresis lockout) + antacid torpedo emitting level:neutralize. Enemy framework with floater/seeker/turret and turn-rate-limited homing darts, spawned from C's level:event. Coat/hull model and the full bus surface E builds against. Measured (deterministic stepper): flow-lock exact (3s at flow 14 -> s=44.0); boost peak 30.8 u/s; heat 4s fire -> 2s lockout; wall slam 13.3 coat at 11.3 u/s with forward speed untouched; 10 combat draws at 55 live enemies (budget <=80); 0.10 ms/frame CPU; 0 leaks over 20 create/dispose cycles. fps NOT claimed — rAF does not run in the automated pane. Evidence: docs/shots/laneB/. Three findings escalated in LANE_B_NOTES: - qa.sh gate #1 is a no-op: node --check silently exits 0 on ESM, so the syntax gate has never checked anything. I shipped a real SyntaxError past a GREEN qa. Fix + verification handed to F. - Surf is structurally broken: the peristalsis wave (13.64 u/s) is slower than the player (19.6 u/s), so level 2's signature mechanic loses to mashing throttle. Not tunable from this lane; escalated to A/F with options. - Custom ShaderMaterials need #include <colorspace_fragment>, else ART_BIBLE's hostile amber reaches the display as rgb(255,26,6). Fixed here; stub/walls inherit it. js/flight/dev.html is a dev harness (boot.js has no player wiring yet); it retires once F pastes the snippet in LANE_B_NOTES. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
59 lines
2.2 KiB
JavaScript
59 lines
2.2 KiB
JavaScript
// combat/index.js (Lane B) — the createCombat factory. Composes enemies + weapons and
|
|
// feeds them Lane C's level events. All the wiring, none of the behaviour.
|
|
//
|
|
// Update order inside a frame is load-bearing:
|
|
// boot: player.update(dt) -> combat.update(dt)
|
|
// here: enemies.update(dt) -> weapons.update(dt)
|
|
// Enemies move first so weapons hit-test against this frame's positions, not last frame's.
|
|
|
|
import { createEnemies } from './enemies.js';
|
|
import { createWeapons } from './weapons.js';
|
|
|
|
export function createCombat({ scene, world, bus, rng, flags = {}, assets = null, player }) {
|
|
const enemies = createEnemies({ scene, world, bus, rng, assets });
|
|
const weapons = createWeapons({ scene, world, bus, rng, player, targets: enemies });
|
|
|
|
enemies.setBlastFx((pos, size, dur) => weapons.spawnBlast(pos, size, dur));
|
|
enemies.setOnDeath((e) => weapons.spawnBlast(e.pos, e.radius * 1.6, 0.3));
|
|
|
|
const score = { value: 0, kills: 0 };
|
|
|
|
// Lane C's events arrive on the bus as the player crosses their `s`. We only care about
|
|
// `spawn`; checkpoints/hazards/bosses belong to C and E this round. Unknown types are
|
|
// ignored on purpose — C may add event kinds without breaking combat.
|
|
const offEvent = bus.on('level:event', (ev) => {
|
|
if (!ev || ev.type !== 'spawn') return;
|
|
const n = ev.count ?? 1;
|
|
for (let i = 0; i < n; i++) {
|
|
// fan the group out over a short stretch of s so a count of 6 is an encounter,
|
|
// not a wall of enemies at one arclength
|
|
enemies.spawn(ev.enemy, { s: (ev.s ?? 0) + i * 3.5, theta: ev.theta ?? null, rho: ev.rho ?? null });
|
|
}
|
|
});
|
|
|
|
const offDie = bus.on('enemy:die', (e) => {
|
|
score.value += e.score ?? 0;
|
|
score.kills++;
|
|
});
|
|
|
|
function update(dt) {
|
|
enemies.update(dt, player);
|
|
weapons.update(dt);
|
|
// combat's slice of the HUD state (E reads player:state for the flight half)
|
|
bus.emit('combat:state', {
|
|
heat: weapons.heat, heatMax: weapons.heatMax, overheated: weapons.overheated,
|
|
ammo: weapons.ammo, ammoMax: weapons.ammoMax,
|
|
score: score.value, kills: score.kills, enemies: enemies.count,
|
|
});
|
|
}
|
|
|
|
return {
|
|
update,
|
|
enemies, weapons, score,
|
|
dispose() {
|
|
offEvent(); offDie();
|
|
enemies.dispose(); weapons.dispose();
|
|
},
|
|
};
|
|
}
|