Gitea #46: ammo bay fire now DETONATES and KILLS (+ #47 fire icon) -- three stacked kill-switches removed, the fuse decoded, and a vptr-alias corruption caught by regression
Field report (night 3): "two ammo bay fires and no death" (Cyd + RajelAran; one
purged, one left burning). Root cause = THREE independent kill-switches stacked
on the same path, all in ammobin.cpp:
1. `GameClock::Now() { return 0; }` -- `cookOffTime < Now()` was `0 < 0`, so
an ARMED bay fire never detonated.
2. `InjectHeat(void*) {}` -- the detonation body was a no-op.
3. The bin's Damage record was never stamped -- 0 damage of type 0 (which the
mech TakeDamage handler drops) even if 1+2 had fired.
THE FUSE (raw disasm, scratchpad/disammo.py): the old "RandomDelay" was a Ghidra
carve artifact -- FUN_004dcd94 is __ftol and the export DROPPED the caller's x87
expression. The real bytes @004bd450: fld 10.0 / fmul [ticksPerSecond] /
fadd 0.5 / __ftol -- a FIXED 10.0-SECOND fuse in clock ticks. New gotcha #19
(reconstruction-gotchas.md) documents the __ftol export blind spot.
THE DAMAGE RECORD: bin+0x1F0..0x21C is a real engine `Damage` (FUN_0041db7c IS
Damage::Damage(), byte-matched to T0 DAMAGE.cpp). The linked ProjectileWeapon's
ctor @004bc3fc stamps it from weapon->damageData @0x3A8 via
owner->roster[0x128][res+0x1C0] -- projweap.cpp's old comment called this "the
bin's HUD display block ... wired in the AmmoBin family"; both halves were wrong
and it was wired nowhere. Now stamped (before MissileLauncher's ctor divides by
missileCount -- a missile bin authentically holds the per-SALVO amount).
THE DETONATION (@004ac274 = MechSubsystem::DistributeCriticalHit -- the old
"HeatableSubsystem::InjectHeat" label was wrong, and the old reconstruction
iterated a stand-in CriticalChain whose First()/Next() returned 0):
statusAlarm pulse Exploding(2)->Destroyed(1) (slot 13 = the printSimulationState
state PRINT @004ac8c0, not an "explosion notify"), own private zone pinned
destroyed, then collect the mech DamageZones whose crit entries plug the bin
(the binary filters plug classID 0x4E = DamageZoneClassID -- VDATA.h idx 78,
cross-checked via idx 28 = AudioStateTrigger), split the amount evenly, and send
the OWNER one full Entity::TakeDamageMessage per zone: inflictingEntity = SELF,
damageZone = the zone index, inflictingSubsystemID = the bin (the message-
manager explosion-bundling key, ENTITY3.h's own NOTE), printing the binary's
exact "ammo explosion damaging <zoneName>" @0050df61.
Port shape: Mech::AmmoExplosionFanOut (mechdmg.cpp) behind a databinding bridge;
guarded deviation: zoneCount==0 warns instead of the binary's unguarded divide.
CriticalChain/CriticalEntry stand-ins DELETED from mechrecon.hpp.
VERIFIED LIVE (BT_BAYTEST hook = message 1, the crit-induced arm channel):
scratchpad/baytest.py : arm -> 10s -> "20 rounds x 35 = 700 (type 2)" ->
"ammo explosion damaging dz_ltorso" -> zone cascade -> mech DESTROYED
(authentic death list).
scratchpad/baypurge.py: arm -> eject-hold purge -> "bay fire EXTINGUISHED
(bin empty)", no detonation.
scratchpad/sim3.py : the HEAT route arms organically in combat (overheated
AFC100), detonates "11 x 25 = 275 (type 1)" split across dz_larm + dz_lgun.
BAYBOOM matchlog record added for MP field forensics.
FIX-OF-THE-FIX (caught by the sim3 regression, would have shipped a crash):
MechSubsystem's ReconDamageZone proxy puts structureLevel at OFFSET 0 -- which
ALIASES THE REAL DamageZone's VTABLE POINTER (the private zone is `new
DamageZone`, mechsub.cpp:154; mechsub.hpp:260 documents the alias). My first
DistributeCriticalHit kept the old body's `damageZone->structureLevel = 1.0f`
and OVERWROTE THE ZONE'S VPTR with 0x3F800000; the respawn sweep's virtual
SetGraphicState (vtable+0xC) then called through it -> AV at 0x3f80000c in
RespawnRepair, one frame after a bay-fire death. ALL EIGHT proxy-view sites in
mechsub.cpp swept to the engine view (((DamageZone*)damageZone)->damageLevel
@0x158) -- including two silently-wrong LIVE readers: GetStatusFlags (vptr as
float -> always "intact") and ApplyDamageAndMeasure (the crit cascade's
measure). Ruled out first by evidence: the weapon->bin stamps were all clean
(six stamps, all classID 0xbcb, logged).
#47 (half 1 -- the FIRE ICON): BallisticWeaponCluster::Execute @004c9a38 reads
bin+0x18C = cookOffArmed into the btefire.pcc TwoState, and while armed computes
(Now - cookOffTime)/ticksPerSecond -- the COOK-OFF COUNTDOWN -- into the numeric
beside it. The old reconstruction misread 0x18C as "the reload state" and
bridged the icon to BTAmmoBinFeeding, so it blinked on every feed and never lit
on a bay fire (RajelAran: "it doesn't"). Now driven by the
BTAmmoBinCookOffArmed/CookOffTime complete-type bridges. [T2 -- the data path
is rig-verified; the pixels await the next live session.]
#47 (half 2 -- the ENG-BUTTON FLASH): fully mapped, deliberately NOT built this
session. The authored data SHIPS (BTL4.RES carries exactly one type-31
GaugeAlarmStream); the chain is alarm SetLevel -> gauge-watcher socket ->
Renderer msg 7 -> GaugeAlarmManager::Activate @00448d00 (T0) -> the BTL4
override @004cc148..@004cc2fc (btl4galm.cpp's provenance note claiming "no
override body exists" is WRONG -- corrected in-file) -> LampManager::FindLamp
@00444c80 -> Lamp::SetAlertState @00444e64 (flash counter) -> the L4 flush
@00474e94 emitting flashFast states 0x37/0x13 (== T0 L4LAMP.cpp:234-239).
Missing: the override bodies, the gauge-watcher sender, the aux-button lamps.
3-piece plan in context/open-questions.md.
Also logged: HandleMessage is vtable slot 8/9 in the binary but NON-virtual
across 10 reconstruction classes (bit the BT_BAYTEST hook; typed call used, gap
documented in open-questions).
KB: combat-damage.md (the full cook-off section), decomp-reference.md (the
cluster addresses + the GaugeAlarm/lamp map + BT_BAYTEST env), gauges-hud.md
(the fire-icon correction), reconstruction-gotchas.md #19 (__ftol),
open-questions.md (2 entries), btl4galm.cpp provenance correction.
checkctx CLEAN. 40 LNK2019 unchanged (the two pre-existing families).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
d39227ef39
commit
5ae4410914
@@ -271,6 +271,53 @@ autocannon/Gauss scaled against armor via `damageScale[Explosive]` not `damageSc
|
||||
(missiles still resolve Explosive from their own type; autocannon/Gauss now Ballistic). The
|
||||
missile-SPLASH template stays Explosive. [T2 verify: an autocannon mech produces type=1 DMG.]
|
||||
|
||||
## AMMO BAY FIRE / COOK-OFF — FIXED (2026-07-25, issue #46) [T1 mechanism, T2 live]
|
||||
Field report (night 3): "two ammo bay fires and no death" (Cyd + RajelAran, one purged one
|
||||
left burning). Root cause = **three stacked kill-switches** on the same path in `ammobin.cpp`:
|
||||
`GameClock::Now(){return 0}` (→ `cookOffTime < 0<0` — an armed fire NEVER detonated),
|
||||
`InjectHeat(void*){}` (detonation applied nothing), and a never-stamped damage record (0 dmg of
|
||||
type 0 even if it had). The authentic mechanism, raw-disasm verified (`scratchpad/disammo.py`):
|
||||
- **Arming**: bin `heatAlarm` FAILURE(2) with rounds left, or message 1 (crit-induced,
|
||||
`HandleMessage @004bdb94`). `cookOffTime = Now().ticks + round(10.0 × ticksPerSecond)` — a
|
||||
**FIXED 10-second fuse**. (The old "RandomDelay" was a Ghidra artifact: `FUN_004dcd94` is
|
||||
`__ftol`; the caller's x87 expression `10.0 × DAT_0052140c + 0.5` was dropped from the export.
|
||||
`DAT_0052140c` = SystemClock ticksPerSecond. See [[reconstruction-gotchas]] §__ftol.)
|
||||
- **Purge cancels**: bin Empty (eject-hold bay dump) → `cookOffArmed=0`, no detonation.
|
||||
- **The bin's damage record** (`Damage cookOffDamage @0x1F0..0x21C`, was mislabeled "heat
|
||||
descriptor"/"heatPerRound"): stamped at construction by the linked **ProjectileWeapon ctor
|
||||
@004bc3fc** — `bin+0x1F0.. = weapon->damageData @0x3A8` (type/per-round amount/force/normal/
|
||||
impact/burst), via `owner->roster[0x128][resource+0x1C0]`, UNGUARDED. Sequencing is
|
||||
load-bearing: the stamp precedes MissileLauncher's chained ctor dividing its own damageData by
|
||||
missileCount — a missile bin holds the per-SALVO amount. (projweap.cpp's old comment called
|
||||
this "the bin's HUD display block", and it was never wired.)
|
||||
- **Detonation** (`CookOff @004bd300` → `@004ac274` = `MechSubsystem::DistributeCriticalHit`):
|
||||
local Damage = record, amount = ammoCount × per-round; statusAlarm pulse Exploding(2)→
|
||||
Destroyed(1) (slot-13 = the `printSimulationState` state PRINT @004ac8c0, NOT an "explosion
|
||||
notify"); own private zone `structureLevel = 1.0`; then **collect the mech DamageZones whose
|
||||
crit entries plug this subsystem** (the binary iterates the bin's Node links filtering
|
||||
classID 0x4E = `DamageZoneClassID`, VDATA.h idx 78), split the amount evenly, and send the
|
||||
OWNER one full `TakeDamageMessage` per zone — `inflictingEntity`=SELF, `damageZone`=index,
|
||||
`inflictingSubsystemID`=the bin (the message-manager's explosion-resource bundling key,
|
||||
ENTITY3.h's own NOTE) — printing `"ammo explosion damaging <zoneName>"` (@0050df61). Port:
|
||||
`Mech::AmmoExplosionFanOut` (mechdmg.cpp) behind `BTAmmoExplosionFanOut`; guarded deviation:
|
||||
zoneCount==0 warns instead of the binary's unguarded divide.
|
||||
- **Verified live** (`scratchpad/baytest.py` / `baypurge.py`, hook `BT_BAYTEST=<frame>`):
|
||||
arm → 10s → `20 rounds × 35 = 700 (type 2 Explosive)` → `ammo explosion damaging dz_ltorso`
|
||||
→ zone cascade → **mech DESTROYED** (authentic death list); purge arm → `EXTINGUISHED`, no
|
||||
detonation. `BAYBOOM` matchlog record added for MP forensics.
|
||||
⚠ Family gap logged in [[open-questions]]: `HandleMessage` is vtable slot 8/9 in the binary but
|
||||
the reconstruction declares it NON-virtual across 10 classes (no live base-typed callers yet).
|
||||
⚠ **Fix-of-the-fix (caught by the sim3 regression, same day):** `MechSubsystem`'s
|
||||
`ReconDamageZone` proxy puts `structureLevel` at **offset 0 — aliasing the real engine
|
||||
`DamageZone`'s VTABLE pointer** (the private zone IS `new DamageZone`, mechsub.cpp:154).
|
||||
Writing `damageZone->structureLevel = 1.0f` through the proxy overwrote the zone's vptr with
|
||||
`0x3F800000`; the respawn sweep's virtual `SetGraphicState` (slot 3 = vtable+0xC) then called
|
||||
through it → AV at `0x3f80000c` in `RespawnRepair` (`sim3` pod3, one frame after a bay-fire
|
||||
death). ALL EIGHT proxy-view sites in mechsub.cpp swept to the engine view
|
||||
(`((DamageZone*)damageZone)->damageLevel` @0x158) — including two silently-wrong LIVE readers:
|
||||
`GetStatusFlags` (read the vptr as a float → always "intact") and `ApplyDamageAndMeasure` (the
|
||||
crit cascade's damage-measure read garbage). Gotcha §5 (alias fields), new archetype.
|
||||
|
||||
## Damage delivery + the real damage model
|
||||
`Entity::TakeDamageMessage(id, size, inflictingEntityID, zone, Damage&)` → `target->Dispatch`.
|
||||
**Base handler IGNORES zone==−1** (`Entity::TakeDamageMessageHandler`, ENTITY.cpp:878 — returns on
|
||||
|
||||
@@ -319,6 +319,43 @@ From the weapon `.SUB` records + the charge-curve `.data` constants (PE-parsed a
|
||||
Loaded ("full == the gear's seek voltage" is the arcade's own discharge algebra). Repro +
|
||||
verify: BT_SEEKTEST seek-abuse -- pre-fix deadlocks at pct=0/alarm=3; post-fix 38 rescues,
|
||||
ends Loaded/pct=1. `[seek]` log prints the per-gear table + rescue events.
|
||||
- **AMMO COOK-OFF cluster DECODED 2026-07-25 (issue #46, raw disasm `scratchpad/disammo.py`) [T1]:**
|
||||
`@004bd394` AmmoBinSimulation (arm on heatAlarm FAILURE / detonate / cancel-on-Empty);
|
||||
`@004bdb94` HandleMessage(1) = crit-induced arm; the FUSE = `Now().ticks +
|
||||
round(10.0 × DAT_0052140c)` — **fixed 10 s** (`FUN_004dcd94` is `__ftol`, the caller's x87
|
||||
expression was dropped by the export — see [[reconstruction-gotchas]] §19; `DAT_0052140c` =
|
||||
SystemClock ticksPerSecond, set 28.0/18.206 at part_000.c:921/926; `FUN_00414b60` = `Now()`
|
||||
returning `&Time`, pause-aware); `@004bd300` CookOff = local Damage copy, amount = ammoCount ×
|
||||
`bin+0x1F4` (**float** — `fmul`, not fild), → `@004ac274`; `@0041db7c` = **`Damage::Damage()`**
|
||||
(== T0 DAMAGE.cpp); `@004ac274` = **`MechSubsystem::DistributeCriticalHit`** — statusAlarm
|
||||
pulse 2→1 (slot 13 `+0x34` = @004ac8c0 the `printSimulationState`@+0x104-gated state PRINT,
|
||||
`__DefaultState/__Destroyed/__Exploding` — NOT an explosion notify), own zone
|
||||
`+0xE0→+0x158 = 1.0`, then per-DamageZone `TakeDamageMessage` (id 0x12, 100 B) to the owner —
|
||||
the collected plugs filter `classID@+4 == 0x4E` = **`DamageZoneClassID`** (VDATA.h idx 78;
|
||||
idx 28 = AudioStateTrigger cross-checks the enum count), `damageZone` = zone`+0x13C`
|
||||
(damageZoneIndex), `inflictingEntity` = mech`+0x184` (SELF), `inflictingSubsystemID` =
|
||||
bin`+0xD8` (the explosion-bundling key); `@004bc3fc` ProjectileWeapon ctor **stamps
|
||||
`bin+0x1F0..0x21C ← weapon->damageData@0x3A8`** via `owner->roster[0x128][res+0x1C0]`,
|
||||
unguarded, BEFORE MissileLauncher's ctor divides by missileCount. Env: `BT_BAYTEST=<frame>`
|
||||
(arm via message 1), `[ammo] BAY FIRE / DETONATION / EXTINGUISHED` always-on forensics,
|
||||
`BAYBOOM` matchlog record.
|
||||
- **GaugeAlarm / lamp-flash machinery MAPPED 2026-07-25 (issue #47, the eng-button flash) [T1]:**
|
||||
the shipped `BTL4.RES` carries **one `GaugeAlarmStream` resource (type 31)** — the authored
|
||||
{condition, level, lampCode} annunciator data survives, baked. Chain: subsystem-alarm
|
||||
`SetLevel` → StateIndicator gauge-watcher socket → `Renderer::StartEntityAlarmMessage` (id 7;
|
||||
Stop = 8) → `GaugeAlarmManager::Activate @00448d00` / `Deactivate @00448e00` (T0
|
||||
GAUGALRM.cpp, manager at L4GaugeRenderer`+0x1c0cc`) → the **BTL4 override bodies @004cc148..
|
||||
@004cc2fc** (`@004cc2fc` ReadGaugeAlarmStreamItem: stream-read {level, lampCode}; lampCode
|
||||
`<0x80` fixed via @004cc148, `≥0x80` SUBSYSTEM-RELATIVE eng-button via @004cc1a0; Condenser
|
||||
(0x50e4fc) / power-family (0x50fb60) specials @004cc264/@004cc27c) → `LampManager::FindLamp
|
||||
@00444c80` (lamp map at renderer`+0x1c0c8`) → **`Lamp::SetAlertState @00444e64`** (the flash
|
||||
COUNTER at lamp+0x1C) → the L4 lamp flush `@00474e94` emits `0x37`/`0x13` = `flashFast` RIO
|
||||
states (== T0 L4LAMP.cpp:234-239) → `RIO::SetLamp` (binary `FUN_00476568`; cached wrapper
|
||||
`@00474d54`). T0-compiled already: LAMP.cpp / L4LAMP.cpp / GAUGALRM.cpp / RENDERER messages.
|
||||
**Missing in the port (3 pieces)**: the BTL4 override bodies (btl4galm.cpp is best-effort and
|
||||
its provenance note claiming "no override exists in the binary" is WRONG — @004cc148-2fc IS
|
||||
it), the gauge-watcher sender registration, and the lamp objects for the aux buttons. Plan in
|
||||
[[open-questions]].
|
||||
- **MechRIOMapper** @0x51DD30: RE-REGISTERS the base ids 3..0x13 (Aux1Quad..ZoomOut) with the
|
||||
RIO override handlers @004d22fc..@004d24f8, + {0x19, Keypress→@004d2514} (RIO's OWN keypress,
|
||||
unreconstructed — we use the shared L4 0x17 @004d1bf0) + {0x1a, Hotbox→@004d2574} @0x51DE98.
|
||||
@@ -368,6 +405,7 @@ default-ON (`'0'` disables).
|
||||
| `BT_GAUGE_ATTR_LOG` | `[attr] <binding> OK/NULL` per config binding |
|
||||
| `BT_GAUGE_SKIP_LOG` | `[gskip] <primitive>` per unregistered gauge widget the parse skips |
|
||||
| `BT_VALVE_LOG` | condenser valve flow distribution |
|
||||
| `BT_BAYTEST=<frame>` | #46 rig: send message 1 (crit-induced cook-off arm) to the first AmmoBin at the given sim frame — the 10 s fuse then runs live (`scratchpad/baytest.py` / `baypurge.py`) |
|
||||
| `BT_LOOP_AUDIT` | `[loop-audit]` per-sound `sample-flag × source-render-type -> AL_LOOPING` (Gitea #51; rig `scratchpad/loopaudit.py`) |
|
||||
| `BT_LOOP_LEGACY=1` | restore the OLD always-loop rule (`AL_LOOPING = sample != ForceStatic`) for a field A/B — see the loop-flag note in [[wintesla-port]] |
|
||||
| `BT_AUDIO_DUMP` | once-a-second `[playing]` dump of every playing AL source + gain/pitch/**loop** — catches a stuck looping source red-handed |
|
||||
|
||||
@@ -351,6 +351,18 @@ authentically ANIMATES 0→1 through each reload. (The census's four other writ
|
||||
@004b9c9c is the MechWeapon/projectile base body.) The launcher panel's other live indicators:
|
||||
AMMO DIGITS (AmmoBin::ammoCount via the complete-type bridges), jam/fire lamps, eject wipe.
|
||||
|
||||
## The ballistic panel's FIRE icon = the BAY-FIRE annunciator — FIXED (2026-07-25, #47) [T1 decomp / T2 live]
|
||||
`BallisticWeaponCluster::Execute @004c9a38` (raw decomp): `this[0x3b] = *(bin+0x18C)` —
|
||||
**`cookOffArmed`**, the field the `btefire.pcc` TwoState watches; while armed, `this[0x3e] =
|
||||
(Now().ticks − bin->cookOffTime@0x190) / ticksPerSecond` — the **cook-off countdown seconds**
|
||||
(negative → 0 at detonation) shown by the `NumericDisplayScalarTwoState` beside it. The old
|
||||
reconstruction misread `bin+0x18C` as "the reload state" and bridged the icon to
|
||||
`BTAmmoBinFeeding` — so it blinked with every feed cycle and never lit on a bay fire
|
||||
(RajelAran: "it doesn't"). Now driven by `BTAmmoBinCookOffArmed/CookOffTime` complete-type
|
||||
bridges (ammobin.cpp). The member names `firing/reloading/reloadSeconds` in btl4gau2 are
|
||||
historic; semantically bayFireArmed / latch / secondsToDetonation. The ENG-BUTTON FLASH half
|
||||
of #47 (the GaugeAlarm/lamp machinery) is mapped-but-unbuilt — see [[open-questions]].
|
||||
|
||||
## ConfigMapGauge (the weapon panel's trigger-config joystick) — LIVE via LinkToEntity (2026-07-21)
|
||||
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
|
||||
|
||||
@@ -200,6 +200,27 @@ register. ⚠ The audit also flags the damage-economy item as SELF-CONTRADICTOR
|
||||
not-novice experience flag via the BTPlayerExperienceSimLive bridge ([[experience-levels]]).
|
||||
See [[rendering]] fog section. (The pre-#61 "verified inert live: BT_FOG_LOG zero `SetFogStyle(2/3)`"
|
||||
observation still holds — reason (1), the un-constructed renderable, remains.)
|
||||
- **`HandleMessage` is vtable slot 8/9 in the binary but NON-virtual across the reconstruction
|
||||
(2026-07-25, found via #46).** Ten classes declare it (ammobin/heat×2/hud/mechsub/myomers/
|
||||
powersub×3/projweap) as reconstructions of vtable-slot overrides, none `virtual` — a
|
||||
base-typed `sub->HandleMessage(n)` statically binds to the base and SKIPS the override
|
||||
(bit the `BT_BAYTEST` hook; fixed there with a typed AmmoBin call). No live base-typed
|
||||
caller exists in the port today, so nothing is currently broken — but any future
|
||||
reconstruction that dispatches subsystem messages through a base pointer (the binary's crit
|
||||
path may) must first make the family virtual in one sweep (signatures must agree; some
|
||||
declare `Logical`, some `void`+int variants — audit before flipping).
|
||||
- **#47 remainder: the ENG-button attention FLASH (jam / bay fire) — machinery fully mapped,
|
||||
3 pieces unbuilt (2026-07-25).** The authored data SHIPS: `BTL4.RES` carries one
|
||||
`GaugeAlarmStream` (type 31). The full chain + addresses: [[decomp-reference]] §GaugeAlarm.
|
||||
To make it live: (1) reconstruct the REAL `BTL4GaugeAlarmManager` override bodies from
|
||||
@004cc148..@004cc2fc (btl4galm.cpp is best-effort scaffolding and its provenance note —
|
||||
"no override body exists in the image" — is WRONG; that code region IS the override,
|
||||
including the ≥0x80 subsystem-relative eng-button lamp mapping); (2) register the
|
||||
gauge-side StateIndicator watcher that sends `StartEntityAlarm` (id 7) on subsystem-alarm
|
||||
transitions (the AUDWTHR parallel on the gaugeWatcherSocket); (3) build the LampManager
|
||||
lamps for the aux/eng bezel buttons (LAMP.cpp/L4LAMP.cpp are T0-compiled; the glass panels
|
||||
+ pod RIO both consume `RIO::SetLamp`, so the flash lands on-screen AND on the pod).
|
||||
Decode the type-31 stream format first — it names which conditions flash which lamps.
|
||||
- **Factory capability-roster loops 2-4 are STILL DEAD (task #57 discovery).** mech.cpp's
|
||||
post-roster loops add to `heatableSubsystems`(0x51155c)/`weaponRoster`(0x511830)/
|
||||
`damageableSubsystems`(0x50e4fc) through the local `SubProxy` stub whose `IsDerivedFrom`
|
||||
|
||||
@@ -581,3 +581,22 @@ global sequence is untouched).
|
||||
**Rule:** when reconstructing, ask of every load-time-scoped hook — *what happens if this
|
||||
resource loads twice?* If the answer differs from the first load, either re-install the scope
|
||||
at every load site or cache the loaded object. Both are legitimate; silently re-parsing is not.
|
||||
|
||||
## 19. Ghidra drops the x87 expression around `__ftol` (the "RandomDelay" that was a 10-second constant)
|
||||
|
||||
`FUN_004dcd94` decompiles as a bare `return (int)ROUND(in_ST0);` — it is **`__ftol`**, the
|
||||
compiler's float→int helper, called with the value already in ST0. When Ghidra carves it as a
|
||||
normal function, the CALLER's decompile can silently LOSE the whole floating-point expression
|
||||
and show a bald `iVar = FUN_004dcd94();` — the computation vanishes from the export.
|
||||
|
||||
Archetype (issue #46): the AmmoBin cook-off delay read as `Now() + FUN_004dcd94()` and was
|
||||
reconstructed as "a randomised delay" stubbed to 0. The raw bytes @004bd450 are
|
||||
`fld 10.0 / fmul [ticksPerSecond] / fadd 0.5 / call __ftol` — a **fixed 10.0-second fuse**.
|
||||
The stub meant an armed bay fire never detonated.
|
||||
|
||||
**Rule:** any decomp line of the shape `iVar = FUN_004dcd94()` (or another arg-less FUN whose
|
||||
body reads `in_ST0`) is a **carve artifact** — raw-disassemble the call site
|
||||
(`scratchpad/disammo.py` pattern) and recover the x87 sequence before reconstructing. Related
|
||||
export blind spots: mid-function iterator carves at odd addresses (FUN_004acfa9-style thunks),
|
||||
truncated vtables.tsv rows (dump exe bytes at vtable+slot*4), and whole-function gaps (#60 —
|
||||
raw-disasm recovered @004b838c and @004bb9b8).
|
||||
|
||||
Reference in New Issue
Block a user