Files
Syplheed-Reborn/docs/re/flight-speed-law.md
Claude (auto-RE) ae5f322c03 re(flight): the linear factor is the CLOCK — 1.260 measured against 1.267
Cleanest linear measurement: neutral throttle (HUD = CruisingVelocity 350), sticks
centred, 20 s of perfectly straight flight (displacement/path = 1.000):

  8 900 world units in 20.1 s -> 443.6 /s -> 1.267x the HUD's 350

And the game's own mission timer across a wall-clock interval:

  TIME 00:08.79 -> 00:46.97 = 38.18 s of game time in 30.29 s wall = 1.260

Same number. So the linear discrepancy is not a unit difference: the mission clock
runs ~1.26x faster than wall time under this emulator, and dividing world
displacement by WALL seconds inflates speed by exactly that. World units and
displayed speed share one unit; the definition velocities are per GAME second.

This supersedes the previous "world-unit vs displayed-speed" reading.

Left open (): settled turn rates measured 74.9/41.1 deg/s in wall time against
AV_PitchMinus_Min/Max 75/40, but the clock argument predicts ~94 for the first.
Either that agreement was luck inside a noisy sample (per-window rates spanned
61-96) or angular integration is frame-based where linear is time-based. The check
is to re-measure pitch and convert wall->game seconds with the clock ratio.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NptfmpjdpNCKEez6d2xvA9
2026-08-13 17:26:27 +00:00

345 lines
17 KiB
Markdown
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.
# 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 (~470560 units/s², against `Acceleration` 600).
## 🟡 The unit scale
Measured world-space speeds run **≈1.21.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: 35 °/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 125200 °/s, pitch-up 30 vs 70150). 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 2001 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·(1200350) = 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.191.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.
---
# ✅ The linear factor IS the clock — measured against the game's own mission timer
The cleanest possible version of the linear measurement: neutral throttle (HUD reads
exactly `CruisingVelocity` 350), sticks centred, **20 s of perfectly straight flight**
(`straightness = displacement/path = 1.000`, so no manoeuvring contaminates it) —
[`tools/re-capture/cruise_ratio.py`](../../tools/re-capture/cruise_ratio.py):
```
displacement 8 900 world units in 20.1 s -> 443.6 /s
ratio vs the HUD's 350 -> 1.267
```
And the game's **own clock**, read off the HUD across a wall-clock interval:
```
mission TIME 00:08.79 -> 00:46.97 = 38.18 s of game time
wall clock = 30.29 s
ratio = 1.260
```
**1.260 against 1.267 — the same number.** So the linear "discrepancy" was never a
unit difference: **the mission clock runs ~1.26× faster than wall time under this
emulator**, and a speed derived by dividing world displacement by *wall* seconds is
inflated by exactly that. World units and the displayed speed share one unit, and a
reimplementation should take `CruisingVelocity`/`MaximumVelocity` at face value —
per **game** second.
This supersedes the previous section's "world-unit vs displayed-speed difference".
**One thing does not fit, and is left open.** The settled turn rates measured
74.9 and 41.1 °/s *in wall time* against `AV_PitchMinus_{Min,Max}` 75/40 — but if
game time runs 1.26× fast, a 75 °/game-second cap should have read ~94 °/wall-second.
Either that agreement was luck inside a noisy sample (the per-window rates spanned
6196), or angular integration is frame-based where linear is time-based. The check:
re-measure pitch and convert wall→game seconds with the clock ratio, expecting ~94
if the clock explanation is universal.