velocity/dt audit: DRAG WAS THE GOVERNOR -- the binary applies -0.001*v^2 every frame, burn AND coast (@004bef78 + thruster @004be474, ctor precomputes terminal sqrt(T/COD) into +0x348 as the seeker lead base; authored COD 0.001 byte-read from BTL4.RES type-15 srm/lrm/strk/nrk). The port omitted it: LRM hit 3030 u/s at burnout vs the machine's 547.7 terminal (5.5x), 3912u downrange at t=5s vs 2108u -- a major slice of the 'missiles reach anything' field feel. Pool now integrates speed += (accel - 0.001*v^2)*dt. The dt MODEL itself verified FAITHFUL (Mover zeroes accel per frame + integrates x dt in seconds, matched line-for-line to T0 MOVER.cpp -- the 28Hz myomer idiom does NOT apply; open-question closed). AC cosmetic tracer TOF constants landed (2.0f @0x4bc678, 1/9.8 x87 EXTENDED @0x4bc67c, muzzleHeight term via ResolveLaunchVelocity deferred-first-use). Stale banners swept (misthrst @004be474 recovered; projweap #168 flight-contact banner gone). KB: decomp-reference Mover-gravity fix + flight-model bullet, coast gravity = 6.5 not 9.8. Drift audit: SSVELOCITY/DT + SSRANGE AUTHENTICITY (manual cross-check: PPC/AFC100 MATCH, SRM 450-vs-800 authoring drift; rangefinder ladder NOT miscalibrated -- fixed 0-1200 + authored pips both sides; -100m range-bias state unimplemented, parked). Build clean
This commit is contained in:
@@ -289,6 +289,20 @@ From the weapon `.SUB` records + the charge-curve `.data` constants (PE-parsed a
|
||||
**Proximity fuse**: seeker rangeToTarget(+0x10C) < `_DAT_004bf5a4` = **4.0** → detonate on
|
||||
targetEntity without a geometry hit. Seeker drops a destroyed target (movementMode 2|9,
|
||||
FUN_0049fb54).
|
||||
- **Missile FLIGHT MODEL (velocity/dt audit 2026-08-13) [T1]:** per frame:
|
||||
`MissileThrusterSimulation` **@004be474** (the standalone Performance body — recovered;
|
||||
misthrst.cpp's "folded" note was stale) slerps the missile toward the seeker aim
|
||||
≤ MaxThrusterRotationRate·dt and, while burning, adds `(0,0,−ThrusterAccel)` to the
|
||||
frame-zeroed accumulator; @4bef78 adds quadratic drag `−COD·ρ·sign(v)·v²` per body axis
|
||||
(burn AND coast; the dead plain-Projectile @4bddec uses power **1.0** via
|
||||
ApplyAirResistanceAndGravity(1.0)), zeroes LATERAL local vel/accel while burning (velocity
|
||||
slaved to the nose; slaving STOPS at burnout), applies gravity (env, default **6.5**) ONLY
|
||||
when coasting, then integrates ×dt (@00421bac). Net: `dv/dt = T − COD·v²`. **Authored
|
||||
CODs = 0.001 ALL axes, all four models** (BTL4.RES type-15 rid 315 srm / 319 lrm /
|
||||
323 strk / 327 nrk, raw floats); ctor @4bf5b4 (part_013.c:18416) precomputes **terminal
|
||||
speed `sqrt(T/negCOD.z)` → missile+0x348** (the seeker lead-time base): LRM/Streak/NRK
|
||||
**547.7 u/s**, SRM **774.6**. Port pool carries the same ODE since the 2026-08-13 drag
|
||||
fix (mech4.cpp; drag was previously omitted as "negligible" — it is the speed governor).
|
||||
- **Performance split** (Projectile ctor @4be1bc / Missile ctor @4bf5b4): instance
|
||||
(flags&0xC)==ReplicantInstance(4) → FUN_004221c0 dead-reckoning smoother (no physics);
|
||||
master → 4bddec/4bef78. (CLASSMAP's old authoritative/ghost labels were swapped —
|
||||
@@ -603,9 +617,14 @@ From the weapon `.SUB` records + the charge-curve `.data` constants (PE-parsed a
|
||||
(`FUN_0041bd98`) — **VARIABLE-STEP, slice in real SECONDS**, line-for-line the
|
||||
WinTesla SIMULATE.cpp body. Corollaries: every Performance's `time_slice` is
|
||||
seconds in BOTH binary and port (no hidden tick/second unit gap); a dt-LESS
|
||||
per-frame term (myomer kinetic @4b8d18's middle term; the Mover gravity
|
||||
`-= **(mover+0x250)` @0x421e77) fires at the FRAME cadence = the tick rate,
|
||||
28 Hz nominal, sagging under load. Mover velocity is u/s (the replicant
|
||||
per-frame term into PERSISTENT state (myomer kinetic @4b8d18's middle term)
|
||||
fires at the FRAME cadence = the tick rate, 28 Hz nominal, sagging under load.
|
||||
⚠ The Mover gravity `-= **(mover+0x250)` @0x421e77 is NOT in that class
|
||||
(corrected 2026-08-13, velocity/dt audit): `Mover::PerformAndWatch` =
|
||||
**`FUN_00422360`** zeroes `localAcceleration` every frame (== T0
|
||||
MOVER.cpp:672), so gravity/drag/thrust adds define a fresh per-frame
|
||||
ACCELERATION integrated ×dt by `ApplyWorldAccelerations` @00421bac —
|
||||
frame-rate-independent, no 28 Hz rescale. Mover velocity is u/s (the replicant
|
||||
dead-reckoner @0x421f7c does `pos += vel × ticksΔ/DAT_0052140c`). The myomer
|
||||
heat term's ONE caller is the **unexported MyomersSimulation body
|
||||
@0x4b8b9a-0x4b8d0d** (E8-scan + raw disasm; the export gap hid it): calls
|
||||
|
||||
@@ -194,7 +194,8 @@ register. ⚠ The audit also flags the damage-economy item as SELF-CONTRADICTOR
|
||||
- **Environment gravity unwired — the myomer CLIMB-heat term is inert (found 2026-07-31, #85).**
|
||||
`Mover::localEnvironment` (MOVER.h:271) is declared and never populated anywhere in the port;
|
||||
`GetEnvironment()->gravityConstant` is the named analog of the original Mover's gravity POINTER at
|
||||
`+0x250` (`FUN_00421e2c` does `vy -= **(+0x250)` per tick), which the myomer drive-heat
|
||||
`+0x250` = `localEnvironment` (MOVER.h; `FUN_00421e2c` = ApplyAirResistanceAndGravity does
|
||||
`worldAccel.y -= **(+0x250)` each frame onto the frame-zeroed accumulator), which the myomer drive-heat
|
||||
integrator uses for its `m·g·|vy|·dt` climb-work term. The bridge (`BTMechMyomerMotionSample`)
|
||||
null-guards it, so climbing currently generates no myomer heat (flat maps: moot; cavern slopes:
|
||||
a missing cost). EnvironmentZone resources (RES type 23) exist in BTL4.RES — wiring them (or at
|
||||
@@ -715,12 +716,19 @@ register. ⚠ The audit also flags the damage-economy item as SELF-CONTRADICTOR
|
||||
the Mover holds to the live gravity cell). Harmless on flat ground (|vy|≈0 anyway) but the
|
||||
climb-work heat never accrues on slopes. Find where our Environment gravity actually lives
|
||||
and re-point the bridge. [T2 measured]
|
||||
- **Engine Mover gravity is dt-LESS per-frame in the binary** (`@0x421e77: vy -= **(+0x250)`,
|
||||
raw, once per Perform — same class as the myomer kinetic term). If our linked WinTesla
|
||||
MOVER.cpp kept that form, gravity acceleration in the port scales with OUR ~59Hz frame rate
|
||||
(≈2.1× the pod's 28) — jump arcs, falls, missile droop all stiffer than the pod. CHECK the
|
||||
WinTesla source's gravity line; if per-frame, it needs the same 28Hz reference-rate
|
||||
normalization the myomer kinetic term got. [T1 binary side / T4 port impact until read]
|
||||
- **✅ Engine Mover gravity needs NO 28Hz rescale — CLOSED 2026-08-13 (velocity/dt audit) [T1/T0].**
|
||||
The worry was that the per-frame `worldAccel.y -= **(+0x250)` (@0x421e77, no dt) would scale
|
||||
with our frame rate. It cannot: **`Mover::PerformAndWatch` = `FUN_00422360` ZEROES
|
||||
`localAcceleration` at the top of every frame** (`FUN_0040a7f4(+0x1dc, &DAT_004e0fd4)` ==
|
||||
T0 MOVER.cpp:672 `localAcceleration = Motion::Identity`), so gravity/drag/thrust adds are
|
||||
per-frame *acceleration definitions* on a fresh accumulator — a constant `a` integrated ×dt
|
||||
by `ApplyWorldAccelerations` @00421bac (`v += a·dt`), correct at ANY frame rate. The port's
|
||||
linked MOVER.cpp has the identical structure. (Distinct from the myomer per-tick accumulates,
|
||||
which land in PERSISTENT state and did need the ×(dt·28) fix.) The separate gap — the port's
|
||||
`GetEnvironment()->gravityConstant` sampling 0 (unwired Environment, entry above) — is still
|
||||
open; note the target constant is **6.5** (LATTICE.cpp:413 default), not 9.8 (9.8 appears only
|
||||
in the AC tracer fire-control math @0x4bc67c). Full audit:
|
||||
`docs/WEAPONS_DRIFT_AUDIT.md` §VELOCITY/DT INTEGRATION.
|
||||
- **Gait-noise phantom acceleration feeds the myomer accel term** (`[myoheat] a=4..33` at
|
||||
steady cruise — our per-frame velocity re-derivation jitters; the binary read the same
|
||||
`AccelerationLastFrame` cell off ITS OWN gait, noise level unknown). Bounded contributor
|
||||
|
||||
@@ -635,6 +635,14 @@ CONCLUSION: the coolant-loop reconstruction is faithful; every difference is 4.0
|
||||
(loadout rework on Loki, small-laser redistribution elsewhere), NOT a bug. `BT_SPEC_LOG` (mech4)
|
||||
re-dumps on demand.
|
||||
|
||||
### Weapon-range cross-check RESULT (2026-08-13) [T1] — manual prints only 4 range figures
|
||||
The manual has NO weapons table and prints NO per-weapon damage number anywhere; the per-mech
|
||||
stat sheets carry no range/damage column. The only printed ranges are MFD screenshot mockups:
|
||||
PPC 900M (p20) and AFC100 400M (p21) MATCH the shipped 4.10 content; SRM4/SRM6 450M (p22/p23)
|
||||
vs the shipped 800 is the same 4.0→4.10 drift class as the Loki rework (and missile WeaponRange
|
||||
is display-only — no physics change either way). Full table + the rangefinder-scale verdict:
|
||||
`docs/WEAPONS_DRIFT_AUDIT.md` §RANGE AUTHENTICITY.
|
||||
|
||||
## Key Relationships
|
||||
- Renders: [[gauges-hud]] (the MFD surfaces), [[rendering]] (the main 3D view).
|
||||
- Input: [[locomotion]] (the mapper).
|
||||
|
||||
@@ -122,7 +122,10 @@ Making a base byte-exact GROWS every subclass — they must be re-based TOGETHER
|
||||
(mech4.cpp): `+0x1C4` vec = `localVelocity.linearMotion`; `+0x82C` vec =
|
||||
`localAcceleration.linearMotion` (the authentic 15-ring smoothed AccelerationLastFrame);
|
||||
`+0x20C` = `moverMass` (the collision divert's cell — the old "motion gain" label was wrong);
|
||||
`*(+0x250)` = the environment **gravity pointer** (`FUN_00421e2c` does `vy -= **(+0x250)`/tick).
|
||||
`*(+0x250)` = the environment **gravity pointer** (= `Mover::localEnvironment`, MOVER.h;
|
||||
`FUN_00421e2c` = ApplyAirResistanceAndGravity does `worldAccel.y -= **(+0x250)` each frame —
|
||||
onto an accumulator `Mover::PerformAndWatch` @00422360 zeroes every frame, so it is a constant
|
||||
acceleration integrated ×dt, NOT an accumulating per-tick term; velocity/dt audit 2026-08-13).
|
||||
Physics: `heat += ratio²·(1+dmg)·[0.005·½mv² + 0.2·m·g·|vy|·dt + 0.2·m|v||a|·dt]` — kinetic work
|
||||
+ climb power + acceleration power. The binary `fabs`es v.y (the port's old draft didn't).
|
||||
**Authored tuning extracted from BTL4.RES** (all 18 mech variants share one record):
|
||||
|
||||
@@ -382,3 +382,207 @@ 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) | 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):**
|
||||
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.)
|
||||
|
||||
@@ -817,6 +817,28 @@ void
|
||||
out = m->localOrigin.linearPosition; // safe non-garbage fallback (owner origin)
|
||||
}
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// BTWeaponMountHeight -- the @004bc3fc ctor's muzzle-height operand (velocity/
|
||||
// dt integration audit 2026-08-13): the ProjectileWeapon ctor computes
|
||||
// launchVelocity.z = -effRange / sqrt((1/9.8) * 2.0 * muzzleTransform.y)
|
||||
// (@004bc3fc part_013.c:16157-16164 -- FUN_004b9948 muzzle transform, then
|
||||
// FUN_0040a968 extracts the translation and local_5c = its Y). The binary
|
||||
// takes that Y at CTOR time = the bind-pose mount height; the port resolves
|
||||
// lazily at first use (ctor runs mid-roster-build), so return the mount's
|
||||
// height ABOVE THE MECH ORIGIN -- the same quantity for a standing mech,
|
||||
// and terrain-independent. Complete-Mech-TU bridge per the databinding rule.
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Scalar
|
||||
BTWeaponMountHeight(void *ownerMech, int segIndex)
|
||||
{
|
||||
Mech *m = (Mech *)ownerMech;
|
||||
if (m == 0)
|
||||
return 0.0f;
|
||||
Point3D p;
|
||||
BTResolveWeaponMuzzle(ownerMech, segIndex, p);
|
||||
return p.y - m->localOrigin.linearPosition.y;
|
||||
}
|
||||
|
||||
// First vital damage-zone index (Mech__DamageZone::vitalDamageZone is protected; Mech has access).
|
||||
int
|
||||
Mech::FirstVitalZone() const
|
||||
@@ -1800,22 +1822,45 @@ static void
|
||||
}
|
||||
}
|
||||
|
||||
// #84 THRUSTER BURN: the binary Missile hosts a MissileThruster whose
|
||||
// authored acceleration drives the round while BurnTime remains
|
||||
// (MISTHRST: acceleration = (0,0,-thrusterAccel) in the missile frame,
|
||||
// integrated by Missile::MoveAndCollide @4bef78). The pool flew at
|
||||
// CONSTANT |MuzzleVelocity| -- the rack-eject speed (SRM 100, LRM 30
|
||||
// u/s) -- which is why field missiles crawled (Oracle/Rajel, #84).
|
||||
// Authored (BTL4.RES type-15 missile models, +0x44/+0x48): SRM
|
||||
// burn 2.5s @ 600 u/s^2, LRM 10s @ 300, Streak 3s @ 300. Drag is
|
||||
// authored ~0.001 (negligible at combat ranges) -- integrate accel
|
||||
// along the current heading, un-damped, range-capped as before. [T1
|
||||
// values / T2 integration]
|
||||
if (p.burnLeft > 0.0f && p.accel > 0.0f)
|
||||
// #84 THRUSTER BURN + QUADRATIC DRAG (velocity/dt integration audit
|
||||
// 2026-08-13). Binary model [T1]: Mover::PerformAndWatch @00422360
|
||||
// ZEROES localAcceleration EVERY frame (`FUN_0040a7f4(+0x1dc,
|
||||
// &DAT_004e0fd4)` = the T0 MOVER.cpp:672 `localAcceleration =
|
||||
// Motion::Identity`), the MissileThruster's own Performance @004be474
|
||||
// (the standalone MissileThrusterSimulation body -- recovered by the
|
||||
// 2026-08-06 re-export) re-adds (0,0,-ThrusterAccel) while burning,
|
||||
// and Missile::MoveAndCollide @4bef78 adds the Mover quadratic drag
|
||||
// -COD*airDensity*sign(v)*v^2 per body axis, then integrates with dt
|
||||
// in SECONDS (@00421bac ApplyWorldAccelerations: v += a*dt). Net:
|
||||
// dv/dt = ThrusterAccel - COD*v^2 (burn; lateral vel zeroed)
|
||||
// -- a clean variable-step ODE. There is NO myomer-style 28Hz
|
||||
// per-tick accumulate in this lane: `speed += accel*dt` is the
|
||||
// faithful form and needs no (dt*28) rescale.
|
||||
// AUTHORED [T1, raw floats read from BTL4.RES type-15 records
|
||||
// 2026-08-13]: linear CODs = 0.001 on ALL axes of ALL four models
|
||||
// (srm rid=315, lrm 319, strk 323, nrk 327); airDensity = 1.0
|
||||
// (engine default, LATTICE.cpp:413). Drag is NOT negligible -- it is
|
||||
// the flight model's GOVERNOR: the Missile ctor @004bf5b4 precomputes
|
||||
// terminal speed sqrt(ThrusterAccel/negCOD.z) into missile+0x348 for
|
||||
// the seeker's lead time (part_013.c:18416). Terminal: LRM/Streak/
|
||||
// NRK 547.7 u/s, SRM 774.6. The old "drag ~0.001 negligible" note
|
||||
// (#84) let an LRM reach 3030 u/s by burnout -- 5.5x the binary cap
|
||||
// (sim: binary 492 u/s at 800u vs undamped 695; port t+15% to 800u).
|
||||
// Drag runs during burn AND coast (a burned-out round DECELERATES,
|
||||
// v(t)=v0/(1+COD*v0*t)); thruster rounds only -- the AC round is the
|
||||
// 0xBCD renderer tracer, a cosmetic with no Mover physics. Still
|
||||
// unported [T3 minor]: coast gravity (-6.5 world-Y, @4bef78
|
||||
// else-branch) and the binary's burnout end to velocity-slaving --
|
||||
// both live beyond practical impact ranges (see WEAPONS_DRIFT_AUDIT
|
||||
// 'VELOCITY/DT INTEGRATION').
|
||||
if (p.accel > 0.0f)
|
||||
{
|
||||
Scalar burn_dt = (dt < p.burnLeft) ? dt : p.burnLeft;
|
||||
p.burnLeft -= burn_dt;
|
||||
Scalar ns = p.speed + p.accel * burn_dt;
|
||||
if (burn_dt > 0.0f)
|
||||
p.burnLeft -= burn_dt;
|
||||
Scalar ns = p.speed + p.accel * burn_dt
|
||||
- 0.001f * p.speed * p.speed * dt; // authored COD x airDensity 1.0 [T1]
|
||||
if (ns < 1.0f) ns = 1.0f; // guard: drag alone never reverses flight
|
||||
if (p.speed > 0.01f)
|
||||
{
|
||||
Scalar k = ns / p.speed;
|
||||
|
||||
@@ -14,11 +14,21 @@
|
||||
//
|
||||
// Coverage:
|
||||
// confident : ctor @004be7c4, dtor thunk @004be8bc, TestInstance @004be8e8,
|
||||
// CreateStreamedSubsystem @004bf8ec
|
||||
// best-effort: MissileThrusterSimulation -- the Performance method pointer is
|
||||
// installed by the ctor (PTR_LAB_00512b20) but its standalone body
|
||||
// is folded into the host Missile::MoveAndCollide @004bef78 (which
|
||||
// samples the thruster acceleration buffer at missile+0x234/+0x250).
|
||||
// CreateStreamedSubsystem @004bf8ec,
|
||||
// MissileThrusterSimulation @004be474 -- the STANDALONE
|
||||
// Performance body, RECOVERED by the 2026-08-06 re-export
|
||||
// (the old "folded into Missile::MoveAndCollide, no distinct
|
||||
// @ADDR survives" note here was stale). @004be474 does BOTH
|
||||
// jobs each frame: (1) slerp the owner Missile's orientation
|
||||
// toward the Seeker aim point (+0xE4), rate-capped at
|
||||
// maxThrusterRotationRate*dt; (2) while ThrusterBurning:
|
||||
// burnTimeRemaining -= dt and owner->localAcceleration +=
|
||||
// this->acceleration (0,0,-thrusterAccel) -- onto the
|
||||
// accumulator Mover::PerformAndWatch @00422360 zeroes every
|
||||
// frame, i.e. a constant acceleration, dt-correct. The
|
||||
// simplified body below remains the dormant-TU stand-in;
|
||||
// the LIVE port path is the mech4.cpp pool (see
|
||||
// docs/WEAPONS_DRIFT_AUDIT.md 'VELOCITY/DT INTEGRATION').
|
||||
// excluded : the 0x41xxxx engine vtable slots (pure Subsystem base behaviour;
|
||||
// vtable @00512bbc overrides only slot0 = destructor)
|
||||
//
|
||||
|
||||
@@ -28,11 +28,14 @@
|
||||
// ProjectileWeaponSimulation @004bbd04 (Performance -- FULLY RECOVERED
|
||||
// by capstone disasm, Gitea #12 2026-07-19; see context/decomp-reference.md
|
||||
// s5 -- the old RivetGun-modeled body is retired).
|
||||
// best-effort (vtable slot proven, function prologue present, BODY NOT
|
||||
// recovered by the decompiler -- bodies below are reconstructed from context
|
||||
// and clearly marked):
|
||||
// FireWeapon @004bc104 (slot 18), GetStatusFlags @004bbf88 (slot 12),
|
||||
// UpdateWeaponState @004bbc20 (slot 16).
|
||||
// FireWeapon @004bc104 (slot 18) -- RECOVERED by the 2026-08-06
|
||||
// re-export (part_013.c:16023-16125; trigger-time hitscan, implemented
|
||||
// in the #171 batch. The old "BODY NOT recovered" line here was the
|
||||
// stale banner behind the #168 mis-fix -- see the FireWeapon block).
|
||||
// best-effort (vtable slot proven, function prologue present, body still
|
||||
// unrecovered -- reconstructed from context and clearly marked):
|
||||
// GetStatusFlags @004bbf88 (slot 12), UpdateWeaponState @004bbc20
|
||||
// (slot 16).
|
||||
// excluded (belong to sibling/derived classes, NOT ProjectileWeapon):
|
||||
// Emitter family @004bb120/@004bb888/@004ba4d0-@004bb478 (energy weapons),
|
||||
// MissileLauncher @004bcff0/@004bd060/@004bd08c & FireWeapon @004bcc60,
|
||||
@@ -45,6 +48,9 @@
|
||||
// _DAT_004bc068 = 0000803f = 1.0f (jam-chance clamp ceiling)
|
||||
// _DAT_004bb3b0 = 000080bf = -1.0f (resource "unset" sentinel)
|
||||
// _DAT_004bb3b4 = 0000003f = 0.5f
|
||||
// _DAT_004bc678 = 00000040 = 2.0f (tracer fall-time factor; audit 2026-08-13)
|
||||
// _DAT_004bc67c = 8a6c379943c6fad0fb3f = 1/9.8 as 80-bit x87 EXTENDED
|
||||
// (tracer gravity term; audit 2026-08-13)
|
||||
// DAT_004e0f74 = {0,0,0} (zero vector)
|
||||
// DAT_004e0fd4 = {0,0,0} (zero point)
|
||||
// 0x40400000 = 3.0f (TotalTimeToEject default)
|
||||
@@ -145,8 +151,18 @@ namespace {
|
||||
static const Scalar _DAT_004bc100 = 1.0f; // time-of-flight bias
|
||||
static const Scalar _DAT_004bc064 = 0.41f; // heat -> jam-chance coefficient
|
||||
static const Scalar _DAT_004bc068 = 1.0f; // jam-chance clamp ceiling
|
||||
static const Scalar _DAT_004bc678 = 1.0f; // muzzle-speed term (unresolved)
|
||||
static const Scalar _DAT_004bc67c = 1.0f; // muzzle-speed term (unresolved)
|
||||
// Tracer launch-speed constants -- BYTE-CONFIRMED by the 2026-08-13 weapons
|
||||
// audit (WEAPONS_DRIFT_AUDIT.md entry 7 / evidence log 3); the old 1.0f
|
||||
// "unresolved" stand-ins are RETIRED:
|
||||
// @0x4bc678 = 40000000 = 2.0f (float32)
|
||||
// @0x4bc67c = 8a6c379943c6fad0fb3f = 0.10204081... = 1/9.8, stored as an
|
||||
// 80-bit x87 EXTENDED (not float32-decodable -- why it sat
|
||||
// "unresolved"). Together with the ctor's muzzle-height term:
|
||||
// launchVelocity.z = -effRange / sqrt((1/9.8) * 2.0 * muzzleHeight)
|
||||
// i.e. the tracer crosses effectiveRange in the time a shell would fall
|
||||
// from muzzle height under g=9.8 (~1s for a ~5u mount).
|
||||
static const Scalar _DAT_004bc678 = 2.0f; // fall-time factor (2h/g)
|
||||
static const Scalar _DAT_004bc67c = 0.10204081f; // 1/9.8 (x87 extended in the image)
|
||||
|
||||
// FUN_00408050 -- uniform [0,1) random (jam roll). The old `return 0.0f` stub ALWAYS
|
||||
// jammed (0 < any positive jam chance), so a projectile weapon could never fire. A real
|
||||
@@ -311,11 +327,16 @@ ProjectileWeapon::ProjectileWeapon(
|
||||
minVoltagePercentToFire = subsystem_resource->minVoltagePercentToFire; // +0x1C8 -> 0x404
|
||||
tracerCounter = 0; // 0x408
|
||||
|
||||
// launchVelocity seed = (0,0,0) then z = -(effectiveRange / muzzleSpeed)
|
||||
// launchVelocity seed = (0,0,0) then z = -(effectiveRange / muzzleSpeed),
|
||||
// muzzleSpeed = sqrt((1/9.8) * 2.0 * muzzleHeight) -- @004bc3fc:16157-16164
|
||||
// resolves the muzzle transform (FUN_004b9948) INSIDE the ctor and uses its
|
||||
// translation Y. The port DEFERS the z term to first use
|
||||
// (ResolveLaunchVelocity below): our ctor runs mid-roster-build, where the
|
||||
// segment resolve can fall back to the mech origin and would bake a garbage
|
||||
// constant. z == 0 is the unresolved sentinel (the binary's z is always
|
||||
// negative after its ctor).
|
||||
launchVelocity = Vector3D(0,0,0); // FUN_00408440(this+0x410, DAT_004e0f74)
|
||||
Scalar muzzleSpeed = (Scalar)Sqrt(_DAT_004bc67c * _DAT_004bc678); // FUN_004dd138
|
||||
tracerOrigin = Vector3D(0,0,0); // FUN_00408440(this+0x42c, DAT_004e0f74)
|
||||
launchVelocity.z = -(effectiveRange / muzzleSpeed); // this[0x106] = -(this[0xca]/speed)
|
||||
|
||||
totalTimeToEject = 3.0f; // 0x3F0 (0x40400000)
|
||||
timeToEject = totalTimeToEject; // 0x3F4
|
||||
@@ -706,6 +727,30 @@ Logical
|
||||
return (p > UniformRandom()) ? True : False; // FUN_00408050
|
||||
}
|
||||
|
||||
//
|
||||
// The @004bc3fc ctor launch-speed expression, deferred to first use (see the
|
||||
// ctor note). Byte-grounded constants (audit 2026-08-13): @0x4bc678 = 2.0f,
|
||||
// @0x4bc67c = 1/9.8 (80-bit x87 extended `8a6c379943c6fad0fb3f`):
|
||||
// launchVelocity.z = -effectiveRange / sqrt((1/9.8) * 2.0 * muzzleHeight)
|
||||
// The binary computes it once at ctor time (bind pose, effectiveRange still ==
|
||||
// authored WeaponRange); the port computes it once at first use with the mount
|
||||
// height above the mech origin -- the same quantity for a standing mech.
|
||||
//
|
||||
void
|
||||
ProjectileWeapon::ResolveLaunchVelocity()
|
||||
{
|
||||
if (launchVelocity.z != 0.0f)
|
||||
return; // already resolved
|
||||
extern Scalar BTWeaponMountHeight(void *ownerMech, int segIndex);
|
||||
Scalar muzzleHeight = BTWeaponMountHeight(owner, GetSegmentIndex());
|
||||
if (muzzleHeight < 0.5f)
|
||||
muzzleHeight = 0.5f; // port guard: unresolved segment (muzzle==origin
|
||||
// fallback) -- the binary's bind-pose height is
|
||||
// always positive; keeps sqrt/divide sane
|
||||
Scalar muzzleSpeed = (Scalar)Sqrt(_DAT_004bc67c * _DAT_004bc678 * muzzleHeight); // FUN_004dd138
|
||||
launchVelocity.z = -(effectiveRange / muzzleSpeed); // this[0x106] = -(this[0xca]/speed)
|
||||
}
|
||||
|
||||
//
|
||||
// @004bc06c -- lead-solution time-of-flight. Sample the current target
|
||||
// position, advance it by launchVelocity, and re-run targeting. With no target
|
||||
@@ -715,6 +760,7 @@ Logical
|
||||
Scalar
|
||||
ProjectileWeapon::ComputeTimeOfFlight()
|
||||
{
|
||||
ResolveLaunchVelocity(); // deferred @004bc3fc z term
|
||||
GetTargetPosition(leadPosition); // FUN_004b9cbc(this, this+0x420)
|
||||
leadPosition.x += launchVelocity.x; // this+0x420 += this+0x410
|
||||
leadPosition.y += launchVelocity.y;
|
||||
@@ -985,10 +1031,11 @@ void
|
||||
// single-panel Damage (amount = batchedCount x DamageAmount) to the LOCKED
|
||||
// target *(mech+0x388) via SendDamageMessage @004b9728 -- at fire time.
|
||||
// A locked AC inside range cannot miss; out of range the batch silently
|
||||
// discards (tracer still flies). Reconstruction pending the #171 group
|
||||
// decision (Oracle's HOLD) -- the current port body below still carries
|
||||
// the #168 flight-contact model. MissileLauncher (@004bcc60) is the
|
||||
// genuinely-flying family (Model B) and overrides this slot.
|
||||
// discards (tracer still flies). The body below IMPLEMENTS that hitscan
|
||||
// (landed in the #171 batch, bbb3b4e; the earlier "still carries the #168
|
||||
// flight-contact model" sentence here outlived the change -- the exact
|
||||
// stale-banner genus this block documents). MissileLauncher (@004bcc60)
|
||||
// is the genuinely-flying family (Model B) and overrides this slot.
|
||||
//
|
||||
// Gitea #12: the view/target gate, the ammo pull and the recoil set are
|
||||
// STRIPPED from this body -- they belong to the CALLER (the recovered
|
||||
@@ -1110,7 +1157,10 @@ void
|
||||
// The VISIBLE round: the binary's 0xBCD renderer tracer, cosmetic only
|
||||
// (the shell entity 0xBD1 is dead code -- nothing shipped ever spawns a
|
||||
// damage-carrying ballistic round). Fly the pool round as that visual:
|
||||
// damage 0, straight at the pick, authentic muzzle speed.
|
||||
// damage 0, straight at the pick, authentic muzzle speed
|
||||
// (= effRange / sqrt(2*(1/9.8)*muzzleHeight), the recovered @004bc3fc
|
||||
// constants -- see ResolveLaunchVelocity).
|
||||
ResolveLaunchVelocity();
|
||||
Scalar speed = (Scalar)sqrtf(launchVelocity.x*launchVelocity.x
|
||||
+ launchVelocity.y*launchVelocity.y
|
||||
+ launchVelocity.z*launchVelocity.z);
|
||||
@@ -1178,6 +1228,7 @@ void
|
||||
// plus the DAFC muzzle flash.
|
||||
Point3D mz;
|
||||
GetMuzzlePoint(mz); // @004b9948
|
||||
ResolveLaunchVelocity(); // deferred @004bc3fc z term
|
||||
Scalar spd = (Scalar)sqrtf(launchVelocity.x*launchVelocity.x
|
||||
+ launchVelocity.y*launchVelocity.y
|
||||
+ launchVelocity.z*launchVelocity.z);
|
||||
|
||||
@@ -283,6 +283,14 @@ class NotationFile;
|
||||
Scalar
|
||||
ComputeTimeOfFlight();
|
||||
|
||||
// The @004bc3fc ctor launch-speed expression
|
||||
// (launchVelocity.z = -effRange / sqrt((1/9.8) * 2.0 * muzzleHeight);
|
||||
// constants byte-confirmed @0x4bc678/@0x4bc67c, audit 2026-08-13),
|
||||
// deferred to first use because the port ctor runs mid-roster-build.
|
||||
// z == 0 is the unresolved sentinel; no data member added (layout locked).
|
||||
void
|
||||
ResolveLaunchVelocity();
|
||||
|
||||
// --- cross-family isolation helpers (owning Mech subsystem roster +
|
||||
// cockpit controls live in the mech/controls families). Declared
|
||||
// here so the recovered bodies compile; the mech family wires the
|
||||
|
||||
Reference in New Issue
Block a user