RT is an analogue trigger, so "held" was one point on a curve. Walking it 0.00 -> 1.00 (throttle_curve.py) gives a straight ramp: 438, 626, 879, 1094, 1342 units/s. Dividing by the ~1.2 time-base factor, the endpoints land on the definition's own numbers (365 vs CruisingVelocity 350; 1118 vs MaximumVelocity 1200) and the midpoint follows, so target speed = CruisingVelocity + RT * (MaximumVelocity - CruisingVelocity) which refines the earlier "selects one of three targets" reading: those three are the curve's endpoints. It also refutes the standing afterburner hypothesis that full RT is the burner: the curve is smooth through full deflection with no step, and the shield does not move. The LT half is not measured yet — the entity scan needs the craft moving when it runs, so a mission left idling drops out of it. Bind early. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NptfmpjdpNCKEez6d2xvA9
176 lines
9.1 KiB
Markdown
176 lines
9.1 KiB
Markdown
# 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 is a TIME BASE, not a unit scale
|
||
|
||
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)
|
||
```
|
||
|
||
with `LT` presumably mirroring it down to `MinimumVelocity` (measured only at full
|
||
deflection so far: ~125 against 100).
|
||
|
||
**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 `LT` half of the curve is **not measured**: the entity scan that locks onto
|
||
the player needs the craft to be *moving* when it runs (it searches changing position
|
||
triples), and a mission left idling long enough for the craft to slow or die drops
|
||
out of the scan. Bind early, while the craft still has speed.
|