guts/web/js/flight/tuning.js
jing 4ed3433e55 [lane B] Round 2 WIP (session cut off): surf speed-lock, hazards + pickups modules, rho-fraction spawns
Landed before the cut: surf as a speed-lock onto world.crestSpeed(s) with surf.authority
(ruling #1) — riding beats throttle and holding a crest is throttle discipline; tuning.js
header corrected to the real wallRho frame (C's flag); enemies.js reads rho as a FRACTION
of safe radius (C's placement law); balance.js hazard + pickup constants; NEW hazards.js
(reflux_surge / aortic_squeeze / ring_gate, self-scheduling telegraphs, reset(fromS) built
for respawn) and NEW pickups.js (all five kinds, shape-coded).

Cut off before: wiring — nothing imports hazards/pickups yet; the fiction-id -> archetype
resolve (round-2 task #1) does NOT exist despite the enemies.js comment saying index.js
does it; player.kill/shove/refill are called but were never added; checkpoint/respawn and
gate -> level:complete not started; NOTES unwritten. F lands the glue next commit.
Committed by F to protect the shared tree.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 16:26:15 +10:00

126 lines
7.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// flight/tuning.js (Lane B) — THE FEEL. Every number that decides how GUTS handles lives
// here and nowhere else. Tuned by hand on the stub esophagus; re-tune per biome later.
//
// Reference frame these are sized against — CORRECTED round 2 (C caught the stale numbers,
// LANE_C_NOTES §→ Lane B #4). The round-1 header quoted the round-0 stub; A's real world has
// since measured the esophagus wave at amp 1.4 (not 0.9) + breathe 0.15, and `wallRho`
// subtracts BOTH plus SKIN:
// biome.flow = 14 u/s · radius 10 ±15% wobble => 8.50 .. 11.50
// wallRho = radius (amp 1.4 + breathe 0.15) SKIN 0.6 => 6.35 .. 9.35 playable disc
// minus ship.radius 0.9 => 5.45 .. 8.45 free disc
// The envelope shrank ~0.65 u under us. disc.maxSpeed/accel and the graze numbers were sized
// against the old 7.0..10.0 and still read fine at 6.35..9.35, but that is luck, not design —
// if A retunes the wave again, re-check `disc` and `wall` here, not just C's clearances.
//
// SURF: FIXED THIS ROUND — it is a ride now, not a shove.
// Round 1 measured the mechanic as strictly dominated: a crest travelling at flow (13.64 u/s)
// cannot carry anything faster than itself, while throttle-mashing gave 19.6 u/s, so the
// correct play was to ignore the level's signature mechanic. Tuning could not fix it (the
// crest window is fixed in SPACE, so a bigger bonus just ejected you sooner — measured ~3.0
// units gained per crest at gain 0.4, 0.75 AND 1.2, identical).
//
// F's round-2 ruling #1 fixed it at the source: `crestSpeed = CREST_FACTOR(1.6) × flow`, which
// beats throttleMax 1.4. So the wave now outruns the player and B **speed-locks to
// world.crestSpeed(s)** while riding instead of adding a flat bonus. Riding at flow 14 =
// 22.4 u/s vs 19.6 for mashing: +14%, and because you match the crest's own speed you STAY on
// it instead of being thrown off the front. Throttle still decides how long you hold it —
// see surf.authority. Read the crest, don't hold W.
export const TUNING = {
ship: {
radius: 0.9, // collision sphere (hull reads ~1.9 long, 1.2 wide)
},
// --- forward motion along s -------------------------------------------------------
flow: {
throttleMin: 0.6, // GDD: throttle is ±40% of biome flow, never a full stop
throttleMax: 1.4,
throttleRate: 1.8, // throttle units/s while held (0.6 -> 1.4 in ~0.45 s)
throttleReturn: 0.9, // /s spring back to neutral 1.0 when hands off
},
boost: {
gain: 1.2, // +120% of biome flow (GDD)
duration: 0.55, // s of burst
cooldown: 2.4, // s from spend until available again
iframes: 0.35, // s invulnerable "at spend" (GDD) — shorter than the burst,
// so boosting *through* something is a read, not a get-out
fovKick: 13, // degrees of extra FOV at full burst (speed you feel)
},
surf: {
threshold: 0.45, // world.flowPulse(s,t) above this = on the crest.
// pulse=pow(sin,3) so 0.45 => sin>0.766 => the peak 22% of each
// wave. Under CREST 1.6 the wavelength grew to ~45.7 u at flow
// 14, so that window is now ~10 u long (was ~6.3) — the crest is
// a wider, faster lane than it was in round 1.
authority: 0.85, // how completely the crest owns your speed while riding, 0..1.
// THE knob. At 1.0 the wave takes you at exactly crestSpeed and
// throttle stops mattering while surfing — you'd ride forever
// and the mechanic would have no skill floor. At 0.85 you sit
// just under the crest and drift off the back at a rate YOU set:
// throttle 1.4 -> 21.98 vs crest 22.4 => drift 0.42 u/s, ~12 s ride
// throttle 1.0 -> 21.14 => drift 1.26 u/s, ~4 s ride
// throttle 0.6 -> 20.30 => drift 2.10 u/s, ~2 s ride
// So catching a crest is positioning; HOLDING one is throttle
// discipline. Never applied when it would slow you (boosting
// past the crest keeps your speed — see player.js).
ramp: 3.0, // /s blend in/out so cresting doesn't pop
},
// --- movement inside the cross-section disc ---------------------------------------
disc: {
accel: 78, // u/s²
maxSpeed: 13, // u/s — crosses the ~16 u tube in ~1.2 s: dodgeable, not twitchy
damping: 6.5, // /s exponential drag => coasts to rest in ~0.3 s
},
// --- aim: the reticle leads, the ship follows (Star Fox lag) -----------------------
aim: {
distance: 26, // u ahead (in s) that the reticle plane floats
sensitivity: 0.022, // disc units per pixel of mouse movement
padSpeed: 20, // u/s at full right-stick deflection
range: 9, // max |aim| from the centreline (≈ the widest wallRho)
recenter: 0.8, // /s drift back to centre when hands off
lag: 9, // /s spring — the ship's nose chases the reticle, never snaps
},
// --- attitude: bank into lateral motion -------------------------------------------
bank: {
gain: 0.055, // rad of roll per u/s of lateral disc velocity
max: 0.75, // rad (~43°)
rate: 7, // /s spring toward target roll
pitchGain: 0.03, // rad of pitch per u/s of vertical disc velocity
},
// --- chase camera ------------------------------------------------------------------
cam: {
back: 7.2, // u behind the ship (along tan, measured in s)
up: 1.4, // u along the frame normal — ship sits low-centre
lookAhead: 16, // u ahead of the ship for the look target
discLag: 8.0, // /s spring on the camera's own disc offset
discFollow: 0.82, // camera tracks only 82% of the ship's offset, so hard dodges
// visibly move the ship within the frame instead of gluing it
aimInfluence: 0.35, // how much the look target drifts toward the reticle
fov: 75, // matches boot.js's camera so wiring in changes nothing
fovRate: 4.5, // /s toward target fov
},
// --- durability (GDD: regenerating mucus coat over a pickup-only hull) -------------
coat: {
max: 100,
regen: 8, // /s
regenDelay: 2.5, // s after the last damage before regen resumes
// ambient drain is the biome's: world.biomeAt(s).coatDrain (esophagus = 0.5/s)
},
hull: { max: 100 },
// --- wall contact: arcade. Shove + graze damage, NEVER a hard stop -----------------
wall: {
restitution: 0.35, // fraction of inward velocity returned on contact
grazeFloor: 3.0, // impact u/s below this is free — you can lean on the wall
damagePerSpeed: 1.6, // coat damage per u/s of impact above grazeFloor
maxHit: 28, // cap: a full-speed slam is a mistake, not a death
shoveOut: 1.0, // extra u/s pushed off the wall
cooldown: 0.25, // s between wall damage ticks (scraping ≠ machine-gun damage)
},
};