Oracle: "no range finder on this drop" + a screenshot -- tick marks present,
moving caret absent, one drop, only tester affected.
Not the host, not the chassis, not his destroyed HUD. From the four field
logs: he WAS hosting (`[lobby] host:` appears only in his log) but range
computed fine on his node (1806 nonzero samples) and the reticle built on all
6 drops; a second tester flew a Thor the same night without hosting and saw
nothing, and the ladder is shared HudSimulation/BTReticleRenderable, not
per-chassis content; his HUD was destroyed twice but for 11s and 26s only, and
a destroyed HUD costs the LOCK (_DAT_004b7ec4 = 0.75), not the caret.
THE DEFECT. sShownRange -- what the caret binds to -- is a function-level
static in mech4's targeting step: one cell for the whole process, shared by
every mech, carried across drops, never re-seeded. NaN is ABSORBING in
step = trueRange - sShownRange;
if (step > maxStep) step = maxStep; // false for NaN
if (step < -maxStep) step = -maxStep; // false for NaN
sShownRange += step;
so one poisoned frame makes it NaN for the life of the process. The consumer
repeats the mistake -- BTReticleRenderable::Draw clamps with the same two
comparisons -- so NaN reaches AddPoint/ConcatMatrix and the caret + its bar
become degenerate geometry that STOPS RENDERING, while every static reticle
element including the tick marks still draws. That is the reported symptom
exactly, and it is sticky until relaunch.
WHY NO LOG COULD SETTLE IT. The caret's actual input had NO diagnostic
anywhere: BT_RANGE_LOG instruments the PICK (#4), and [target]'s `range=` is a
SEPARATE locally-recomputed Sqrt in the weapon-range check -- neither is
sShownRange or gBTHudRangeStorage. Grepping the field logs for NaN returns
nothing because the poisoned variable was never printed. Absence of the
signal was not evidence of absence.
FIX (4 parts):
1. re-seed sShownRange when the viewpoint mech CHANGES, so a new drop starts
at the binary's 1200 default. Deliberately NOT on respawn -- that reuses
the entity, and the binary does not reset the readout on respawn either.
2. producer NaN trap -> re-seed to 1200 instead of propagating.
3. NaN-safe consumer clamp (test x == x first) -> fall back to the authentic
no-target peg rather than rendering nothing.
4. BT_RANGE_LOG now prints the caret's real input at 1 Hz plus a
"[range] NaN TRAPPED" receipt, so the next field log CAN settle it.
STATUS [T3 on the field link]. The defect and the symptom match exactly and
the fix stands on its own merits -- a process-lifetime static feeding unguarded
float geometry is a bug regardless. But the causal link to Oracle's report is
INFERENCE: the NaN source is unidentified and this has not been reproduced.
Field-verify with BT_RANGE_LOG=1.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018SgmXGNMXavXiafKXf9MDC
89 lines
4.4 KiB
Python
89 lines
4.4 KiB
Python
"""Night 13: rewrite #147's body (the inline attempt was mangled by the shell). ASCII only."""
|
|
import sys
|
|
sys.path.insert(0, r"C:\git\bt411\scratchpad\night7")
|
|
import gitea
|
|
|
|
BODY = """**Reported by Oracle (2026-08-06 session):** "no range finder on this drop" + a screenshot.
|
|
Symptom confirmed by eye: **the ladder tick marks were present, the moving caret was not.**
|
|
Intermittent -- one drop, not every drop. He was the only tester who hit it.
|
|
|
|
## What it is NOT
|
|
|
|
Ruled out from the four field logs:
|
|
|
|
* **Not a Steam-host bug.** Oracle *was* hosting (`[lobby] host: lobby up` / `GO with N member(s)`
|
|
appears in his log and no other), but the host path is uninvolved: the range **computed** fine on
|
|
his node (1806 nonzero `[target] range=` samples) and the reticle **built** fine on every drop
|
|
(`[hud] reticle built: 7 weapon pip(s) registered` -- 6 drops, 6 builds).
|
|
* **Not the Thor chassis.** He flew a Thor, which is why the report reads as chassis-specific -- but
|
|
a second tester flew a Thor the same night *without* hosting and reported nothing, and the range
|
|
ladder is drawn by the shared `HudSimulation` / `BTReticleRenderable`, not per-chassis cockpit
|
|
content. No Thor asset-missing warnings in any log (only the known wreck-model fallbacks).
|
|
* **Not his destroyed HUD.** His HUD subsystem *was* destroyed twice -- the only tester to reach
|
|
`condition 0` all night -- but only for ~11 s and ~26 s, repaired by respawn each time. And a
|
|
destroyed HUD costs you the fire-control LOCK (own host zone >= 0.75 damage, `_DAT_004b7ec4`),
|
|
not the caret.
|
|
|
|
## The defect
|
|
|
|
`sShownRange` -- the value the caret ultimately binds to -- is a **function-level static** in
|
|
mech4.cpp's targeting step. One cell for the whole process, shared by every mech, carried across
|
|
drops, never re-seeded. Its update:
|
|
|
|
```c
|
|
float step = trueRange - sShownRange;
|
|
if (step > maxStep) step = maxStep;
|
|
if (step < -maxStep) step = -maxStep;
|
|
sShownRange += step;
|
|
```
|
|
|
|
**NaN is absorbing here, and the clamps cannot catch it** -- `step > maxStep` and `step < -maxStep`
|
|
are BOTH false for NaN. So a single poisoned frame makes `sShownRange` NaN and it stays NaN *for
|
|
the life of the process*.
|
|
|
|
The consumer repeats the same mistake -- `BTReticleRenderable::Draw`:
|
|
|
|
```c
|
|
Scalar range = (rangeAttr2 != 0) ? *rangeAttr2 : 0.0f;
|
|
if (range < minRange) range = minRange; // false for NaN
|
|
if (range > maxRange) range = maxRange; // false for NaN
|
|
Scalar frac = (range - minRange) / (maxRange - minRange);
|
|
```
|
|
|
|
NaN flows into `AddPoint` / `ConcatMatrix`, so the caret and its bar become **degenerate geometry
|
|
and stop rendering** -- while every static reticle element, tick marks included, still draws.
|
|
|
|
That is exactly the reported symptom: ticks present, caret gone, sticky until relaunch.
|
|
|
|
## Why no log could confirm it
|
|
|
|
**The caret's actual input had no diagnostic anywhere.** `BT_RANGE_LOG` instruments the *pick*
|
|
(#4), and the `range=` field in `[target]` is a separate, locally recomputed
|
|
`Sqrt(ddx*ddx + ddy*ddy + ddz*ddz)` inside the weapon-range check -- it is neither `sShownRange`
|
|
nor `gBTHudRangeStorage`. Grepping the field logs for NaN returns nothing because **the poisoned
|
|
variable was never printed.** Absence of the signal was not evidence of absence.
|
|
|
|
## Fixed (unreleased)
|
|
|
|
1. **Re-seed on mech change** -- a new drop starts at the binary's 1200 default instead of
|
|
inheriting the previous mission's slid value. Deliberately does NOT fire on respawn: that
|
|
reuses the entity, and the binary does not reset the readout on respawn either.
|
|
2. **NaN trap at the producer** -- re-seed to 1200 rather than propagate.
|
|
3. **NaN-safe clamp at the consumer** -- test `x == x` first, and fall back to the authentic
|
|
no-target peg (1200) instead of rendering nothing.
|
|
4. **`BT_RANGE_LOG` now prints the caret's real input** -- `[range] caret input shown=... true=...
|
|
lock=...` at 1 Hz, plus a `[range] NaN TRAPPED` receipt.
|
|
|
|
## Status [T3 -- honest]
|
|
|
|
The defect and the symptom match exactly, and the fix is correct on its own merits: a
|
|
process-lifetime static feeding unguarded float geometry is a bug regardless of who reported what.
|
|
|
|
But **the causal link to Oracle's report is INFERENCE, not proof.** The NaN source is unidentified
|
|
and the failure has not been reproduced. What would settle it: fly with `BT_RANGE_LOG=1` -- if the
|
|
caret dies again, the log now names the frame it happened on.
|
|
"""
|
|
|
|
gitea.call("/issues/147", method="PATCH", payload={"body": BODY})
|
|
print("rewrote #147 body")
|