MP live-play wave: collision economy, missiles, radar transform, panel polarity, comm ticker
The interactive 2-node playtest wave -- every fix decomp-grounded and live-verified: COLLISION ECONOMY (the ram one-shot): StaticBounce mutates worldLinearVelocity per contact and ProcessCollisionList walks EVERY touched solid per frame; with 2007 terrain-as-solids the reflections compounded x4-x40 within one frame and a walking bump one-shot a pristine mech for 112,375 pts (62-pt authentic economy). Fix: frameEntryWorldVelocity restore per contact (damage always priced at the real approach speed -- all the binary's physics ever saw); Mech::Reset zeroes the mover motion (respawn = teleport); [collide-tx]/[mp-hdlr] telemetry. Gotcha #16 (engine-facility drift class). MISSILES: peer-visible salvos (the launcher record extension carries a salvo counter + aim point; ForceUpdate actually enqueues it -- the dirty flag alone never serialized), the authentic arc (authored MuzzleVelocity vector + the Seeker's 200m/0.1/300 loft + gain-4 steering, decoded from @004beae4/@004bef78), world-impact bursts (rounds detonate on cave geometry instead of phasing through), contact-only damage (flight-cap expiry = fizzle, no more teleport damage), live re-lead, and ballistic (unguided) shells for autocannons. projweap's stale BTPushProjectile extern (the /FORCE signature trap, gotcha #6 corollary) crashed the Avatar's first AFC100 shot -- fixed + sweep rule recorded. RADAR: two transcription bugs made the scope permanently empty -- FUN_0040b244 is the affine INVERSE (not a copy) and FUN_0040adec writes ONLY the 3x3 rotation (never the translation); worldToView now Invert(view) built rotation-first. CulturalIcons sorted out of the moving grid (the phantom red pips were map props), visible-radius culls on all three draw passes, live pip verified at |delta| x ppm px. Gotcha #17 (verify the FUN_ body, not its call shape). WEAPON PANELS (the frozen-dial hunt): the binary's *(subsystem+0x40) means FAILED -- the recon's 'operating' name was backwards, inverting the destroyed-X lamps, the panel look, the children enable and the ready-lamp gate (which had NEVER executed). Polarity chain corrected end-to-end (failedState, fed by real damage saturation). Root cause of the freezes: MFD page-mode gating -- the dev composite shows ALL pages at once, so off-page dials legitimately stopped; under BT_DEV_GAUGES the 15 page-plane bits stay active (the exclusive secondary trio untouched). The SEH gauge guard now names its kills; repaint-heal resets the incremental arc after panel repaints; [panel]/[arc] probes added. COMM/SCORE: MessageBoard LIVE (the engine already shipped the whole Player__StatusMessage queue; wired the binary's one producer -- the kill branch, victim's name, 6s -- plus the consumer bridge and a lazy source bind); MP DEATHS counted via the observed-death tally (each node scores every pilot from locally observed events, the same model as the KILLS credit) and the -2/-1 engine seed clamped for display. DEV UX: node-tagged window titles (-net port), gauge panel reworked (1320x480, true 4:3 MFD cells, the portrait secondary UNROTATED upright, linear filtering, BT_GAUGE_SCALE), fixed close spawns via BT_SPAWN_XZ, Boreas flies an Avatar (first second-chassis live outing). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
dd27238ceb
commit
bb795e2805
@@ -233,6 +233,24 @@ Two non-field fixes were needed: **message-handler chaining** (an unchained set
|
||||
silently) + **entity validity** (`SetValidFlag()` on a manually-spawned entity) — see
|
||||
[[reconstruction-gotchas]] §9. [T2]
|
||||
|
||||
## Ram (mech-vs-mech collision) damage — the economy (2026-07-12)
|
||||
The binary's `Mech::ProcessCollision` (part_012.c:15280-15413) computes contact damage with the
|
||||
engine `Mover::StaticBounce` [T0 MOVER.cpp]: **damage = 0.0005 × (1−e²) × impact² × moverMass**
|
||||
(KE loss; `impact` = own `worldLinearVelocity·normal`; mass/elasticity/friction stream from the
|
||||
model gamedata `MoverMass` etc.). On a Mover owner it gates on separating-contact (`normal·rel <
|
||||
−1e-4 → return`), then on `ClassID==0xbb9` dispatches a `DamageMessage{id=0x64, zone=−1,
|
||||
CollisionDamageType}` at the OTHER mech (the receiver's cylinder table resolves the zone). Ported
|
||||
in `BTDispatchCollisionDamage` (mech4.cpp) [T1]. **Observed live economy [T2]:** weapons 3.4-11.8
|
||||
per hit; a walking/running ram = 13-62 per contact frame; a sustained ram killed a pristine mech
|
||||
at 102.5 total (4 contacts) via (presumably) a vital torso zone — per-zone HP = the authored
|
||||
`CollisionDamagePoints` (fallback `WeaponDamagePoints`), scale = 1/points, replayed from the
|
||||
type-0x14 stream. Whether ~100-pt ram lethality matches the pod is unverified (the authored
|
||||
collision points for the torso zones haven't been dumped) [T3].
|
||||
**⚠ The one-shot bug (fixed 2026-07-12):** multi-solid frames amplified the velocity StaticBounce
|
||||
priced damage from (62 → 112,375; killed a pristine mech through a single bump) — gotcha #16 in
|
||||
[[reconstruction-gotchas]]; fix = `frameEntryWorldVelocity` restore per contact + `Mech::Reset`
|
||||
motion zeroing. `[collide-tx]` (BT_MP_NET) logs every dispatched ram with the pricing velocity.
|
||||
|
||||
## Death — the wreck STAYS (P5 CLOSED)
|
||||
A killed mech does NOT disappear — BT death is a STATE transition
|
||||
(`SetGraphicState(DestroyedGraphicState)` + death anim + effect/splash), the mech becomes a persistent
|
||||
|
||||
+33
-1
@@ -6,8 +6,10 @@ source_sections: "docs/GAUGE_COMPOSITE.md (full map); CLAUDE.md §8"
|
||||
related_topics: [reconstruction-gotchas, decomp-reference, subsystems, pod-hardware]
|
||||
key_terms: [gauge, methodDescription, attribute-pointer, GaugeRenderer, MFD, PCC]
|
||||
open_questions:
|
||||
- "Status-message queue (StatusMessagePool) is a NULL stub -> MessageBoard is empty"
|
||||
- "MessageBoard LIVE 2026-07-12 (kill ticker); remaining: only strip 0 (kill) is produced -- survey the other btsmsgs.pcx strips for authentic producers"
|
||||
- "HUD binary attr table @005110c0 offsets conflict with the port HUD layout on 3 slots (0x1D8/0x1EC/0x1F8) -- re-dump before publishing"
|
||||
- "Secondary-view cycling (Damage/Critical/Heat) unreachable from the desktop keyboard: the mapper's 0x13d/0x13e cases are dead DOS F3/F4 codes (VK_F3/F4 = 0x72/0x73 collide with 'r'/'s') -- wire a free key to CycleControlMode"
|
||||
- "MP DEATHS resolved 2026-07-12 (observed-death tally + display clamp); remaining: verify multi-death tallies stay in sync across a long session (GAUGE_COMPOSITE.md)"
|
||||
---
|
||||
|
||||
# Cockpit Gauges / MFD HUD
|
||||
@@ -68,6 +70,36 @@ over one shared `SVGA16` pixelBuffer; `SVGA16::DrawDevSurface`). On the POD they
|
||||
intact). The `overlay` port (SectorDisplay lives there) shares the `sec` physical surface via a
|
||||
different bit-plane (0x00C0). [T2]
|
||||
|
||||
## The secondary screen's THREE views (Damage / Critical / Heat) — mode-gated [T0/T1]
|
||||
The `sec` port stacks three mode-gated mech-schematic layers at offset (50,0) over the
|
||||
always-on radar/heading/speed/messageBoard (`Secondary1`): **Damage** (`ModeSecondaryDamage`,
|
||||
`<mech>dama.pcc` + `colorMapArmor`/`colorMapperMultiArmor` — 4 silhouettes front/left/right/back,
|
||||
pixel-plane ids 60-63, per-DAMAGE-ZONE `dz_*` tint through the adpal→adpal2 ramp), **Critical**
|
||||
(`ModeSecondaryCritical`, `<mech>crit.pcc` + the cmCrit per-SUBSYSTEM list), **Heat**
|
||||
(`ModeSecondaryHeat` + cmHeat). Mode bits (BTL4MODE.HPP, `nextModeBit`=0 [T0]): Mapping=0x8000,
|
||||
NonMapping=0x10000, Intercom=0x20000, **SecondaryDamage=0x40000, SecondaryCritical=0x80000,
|
||||
SecondaryHeat=0x100000** (bits 18-20). `ModeInitial` includes **SecondaryDamage** → the ARMOR
|
||||
view is the default-on layer (our port creates `BTL4ModeManager(ModeInitial)`, btl4app.cpp:303).
|
||||
`L4MechControlsMapper::SetControlMode` @004d1ae4 is the SELECTOR — its mask table
|
||||
{0x40000,0x80000,0x100000} clears bits 18-20 and sets one — despite the "control mode" name it
|
||||
switches the secondary VIEW. ⚠ Desktop gap: the Keypress cases `0x13d`/`0x13e`
|
||||
(CycleControlMode/CycleDisplayMode) are the DOS Tesla extended F3/F4 codes and never fire under
|
||||
the WinTesla VK map (VK_F3=0x72 collides with 'r', VK_F4=0x73 with 's' — the same collision
|
||||
class as the documented 'p'/VK_F1 drop), so the desktop is PINNED on the Damage view. The
|
||||
schematic shows the pilot's OWN mech only — there is no target-damage readout in the cockpit.
|
||||
|
||||
## pilotList (Comm KILLS/DEATHS) row semantics + the −1 [T1/T2]
|
||||
One ROW PER PILOT in the mission (2-player MP = 2 rows — not duplicate displays). KILLS =
|
||||
`killCount` (the victim's ScoreMessageHandler credits the shooter cross-player, works for both
|
||||
rows); DEATHS = `Player::deathCount`: engine-inits to **−2** (PLAYER.cpp:759), the LOCAL
|
||||
vehicle-acquire branch zeroes it (btplayer.cpp:1118), then VehicleDead(-1) ++s per death. A
|
||||
REMOTE player's Player object never runs the local acquire → −2 +1 spawn increment = **−1
|
||||
locked**. **RESOLVED 2026-07-12 [T2]:** deaths now tally per node from LOCALLY OBSERVED events
|
||||
(the same model as the cross-pod KILLS credit) — a replicant's once-per-death transition calls
|
||||
`BTPlayerCountObservedDeath` on its owning player's local copy (replicant-gated: the master's
|
||||
own VehicleDead path counts its node), and `BTPilotDeaths` clamps the −2/−1 pre-acquire seed
|
||||
to 0 for display. Own row counts correctly as before.
|
||||
|
||||
## ConfigMapGauge (the weapon panel's regroup lamp column) — authentically DORMANT
|
||||
The per-weapon btjoy.pcc joystick image + 4 cm_* state lamps (off/other/only/both) showing,
|
||||
for each mappable fire button (Pinky/ThumbLow/Trigger/ThumbHigh), whether THIS panel's weapon
|
||||
|
||||
@@ -92,10 +92,17 @@ authentic path scoped.
|
||||
chain** ([0x31c]/[0x320] dpl objects + the PNAME1-8.bgf player-name meshes over the locked
|
||||
target — matters for MP). (c) the compass rotation SIGN is derived-not-verified [T3] (turn
|
||||
left → the stem should swing right; flip the sin sign in Draw if footage disagrees).
|
||||
- **StatusMessagePool (NULL stub, btstubs.cpp:62)** — the per-player status-message queue.
|
||||
BTPlayer+0x1dc is never populated → MessageBoard is empty (authentic for bring-up). What
|
||||
resolves it: wire the pool + decode `AddStatusMessage @0042e580` + the `Player__StatusMessage`
|
||||
record ({messageId@+0xc, nameEntity@+0x10}).
|
||||
- **✅ StatusMessagePool / MessageBoard — LIVE (2026-07-12) [T1/T2].** Decoded: FUN_0042e580 =
|
||||
the engine `Player::AddStatusMessage` (chain add at +0x1e4); FUN_0042e5a0 = `StatusMessageUpdate`
|
||||
(+0x1dc = the displayed message; +0x14 displayTime countdown, expiry-deleted) — ALL already in
|
||||
the engine [T0 PLAYER.cpp]. The ONE binary producer = BTPlayer::ScoreMessageHandler's KILL
|
||||
branch (@004c02e4 type-2): pool-alloc @00512f6c + ctor(victim's player, strip 0, 6.0s) +
|
||||
AddStatusMessage. Port: engine `new Player__StatusMessage` + Register_Object (the engine
|
||||
reclaims with delete — the binary pool is an allocation detail); consumer =
|
||||
BTResolveMessageBoard reads the mission player's statusMessagePointer + resolves the name via
|
||||
Mission::GetSmallNameBitmap; the board's SetSource binder (no recovered caller) is replaced by
|
||||
a lazy viewpoint-entity bind in Execute. Ticker pops on BOTH nodes (each node's death
|
||||
transition posts its local KillScore).
|
||||
- **Gyroscope (0xBC4) — RE-ENABLED LIVE (task #56, 2026-07-10).** The NaN revert's root causes
|
||||
are fixed byte-exact (ctor field map @004b3778 — springConstant@0x1E8/dampingConstant@0x1F4
|
||||
were mislabelled, the 0x254-0x2B3 block was missing, clamps/accumulators uninitialised; the
|
||||
@@ -292,6 +299,14 @@ authentic path scoped.
|
||||
under reconstruction: config-mode regrouping (task #6), generator reassignment (ids 4-8),
|
||||
coolant valves.
|
||||
|
||||
- **Subsystem-panel online/offline gate — fields unidentified [T4] (2026-07-12).** The binary's
|
||||
SubsystemCluster draw-state reads `*(subsystem+0x40)==1` and `*(subsystem+0x278)!=4`; neither
|
||||
1995 field is identified (weapon simulationState is 0/2/3/4 -- +0x40 isn't it). The port pins
|
||||
the panel ONLINE (GetDrawState()==1) because the old raw reads were layout NOISE that flapped
|
||||
the state and black-filled the panel over the recharge arc (the frozen-PPC-dial bug; the
|
||||
WeaponCluster repaint-heal also landed). Identify both fields to restore the authentic
|
||||
destroyed-subsystem dark-panel look.
|
||||
|
||||
## Locomotion / combat polish (non-gating)
|
||||
- Authentic per-mech TURN-RATE constant (currently a bring-up constant rate).
|
||||
- Body-callback gimp handlers (states 16-19, targets 0x70b2/0x7161 undecoded → fall back to stand);
|
||||
|
||||
@@ -113,6 +113,14 @@ near the image base, grep the link log for "unresolved external" — the "succes
|
||||
lying.** Corollary: a bridge fn / a `.data` fn-ptr callback MUST have a real (stub) definition.
|
||||
A `SetVideoPathPriority` defined in an anonymous namespace → internal linkage → unresolved in
|
||||
another TU → stubbed by /FORCE → AV in `LoadMissionImplementation`. [T2]
|
||||
**Signature-change corollary (user-hit crash 2026-07-12):** changing a shared free-function
|
||||
bridge's SIGNATURE changes its mangled name — every OTHER TU's local `extern` decl now
|
||||
references a symbol that no longer exists, /FORCE tolerates it, and the crash lands on the
|
||||
first call from the un-updated TU (the missile-arc wave updated `BTPushProjectile` +
|
||||
mislanch.cpp's extern but not projweap.cpp's → first AUTOCANNON shot AV'd). **Rule: after any
|
||||
bridge-signature change, `grep -rn "extern .*<name>"` and update every declaration; then grep
|
||||
the fresh link output for the symbol name** — the pre-existing LNK2019 wall camouflages new
|
||||
entries if you only eyeball it.
|
||||
|
||||
## 7. Dtor-epilogue rule — do not reconstruct compiler glue
|
||||
|
||||
@@ -293,6 +301,50 @@ corrupts the metric.
|
||||
canopies are open lattices. Namespace edge keys by patch identity (and remember l/r patches are
|
||||
MIRRORED — winding handedness flips, so no global winding choice can be right; orient per-face).
|
||||
|
||||
## 16. Engine-facility drift: 2007 terrain-solids amplify 1995 per-contact physics (the MP ram one-shot)
|
||||
|
||||
`Mover::StaticBounce` [T0] MUTATES `worldLinearVelocity` (`+= delta_v`, a ×(1+e) reflection) on
|
||||
every call, and `ProcessCollisionList` calls it once PER CONTACTED SOLID in the frame. In the 1995
|
||||
binary the ground was a heightfield probe (FUN_0040e5f0 lineage) — never a collision-list entry —
|
||||
so a mech's list held ~one solid and the mutation was harmless. The 2007 WinTesla engine models
|
||||
TERRAIN AS COLLISION SOLIDS: a mech touching ground + rock + another mech reflects 2-4× in ONE
|
||||
frame, compounding velocity ×4-×40, and the mech-vs-mech damage dispatch later in the list priced
|
||||
ram damage off the amplified value — a 62-point bump economy produced 1,074- and 112,375-point
|
||||
one-shots (mp_a.log:32651, 2026-07-12: a pristine mech killed by a walking bump). [T2]
|
||||
|
||||
- **Fix pattern:** snapshot the TRUE frame-entry motion (`frameEntryWorldVelocity`, set beside the
|
||||
`ProcessCollisionList` call sites) and restore it at the top of every `Mech::ProcessCollision` —
|
||||
each contact prices damage at the mech's real approach speed, which is all the binary's
|
||||
StaticBounce ever saw. The post-list velocity is discarded anyway (frame-rejection response /
|
||||
next frame's position-delta derive). Also: `Mech::Reset` must zero `worldLinearVelocity` +
|
||||
`localVelocity` (respawn is a TELEPORT; stale death-frame motion must not survive it).
|
||||
- **Tell:** damage amounts orders of magnitude outside the weapon economy (weapons 3-12/hit, rams
|
||||
~13-62), CONSTANT repeated values (a stable grind oscillation), or spikes scaling with how many
|
||||
solids surround the contact. Damage = `0.0005 × (1−e²) × impact² × moverMass` [T0 MOVER.cpp] —
|
||||
invert it to read the implied impact speed; >100 m/s means amplified/garbage velocity, not motion.
|
||||
- **Class rule:** when a 1995 per-event computation reads MUTABLE engine state, audit what ELSE the
|
||||
2007 engine feeds that state within the same event batch. (Family of gotcha #12's frame-pacing
|
||||
trap: the binary's physics assumed its own engine's event granularity.)
|
||||
|
||||
## 17. Engine-helper identity: verify the FUN_ body, not its call shape (the empty-radar bug)
|
||||
|
||||
Two adjacent matrix helpers in the radar's DrawDisplay were transcribed by CALL SHAPE and both
|
||||
were wrong — producing a plausible-looking but broken world→view transform that drew every pip
|
||||
hundreds of pixels off-scope (the radar looked simply "empty"; nothing crashed, nothing warned):
|
||||
- `FUN_0040b244(dst, src)` read as a COPY — it is the full affine INVERSE (cofactor expansion +
|
||||
determinant divide, part_001.c:172). → `worldToView.Invert(view)`.
|
||||
- `FUN_0040adec(matrix, quat)` read as a COMPOSE (`view *= yaw`) — it writes ONLY the 3×3
|
||||
rotation elements and NEVER touches [3]/[7]/[11] (the translation row). The engine's
|
||||
`operator*=(Quaternion)` composes fully (rotates the translation too) — the pre-set center got
|
||||
corrupted BEFORE the invert. → rotation-only assignment first, `SetFromAxis(W_Axis, center)`
|
||||
LAST. [T1 both, verified live: blip at exactly |delta|·ppm px after the fix]
|
||||
- **Tell:** a transform chain whose output is self-inconsistent — check whether the matrix maps
|
||||
its own reference point where it must (here: the viewer's position → the scope origin; it
|
||||
mapped to (−54, −599)). One logged matrix dump falsifies the whole chain in one frame.
|
||||
- **Rule:** for ANY engine-helper FUN_ in a reconstruction, read its BODY once (a 30-line
|
||||
decompile) before assigning it an engine method — a wrong-but-plausible identity survives
|
||||
every compile and every "it runs" test.
|
||||
|
||||
---
|
||||
|
||||
## Diagnostic recipe (the standard loop)
|
||||
|
||||
Reference in New Issue
Block a user