"""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")