Add the BFG - #7
Conversation
|
@doug-leonhardt is attempting to deploy a commit to the Delineator Team on Vercel. A member of the Team first needs to authorize it. |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
sbddesign
left a comment
There was a problem hiding this comment.
The weapon is thoughtfully separated and the exact PR head passes npm run check plus npm run build, but I found two control-contract bugs that should be fixed before merge, and current main adds a Shield interaction that must be made explicit during the rebase.
The PR is also currently non-mergeable after #6: Git reports conflicts in README.md, both harnesses, fx.ts, game.ts, hud.ts, and style.css. Please rebase/merge current main, preserve the power-up behavior while resolving those files, add the release-edge and post-warp first-frame regression cases below, define and test Shield versus BFG self-damage, then rerun the full package checks and production build on the new head.
| if (event) events.push(event) | ||
| // A launch consumes the trigger. Holding the button down does not | ||
| // immediately start winding the next one — let go and mean it. | ||
| recovery = ABORT_RECOVERY |
There was a problem hiding this comment.
[P1] Require a release edge before the spool can re-arm. recovery only delays the next canSpool check; once the 0.6s expires, frame.hold is still true and the second round automatically spools and launches. That contradicts the comment immediately above and can consume both scarce rounds from one long hold. The ammo test currently masks this by holding continuously and expecting two launches—please latch needsRelease (or model trigger edges) and assert that holding after the first launch leaves one charge.
| // Spooling the BFG costs you everything else. Cold guns, no dash and a | ||
| // throttle ceiling is what turns "press the big button" into a decision | ||
| // about where you are willing to be for the next second and a half. | ||
| if (bfg.spooling) { |
There was a problem hiding this comment.
[P1] Gate on the current secondary-trigger intent, not only the previous frame's bfg.spooling state. player.step(readPlayerControls(...)) runs before resolveBfg, so on the first charge frame this condition is false and a ready primary gun fires; the Hornet can initiate dash on that same frame too. I reproduced the gun leak after waiting for warp-in to clear: the regression probe failed with shots=1. The committed test starts immediately after game.start, so the ship's normal 0.85s warp fire lock hides the bug.
| target.velocity.addScaledVector(_push.normalize(), BLAST_KNOCKBACK * fraction) | ||
|
|
||
| const before = target.alive | ||
| target.takeDamage(damage, own ? 'enemy' : round.team) |
There was a problem hiding this comment.
[P2] Reconcile this with the Shield now on main before resolving the conflicts. Current Ship.takeDamage returns without applying damage whenever shieldTimer > 0, so after the rebase a shielded pilot can stand in their own blast for zero damage. That bypasses this PR's stated principal cost and its “point-blank BFG kills its pilot” balance contract. Either make Shield an intentional exception and update the contract/docs, or make BFG self-damage bypass Shield; add a merged-state regression test for the chosen rule.
Officially a Bulk Fusion Generator. Nobody calls it that. Requested in the gamedev channel as "BFG sized really big guns". The damage was the easy part; the interesting part is what it costs to use. Hold F (or right mouse) for 1.3s and a slow green round leaves the nose. Anything within 340 units of where it stops takes up to 260 damage on a steep falloff — lethal to any airframe at the centre, a hard shove and a scare at the rim. Three rules do the design work: Two rounds a run, no refills. Anything renewable turns into a rotation you press on cooldown. Two means every launch is a judgement about whether this is the moment. Charging costs you everything else. Guns cold, dash locked, throttle capped at 55% for the whole spool. You are committing to a heading for a second and a half while everyone else is still manoeuvring. Releasing early aborts and keeps the round, so a mispress costs a moment rather than a third of your firepower. The blast does not care who fired it. The pilot takes 60% — 156 at point-blank, which kills a Wasp outright and takes a bite out of a Drone. The distance you keep is the price of the damage you get. Hostiles run from a live round: each registers as a steering hazard the size of its own blast, so the AI scatters as it crosses the arena. A round that hits nothing still breaks a formation off your tail, which makes it a zoning tool as much as a killing one. It also chain-detonates mines it goes off near. src/game/bfg.ts is pure maths over three.js vectors apart from the two meshes it owns, so the whole weapon runs headless. Twelve of the new assertions in simcheck.ts cover spool, abort, ammo, falloff, self-damage, knockback, mine chaining, AI avoidance and that charging really does silence the guns. The balance harness gained a BFG section plus two contract checks: it cannot replace the guns over an engagement, and it has to be able to kill the pilot who fired it. A blast that is safe to stand in is a free button, and a free button gets pressed on cooldown. Accuracy stays honest: a launch counts as one shot, and a blast that catches anything counts as exactly one hit rather than one per hull. Verified in a real browser as well as headlessly — flew a Drone with the guns never touched and killed a Hornet with the round, and confirmed the spool holds the guns cold. The first cut of the detonation threw 600 particles past the kill radius for two and a half seconds; the reward for the best shot in the game was a screen you could not see out of. It is now tight, short, and its middle shockwave ring stops exactly on the damage boundary, because a wave drawn wider than it kills teaches the wrong distance. The seeded end-to-end run in simcheck now clears in 13.0s instead of 13.4s. The weapon is not in that scenario at all: three.js draws Math.random for object UUIDs, so allocating the round meshes shifts the seeded stream. Same assertions, same outcome, different arithmetic. Co-authored-by: Doug Leonhardt <doug.leonhardt@gmail.com> Signed-off-by: Doug Leonhardt <doug.leonhardt@gmail.com>
13b4aa0 to
f996b3e
Compare
Requested in the
#gamedevchannel as "BFG sized really big guns". The damage was the easy part. The interesting part is what it costs to use.Hold
F(or right mouse) for 1.3 seconds and a slow green round leaves the nose. Anything within 340 units of where it stops takes up to 260 damage on a steep falloff — lethal to any airframe at the centre, a hard shove and a scare at the rim.Three rules do the design work
Two rounds a run, no refills. Anything renewable turns into a rotation you press on cooldown. Two means every launch is a judgement about whether this is the moment, and firing the second one is a small tragedy.
Charging costs you everything else. Guns cold, dash locked, throttle capped at 55% for the whole spool. You are committing to a heading for a second and a half in an arena where everyone else is still manoeuvring. Releasing early aborts and keeps the round, so a mispress costs a moment rather than a third of your firepower.
The blast does not care who fired it. The pilot takes 60% — 156 damage at point-blank, which kills a Wasp outright and takes a bite out of a Drone. The distance you keep is the price of the damage you get, in the same currency the enemy pays.
Hostiles also run from a live round: each one registers as an AI steering hazard the size of its own blast, so the squadron scatters as it crosses the arena. A round that hits nothing still breaks a formation off your tail — it is a zoning tool as much as a killing one. It chain-detonates any mines it goes off near, which the minefield makes considerably more interesting.
Player-only. An AI holding one of these would either never use it or nuke its own wing, and neither is a fight anyone wants.
Shape of the change
src/game/bfg.tsis pure maths over three.js vectors apart from the two meshes it owns, so the whole weapon runs headless. The game loop translates its events into sound, light and score; the weapon itself knows nothing about either.input.ts—secondaryon the input state (F/ right mouse, context menu suppressed over the canvas)game.ts— the interlock, event handling, AI hazard concat, score and accuracyhud.ts/style.css— spool gauge and two ammo pipsaudio.ts— a spool ratchet, launch and detonation. Deliberately a ratchet rather than a held whine: every other voice in the mix is a transient with its stop already scheduled, and a sustained tone would be the only thing needing to be turned off by hand — the exact bug that got the engine note deleted in Remove the engine sound #4. It also carries information a drone does not, since the interval tightens as the charge fills.fx.ts— the detonationAccuracy stays honest. A launch counts as one shot, and a blast that catches anything counts as exactly one hit rather than one per hull — otherwise a single trigger pull could report three hits and put accuracy over 100%.
Verification
Headless — 30 new assertions in
simcheck.tsacross four scenarios: spool and abort, ammo limits, falloff at the centre / edge / outside, self-damage ratio, knockback, mine chaining, AI avoidance appearing and clearing, and that charging really does hold the guns cold and hand them back on release. The balance harness gained a BFG damage table plus two contract checks: it cannot replace the guns over an engagement (17.3 DPS across 30s against the weakest gun's 46.7), and it has to be able to kill the pilot who fired it. A blast that is safe to stand inside is a free button, and a free button gets pressed on cooldown.In a real browser (Playwright, Chromium) — flew a Drone with the guns never touched and killed a Hornet with the round alone, confirmed the spool holds
shotsFiredat zero and hands the guns straight back, and confirmed the HUD pips and gauge track ammo and charge.That browser pass earned its keep: the first cut of the detonation threw 600 particles past the kill radius for two and a half seconds, so the reward for landing the best shot in the game was a screen you could not see out of. It is now tight and short, and its middle shockwave ring stops exactly on the damage boundary — a wave drawn wider than it kills teaches the wrong distance, and this is a weapon where distance is the entire decision.
One thing to know before merging
The seeded end-to-end run now clears in 13.0s instead of 13.4s. The BFG is not in that scenario at all — the scripted pilot never touches the trigger. three.js draws
Math.randomfor object UUIDs, so allocating the round meshes at game construction shifts the seeded stream. Same assertions, same outcome, different arithmetic. Worth knowing because it will happen again to anyone who adds an object tocreateGame.npm run checkis green: typecheck, simulation and the balance contract.Not done
Nobody has played this against their instincts. Every number is at the top of
src/game/bfg.ts—SPOOL_TIME,BLAST_RADIUS,BLAST_DAMAGE,SELF_DAMAGE,BFG_CHARGES. My suspicion is that two charges is right and 1.3 seconds is the dial most likely to be wrong.Opened from a fork: no write access on this repo.
Discussed in
#gamedevon Buzz.