# The throttle is a TARGET-SPEED selector — measured against the definition (2026-08-13) **Status: āœ… for the shape of the law, 🟔 for the unit scale.** The unit definition gives `MinimumVelocity 100`, `CruisingVelocity 350`, `MaximumVelocity 1200`, `Acceleration 600` and `Deceleration 500` for `UN_f001_TCAF_DeltaSaber_T_Player` ([values](captures/unit-runtime-fields.csv)), but not **how** the game applies them. This measures it. Method — [`tools/re-capture/speed_law.py`](../../tools/re-capture/speed_law.py): lock onto the player entity once, then sample its own position triple in guest RAM while holding each throttle input in turn (8 s per phase, 20 Hz). Speed is differentiated over **1-second windows**, never per sample. ## Result | phase | measured speed (1 s windows) | settles to | definition field | |---|---|---|---| | no throttle | 367 403 471 365 463 463 365 | **~420** | `CruisingVelocity` 350 | | `RT` held | 1041 1376 1551 1379 1596 1510 1620 | **~1 530** | `MaximumVelocity` 1200 | | release | 1314 672 432 452 432 450 408 | back to **~440** | — | | `LT` held | 203 121 135 121 115 146 122 | **~125** | `MinimumVelocity` 100 | | release | 369 451 427 425 445 390 465 | back to **~430** | — | So the throttle **selects a target speed** and the craft converges to it; releasing either trigger returns it to cruise. (**Refined below**: the trigger is analogue, so the target is a continuous interpolation and these three are its endpoints.) It is not a force model with the throttle adding thrust, which is what a reimplementation would most likely have assumed from `Acceleration`/`Deceleration` alone. Those two fields govern the **convergence rate**: the release phase falls ~1 314 → ~432 in about two seconds (~440 units/s², against `Deceleration` 500) and the `RT` phase climbs ~420 → ~1 550 in two to three seconds (~470–560 units/s², against `Acceleration` 600). ## 🟔 The unit scale Measured world-space speeds run **ā‰ˆ1.2–1.3Ɨ the definition numbers** in all three regimes (cruise 1.21, maximum 1.28, minimum 1.32). The HUD, meanwhile, reads **350** at cruise — the definition value exactly. So the definition's velocity unit is the HUD's, and entity world coordinates are a constant multiple of it, close to **1.25**. The spread across regimes is larger than the constant itself is precise, because the craft is manoeuvring under fire while sampled (straight-line displacement per window under-reads a curving path), so this is recorded as a measured range rather than a pinned constant. ## Traps * **`RT`/`LT` are analogue triggers**, so `vgamepad trig RT 1.0` holds the throttle; the button verb `hold RT` is a **silent no-op**. The first run of this probe measured the drift of a craft nobody was flying. * **Do not differentiate per sample.** The guest updates the position slower than 20 Hz, so per-sample differences alternate between 0 and a double step — `0, 1519, 1985, 0, 2681…` for a craft flying smoothly. * **Bind late, measure fast.** The player entity is not in the typed-entity scan for the first ~15 s of a mission, and the craft dies within a few minutes if nobody is flying it — one earlier attempt ended at `GAME OVER` mid-probe. Raw samples: [`captures/speed-law-throttle-phases.csv`](captures/speed-law-throttle-phases.csv). --- # Turn rates: `AV_*` are rate caps, and `_Min`/`_Max` mean *at minimum/maximum speed* `tools/re-capture/turn_law.py` pins the speed regime with a throttle, holds a stick axis, and differentiates the craft's **own forward vector** over 1-second windows. **Control mapping, measured** — `LX` is **roll** (the forward vector barely moves under it: 3–5 °/s residual), `LY` is **pitch** (`vgamepad` documents `LY: -1 = up`, so `+1` is nose down = the `PitchMinus` family), and the **right stick does not steer at all** (0 °/s). | phase | measured (1 s windows) | definition | |---|---|---| | slow + nose down | 85 Ā· 96 Ā· 82 → **~87** | `AV_PitchMinus_Min` **75** | | fast + nose down | 55 Ā· 45 Ā· 63 → **~54** | `AV_PitchMinus_Max` **40** | | slow + nose up | 181 Ā· 164 Ā· 181 → **~175** | `AV_PitchPlus_Min` **150** | | fast + nose up | 103 Ā· 130 Ā· 175 → **~136** | `AV_PitchPlus_Max` **70** | Two claims of the field names are confirmed: * **Agility falls with speed** — every rate drops when the throttle goes from `LT` to `RT`, so `_Min`/`_Max` are *at minimum / maximum speed*, not floor/ceiling of the rate. * **Pitching up is about twice as fast as pitching down**, exactly as the pair 150/75 (slow) and 70/40 (fast) says. ## The ~1.2Ɨ factor — āš ļø WITHDRAWN, see "Settled rates match exactly" below The speed law measured 1.21 / 1.28 / 1.32 Ɨ its definition values; the pitch rates measure 1.16 / 1.35 / 1.17 / 1.94 Ɨ theirs. A **unit scale** would have to differ between metres-per-second and degrees-per-second — it cannot explain both. A **time base** does: if the guest's simulated second is shorter than the wall-clock second this probe measures against, every rate reads high by the same factor. So the definition numbers are self-consistent and a reimplementation should take them at face value; these measurements confirm the **shape** of the law (which field governs what, and how the regimes switch), not a scale correction. 🟔 **No yaw input was found.** `AV_Yaw_{Min,Max}` (45 / 25 °/s) exists, but neither stick yaws the craft. With `MaximumBank_Normal` (60) and roll on `LX`, a **bank-to-turn** model is the obvious reading — sustained turns come from rolling and pitching — but the yaw fields may equally belong to the AI or to an input this probe has not driven. Raw samples: [`captures/turn-law-pitch-phases.csv`](captures/turn-law-pitch-phases.csv). --- # The afterburner is not on the obvious buttons — a bounded negative The definition describes the burner without naming its input: `AB_ConsumeShield_Begin` **50**, `AB_ConsumeShield` **10**, and a set of `AB_AV_*` turn caps far below the normal ones (roll 40 vs 125–200 °/s, pitch-up 30 vs 70–150). Notably there is **no `AB_*Velocity`**, so the burner may not raise the speed target at all. Two independent probes, both negative for `A`, `B`, `X`, `LB` (and `LS`/`RS` on the first): * [`tools/re-capture/ab_probe.py`](../../tools/re-capture/ab_probe.py) — hold `RT` for a max-speed baseline, then hold each candidate: speed stayed inside the baseline's own noise band (1 200–1 580 units/s) for every one. * [`tools/re-capture/ab_state_probe.py`](../../tools/re-capture/ab_state_probe.py) — sample a window of the player object while each candidate is held and report any float that falls during the hold: **nothing fell**. * And the cheapest oracle of the three, needing no offsets at all: **count the green pixels of the HUD's SHIELD bar** before and after each hold, on a freshly spawned craft with a full shield. `AB_ConsumeShield_Begin 50` should take a visible bite; the bar read **156, 156, 156, 157, 157** across baseline and all four buttons. So the burner is **not a simple hold on `A`/`B`/`X`/`LB`/`LS`/`RS`**. What remains: a chord (something plus `RT`), an input this pad cannot reach, a craft variant that has it, or fields that belong to the AI rather than the player. Worth knowing before anyone re-probes the obvious buttons. (`RB` is the nose gun, `Y` the missile mount and the d-pad the tactical map — see [flight controls](flight-controls-runtime.md) — so those were not held here.) --- # The throttle is ANALOGUE: the target speed interpolates cruise → maximum `RT` is an analogue trigger, so "held" was only ever one point on a curve. Walking it in five steps ([`tools/re-capture/throttle_curve.py`](../../tools/re-capture/throttle_curve.py), 2.5 s to converge then 5 s of sampling per step): | `RT` | settled speed (1 s windows) | mean | |---|---|---| | 0.00 | 465 Ā· 411 Ā· 373 Ā· 504 | **438** | | 0.25 | 648 Ā· 554 Ā· 678 Ā· 622 | **626** | | 0.50 | 739 Ā· 961 Ā· 897 Ā· 919 | **879** | | 0.75 | 1028 Ā· 1086 Ā· 977 Ā· 1283 | **1094** | | 1.00 | 1359 Ā· 1306 Ā· 1517 Ā· 1186 | **1342** | A straight ramp. Dividing by the ~1.2 time-base factor established above, the endpoints land on the definition's own numbers — 438/1.2 ā‰ˆ **365** against `CruisingVelocity` **350**, and 1342/1.2 ā‰ˆ **1118** against `MaximumVelocity` **1200** — and the midpoint follows: 879/1.2 ā‰ˆ 733 against the predicted 350 + 0.5Ā·(1200āˆ’350) = 775. So ``` target speed = CruisingVelocity + RT Ā· (MaximumVelocity āˆ’ CruisingVelocity) ``` and `LT` mirrors it down to `MinimumVelocity` — **measured**, see below. **This also refutes a live hypothesis about the afterburner.** Full `RT` producing more than `MaximumVelocity` looked like it might *be* the burner; it is not — the curve is smooth through full deflection, with no step, and the shield does not move. Full throttle is simply full throttle. Raw samples: [`captures/throttle-curve-rt.csv`](captures/throttle-curve-rt.csv). (The entity scan that locks onto the player searches *changing* position triples, so it only sees the craft while it still has speed — bind early, or a mission left idling drops out of the scan entirely and every tool reports "player entity not found" while the game is visibly flying.) --- # `LT` mirrors `RT`, and roll does NOT depend on speed One flight, both measurements ([`tools/re-capture/flight_law2.py`](../../tools/re-capture/flight_law2.py)). ## The braking half of the curve | `LT` | 0.00 | 0.25 | 0.50 | 0.75 | 1.00 | |---|---|---|---|---|---| | speed | 436 | 379 | 289 | 209 | **126** | A straight ramp down from cruise to ~126, and dividing by the ~1.2 time-base factor the endpoints are the definition's own numbers again — 363 against `CruisingVelocity` **350** and 105 against `MinimumVelocity` **100**. So the throttle law is symmetric: ``` RT held: target = Cruising + RT Ā· (Maximum āˆ’ Cruising) LT held: target = Cruising āˆ’ LT Ā· (Cruising āˆ’ Minimum) ``` ## Roll Roll turns the craft about its own forward axis, so it has to be measured on a different row of the rotation matrix than pitch was: | phase | rate per 1 s window | mean | |---|---|---| | minimum speed + full roll | 171 Ā· 103 Ā· 157 | **~144 °/s** | | maximum speed + full roll | 140 Ā· 145 Ā· 164 | **~150 °/s** | **Roll shows no speed dependence** — the two regimes agree inside the noise, where pitch dropped by a third to a half between them. Against the definition (`AV_Roll_Min` 200, `AV_Roll_Max` 125) the measured ~145 °/s is ~121 after the time-base factor, i.e. **the `Max` value in both regimes**. So the `_Min`/`_Max` pair does *not* mean the same thing for every axis: pitch interpolates between them with speed, roll appears pinned at `Max`. A reimplementation that applies one rule to all axes would get roll wrong at low speed by ~60 %. 🟔 Caveat kept: the roll phases were 2 s of settling apart, which is marginal for a full 126 → 1 342 speed change, so "no dependence" is a measurement over a real but not perfectly separated pair of regimes. Raw samples: [`captures/throttle-curve-lt.csv`](captures/throttle-curve-lt.csv) Ā· [`captures/turn-law-roll.csv`](captures/turn-law-roll.csv). --- # āš ļø Settled rates match the definition EXACTLY — the "time base" reading is withdrawn The earlier turn measurements settled each phase for 1.5 s and then differentiated from the first second. Re-running with **5 s of settle** and the craft's speed recorded *at the moment the turn starts* ([`tools/re-capture/flight_law3.py`](../../tools/re-capture/flight_law3.py)): | phase | speed at turn | measured rate | definition | |---|---|---|---| | pitch, minimum speed | 130 /s | **74.9 °/s** | `AV_PitchMinus_Min` **75** | | pitch, half throttle | 388 /s | 73.7 °/s | (between, see below) | | pitch, maximum speed | 1 821 /s | **41.1 °/s** | `AV_PitchMinus_Max` **40** | | roll, minimum speed | 110 /s | 129.5 °/s | `AV_Roll_Min` 200 | | roll, maximum speed | 1 722 /s | 149.3 °/s | `AV_Roll_Max` 125 | **Pitch lands on 75 and 40 — the definition's own numbers, with no scale factor.** So the ~1.2Ɨ I attributed to the emulated time base was an artefact of measuring during the `AA_*` acceleration ramp with too little settle, and that explanation is **withdrawn**: angular rates need no correction, and a reimplementation should use `AV_*` verbatim. What remains unexplained is only on the **linear** side: settled speeds still read high and vary between runs (`RT` full measured 1 342 in one flight and 1 821 in another, against `MaximumVelocity` 1 200), which is what a craft being shoved around in a firefight looks like. The HUD, meanwhile, reads exactly `CruisingVelocity` at neutral throttle. A clean linear measurement wants a quiet corner of a map, not this one — until then, no cause is claimed. **Roll, re-measured with proper settles, confirms the axis difference**: 129.5 at 110 /s and 149.3 at 1 722 /s — no speed dependence, both near `AV_Roll_Max` **125**, where pitch moved 75 → 40 across the same range. The mid-throttle pitch point does not discriminate lerp-versus-switch, because at half throttle the craft only reached 388 /s, where an interpolation would predict ~66 °/s against a measured 73.7 — inside this probe's noise. Raw samples: [`captures/turn-law-settled.csv`](captures/turn-law-settled.csv). --- # The linear discrepancy is real, and it is between WORLD units and DISPLAYED speed Last section removed the "time base" explanation by showing settled *angular* rates match the definition exactly. That leaves the linear side, and the HUD settles it: screenshotting the speed readout at each throttle step, next to the position-derived measurement of the same moment ([`captures/throttle-curve-hud.csv`](captures/throttle-curve-hud.csv)): | `RT` | HUD readout | position-derived | ratio | |---|---|---|---| | 0.00 | **350** (= `CruisingVelocity`) | ~447 | 1.28 | | 0.25 | **507** | ~652 | 1.29 | | 0.75 | **963** | ~1 141 | 1.19 | Two things follow. * **The HUD speaks the definition's language.** It reads exactly `CruisingVelocity` at neutral and climbs toward `MaximumVelocity` as the trigger is pressed — 963 at three-quarters, against the 987 the interpolation predicts. So the throttle law stated above is confirmed in the game's *own* units, independent of any position sampling. * **World displacement runs ~1.2Ɨ the displayed speed.** Since the angular rates need no such factor, this is not a clock effect: it is a **unit difference between the position triple and the velocity fields**. A reimplementation that places entities in world coordinates and moves them at `MaximumVelocity` will be ~20 % slow. 🟔 The ratio is 1.19–1.29 across the three points rather than a clean constant, and every sample was taken in a firefight where the craft is also being pushed. Pinning it exactly wants a quiet map or a scripted straight run; the *existence and rough size* of the factor is what these measurements support. **A methodological note worth keeping.** This line of work went: hold a trigger → "three target speeds" → analogue interpolation → an unexplained 1.2Ɨ → "time base" → withdrawn → a *unit* difference confined to the linear side. Every step came from one of two moves: **let a held input vary** (the trigger's analogue range), or **bring in an independent oracle** (the HUD, the definitions, a longer settle). The wrong turn — the time base — came from explaining a number instead of first measuring the same quantity a second, cleaner way.