Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016bw71WVsccjwW7uKRg4aWD
618 lines
36 KiB
Markdown
618 lines
36 KiB
Markdown
# 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) | FAITHFUL (brick claim RETRACTED 2026-08-13 -- constant-width misread; binary clamps overcharge to 1.0 and fires; witnesses 6-0) |
|
||
| 6 | Autocannons AFC25/50/100 (0xBCD) | FIXED-THIS-BUILD (#171; T2 field receipts pending) |
|
||
| 7 | AC cosmetic tracer visual | FIXED-THIS-BUILD (recovered TOF constants 2.0f + 1/9.8 applied, b94636b) |
|
||
| 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 | FIXED-THIS-BUILD (capsule sweep [T3 radius]; T2 field receipts pending) |
|
||
| 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<=effectiveRange` gate 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.cpp` live-verified same machine end-to-end; 60Hz
|
||
sub-stepped Loading tick (±1% snap-window robustness, port mechanism);
|
||
beam from real `ermlaser.bgf` tube via `BTPushBeamKind` ([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 `TakeDamageMessageHandler`
|
||
body 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) -- brick claim RETRACTED 2026-08-13
|
||
- **Binary [T1]:** `ToggleSeekVoltage` @004ba478 (vet/expert only): wrap
|
||
from gear 4→1 skips `ResetFiringState` → charge above snap window →
|
||
~~overcharge reads ZERO → PERMANENTLY BRICKED~~ **RETRACTED: _DAT_004ba830
|
||
is a DOUBLE 1.0 (width misread); the binary CLAMPS overcharge to 1.0 and
|
||
the weapon fires -- the machine never bricked. Era witnesses (6-0) triggered
|
||
the re-audit; full detail in context/decomp-reference.md.**
|
||
- **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
|
||
effectiveRange` gate, 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.cpp` at bbb3b4e implements 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** (bytes `8a 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.cpp` still 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 01` size 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 (commit 80037a7, 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 0xBD1 `Make` @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 radius / port-shape, landed 2026-08-13]:** `mech4.cpp
|
||
BTUpdateProjectiles` — per frame, damage-carrying GUIDED rounds (the one
|
||
salvo lead) run a nearest-approach test of the flight segment against a
|
||
VERTICAL CAPSULE per other registered mech (BTGetTargetCandidates walk;
|
||
shooter + locked target excluded — the target keeps its own 4.0u fuze).
|
||
Capsule derived from the victim's own collision template
|
||
(Mover::GetCollisionTemplate BoxedSolid: axis = origin up minY..maxY,
|
||
radius = larger horizontal half-extent; fallback 3.5u × 14u when the
|
||
template is unresolved [T3]). Earliest contact along the segment wins
|
||
(bystander vs target fuze compared by segment param); the struck mech
|
||
becomes the DIRECT victim through the unchanged dispatch path (cluster
|
||
roll + splash semantics identical, splash excludes the new direct
|
||
victim). Wrecks stay in the sweep — binary detonates on any solid; the
|
||
#174 victim-side guard keeps dead zones from cascading/scoring. Capped
|
||
`[projectile] BYSTANDER` receipt (24 prints) for field forensics.
|
||
- **Verdict:** FIXED-THIS-BUILD (radius is a [T3] capsule approximation of
|
||
the real solid sweep; T2 field receipts pending — Friday watch item
|
||
unchanged: "hit by missiles aimed at someone else" now expected INSIDE
|
||
splash range too).
|
||
- **#175 addendum (2026-08-13, 7b12053): the STATIC-WORLD half landed too.**
|
||
The flight step now also rays the static collision solid tree
|
||
(`WorldStructurePick` → `Mover::FindStaticSolidHitBy`) beside the visual
|
||
heightfield march, earliest hit wins — rounds burst on hills/structures
|
||
instead of tunneling (user-observed on grass: an LRM duel through a
|
||
mound; ALL 345 bench WORLD-bursts were struct-class — the mound was
|
||
solid-tree geometry the heightfield test was blind to). Struck solid's
|
||
owner takes the binary's zone=-1 direct dispatch (@004be078); salvo-lead
|
||
world bursts splash (near-miss vs cover). [T3 minor, pre-existing]:
|
||
world hits are tested before the same frame's fuze/bystander contact
|
||
rather than in one unified earliest-t arbitration — mixed ordering only
|
||
matters when a round crosses cover AND reaches its fuze point within a
|
||
single ~10-16u frame step. BT_WORLDHIT=0 = rollback lever.
|
||
|
||
### 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)
|
||
|
||
1. **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.
|
||
2. **K constant:** `_DAT_004b9c98` dumped: CODE file+0xb9a98 = `0000803f`
|
||
= 1.0f. [T4] closed → [T1]; undamaged effRange = exactly authored
|
||
WeaponRange; port's 1.0 (`mechweap.cpp:558`) correct.
|
||
3. **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.
|
||
4. **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).
|
||
5. **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").
|
||
6. **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).
|
||
7. **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.
|
||
8. **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.
|
||
9. **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).
|
||
10. **@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 and `context/combat-damage.md` Gauss
|
||
claims.
|
||
- Any `projweap.cpp` / gauge banner repeating the no-damage framing.
|
||
|
||
## Stale banners confirmed present (sweep with the next projweap change)
|
||
|
||
- `projweap.cpp:990` still 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`/`67c` are now false
|
||
(constants byte-confirmed, see entry 7).
|
||
|
||
## Actionable closures queued (next build)
|
||
|
||
1. 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.
|
||
2. Jam roll: remove the `heatAlarm >= DegradationHeat` gate
|
||
(`projweap.cpp:692`) — binary rolls at any nonzero heat alarm.
|
||
Announced to the vets in the field guide §9.
|
||
3. PPC gauge-scramble rider: BLOCKED on era testimony (field guide §3) —
|
||
build the visual only after the group answers.
|
||
4. Missile bystander sweep: LANDED 2026-08-13 (lane 12 entry) together
|
||
with coast gravity (dt-table row); field guide §6's Friday watch item
|
||
now doubles as the T2 verification channel.
|
||
|
||
## 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:2382` kRetMaxRange = 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
|
||
transcription `btl4vid.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-82` tracks 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) | **WAS DIVERGENT → FIXED 2026-08-13** (`vel.y −= 6.5·coast_dt` on thruster rounds after burnout, exact at the burnout boundary; speed re-synced to |vel|) | post-burnout flights only; missed rounds now drop instead of flying level |
|
||
| 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):**
|
||
1. `mech4.cpp BTUpdateProjectiles` — quadratic drag `0.001·v²` on thruster
|
||
rounds, burn and coast; stale "drag negligible / range-capped" comment
|
||
replaced with the byte-cited model.
|
||
2. `projweap.cpp` — `_DAT_004bc678 = 2.0f`, `_DAT_004bc67c = 1/9.8`
|
||
(bytes above), muzzleHeight term restored via
|
||
`ResolveLaunchVelocity()` (deferred to first use — the port ctor runs
|
||
mid-roster-build; z==0 sentinel; complete-Mech-TU bridge
|
||
`BTWeaponMountHeight` in mech4.cpp). Stale "unresolved" constant
|
||
comments, the :990 "#168 flight-contact model" banner, and the header
|
||
"FireWeapon BODY NOT recovered" line all swept.
|
||
3. `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.)
|