guts/web/js/flight/tuning.js
type-two 4ee6ae49a1 [lane B+E+F] Arena mode: in a room, you own the forward axis
The 6DOF unlock the player header has promised since round 1 ("Arena/6DOF mode is round 2;
modeAt() is guarded below"). It unblocks L1 and L3, which are both arena levels in the GDD and
have been skeletons waiting on this.

The insight that made it small: spline space ALREADY spans a room — s is the axial coordinate,
x/y are the cross-section — and world.collide() already resolves against arena spheres
(arenaSpatial, Lane A round 2). So this is not a new controller. The only real difference
between a tube and a room is WHO OWNS s: in a tube the flow carries you and you may only trim
it +/-40%; in a room `intent.throttle` becomes signed thrust with its own damping, so you can
stop dead and you can reverse. That is the whole feature, and it is what "slower, spatial,
exploratory" actually means in play.

- tuning.js: arena{thrust 44, maxSpeed 11, damping 2.2}. maxSpeed sits UNDER the esophagus's
  12-20 flow on purpose — the GDD asks for slower.
- player.js: st.vs (axial velocity), the mode branch, surf disabled in rooms (no travelling
  wave to ride, and a hanging surfBlend would speed-lock you to a crest that isn't there),
  respawn() clears vs so a death in a room can't leak momentum into a tube.
- player:state gains `mode`; `flow` reads 0 in a room and `speed` goes signed.
- hud.js prints "6dof · free" instead of "flow 0 · thr 100%" — two lies on one instrument.

Safe by construction: both arena triggers are opt-in (level.arenas, or a segment marked
mode:"open"). L1/L2/L4 declare neither and are byte-for-byte unaffected.

Verified in L3's stomach: tube hands-off carries you at 4.53 u/s, acid-sea hands-off coasts to
a dead stop (0.0), and a room lets you travel BACKWARDS (-3.8u). Bus reports
{mode:arena,flow:0,speed:0} vs {mode:tube,flow:3,speed:3}.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-25 22:39:06 +10:00

137 lines
8.2 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
},
// --- arena / 6DOF: in a room, the anatomy stops carrying you -----------------------
// GDD: "the same controller unlocked to free 6DOF, Descent-style, no gravity. Slower,
// spatial, exploratory." The felt difference is NOT extra axes — spline space already spans
// a room (s is the axial one, x/y are the cross-section) — it is that YOU own the forward
// axis. You can stop. You can reverse. Nothing is shoving you at the boss.
arena: {
thrust: 44, // u/s² axial. Softer than disc accel 78: a room is read, not strafed
maxSpeed: 11, // u/s — under the esophagus's 12-20 flow on purpose (GDD: "slower")
damping: 2.2, // /s — a long coast (no gravity), but it does come to rest
},
// --- 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)
},
};