34 KiB
Weapons Drift Audit — binary vs port, per weapon lane (2026-08-13)
What this is: the AI-facing ledger of the per-weapon-class physics audit
(the #171 batch + this session's raw-byte verifications). The tester-facing
twin is docs/WEAPONS_FIELD_GUIDE.txt — keep the two in sync; the field
guide translates every address below into behavior and carries the era
questions to the group.
Evidence tiers: T1 = decomp/raw-byte verified · T2 = runtime receipts ·
T3 = port approximation · T4 = guess. Addresses are @ADDR in the rebuilt
2026-08-06 export (reference/decomp/all/part_*.c).
HOLD notice: corrections in the "Corrections owed" section are HELD for the #171 group decision per Oracle's HOLD — do not sweep them into the KB or the queued ticket comment until the group signs off.
Verdict summary
| # | Lane | Verdict |
|---|---|---|
| 1 | Lasers (Emitter 0xBC8, 6 marks) | FAITHFUL |
| 2 | PPC/ERPPC fire chain (0xBD4) | FAITHFUL |
| 3 | PPC rider (a) victim display scramble | KNOWN-DIVERGENCE [T3] (unimplemented; visual OPEN) |
| 4 | PPC rider (b) victim generator short | FAITHFUL (tag-label scope OPEN) |
| 5 | Seek-voltage gears + #21 overcharge brick | KNOWN-DIVERGENCE (deliberate rescue) |
| 6 | Autocannons AFC25/50/100 (0xBCD) | FIXED-THIS-BUILD (#171; T2 field receipts pending) |
| 7 | AC cosmetic tracer visual | KNOWN-DIVERGENCE [T3] (cosmetic; constants recovered — close) |
| 8 | GAUSS (fielded, 0xBCD record) | FAITHFUL (correction sweep owed, HELD) |
| 9 | GaussRifle class 0xBCE (+ shell 0xBD1) | FAITHFUL (dead code both sides) |
| 10 | MissileLauncher core (0xBD0, all racks) | FAITHFUL |
| 11 | Missile presentation (salvo visuals/gains) | KNOWN-DIVERGENCE [T3] (presentation-only) |
| 12 | Missile bystander sweep | KNOWN-DIVERGENCE [T3] (largest remaining gap) |
| 13 | Missile splash (@0042fad0) | FAITHFUL under stock content (gate wiring [T3] on custom eggs) |
| 14 | LRM flight + loft | FAITHFUL |
| 15 | Streak racks (strk model) | FAITHFUL |
| 16 | NRK5 / nrk-fed racks | FAITHFUL (KB model-table wording sweep owed) |
| 17 | Shared reload/ammo/eject chain | FAITHFUL |
| 18 | Jam roll | KNOWN-DIVERGENCE [T3] (extra gate — remove) |
| 19 | Incoming-missile warning | OPEN (consumer unlocated; era testimony wanted) |
Per-lane entries
1. Lasers — Emitter 0xBC8 (SLaser/ERSLaser/MLaser/ERMLaser/LLaser/ERLLaser)
One C++ class, data-differentiated; no pulse variants exist in shipped content.
- Binary [T1]: trigger-time hitscan beam @004bace8: charge-gear state
machine (4 states, no jam/ammo), lock required (
*(mech+0x388)!=0), viewFireEnable look-direction arming,dist<=effectiveRangegate AT FIRE. K byte-confirmed 1.0f @004b9c98 → effRange = (1.0 − zoneDamage) × WeaponRange. Damage = authored × (charge/seekV)². ONE aimed single-panel message. Out-of-range: beam draws, damage silently discarded. 0.2s target-tracking afterglow. Hard heat-FAILURE stop (no gradual degradation). Heat/gear-toggle vet-expert only. - Port [T2]:
emitter.cpplive-verified same machine end-to-end; 60Hz sub-stepped Loading tick (±1% snap-window robustness, port mechanism); beam from realermlaser.bgftube viaBTPushBeamKind([T3] dpl layer unported, #164 stuck-beam guards). - Verdict: FAITHFUL.
2. PPC / ERPPC — 0xBD4 fire chain
- Binary [T1]: one-line tail-call into
Emitter::FireWeapon(@004bb878); vtable/ctor/CSS otherwise identical — everything distinctive is authored data (EnergyDamage(4), 12/16 pts, 900/950 range, 5.0s recharge, blue pip). - Port [T2]: same tail-call structure; full charge = authored 12/16 verified live; blue beam under its own renderer case.
- Verdict: FAITHFUL.
3. PPC EnergyDamage rider (a): victim display scramble
- Binary [T1 call, T4 visual]: @004a0230 (~0x4a03f3): damageType==4 →
no-arg virtual on app-global +0x4c — identified via the port's own app
decode as THE GAUGE RENDERER (
DAT_004efc94+0x4c= GetGaugeRenderer,btl4mppr.cpp:249, cf.btl4galm.cpp:404) — a cockpit gauge/MFD scramble per Energy hit. Exact visual/duration unread (which vtable slot +0x4c does visually: OPEN). - Port: NOT implemented — grep of the port
TakeDamageMessageHandlerbody finds no damageType==4 gauge call. Candidate for the #156/ weapon-state polish list. - Verdict: KNOWN-DIVERGENCE [T3] (unimplemented rider; visual detail OPEN — era testimony wanted BEFORE building; question shipped in the field guide).
4. PPC EnergyDamage rider (b): victim generator short
- Binary [T1]: @0049c690: damageType==4 + critical subsystems in struck
zone → one random critical rolls
ForceShortRecovery@004b11bc (brownout, charge stall, cockpit electronics dip). Tag 0x50f4bc Generator-vs-PoweredSubsystem label conflict still open. - Port [T2]: implemented —
mechdmg.cpp:471-477(damageType==4 && criticalSubsystemCount>0 → ForceShortRecovery). - Verdict: FAITHFUL (tag-label scope OPEN). NOTE: the two riders' port status differs — never lump them in vet-facing docs.
5. Seek-voltage gears + #21 overcharge brick (all energy weapons)
- Binary [T1]:
ToggleSeekVoltage@004ba478 (vet/expert only): wrap from gear 4→1 skipsResetFiringState→ charge above snap window →ComputeOutputVoltageovercharge reads ZERO → weapon PERMANENTLY BRICKED. The arcade shipped this latent brick. - Port [T3 deliberate, documented]: overcharge counts as Loaded (issue #21, 38 benched rescues) — the port's ONE deliberate energy-lane divergence; lives in the deviations appendix.
- Verdict: KNOWN-DIVERGENCE (deliberate rescue of a shipped latent brick).
6. Autocannons — AFC25/50/100 (ProjectileWeapon 0xBCD)
- Binary [T1]: trigger-time hitscan @004bc104: lock required, batched
per TracerInterval (all authored 1), fire-time
dist<=live effectiveRangegate, ONE single-panel Ballistic message to the locked target, out-of-range batch silently discarded (round consumed, tracer flies), gyro recoil kick dmg/16 dir (0, 0.6, −1.5) (constants 3.0f/0.0625 byte-confirmed), jam p=0.41·T/Tfail clamped [0.05,1.0] vet/expert-only, EJECT tap=one round / hold 3.0s=dump bay, shell 0xBD1 dead code. - Port [T2 bench]:
projweap.cppatbbb3b4eimplements the hitscan line-by-line (13-round batch + DET dmg=0 receipts); closes #112 single-panel and the #168 mis-fix. Field verify armed for Friday (local rig can't form avatar locks). - Verdict: FIXED-THIS-BUILD (#171 batch; T2 field receipts pending Friday). The field guide carries the #27 "rounds can genuinely miss" RETRACTION.
7. AC cosmetic tracer visual
- Binary [T1 weapon-side, T4 renderer-side]:
launchVelocity.z = −effRange/sqrt(2.0·(1/9.8)·muzzleHeight)— both constants BYTE-CONFIRMED this audit: @0x4bc678 = 2.0f (float32); @0x4bc67c = 1/9.8 stored as an 80-bit x87 EXTENDED (bytes8a 6c 37 99 43 c6 fa d0 fb 3f— does NOT decode as float32, likely why it stayed "unresolved") → ~1s to cross full range for a ~5u muzzle. Drawn by the unported 'Tracer' dpl renderable; renderer feed unrecovered. - Port [T3]:
projweap.cppstill ships 1.0f stand-ins for both constants (marked 'unresolved' — now false) and drops the muzzleHeight term: tracer speed = effRange/1.0. Numerically close for ~5u muzzles by accident. - Verdict: KNOWN-DIVERGENCE [T3] (cosmetic-only; constants recovered — CLOSE IT next build; stale 'unresolved' comments must go in the same change).
8. GAUSS — the fielded weapon (lok2 'GAUSS' record)
- Binary [T1 raw bytes]: BTL4.RES @0xf16e6: classID 0xBCD
ProjectileWeapon (bytes
cd 0b/d0 01size 0x1D0, verified this session) — the pod's Gauss ran the FULL AC hitscan chain WITH damage: 20 pts Ballistic single-panel out to 900, 8s cycle, 16 rounds (AmmoBinGAUSS 0xBCB), near-cold (1e6 heat), yellow pip, 1.25 gyro kick. Class histogram across all 18 streams: 78×0xBC8 / 16×0xBCD / 30×0xBD0 / 14×0xBD4, ZERO 0xBCE. - Port [T1]: factory dispatches 0xBCD →
CreateProjectileWeaponSubsystem(mech.cpp:1939-1942) — the port's fielded GAUSS ALSO deals AC-hitscan damage. The "port damage is an invention" framing (commit80037a7, queued #171 comment, projweap banner) is WRONG at the fielded level — port and binary agree. - Verdict: FAITHFUL (mechanics match). Correction sweep owed — HELD (see below). Era question reframed and shipped in the field guide (headline). Chassis attribution (lok2 vs the weapsub scan's nearest-name 'blh') rides the flagged heuristic — the field guide asks the vets which mech carried it rather than asserting.
9. GaussRifle class 0xBCE (+ shell 0xBD1)
- Binary [T1]: real code, dead content:
FireWeapon@004bdca4 = one store (charge=0), no damage/beam/heat; zero shipped records instantiate it. Shell 0xBD1Make@004be384 has zero callers; flight body unreachable. - Port [T1]: same classes exist (GAUSS.CPP shipped source), same factory case, never constructed by shipped content; equally dead.
- Verdict: FAITHFUL (dead code both sides). The CLASS-level "@004bdca4 is a no-op" claim stands even after the fielded-Gauss correction.
10. Missile family core — MissileLauncher 0xBD0 (SRM/LRM/Streak/NRK)
One class, data-differentiated.
- Binary [T1]: genuinely flying, the ONE ungated family: lock-to-fire
via the shared Loaded case (no dumb-fire trigger path, no Streak-special
code), one entity per salvo, per-missile dmg = authored/count, seeker
homes on the fire-time picked panel point with constant-bearing lead,
4.0u fuze + swept contact (bystanders hittable), lock-drop on target
death (@0049fb54 movementMode 2||9), WeaponRange is fire-control display
only — reach is physics (burn + 10.0s coast, y<−1.0 kill plane, expiry
fizzles), impact cluster roll
min(Random(n)+n/4, n)then zone −1 lottery with per-burst re-roll on the victim. - Port [T2]: full chain live-verified (382-impact night-9 corpus matches the roll exactly; Streak 277 u/s; fuze/lock-drop benched); direct Dispatch instead of the SubsystemMessageManager (deliberate, preserves burstCount, #95).
- Verdict: FAITHFUL.
11. Missile presentation (salvo visuals + steering gains)
- Binary [T1]: ONE Missile entity per salvo (single spawn call @004bcc60 — the pod drew one flying round + one explosion); body-turn slerp at authored MaxThrusterRotationRate; velocity slaved to nose while burning.
- Port [T3]: N-round visual ripple (lead round carries damage; N−1 damage-0 tracers), ±2.5° deterministic tube spread, steering gains 4.0/8.0 port-tuned (the old 'MissileTurnGain=4.0' attribution was the proximity fuze), muzzle safety clamp.
- Verdict: KNOWN-DIVERGENCE [T3] (presentation-only; damage economy identical).
12. Missile bystander sweep
- Binary [T1]: FUN_0042291c swept real world geometry including movers each tick — a third mech in the flight path could be struck by someone else's missile.
- Port [T3]: pool rounds contact only the locked target + terrain; a mech between shooter and target is flown through (splash alone can touch bystanders).
- Verdict: KNOWN-DIVERGENCE [T3] — largest remaining missile-physics gap. Field-guide watch item for Friday ("hit by missiles aimed at someone else" beyond splash range).
13. Missile splash — Explosion::SplashDamage @0042fad0
- Binary [T1]: gated on the FIRING player's advancedDamage (+0x264; every stock egg stamps 1 → splash WAS live on the pods), radius 30 authored on all missiles, baseBurst = FULL rack count (post-roll restore @part_013.c:18283-18284, read + confirmed this session), falloff bursts = round(base/dist^1.25) floored 1 (1.25 = shipped double; divide grounded in T0 EXPLODE.cpp), excludes the explosion + direct victim; shooter self-exclusion unprovable from the export.
- Port [T2 mechanics / T3 gate]: same math, full-count baseBurst (now [T1]-pinned correct), excludes shooter + direct victim; enable = SplashRadius>0 instead of the advancedDamage gate — coincides under all shipped eggs, diverges only on custom eggs.
- Verdict: FAITHFUL under stock content (gate wiring KNOWN-DIVERGENCE [T3] on custom eggs — site option; shooter self-splash OPEN — era question shipped).
14. LRM family flight (lrm model) + loft
- Binary [T1]: lrm rid=319: turn 60°/s, burn 10s @ 300 u/s², climb 50, splash 30 — climb>threshold triggers the seeker loft (aim.y += 0.1×min(range−200, 300), up to +30u): the signature climb-then-plunge arc; soft 30 u/s eject, some mounts +5 up-tilted.
- Port [T2]: #84 fixed the thrust resolution (pre-fix 10× slow WAS the 'missiles are slow' field report); loft constants exact (@004bec18/24/28).
- Verdict: FAITHFUL.
15. Streak racks (strk model — 'SRM6' bins on at least one chassis)
- Binary [T1]: strk rid=323: turn 360°/s (3× SRM — 6× the lrm), burn 3s @ 300, climb 0 — the round that out-turns evasion. NO Streak class or special lock code exists; the ammo model alone is the identity.
- Port [T2]: same records resolved; 277 u/s impact speed verified live.
- Verdict: FAITHFUL.
16. NRK5 'Narc' + every nrk-fed rack
Several 'LRM5/10/15' bins author nrk ammo.
- Binary [T1]: no beacon mechanism exists anywhere in the binary (single launcher class, no beacon input on the seeker). nrk rid=327 byte-dumped this session: turn 360°/s, burn 10s @ 300, CLIMB 50, splash 30 — the shipped NRK5 is a light LRM-profile rack WITH Streak-grade tracking AND the loft (missileB's 'no loft' corrected; dataA right). Field-visible agility difference between visually identical 'LRM' racks is authentic.
- Port [T2]: same data-driven resolution; nothing Narc-special to port.
- Verdict: FAITHFUL (KB model-table wording needs the nrk climb/turn correction — sweep owed).
17. Shared reload/ammo/eject chain (0xBCD + 0xBD0)
- Binary [T1]: recoil countdown only while generator route powered, heat-scaled (hot generator reloads slower), MinVolt% 0.3 freeze; EJECT (@004bb9b8, read this session): TAP = one round out + weaponAlarm→3 Loading UNCONDITIONALLY if rounds remain — the in-mission UNJAM (clears Jammed(5)); HOLD 3.0s = DumpAmmo whole bay → NoAmmo(7) roach motel (only re-arm/respawn Reset clears); novice locked out of eject.
- Port [T2]: same machine (
projweap.cpp:751-968); tap-unjam semantics match (the #166 advice rides on it). - Verdict: FAITHFUL.
18. Jam roll (ballistic + missile racks)
- Binary [T1]: @004bbfcc: rolled after each shot whenever simLive AND the weapon's own heatAlarm != 0; p = 0.41·T/Tfail (0.41 byte-confirmed) clamped [0.05, 1.0]. Practically vet/expert-only (std/novice heat model off → never heat-alarms).
- Port [T3 marked bring-up gate]: rolls only at heatAlarm >=
DegradationHeat (
projweap.cpp:692) — added when temps were static; now that the heat economy is live it masks authentic warm-weapon jams. Binary rolls at ANY nonzero heat alarm. - Verdict: KNOWN-DIVERGENCE [T3] (minor; REMOVE the extra gate next build — announced in the field guide).
19. Incoming-missile warning
- Binary [T1 registration, T4 consumer]: Missile ctor registers with a MECH target via a virtual on target+0x418; the consumer (threat lamp? beeper tempo?) is unlocated in the decomp.
- Port [T3]: structurally dead: the authored beeper/tempo attributes
latch (
mech4.cpp:8536) but the only producer lives in the dormant Missile TU — the pool never reports; the beeper never fires (#83). - Verdict: OPEN (needs the consumer decomp + era testimony — question shipped in the field guide).
Contradictions resolved this audit (evidence log)
- GAUSS (the headline): BTL4.RES @0xf16e6 GAUSS record = classID
0xBCD (bytes
cd 0b, size 0x1D0); class histogram 78×0xBC8 / 16×0xBCD / 30×0xBD0 / 14×0xBD4, zero 0xBCE; port factory 0xBCD → CreateProjectileWeaponSubsystem (mech.cpp:1939-1942). The fielded Gauss is FAITHFUL on both sides; the earlier "port damage is an invention" audit examined the content-dead 0xBCE class. CLASS-level claim (@004bdca4 no-op) stands. - K constant:
_DAT_004b9c98dumped: CODE file+0xb9a98 =0000803f= 1.0f. [T4] closed → [T1]; undamaged effRange = exactly authored WeaponRange; port's 1.0 (mechweap.cpp:558) correct. - AC launch-speed constants: @0x4bc678 = 2.0f (float32); @0x4bc67c =
0.10204081 = 1/9.8 as a 10-byte x87 EXTENDED (
8a6c379943c6fad0fb3f) — not float32-decodable, hence the stale "unresolved". projweap.cpp 1.0f stand-ins + dropped muzzleHeight term now closable. - Jam recovery: @004bb9b8 (part_013.c:15709-15723): eject RELEASE path calls FeedAmmo and unconditionally SetLevel(weaponAlarm, 3 Loading) when rounds remain — a tap clears Jammed(5). missileB's "jam permanent until re-arm" refuted; in-sortie unjam = YES (novice locked out).
- nrk flight model: BTL4.RES type-15 rid=327: turn 360°/s, burn 10s, accel 300, climb 50, splash 30. dataA right; missileB's "no loft" corrected. KB sweep owed (combat-damage.md "LRM … the only climber").
- SRM6 count-4 variant: the record authors MissileCount=4, Damage=20 → 5.0/missile (dataA's 3.33 assumed count 6). ALL per-chassis attributions ride the nearest-name heuristic — re-verify against the egg/VehicleTable before any chassis-specific claim ships to the vets (the field guide deliberately names no chassis in Appendix A).
- Splash baseBurst: part_013.c:18271-18290: cluster roll stored to +0x338 for direct dispatch, then IF splash enabled the ORIGINAL full count is RESTORED (+0x338 = uVar3 at :18284) before SplashDamage — the port's full-missileCount baseBurst exactly faithful.
- PPC display-scramble callee: DAT_004efc94+0x4c = the GAUGE RENDERER
(
btl4mppr.cpp:249,btl4galm.cpp:404) — the Energy-hit rider is a gauge/MFD-side scramble virtual. Still open: what slot +0x4c does visually. Port does not implement the call; the generator-short rider IS ported — statuses differ. - SRM2 flight profile — UNRESOLVED: dataA says the ava1 SRM2 bin feeds 'strk'; missileB groups SRM2 under 'srm'. The weapsub scan printed weapons only (no bin ammoModelFile column). Needs the bins pass before any SRM2 homing claim ships (field guide explicitly declines to state it).
- @004bb9b8 relabel: it is the EJECT handler, not fire-consume; FeedAmmo @004bd4f4 is the consume. Carry into any KB address table that copied the old tasking label.
Corrections owed — HELD for the #171 group decision (Oracle HOLD)
- Commit 80037a7's "Gauss deals NO damage in the shipped binary / port damage is an invention" framing.
scratchpad/night16/queued_171_comment.md— Gauss section + the queued era question (the reframed question now lives in the field guide). The #112/#168 sections are correct as written.context/decomp-reference.md§SS5 andcontext/combat-damage.mdGauss claims.- Any
projweap.cpp/ gauge banner repeating the no-damage framing.
Stale banners confirmed present (sweep with the next projweap change)
projweap.cpp:990still reads "the #168 flight-contact model" above the implemented hitscan body — same failure genus as the stale banner that caused #168 (gotcha: verify banners when the body changes).- The "unresolved" comments on
_DAT_004bc678/67care now false (constants byte-confirmed, see entry 7).
Actionable closures queued (next build)
- AC tracer launch speed: land 2.0f + 1/9.8 + the muzzleHeight term in
projweap.cpp; delete the stale comments and the :990 banner. Cosmetic; era question 14 (round pace) tunes the final feel. - Jam roll: remove the
heatAlarm >= DegradationHeatgate (projweap.cpp:692) — binary rolls at any nonzero heat alarm. Announced to the vets in the field guide §9. - PPC gauge-scramble rider: BLOCKED on era testimony (field guide §3) — build the visual only after the group answers.
- Missile bystander sweep: design exists; field guide §6 arms the Friday watch item.
Era questions shipped to the vets
The 14 questions live in docs/WEAPONS_FIELD_GUIDE.txt, distributed into
the per-section "WE NEED YOUR MEMORY" blocks: Gauss killed + carrier
chassis (headline, §5); PPC hit visuals + generator short (§3); splash
proximity + shooter self-splash (§7); ballistic recoil, AFC25 eaten
trigger, out-of-range round fate, round pace (§4); vet-only jams +
tap-eject unjam, NoAmmo latch with ammo remaining (§9); gear-wrap brick
(§2); incoming-missile warning (§10); generator switching mid-charge,
out-of-range beam (§1); rack-name quirks, homing agility + Narc-did-
nothing (§8).
RANGE AUTHENTICITY — printed manual cross-check + the rangefinder scale (2026-08-13)
Why: playtesters report ranges "feel wrong" and hits landing "beyond the maximum range on the range scale." Two legs: (1) diff every weapon range/damage number the original player manual prints against the authored content values (Appendix A of the field guide — this audit's numbers table); (2) pin down how the HUD rangefinder's displayed maximum relates to authored WeaponRange.
Leg 1 — the manual cross-check
Source: reference/manual/Tesla40_BT_manual.pdf (34 pp). Finding about
the source itself [T1 — full-text sweep + page renders, this audit]: the
manual prints no weapons table. The per-mech stat sheets (pp. 25-29)
carry loadouts, coolant loops, tonnage/armor/reservoir/speeds — no
per-weapon range or damage column. The ONLY printed range figures are
MFD screenshot mockups (eng-data panels), four values total; no
per-weapon damage number appears anywhere in the manual (the p. 4
scoring page says damage is delivered "the same way their BattleTech
boardgame counterparts do" — a fidelity claim with no numbers; the
authored 4.10 values do NOT numerically track boardgame values, e.g.
PPC 12 vs the boardgame's 10 — treat the sentence as blurb, not spec).
| Weapon | MANUAL prints | CONTENT authored | Verdict |
|---|---|---|---|
| PPC | RANGE 900M (p. 20, PPC eng-data screen) | 900 | MATCH |
| AFC100 | RANGE 400M (p. 21, AFC eng-data screen) | 400 | MATCH |
| SRM4 | RANGE 450M (p. 22, Engineering MFD mockup) | 800 (display-only) | MISMATCH |
| SRM6 | RANGE 450M (p. 22 AND p. 23, two independent screens) | 800 (display-only) | MISMATCH |
| SLaser | — | 1.5 dmg / 150 | manual-silent |
| ERSLaser | — | 2 / 225 | manual-silent |
| MLaser | — | 2.5 / 350 | manual-silent |
| ERMLaser | — | 3.5 / 500 | manual-silent |
| LLaser | — | 5 / 600 | manual-silent |
| ERLLaser | — | 6 / 750 | manual-silent |
| ERPPC | — | 16 / 950 | manual-silent |
| AFC25 | — | 7 / 900 | manual-silent |
| AFC50 | — | 13 / 750 | manual-silent |
| GAUSS | — | 20 / 900 | manual-silent (pip-color note below) |
| LRM5/10/15/20, NRK5 | — | 6000 (display-only) | manual-silent |
| ALL weapons, damage | — (zero numbers printed) | Appendix A | manual-silent |
All four manual figures verified by page RENDER, not OCR alone (the 450M reads twice on p. 22 and once on p. 23) [T1].
The SRM mismatch read: 450 (manual) vs 800 (shipped 4.10 content,
BLH live pip dump [T2] + field-guide Appendix A). Two mitigations:
(a) the manual's panels are mockups from the earlier software revision —
the same 4.0→4.10 drift already proven on the Loki loadout rework,
the small-laser loop redistribution, and supercharge
(context/pod-hardware.md §Manual); (b) for MISSILE racks,
WeaponRange is fire-control display ONLY (lane 10 [T1]) — real reach is
flight physics on both values, so the 450→800 change altered the pip
position and panel text, never a trajectory. Verdict: authoring drift
between revisions, NOT a port defect; the port displays the shipped
4.10 content values.
Pip-color side note: manual p. 10 codes the readiness pips PPC=Blue, Laser=Red, LRM=Green, SRM=Brown, AFC=Orange, Gauss=Grey — blue/red match our dumps exactly, SRM "brown" is the authored amber (0.6,0.4,0), but the shipped GAUSS record authors a YELLOW pip (lane 8 [T1]). Same drift class as the SRM range; cosmetic.
Leg 2 — the rangefinder scale (what the ladder max actually is)
The tester model behind the complaint — "the scale maxes at the selected
weapon's range" — is FALSE in both the binary and the port. What the
HUD right ladder actually is (full detail context/gauges-hud.md
§Cockpit HUD reticle):
- The ladder is a FIXED 0–1200 m scale — the ctor's hardcoded
calibration (@004cc40c, ctor param 11 = 0x44960000) [T1]; port
identical (
btl4vid.cpp:2382kRetMaxRange = 1200.0f). It is never derived from any weapon's WeaponRange or live effectiveRange. - Each weapon marks its own reach as a PIP at its authored
WeaponRange, CLAMPED into [0..1200] (
AddWeapon@004cdac0; port transcriptionbtl4vid.cpp:3139-3147) [T1]. Caret below a pip = that weapon reaches the target. An LRM/NRK rack authored 6000 therefore pins its pip AT THE LADDER TOP — authentic on both sides. - The caret is the displayed target range, sliding at 500 m/s toward the true pick range (HudSimulation part_013.c:5652 [T1]; port mech4.cpp targeting step [T2]); pegs at 1200 with no target. During depth-discontinuity walks the displayed value legitimately lags true range by hundreds of meters (the Gitea #4 measurement).
- The ONE displayed-range divergence: the binary subtracts
_DAT_004b7ecc= 100.0f from RangeToTarget (@0x1EC) every frame the timed flag @0x22C is set (timer @0x21C to limit @0x1D8) — the port does NOT implement this state (hud.cpp:79-82tracks it;context/open-questions.md§HUD range-bias) [T1 read, unimplemented]. Direction: the shipped machine UNDER-reads range by up to 100 m in that state; the port never does. So when the state is active a true-850 m missile hit against an 800 pip DISPLAYED as in-range on the pod but displays as beyond-the-pip in the port — a bounded (≤100 m) port-side contributor to "hit beyond the scale" reports. Whether the state ever triggers in the field is unknown (what sets @0x22C is the open question) [T4 for field prevalence].
Rangefinder verdict: the displayed scale is AUTHENTIC — no port miscalibration. "Hits beyond the maximum range on the range scale" is expected, authentic behavior with three drivers, largest first: (1) missile reach is flight physics, not WeaponRange — SRMs genuinely hit past their 800 pip and LRMs past the 1200 ladder top [T1]; (2) energy/AC pips mark AUTHORED range while the live fire gate is effectiveRange = (1 − zoneDamage) × authored — a damaged weapon's real reach sits BELOW its pip, never above, so those lanes cannot produce beyond-pip hits; (3) the 500 m/s caret slide lags true range during fast picks. The single true divergence is the unimplemented −100 m bias state (above) — worth reconstructing if Friday's reports cluster in the 0–100 m band past a pip; anything farther past the pip is missiles being missiles.
Guide action: MISMATCHES exist (SRM display ranges + the Gauss pip color) → per the audit rule the field guide was NOT touched; no reassurance paragraph shipped.
VELOCITY/DT INTEGRATION — term-by-term audit (2026-08-13)
Scope: the projectile velocity integration, binary vs port pool
(mech4.cpp BTUpdateProjectiles / BTPushProjectile), plus the AC tracer
launch-speed constants. Binary side read end-to-end this audit:
Missile::MoveAndCollide @004bef78, Seeker::FindTarget @004be9a0 /
LeadTarget @004beae4, MissileThrusterSimulation @004be474 — the
standalone Performance body, RECOVERED (misthrst.cpp's "folded into
MoveAndCollide, no distinct @ADDR survives" note predated the 2026-08-06
re-export and is corrected), Missile ctor @004bf5b4, launch composer
@004bcc60, and the engine Mover lane, decomp↔T0-source matched
line-for-line: FUN_00422360 = Mover::PerformAndWatch,
FUN_00421bac = ApplyWorldAccelerations, FUN_00421e2c =
ApplyAirResistanceAndGravity(power), FUN_00421ca8 = CalculateDrag,
FUN_00421b2c/FUN_00421b6c = UpdateWorldMotion/UpdateLocalMotion;
mover+0x250 = localEnvironment, +0x254 = deadReckoner (MOVER.h/.cpp).
The dt model [T1/T0]: every Performance slice is variable-step REAL
SECONDS (the #96 sim-time result), and Mover::PerformAndWatch
ZEROES localAcceleration at the top of every frame
(@00422360: FUN_0040a7f4(+0x1dc, &DAT_004e0fd4) == T0 MOVER.cpp:672
localAcceleration = Motion::Identity). Thrust, drag and gravity are
re-added FRESH each frame and then integrated ×dt (@00421bac:
p += v·dt + ½a·dt²; v += a·dt). Net missile physics is the clean ODE
dv/dt = ThrusterAccel − COD·ρ·v² — there is NO myomer-style 28 Hz
per-tick accumulate anywhere in this lane, so the port's
speed += accel·dt needs no ×(dt·28) rescale. (The myomer idiom applies
to PERSISTENT accumulators; the Mover acceleration is not one.)
The headline: drag was wrongly waved off. All four missile models
author linear drag CODs 0.001 on every axis (BTL4.RES type-15 raw
floats read this audit: srm rid=315 / lrm 319 / strk 323 / nrk 327;
airDensity = 1.0, engine default). 0.001 multiplies v²: it is the
flight model's GOVERNOR, and the binary says so itself — the Missile
ctor @004bf5b4 (part_013.c:18416) precomputes terminal speed
sqrt(ThrusterAccel / negCOD.z) into missile+0x348 and the seeker
uses it as the lead time base. Terminal speeds: LRM/Streak/NRK
547.7 u/s, SRM 774.6 u/s. The port pool (per #84's "drag
~0.001 = negligible") flew undamped: an LRM reached 3030 u/s by
burnout — 5.5× the binary cap. Simulated head-to-head (28 Hz binary
loop vs the port step): 800u arrival LRM 2.54 s @ 492 u/s (binary) vs
2.22 s @ 695 (undamped port); at t=5 s the undamped LRM had covered
3912u @ 1530 u/s vs the binary's 2108u @ 544. Fixed this build:
the pool integrates speed += (accel_burn − 0.001·speed²)·dt for
thruster rounds, burn AND coast (a burned-out round decelerates,
v(t) = v0/(1+0.001·v0·t) — Streak at t=5 s: 256 u/s, was 1005
frozen). AC tracers (p.accel==0) untouched — the binary's visible AC
round is a renderer cosmetic with no Mover physics.
| Term | Binary [T1] | Port (pre-audit) | Verdict | Player-visible consequence |
|---|---|---|---|---|
| dt model | variable slice, SECONDS; accel accumulator zeroed/frame (@00422360); integrate ×dt (@00421bac) | speed += accel·dt, pos += vel·dt |
FAITHFUL (no per-tick accumulate here) | none |
| Launch velocity | authored MuzzleVelocity (z negated) + shooter velocity (@004bcc60 / FUN_004b9cbc) | same (#67) | FAITHFUL | none |
| Launch acceleration | ZERO in the make message (@004bcc60 authors the DAT_004e0fd4 zero Motion) | thrust applies from frame 1 (binary thruster adds the same frame) | FAITHFUL | none |
| Thrust | @004be474: while burning, burnLeft −= dt, localAccel += (0,0,−ThrusterAccel) onto the fresh accumulator ⇒ constant accel |
speed += accel·burn_dt |
FAITHFUL | none |
| Drag | −COD·ρ·sign(v)·v² per body axis, EVERY frame, burn + coast (@004bef78, power-2 inline; plain-Projectile dead class uses power 1.0 @004bddec); CODs 0.001 [T1 bytes]; terminal sqrt(T/c) precomputed @004bf5b4→+0x348 |
OMITTED ("negligible") | WAS DIVERGENT → FIXED-THIS-BUILD | was: LRM 3030 u/s (5.5× cap), −13% time-to-800u, +40% impact speeds, map-crossing runaways past 2.5 s; now governed at 547.7/774.6 |
| Speed clamp | none — drag IS the clamp | none — drag now is | FAITHFUL (post-fix) | — |
| Velocity↔seeker slaving | burning: lateral local vel/accel zeroed each frame ⇒ velocity ≡ speed×nose; nose slerps ≤ MaxThrusterRotationRate·dt (@004be474); at burnout slaving STOPS (ballistic coast) | rotate-toward-aim at port gains 4/8·dt, speed-normalized; steers through coast | KNOWN-DIVERGENCE [T3] (lane 11) | turn dynamics shape; coast homing (real impacts are in-burn) |
| Gravity | none during burn; coast: worldAccel.y −= 6.5 fresh each frame (env default) + y<−1 kill plane |
absent (loft arc + ttl only) | DIVERGENT [T3 minor, unfixed] | post-burnout flights only (beyond ~1400u SRM / ~4800u LRM); missed rounds fly level instead of dropping |
| Position step | p += v_old·dt + ½a·dt² |
p += v_new·dt (semi-implicit) |
FAITHFUL-approx | ≤ ~12u lead over a full SRM burn @60fps; imperceptible |
| Expiry | burn+10 s, y<−1 (@004bef78 tail) | same (#168) | FAITHFUL | none |
| Seeker lead | aim += targetVel × range/terminal(+0x348); loft climb-gated (thruster+0xF0 > _DAT_004bec1c) |
live-position re-lead; loft constants exact | KNOWN-DIVERGENCE [T3] (lane 11) | small lead-shape difference |
| AC tracer speed | launchVelocity.z = −effRange/sqrt(2.0·(1/9.8)·muzzleHeight) @004bc3fc:16157-16164 (bytes @0x4bc678 = 00000040 2.0f; @0x4bc67c = 8a6c379943c6fad0fb3f 1/9.8 x87 EXTENDED) |
1.0f stand-ins, no muzzleHeight | WAS DIVERGENT (cosmetic) → FIXED-THIS-BUILD | tracer pace now the authentic fall-time scale (~1 s to effRange at a ~5u mount) |
Port changes landed (this audit, build clean):
mech4.cpp BTUpdateProjectiles— quadratic drag0.001·v²on thruster rounds, burn and coast; stale "drag negligible / range-capped" comment replaced with the byte-cited model.projweap.cpp—_DAT_004bc678 = 2.0f,_DAT_004bc67c = 1/9.8(bytes above), muzzleHeight term restored viaResolveLaunchVelocity()(deferred to first use — the port ctor runs mid-roster-build; z==0 sentinel; complete-Mech-TU bridgeBTWeaponMountHeightin mech4.cpp). Stale "unresolved" constant comments, the :990 "#168 flight-contact model" banner, and the header "FireWeapon BODY NOT recovered" line all swept.misthrst.cpp— banner corrected: @004be474 IS the standalone MissileThrusterSimulation (steer + burn + thrust-add), recovered.
KB corollary (swept): the "Mover gravity is dt-less per-frame → may need 28 Hz rescale at our frame rate" open question is CLOSED-NO: the per-frame gravity/drag adds land on an accumulator that PerformAndWatch zeroes every frame — they are per-frame acceleration definitions, not accumulating increments, and integrate ×dt correctly at ANY frame rate. (The port's separate "environment gravity reads 0" gap stays open.)