re(flight): the linear discrepancy is a world-unit vs displayed-speed difference

Screenshotting the HUD speed readout at each throttle step, beside the
position-derived measurement of the same moment:

  RT 0.00   HUD 350 (= CruisingVelocity)   position ~447   ratio 1.28
  RT 0.25   HUD 507                        position ~652   ratio 1.29
  RT 0.75   HUD 963                        position ~1141  ratio 1.19

So (a) the HUD speaks the definition's units — exactly CruisingVelocity at neutral,
963 at three-quarters against the 987 the interpolation predicts — confirming the
throttle law in the game's own numbers without any position sampling; and (b) world
displacement runs ~1.2x the displayed speed. Since settled angular rates need no such
factor, this is a unit difference between the position triple and the velocity
fields, not a clock effect: a reimplementation moving entities at MaximumVelocity in
world coordinates will be ~20% slow.

Also fixes speed_law.find_player: a mission holds more than one *_Player object and
at least one never moves, so the finder now samples each candidate twice and keeps
the one that displaces. Locking onto the static one is what produced a run of exact
zeros while the game was visibly flying.

🟡 The ratio is 1.19-1.29 rather than a clean constant and every sample was taken in
a firefight; pinning it wants a quiet map.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NptfmpjdpNCKEez6d2xvA9
This commit is contained in:
2026-08-13 17:06:35 +00:00
parent f6974ff3f0
commit 0cb81b6200
4 changed files with 656 additions and 4 deletions

View File

@@ -19,13 +19,29 @@ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import gworld, entities2
def find_player(retries=8):
"""Lock onto the player object that is actually FLYING.
A mission holds more than one `*_Player` object — `entities2 list` shows two —
and at least one of them never moves. Taking the first match locks onto that
one, and then every probe reads a speed of exactly 0 while the game is visibly
flying (and the HUD keeps reading 350). So sample each candidate twice and keep
the one that displaces."""
for _ in range(retries):
w = gworld.World()
defs = entities2.definitions(w)
movers = entities2.moving(w.fd, w.size)
for off, nm, pos, sp in entities2.typed(w.fd, defs, movers, 0x130):
if nm.endswith("_Player"):
return w, off, nm
cands = [(off, nm) for off, nm, _, _ in entities2.typed(w.fd, defs, movers, 0x130)
if nm.endswith("_Player")]
best = None
for off, nm in cands:
a = pos_at(w.fd, off)
time.sleep(0.3)
b = pos_at(w.fd, off)
d = sum((b[i] - a[i]) ** 2 for i in range(3)) ** 0.5
if best is None or d > best[0]:
best = (d, off, nm)
if best and best[0] > 0.5:
return w, best[1], best[2]
time.sleep(2)
return None, None, None

View File

@@ -34,7 +34,12 @@ def main():
for v in (0.0, 0.25, 0.5, 0.75, 1.0):
pad("trig", "RT", "0.0"); pad("trig", "LT", "0.0")
pad("trig", axis, str(v))
time.sleep(2.5) # let the speed converge before sampling
time.sleep(6.0) # settle: 5 s+ is what the turn-rate fix showed is needed
# Screenshot the HUD at the settled speed: its readout is in the
# DEFINITION's unit (it shows 350 at neutral = CruisingVelocity), so it is
# the ground truth the position-derived number has to be compared against.
subprocess.run(["screenshot", f"/sylph-home/re/shots/hud_{axis}_{v}.png"],
capture_output=True)
seq, t0 = [], time.time()
while time.time() - t0 < dwell:
seq.append((round(time.time() - t0, 3), *speed_law.pos_at(w.fd, off)))