Impact-FX FORENSICS wave: the i860 specialfx engine, per-round detonations, the ram economy closed

The "we're off the rails" reinvestigation -- a 5-thread evidence workflow
(logs / PFX data / RES model lists / decomp / port audit) + the i860 firmware
decode, then surgical fixes.  Every claim tiered; the wrong turns are on the
record in the KB alongside the corrections.

THE OLD-STYLE SPECIALFX ENGINE [T1, firmware-decoded from VREND.MNG]:
rebuilt scratchpad/i860dis.py (binutils opcode table); mapped the dispatch
(data+0xdd0c; sfx trigger 0xf040cda0, install 0xf040cdc0, step ~0xf0413698,
instance init 0xf04128d8); decoded the heat model EXACTLY: per 30Hz board
frame h *= cool_a; RGB_ch = h_old*(h_new*cook_ch - 0.25) + 0.25 (K=0.25 =
the ember floor @VA 0xF080; kill at h <= 1e-4 @0xF0A0); alpha fades cool_b
x RAW dt (PER-SECOND -- the x30 scaling made laser hits invisible); 7s cap;
y_off = the kill plane; "variance" is DEAD DATA (binary reads "variance",
INI authors "varience").  The 13 descriptors (PPCHit/LaserHit/MissileHit/
Chunks/Sparks/Fireball...) parse from BTDPL.INI and render via the BTPfx
layer -- heat bursts draw the FIERY sheet (brightness over fire), .PFX keeps
GRAYSCALE (authored colours: DNBOOM orange, DDAM gray).

HIT-PACKAGE CENSUS [T1, RES byte-verified]: ppchit=[8] lzrhit=[9] mghit=[7,11]
canhit=[11] mslhit=[10,12,1023] explode=[6]; all 8 mech death lists identical.
Effect routing corrected in BOTH consumers: <100 = specialfx, >=1000 = psfx.

"SILVER MIST x5" ROOT CAUSE: SHKWAVE.PFX (mslhit's 1023) authors maxIssue=5
relPeriod=0.2 rate=1 -- the ONE file where rate contradicts the window; the
emitter trusted rate -> 5 shells at exactly 1Hz.  Emission rate now always
maxIssue/releasePeriod.

PER-ROUND DETONATIONS [T1 @004bef78]: every missile round spawns its own
ExplosionModelFile at ITS impact (hull + terrain) -- a volley ripples 12
fireballs like the demos; rack-tube launch spread [T3] (GUIDED rounds only --
the slot-0 deflection sent every AFC100 shell 3.4deg left/2.5deg down: the
phantom "4th gold beam"); replicant salvos detonate too (launcher index rides
the visual push).  Missile damage bundles through the shooter's messmgr with
the launcher's subsystemID (mslhit fires at the consolidated point; the
binary's dedup CONFIRMED [T1 @0049b784] -- a workflow agent's per-record
claim REFUTED by direct decomp read).

DAMAGE-BAND SEMANTICS [T1]: MechDeathHandler fires the CURRENT band effect on
any damage rise (the binary's changed-flag coalescing) -- a mauled mech under
fire smokes/burns per hit; bands 3/4 are authored fire plumes.

THE RAM ECONOMY -- CLOSED (3 layers, measured live):
 1. armor = POINTS (every zone: damageScale = 1/armorPoints, armor 50-140;
    the [zone-armor] BT_DMG_LOG dump);
 2. StaticBounce prices rams with authored moverMass ~1.3e6 -> ~59,000 pts
    @10m/s; the binary dispatches it RAW but pod MP dropped it on the local
    replicant (MECH.CPP:986 warns) -- ram damage was NETWORK-INERT; our
    task-#47 replicant forwarding surfaced it as a one-shot.  Port
    normalizes x1e-3 to the point economy [T3];
 3. contact EDGE (ramLastVictim/ramContactLinger): the binary's bounce made
    separation implicit; our gait-derived velocity re-priced full rams at
    60Hz (the respawn explode-loop).  One bump = one hit; pressed = BLOCK.

PLUS: sfx size x0.5 + 1.25m visibility floor + hot-phase occlusion [T3];
particle pool 2048->8192 (missile traffic starved laser bursts -- the
"intermittent effects"); replicant beam aging (a lost beam-END record pinned
a stale beam on forever); UV-variant noise stamps (mask-safe mirror/swap);
no scroll on particle stamps (material-path only); BT_FX_TEST/fxshot.py
self-verification harness ([beam-draw]/[zone-armor]/[collide-tx] telemetry).

KB: rendering.md (specialfx engine + census + closeout), combat-damage.md
(ram economy + band semantics).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-13 08:14:12 -05:00
co-authored by Claude Fable 5
parent 48c9c8444f
commit 065c114590
14 changed files with 4404 additions and 82 deletions
+21
View File
@@ -104,6 +104,27 @@ per ER-M laser; [[decomp-reference]] §5). Flying projectiles (LRM/autocannon) a
reconstruction (`BTPushProjectile` — the 2007 Entity is too small for the binary's raw integrator reconstruction (`BTPushProjectile` — the 2007 Entity is too small for the binary's raw integrator
offsets). [T2] offsets). [T2]
## The RAM economy — CLOSED (2026-07-12) [T1 evidence, T3 normalization]
Three-layer story, measured live + decomp-verified:
1. **The armor economy is POINTS**: every zone streams `damageScale[5]` EVEN = `1/armorPoints`
(armor 50140; `damageLevel += amount/armor`, 1.0 = destroyed — the `[zone-armor]` dump,
BT_DMG_LOG, mechdmg.cpp). Weapons author point-scale amounts (laser 11.77 ≈ 10 torso hits). ✓
2. **StaticBounce prices rams with the AUTHORED moverMass ≈ 1.3e6 units** → ~600×v² ≈ 59,000
points for a 10 m/s ram (measured: 59221@9.94, 398@0.81; `[collide-tx]` now logs mass/e).
The binary DISPATCHES it raw (@part_012:15324-15358) — but in pod MP it landed on the local
REPLICANT of the victim (the master never heard it; MECH.CPP:986 warns and proceeds) —
**ram damage was network-inert in the pod**. Our task-#47 replicant forwarding (required
for MP weapon damage) surfaced it as a one-shot. Port: `BTDispatchCollisionDamage`
normalizes ×1e-3 to the armor-point economy (bump = points, charge = tens) [T3].
3. **The contact EDGE**: the binary's bounce reversed the mover's velocity so the frame after
a bump read as separating; our gait re-derives velocity per frame (still closing while
pressed / respawn-overlapped) → sustained contact re-priced full rams at 60Hz (the respawn
explode-loop). Port: `ramLastVictim`/`ramContactLinger` (mech.hpp) — one bump = one damage
event; pressed = BLOCK; re-arms 0.35s after separation; cleared on Reset.
Band effects: `MechDeathHandler::Tick` fires the CURRENT band descriptor on any damage rise
(the binary's changed-flag semantics, workflow-verified) — a damaged mech under fire smokes/
burns per hit; the earlier crossing-only gate was over-tight (the metronome was SHKWAVE).
## Damage delivery + the real damage model ## Damage delivery + the real damage model
`Entity::TakeDamageMessage(id, size, inflictingEntityID, zone, Damage&)``target->Dispatch`. `Entity::TakeDamageMessage(id, size, inflictingEntityID, zone, Damage&)``target->Dispatch`.
**Base handler IGNORES zone==1** (`Entity::TakeDamageMessageHandler`, ENTITY.cpp:878 — returns on **Base handler IGNORES zone==1** (`Entity::TakeDamageMessageHandler`, ENTITY.cpp:878 — returns on
+75
View File
@@ -75,6 +75,81 @@ the slice. Reading it as RGB overlaid 2-3 gray sheets = the "rainbow/graffiti" b
resolves the mech-skin `softer` ramp (defined in OTHER libs). Every mech renders authentic gray-metal resolves the mech-skin `softer` ramp (defined in OTHER libs). Every mech renders authentic gray-metal
paint now. See [[asset-formats]]. [T2] paint now. See [[asset-formats]]. [T2]
## The OLD-STYLE SPECIALFX layer (the weapon-impact action) — RECONSTRUCTED 2026-07-12 (firmware-decoded)
The per-weapon impact visuals are fully AUTHORED in `content/BTDPL.INI` `[effects_to_load]`:
`specialfx3..15` = Chunks1/Chunks2/Sparks/Explosion3/MachineGun/PPCHit/LaserHit/MissileHit/
CannonHit/GroundHit/MinorHit/MajordHit/Fireball — "old-style" (version-1) descriptor sections
(`texture/type/size/velocity/v_bias/y_off/cook(3f)/varience/gravity/cool(2f)/count/repeats`),
most on `btfx:firesmoke1_scr_tex` (MissileHit size 12 ×7; Fireball size 14, gravity 1.5 rises;
Chunks1/2 = ballistic debris vel 40-55 grav 34-35; Sparks NOTEX). [T1: the 1995 binary parses
them — part_008.c:1546-1590, field strings @004f25e4-004f2663 → installer FUN_00490210 packs a
0x44-byte board command via FUN_0048e2c0.] **Simulation semantics (type/cook/cool) live in the
i860 firmware** `content/VREND.MNG` (same decode workflow as the task-#55 'damageize' handler).
- **EFFECT-NUMBER ROUTING (corrects the old claim):** `effect_number < 100` = the dpl board
EFFECT TABLE = this specialfx list (1996 case-4 comments match: 3=Chunks, 12=GroundHit,
15=Fireball), **NOT** a psfx slot; `>= 1000` = psfx slot n1000 (idam/ddam PFX); 100-999 =
ExplosionScripts. The port's `<100 → psfx slot` mapping renders PPCHit(8) as ddam5 smoke and
Chunks(3)/Sparks(5) as ddamN smoke — the "hits are just a little smoke" report. The 2007
WinTesla loader SKIPS all version-1 descriptors (`if (version < 2) continue;` — it only reads
the 1998 INDIE format), which is why the layer was never seen. [T1]
- **Live hit census (mp logs):** weapon hits dispatch idam bands 1009-1013 (30×1009) + raw
8/9 (PPCHit/LaserHit, mis-rendered); **MissileHit(10)/CannonHit(11) never appeared** — the
projectile/salvo damage path likely doesn't thread the firing launcher's subsystemID into the
damage records, so messmgr never queues its explosion. [T2 census; T4 cause]
- **RECONSTRUCTION (2026-07-12, firmware-first):** the i860 sim decoded from VREND.MNG
(dispatch table at data+0xdd0c, VA base: code 0xf0400000, data 0x1000; handlers: sfx trigger
0xf040cda0→ca48, install 0xf040cdc0→cba0, step near 0xf0413698, spawn near 0xf0414afc —
string-anchored via "attempt to step a NULL sfx object"; disassembler REBUILT at
`scratchpad/i860dis.py` from binutils i860 opcodes, `scratchpad/i860_opcodes.h`).
**The heat model [T1]:** per 30Hz board frame `h *= cool_a`; `RGB_ch = h_old*(h_new*cook_ch
0.25) + 0.25` (K=0.25 @VA 0xF080 = the ember floor); kill at h ≤ 1e-4 (@0xF0A0); alpha =
cool_b per frame; `pos += vel`, gravity as accel. y_off = the particle KILL PLANE
(Chunks 1 = die at the ground) [T3 data-indicated]. Burst frame randomly spun (quadratic
small-angle rot, θ from the host-fed random table cmd 0x21). Implemented in L4VIDEO.cpp:
`BTInstallSfx`/`BTStartSfx` + sfxMode particles in the BTPfx layer; parser beside the psfx
page walk (`[sfx] page 'effects_to_load': 13 old-style specialfx installed`). Effect-number
routing fixed in BOTH consumers (ExplosionClassID + DPLIndependantEffect): <100 → specialfx,
≥1000 → psfx slot n1000. **Missile/cannon hits now bundle** [T2]: BTProjectile carries
shooter + weaponSubsys; contact routes through the SHOOTER's SubsystemMessageManager
(AddDamageMessage w/ subsystemID → mslhit/acanhit explosion at the impact), mirroring the
laser path's SendDamageMessage. Cone shape (v_bias as up-bias) + per-type draw weights are
[T3]; the full type-0/1/2 draw-geometry decode in the firmware draw fn (0xf0416160) remains
open.
## Weapon-hit forensics closeout (2026-07-12, workflow-verified) [T1]
- **The HIT PACKAGES (BTL4.RES model lists, byte-verified):** ppchit=[8], lzrhit=[9],
mghit=[7,11], canhit=[11] (no "acanhit"), mslhit=**[10, 12, 1023]** (MissileHit + GroundHit +
psfx23 SHKWAVE), gndhit=[12], minhit=[1002], majhit=[1003], explode=[6]; dam1-4hit=[1002-1005],
desthit/stephit=[1008]; cockpit "i" variants (lzrhiti etc.) = same sfx + idam 1009-1013; ALL
eight mech death lists = ["104","3","4","5","1007","15","1001"].
- **"Silver mist exactly 5×, perfectly timed" ROOT CAUSE:** SHKWAVE.PFX authors maxIssue=5,
releasePeriod=0.2, **rate=1** — the ONE shipped .PFX where rate ≠ maxIssue/releasePeriod; the
emitter trusted 'rate' → 5 huge white shells (exp 350/s) at exactly 1Hz. **Emission-window
rule hardened:** effective rate = maxIssue/releasePeriod always (BTDrawPfx emitter).
- **SpecialFX palette [T1 firmware]:** instance init @0xf04128d8: inst[0x19c]=cool_a (per
board-frame heat decay), inst[0x1a4]=cool_b (× raw dt in the step = PER-SECOND fade),
inst[0x188]=7.0s cap, heat=1.0. The heat channels are BRIGHTNESS over an inherently
FIRE-coloured texture (firesmoke through its material ramp): sfx batch draws the FIERY-ramped
sheet, psfx keeps GRAYSCALE (authored colours decide — DNBOOM orange, DDAM gray). Drawing sfx
grayscale = silver mist; scaling fade ×30 = 0.1s pops ("nothing happens on laser hits").
- **Damage-band .PFX shapes are authored GENTLE (puffy clouds):** bands 1-4 of ndam/idam/ddam
rise at 1-4 u/s with buoyant accel; ONLY band 5 + the BOOM/SHKWAVE files are explosive-omni.
idam = scaled-down WORLD venting smoke (half radius/counts of ddam), not cockpit-only.
N-set = night = same physics as D-set with fewer particles. NSRM/DSRM trails = 0.1s overbright
ORANGE directional puffs (never silver).
- **messmgr explosion DEDUP CONFIRMED [T1, decomp-read @0049b784]:** `Find` (weaponExplosions
vtable slot 0xC) gates `AddValue` (slot 8) — ONE explosion per unique weapon resource per
frame, exactly as reconstructed. (A workflow agent claimed "one per record, staggered
0.1s" — REFUTED; the stagger applies to the deduped queue.) Missile multiplicity comes from
the ORDNANCE side instead: Missile::MoveAndCollide @004bef78 detonates per round — ported as
BTSpawnRoundDetonation (mech4) + rack-tube launch spread [T3]. Alpha-strike drama = distinct
per-weapon-TYPE packages (lzrhit+ppchit) + the victim's band fire, not explosion count.
- Remaining [T3]: sfx cone shape (v_bias as up-bias), size ×0.5 + 1.25m textured floor +
hot-phase occlusion 0.45..0.9, burst scatter ×0.2; trail
density is frame-rate-dependent (2/frame @60fps = 2× pod density); .PFX 'repeats' unconsumed
(all shipped files author 1); replicant visual salvo chases the stale fire-time aim point
(no re-lead without a target handle).
## Weapon beams (the unported dpl_* layer) ## Weapon beams (the unported dpl_* layer)
The `dpl_*` beam renderable was never ported → beams are drawn by `BTPushBeam`/`BTDrawBeams` The `dpl_*` beam renderable was never ported → beams are drawn by `BTPushBeam`/`BTDrawBeams`
(L4VIDEO). The real look is the `ermlaser.bgf` TUBE (native Z 0..2000, UVs tiled) with the scrolling (L4VIDEO). The real look is the `ermlaser.bgf` TUBE (native Z 0..2000, UVs tiled) with the scrolling
+449 -50
View File
@@ -677,6 +677,24 @@ struct BTPfxParticle
float rad, exp, dexp; float rad, exp, dexp;
float colorWarp, alphaWarp; // def-level warps, carried per particle float colorWarp, alphaWarp; // def-level warps, carried per particle
float sI[4], sO[4], eI[4], eO[4]; float sI[4], sO[4], eI[4], eO[4];
// OLD-STYLE SPECIALFX mode (the VREND.MNG heat model, firmware-decoded
// 2026-07-12): when sfxMode != 0 the colour comes from the board's heat
// equation instead of the .PFX colour quads:
// h *= coolMul per board frame (30 Hz);
// channel = h_old*(h_new*cook_ch - 0.25) + 0.25 (0.25 = the ember floor,
// firmware constant @VA 0xF080); kill at h <= 1e-4 (@0xF0A0), at
// fade <= 0 (fade -= coolSub per board frame), or below the kill plane
// (the descriptor's y_off -- Chunks die 1m under the burst: the floor).
int sfxMode;
float heat, coolMul, coolSub, fade;
float cookR, cookG, cookB;
float killY;
int sfxType; // descriptor 'type' (draw weight)
int uvVariant; // 0..7 mirror/swap of the noise UVs
// (de-correlates the stamps like the
// board's random burst spin; mirror/
// swap keeps UVs in 0..1 so the
// CLAMPed radial mask stays valid)
}; };
static std::vector<BTPfxEmitter> gBTPfxEmitters; static std::vector<BTPfxEmitter> gBTPfxEmitters;
static std::vector<BTPfxParticle> gBTPfxParticles; static std::vector<BTPfxParticle> gBTPfxParticles;
@@ -769,7 +787,7 @@ void BTStartPfxFrame(int effect_number, float x, float y, float z,
const BTPfxDef &d = gBTPfxDefs[effect_number]; const BTPfxDef &d = gBTPfxDefs[effect_number];
if (!d.valid) if (!d.valid)
return; return;
if (gBTPfxEmitters.size() > 64) // runaway guard if (gBTPfxEmitters.size() > 256) // runaway guard
return; return;
BTPfxEmitter e; BTPfxEmitter e;
e.def = &d; e.def = &d;
@@ -821,7 +839,8 @@ void BTPfxTrailPuff(int effect_number, float x, float y, float z,
static void BTPfxSpawn(const BTPfxEmitter &e) static void BTPfxSpawn(const BTPfxEmitter &e)
{ {
const BTPfxDef &d = *e.def; const BTPfxDef &d = *e.def;
if (gBTPfxParticles.size() > 2048) // global particle cap if (gBTPfxParticles.size() > 8192) // global particle cap (2048 starved
// laser bursts during missile volleys)
return; return;
BTPfxParticle p; BTPfxParticle p;
// Sample in the effect's LOCAL frame, then orient into the world through // Sample in the effect's LOCAL frame, then orient into the world through
@@ -859,9 +878,126 @@ static void BTPfxSpawn(const BTPfxEmitter &e)
p.eI[i] = d.eI[i] + d.eIv[i] * BTPfxRand01(); p.eI[i] = d.eI[i] + d.eIv[i] * BTPfxRand01();
p.eO[i] = d.eO[i] + d.eOv[i] * BTPfxRand01(); p.eO[i] = d.eO[i] + d.eOv[i] * BTPfxRand01();
} }
p.sfxMode = 0; // .PFX path: authored colour quads
p.uvVariant = (int)(BTPfxRand01() * 7.99f);
gBTPfxParticles.push_back(p); gBTPfxParticles.push_back(p);
} }
//===========================================================================//
// OLD-STYLE SPECIALFX (the "more fire and 'splosions" layer) -- the 1995
// per-weapon impact effects: BTDPL.INI [effects_to_load] "specialfxN=Name" +
// [Name] descriptor sections (Chunks1/Sparks/PPCHit/LaserHit/MissileHit/
// Fireball/...). These are the dpl EFFECT-TABLE entries that effect numbers
// < 100 index (the 1996 ExplosionScripts' own comments match: 3=Chunks,
// 12=GroundHit, 15=Fireball) -- NOT psfx slots.
//
// SEMANTICS [T1, decoded from the i860 firmware VREND.MNG 2026-07-12]:
// - host installer FUN_00490210 ships the descriptor VERBATIM (cmd 0x1C,
// 0x44 bytes; trigger = cmd 0x1B {id, dcs, 4x4 matrix});
// - per board frame (30 Hz): h *= cool_a; RGB_ch = h_old*(h_new*cook_ch
// - 0.25) + 0.25 (superbright start -> 0.25 ember floor; constants pool
// @VA 0xF070: K=0.25 @0xF080, kill floor 1e-4 @0xF0A0);
// - alpha fades linearly (the step: out[0x40] -= dt * rate);
// - pos += vel; the burst frame is randomly SPUN per trigger (quadratic
// small-angle rotation, theta from the host-fed random table);
// - "variance" is DEAD DATA in the shipping pod: the binary looks up
// "variance" (@004f2604) but every INI section spells "varience" -- the
// lookup missed and the field stayed 0. Honoured (not parsed).
// - y_off reads as the particle KILL PLANE relative to the burst origin
// (Chunks -1 = die at the ground; hits -3) [T3 -- strongly data-indicated].
//===========================================================================//
struct BTSfxDef
{
int valid;
int type; // 0 = chunks (solid debris), 1/2 = fiery ball
int noTex; // texture=NOTEX(T): untextured sparks
float size, velocity, vBias, yOff;
float cook[3]; // RGB heat gains
float gravity;
float coolMul, coolSub;
int count, repeats;
};
#define BT_SFX_SLOTS 32
static BTSfxDef gBTSfxDefs[BT_SFX_SLOTS];
int BTInstallSfx(int slot, int type, int no_tex, float size, float velocity,
float v_bias, float y_off, const float cook[3], float gravity,
float cool_mul, float cool_sub, int count, int repeats)
{
if (slot < 0 || slot >= BT_SFX_SLOTS)
return 0;
BTSfxDef &d = gBTSfxDefs[slot];
d.type = type; d.noTex = no_tex;
d.size = size; d.velocity = velocity; d.vBias = v_bias; d.yOff = y_off;
d.cook[0] = cook[0]; d.cook[1] = cook[1]; d.cook[2] = cook[2];
d.gravity = gravity; d.coolMul = cool_mul; d.coolSub = cool_sub;
d.count = count > 0 ? count : 1;
d.repeats = repeats > 0 ? repeats : 1;
d.valid = 1;
return 1;
}
void BTStartSfx(int effect_number, float x, float y, float z)
{
if (effect_number < 0 || effect_number >= BT_SFX_SLOTS)
return;
const BTSfxDef &d = gBTSfxDefs[effect_number];
if (!d.valid)
return;
for (int i = 0; i < d.count; ++i)
{
if (gBTPfxParticles.size() > 8192)
return;
BTPfxParticle p;
memset(&p, 0, sizeof(p));
// Random burst direction; v_bias biases it upward (Sparks 1.4 =
// fountain, GroundHit 0.01 = flat ring, Fireball 0.99 = rising
// column -- the authored values read cleanly under this model).
// The firmware spins the whole burst frame by a random angle;
// per-particle isotropic randoms give the same coverage.
float ax = BTPfxRand01() * 2.0f - 1.0f;
float ay = BTPfxRand01() * 2.0f - 1.0f;
float az = BTPfxRand01() * 2.0f - 1.0f;
float dl = sqrtf(ax * ax + ay * ay + az * az);
if (dl < 1e-4f) { ax = 0; ay = 1; az = 0; dl = 1; }
ax /= dl; ay /= dl; az /= dl;
ay = fabsf(ay) * 0.3f + d.vBias; // upward bias
dl = sqrtf(ax * ax + ay * ay + az * az);
ax /= dl; ay /= dl; az /= dl;
float spd = d.velocity * (0.6f + 0.4f * BTPfxRand01());
// burst scatter: break the perfect overlap of count co-spawned
// sprites (the flash read as a single smooth ball) [T3]
float sc = d.size * 0.2f;
p.pos = D3DXVECTOR3(
x + sc * (BTPfxRand01() * 2.0f - 1.0f),
y + sc * (BTPfxRand01() * 2.0f - 1.0f),
z + sc * (BTPfxRand01() * 2.0f - 1.0f));
p.vel = D3DXVECTOR3(ax * spd, ay * spd, az * spd);
p.accel = D3DXVECTOR3(0.0f, -d.gravity, 0.0f);
p.age = 0.0f;
p.life = 10.0f; // real kills: heat/fade/plane
// size scale [T3]: x0.5 (size ~ diameter) with a 1.25m visibility
// floor for textured bursts -- combat ranges run 100m+; LaserHit
// (authored size 1.9) at <1m rendered ~8px = "practically nothing"
// (user-rejected). MissileHit 12 -> 6m radius core. Sparks/NOTEX
// keep their authored tininess (they read by count, not size).
p.rad = (d.size > 0.01f) ? d.size * 0.5f : 0.15f;
if (!d.noTex && d.size > 0.01f && p.rad < 1.25f) p.rad = 1.25f;
p.exp = 0.0f; p.dexp = 0.0f;
p.colorWarp = 1.0f; p.alphaWarp = 1.0f;
p.sfxMode = 1;
p.heat = 1.0f;
p.coolMul = (d.coolMul > 0.0f && d.coolMul < 1.0f) ? d.coolMul : 0.9f;
p.coolSub = d.coolSub > 0.0f ? d.coolSub : 0.02f;
p.fade = 1.0f;
p.cookR = d.cook[0]; p.cookG = d.cook[1]; p.cookB = d.cook[2];
p.killY = y + d.yOff;
p.sfxType = d.type;
p.uvVariant = (int)(BTPfxRand01() * 7.99f);
gBTPfxParticles.push_back(p);
}
}
// The beams' decoded grit texture (built in BTDrawBeams) -- shared with the // The beams' decoded grit texture (built in BTDrawBeams) -- shared with the
// particles so the fire quads carry the authentic firesmoke-family noise. // particles so the fire quads carry the authentic firesmoke-family noise.
extern LPDIRECT3DTEXTURE9 BTGetBeamGritTexture(); extern LPDIRECT3DTEXTURE9 BTGetBeamGritTexture();
@@ -870,6 +1006,60 @@ void BTDrawPfx(LPDIRECT3DDEVICE9 dev, const D3DXMATRIX *view, float dt)
{ {
// ---- sim ---- // ---- sim ----
if (dt > 0.1f) dt = 0.1f; // stall guard if (dt > 0.1f) dt = 0.1f; // stall guard
// DEV HARNESS: BT_FX_TEST="10" (or "10,1003,15") fires the listed effect
// numbers every 3s, 30m in front of the camera -- iterate the effect look
// via screenshots without a live fire loop. <100 = specialfx descriptor,
// >=1000 = psfx slot n-1000 (the real dispatch encodings).
{
static int s_fxTestN = -1;
static int s_fxTest[8];
static float s_fxTestTimer = 0.0f;
if (s_fxTestN < 0)
{
s_fxTestN = 0;
const char *tv = getenv("BT_FX_TEST");
if (tv != 0)
{
const char *p = tv;
while (*p && s_fxTestN < 8)
{
s_fxTest[s_fxTestN++] = atoi(p);
while (*p && *p != ',') ++p;
if (*p == ',') ++p;
}
}
}
if (s_fxTestN > 0)
{
s_fxTestTimer -= dt;
if (s_fxTestTimer <= 0.0f)
{
s_fxTestTimer = 3.0f;
float cx, cy, cz;
d3d_OBJECT::GetCameraPosition(&cx, &cy, &cz);
// fire on BOTH sides of the camera (view-matrix handedness
// varies; one of the two is dead-centre in frame)
for (int side = -1; side <= 1; side += 2)
{
float fx = cx + view->_13 * 30.0f * side;
float fy = cy + view->_23 * 30.0f * side + 4.0f;
float fz = cz + view->_33 * 30.0f * side;
for (int i = 0; i < s_fxTestN; ++i)
{
extern void BTStartSfx(int, float, float, float);
if (s_fxTest[i] >= 1000)
BTStartPfx(s_fxTest[i] - 1000, fx, fy, fz);
else
BTStartSfx(s_fxTest[i], fx, fy, fz);
}
}
DEBUG_STREAM << "[fxtest] fired +-30m of cam (" << cx << "," << cy << "," << cz
<< ") particles=" << (int)gBTPfxParticles.size()
<< " emitters=" << (int)gBTPfxEmitters.size() << "\n" << std::flush;
}
}
}
for (size_t ei = 0; ei < gBTPfxEmitters.size(); ++ei) for (size_t ei = 0; ei < gBTPfxEmitters.size(); ++ei)
{ {
BTPfxEmitter &e = gBTPfxEmitters[ei]; BTPfxEmitter &e = gBTPfxEmitters[ei];
@@ -881,7 +1071,17 @@ void BTDrawPfx(LPDIRECT3DDEVICE9 dev, const D3DXMATRIX *view, float dt)
// DDAM2 35/16~=2, DDTHSMK 30/3=10) -- release_period IS the emission // DDAM2 35/16~=2, DDTHSMK 30/3=10) -- release_period IS the emission
// WINDOW, not a batch interval. (The old one-burst batching dumped a // WINDOW, not a batch interval. (The old one-burst batching dumped a
// 10-second smoke plume in a single frame's puff.) // 10-second smoke plume in a single frame's puff.)
e.emitAccum += d.rate * dt; // EMISSION-WINDOW RULE (hardened 2026-07-12): release_period IS the
// emission window -- derive the rate from maxIssue/releasePeriod
// instead of trusting the authored 'rate' field. SHKWAVE.PFX (the
// missile-hit shockwave, mslhit's 1023) authors maxIssue=5,
// releasePeriod=0.2, rate=1 -- the one shipped file where they
// disagree; trusting 'rate' stretched the single 0.2s layered
// 5-shell blast into 5 metronomic 1Hz pulses ("silver mist exactly
// 5 times at perfectly timed intervals" -- user-reported).
float erate = (d.releasePeriod > 1e-3f)
? (float)d.maxIssue / d.releasePeriod : d.rate;
e.emitAccum += erate * dt;
int n = (int)e.emitAccum; int n = (int)e.emitAccum;
if (n > 0) if (n > 0)
{ {
@@ -901,12 +1101,28 @@ void BTDrawPfx(LPDIRECT3DDEVICE9 dev, const D3DXMATRIX *view, float dt)
gBTPfxEmitters.resize(w); gBTPfxEmitters.resize(w);
} }
{ // advance + expire particles { // advance + expire particles
const float boardSteps = dt * 30.0f; // the pod stepped the sfx sim per 30Hz frame
size_t w = 0; size_t w = 0;
for (size_t i = 0; i < gBTPfxParticles.size(); ++i) for (size_t i = 0; i < gBTPfxParticles.size(); ++i)
{ {
BTPfxParticle &p = gBTPfxParticles[i]; BTPfxParticle &p = gBTPfxParticles[i];
p.age += dt; p.age += dt;
if (p.age >= p.life) continue; if (p.age >= p.life) continue;
if (p.sfxMode)
{
// the firmware kills (heat floor 1e-4 @0xF0A0; the y_off kill
// plane). TIMEBASE [T1, re-read from the step disasm]: heat
// decays per BOARD FRAME (f18 = f17*f16, no dt -- hence the
// 30Hz boardSteps exponent) but the alpha fade is multiplied
// by RAW dt (@f0413754-64: out[0x40] -= dt * inst[0x1a4]) --
// cool_b is PER-SECOND. The earlier x30 scaling killed every
// burst in ~0.1s: fireballs read as a subliminal pop and
// laser hits as "nothing happens" (user-reported).
p.heat *= powf(p.coolMul, boardSteps);
p.fade -= p.coolSub * dt;
if (p.heat <= 1e-4f || p.fade <= 0.0f) continue;
if (p.pos.y < p.killY) continue;
}
p.vel += p.accel * dt; p.vel += p.accel * dt;
p.pos += p.vel * dt; p.pos += p.vel * dt;
p.rad += p.exp * dt; p.rad += p.exp * dt;
@@ -977,6 +1193,10 @@ void BTDrawPfx(LPDIRECT3DDEVICE9 dev, const D3DXMATRIX *view, float dt)
// Fallback when BINTA.VTX is absent: the old grit x radial single-tex bake. // Fallback when BINTA.VTX is absent: the old grit x radial single-tex bake.
static LPDIRECT3DTEXTURE9 s_pfxTex = 0; // fallback single-texture static LPDIRECT3DTEXTURE9 s_pfxTex = 0; // fallback single-texture
static LPDIRECT3DTEXTURE9 s_pfxNoise = 0; // bintA through the "fiery" ramp static LPDIRECT3DTEXTURE9 s_pfxNoise = 0; // bintA through the "fiery" ramp
static LPDIRECT3DTEXTURE9 s_pfxNoiseGray = 0; // RAW bintA (grayscale) -- the
// old-style specialfx sample the
// TEXTURE, not the ramped material:
// the heat model supplies ALL colour
static LPDIRECT3DTEXTURE9 s_pfxMask = 0; // radial falloff (stage 1, CLAMP) static LPDIRECT3DTEXTURE9 s_pfxMask = 0; // radial falloff (stage 1, CLAMP)
static int s_pfxTexTried = 0; static int s_pfxTexTried = 0;
if (!s_pfxTexTried) if (!s_pfxTexTried)
@@ -1013,6 +1233,28 @@ void BTDrawPfx(LPDIRECT3DDEVICE9 dev, const D3DXMATRIX *view, float dt)
} }
else { s_pfxNoise->Release(); s_pfxNoise = 0; } else { s_pfxNoise->Release(); s_pfxNoise = 0; }
} }
if (s_pfxNoise && fireImg.ok &&
SUCCEEDED(dev->CreateTexture(fireImg.w, fireImg.h, 1, 0, D3DFMT_A8R8G8B8,
D3DPOOL_MANAGED, &s_pfxNoiseGray, NULL)))
{
D3DLOCKED_RECT lr;
if (SUCCEEDED(s_pfxNoiseGray->LockRect(0, &lr, NULL, 0)))
{
for (int y = 0; y < fireImg.h; ++y)
{
DWORD *dst = (DWORD *)((char *)lr.pBits + y * lr.Pitch);
for (int x = 0; x < fireImg.w; ++x)
{
uint32_t s = fireImg.argb[(size_t)y * fireImg.w + x];
int l = (((s >> 16) & 0xFF) + ((s >> 8) & 0xFF) + (s & 0xFF)) / 3;
int a = (int)((0.35f + 0.65f * (l / 255.0f)) * 255.0f);
dst[x] = ((DWORD)a << 24) | (l << 16) | (l << 8) | l;
}
}
s_pfxNoiseGray->UnlockRect(0);
}
else { s_pfxNoiseGray->Release(); s_pfxNoiseGray = 0; }
}
if (s_pfxNoise && if (s_pfxNoise &&
SUCCEEDED(dev->CreateTexture(TW, TH, 1, 0, D3DFMT_A8R8G8B8, SUCCEEDED(dev->CreateTexture(TW, TH, 1, 0, D3DFMT_A8R8G8B8,
D3DPOOL_MANAGED, &s_pfxMask, NULL))) D3DPOOL_MANAGED, &s_pfxMask, NULL)))
@@ -1099,29 +1341,32 @@ void BTDrawPfx(LPDIRECT3DDEVICE9 dev, const D3DXMATRIX *view, float dt)
} }
} }
// The authored SCROLL clock (firesmoke1's rate; the shipped variants share
// the sheet and differ only in rate, so the port rolls every sprite at the
// dominant one). Accumulate the OFFSETS and wrap each into [0,1) -- an
// unbounded time * rate loses float precision over a long session (the
// tsphere scroll-precision lesson).
static float s_pfxScrollU = 0.0f, s_pfxScrollV = 0.0f;
s_pfxScrollU += 0.05f * dt; s_pfxScrollU -= floorf(s_pfxScrollU);
s_pfxScrollV += -0.331f * dt; s_pfxScrollV -= floorf(s_pfxScrollV);
if (s_pfxNoise) if (s_pfxNoise)
{ {
// stage 0: diffuse x the scrolled fire noise (WRAPped) // stage 0: diffuse x the scrolled noise (WRAPped). GRAYSCALE: every
dev->SetTexture(0, s_pfxNoise); // .PFX and specialfx descriptor references the TEXTURE
// btfx:firesmokeN_scr_tex (the raw bintA noise), NOT the fiery-ramped
// material -- the AUTHORED per-particle colours are the sole colour
// source (DNBOOM's 0.8,0.3,0 = the orange fire; DDAM's 0.5-gray = the
// smoke). The earlier ramp-baked sheet gilded the gray smoke bands
// into "gold blasts of light" (user-rejected). The fiery ramp
// belongs to the MATERIAL path only (flamebig etc. via bgfload).
dev->SetTexture(0, s_pfxNoiseGray ? s_pfxNoiseGray : s_pfxNoise);
dev->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE); dev->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
dev->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_DIFFUSE); dev->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_DIFFUSE);
dev->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_TEXTURE); dev->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_TEXTURE);
dev->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE); dev->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE);
dev->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_DIFFUSE); dev->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_DIFFUSE);
dev->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_TEXTURE); dev->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_TEXTURE);
D3DXMATRIX tm; D3DXMatrixIdentity(&tm); // NO SCROLL on particle stamps: the authored SCROLL belongs to the
tm._31 = s_pfxScrollU; tm._32 = s_pfxScrollV; // (u,v) translation // TEXTURE record for the MATERIAL path (flame meshes); sliding the
dev->SetTransform(D3DTS_TEXTURE0, &tm); // noise through short-lived particle sprites reads as a shimmering
dev->SetTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2); // crawl ("not sure what I'm looking at" -- user-rejected). Each
// particle instead gets a FIXED random window into the tileable
// sheet (u0/v0, set at spawn; the WRAP addressing makes any window
// valid) -- the board's per-burst random spin de-correlation.
dev->SetTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE);
dev->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP); dev->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP);
dev->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP); dev->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP);
// stage 1: x the CLAMPed radial mask (same UV set, UNscrolled) -- both // stage 1: x the CLAMPed radial mask (same UV set, UNscrolled) -- both
@@ -1158,6 +1403,15 @@ void BTDrawPfx(LPDIRECT3DDEVICE9 dev, const D3DXMATRIX *view, float dt)
struct V { float x, y, z; DWORD c; float u, v; }; struct V { float x, y, z; DWORD c; float u, v; };
static std::vector<V> verts; static std::vector<V> verts;
verts.clear(); verts.clear();
// SPECIALFX batch: drawn with the FIERY-RAMPED sheet. On the IG board the
// heat channels are BRIGHTNESS multipliers over an inherently fire-
// coloured texture (firesmoke through its material ramp): white-hot flash
// -> ember-orange dissipating cloud (the demo fireballs). The .PFX batch
// keeps the GRAYSCALE sheet (its authored colours decide: DNBOOM orange
// fire, DDAM gray smoke). Drawing sfx ember clouds grayscale made them
// "explosive silver mist" (user-rejected).
static std::vector<V> sfxVerts;
sfxVerts.clear();
// BACK-TO-FRONT order: premultiplied occlusion (smoke) composites in depth // BACK-TO-FRONT order: premultiplied occlusion (smoke) composites in depth
// order; unsorted draws pop when a near puff renders before a far one. // order; unsorted draws pop when a near puff renders before a far one.
@@ -1185,16 +1439,54 @@ void BTDrawPfx(LPDIRECT3DDEVICE9 dev, const D3DXMATRIX *view, float dt)
float tc = powf(t, 1.0f / p.colorWarp); float tc = powf(t, 1.0f / p.colorWarp);
float ta = powf(t, 1.0f / p.alphaWarp); float ta = powf(t, 1.0f / p.alphaWarp);
// two quads: outer glow (2.2x radius) then inner core // The .PFX sI/sO are the INTERIOR/EXTERIOR colours of ONE sprite (the
for (int layer = 0; layer < 2; ++layer) // dpl_PARTICLE model) -- approximated as an exterior quad at the
// AUTHORED radius + an interior core at 0.45x, with the exterior's
// occlusion halved (it is the sprite's soft skirt, not a second
// particle). The old 2.2x "outer glow" inflated every particle and
// doubled its occlusion -- 20 overlapping damage-smoke particles
// merged into one giant black blob (seen in the BT_FX_TEST captures).
// Old-style specialfx draw ONE sprite per particle (the firmware
// draw emits a single projected sprite).
for (int layer = (p.sfxMode ? 1 : 0); layer < 2; ++layer)
{ {
const float *cs = layer ? p.sI : p.sO; const float *cs = layer ? p.sI : p.sO;
const float *ce = layer ? p.eI : p.eO; const float *ce = layer ? p.eI : p.eO;
float scale = layer ? 1.0f : 2.2f; float scale = layer ? 0.45f : 1.0f;
float r = cs[0] + (ce[0] - cs[0]) * tc; if (p.sfxMode) scale = 1.0f;
float g = cs[1] + (ce[1] - cs[1]) * tc; float r, g, b, a;
float b = cs[2] + (ce[2] - cs[2]) * tc; if (p.sfxMode)
float a = cs[3] + (ce[3] - cs[3]) * ta; {
// THE FIRMWARE HEAT EQUATION [T1]: channel = h_old*(h_new*
// cook_ch - 0.25) + 0.25. h was already stepped in the sim
// above; approximate h_old over this frame's step from the
// per-frame factor (h_old = h / coolMul^steps was h before
// the step -- one step back is close enough at 30Hz).
float hNew = p.heat;
float hOld = p.heat / p.coolMul; // one board step back
if (hOld > 1.0f) hOld = 1.0f;
r = hOld * (hNew * p.cookR - 0.25f) + 0.25f;
g = hOld * (hNew * p.cookG - 0.25f) + 0.25f;
b = hOld * (hNew * p.cookB - 0.25f) + 0.25f;
if (r < 0) r = 0; if (r > 1) r = 1;
if (g < 0) g = 0; if (g > 1) g = 1;
if (b < 0) b = 0; if (b > 1) b = 1;
// occlusion weight: solid debris chunks occlude; fiery balls
// mostly-occlude while HOT (bright fire body), thinning as
// the heat dies (0.5 halved the flash into a faint wisp at
// combat range -- "practically nothing", user-rejected)
float occl = (p.sfxType == 0) ? 0.85f
: (0.45f + 0.45f * (p.heat > 1.0f ? 1.0f : p.heat));
a = p.fade * occl;
}
else
{
r = cs[0] + (ce[0] - cs[0]) * tc;
g = cs[1] + (ce[1] - cs[1]) * tc;
b = cs[2] + (ce[2] - cs[2]) * tc;
a = cs[3] + (ce[3] - cs[3]) * ta;
if (layer == 0) a *= 0.5f; // exterior skirt: half occlusion
}
if (a <= 0.0f) continue; if (a <= 0.0f) continue;
if (a > 1.0f) a = 1.0f; if (a > 1.0f) a = 1.0f;
// PREMULTIPLIED: vertex rgb = colour x alpha (the framebuffer // PREMULTIPLIED: vertex rgb = colour x alpha (the framebuffer
@@ -1210,20 +1502,62 @@ void BTDrawPfx(LPDIRECT3DDEVICE9 dev, const D3DXMATRIX *view, float dt)
float rad = p.rad * scale; float rad = p.rad * scale;
D3DXVECTOR3 rv = right * rad, uv = up * rad; D3DXVECTOR3 rv = right * rad, uv = up * rad;
// noise-stamp orientation: mirror/swap the 0..1 UVs by the
// particle's variant (stays in 0..1 -> the CLAMPed mask holds)
float cu[4] = { 0, 0, 1, 1 }; // corners: BL, TL, TR, BR
float cv[4] = { 1, 0, 0, 1 };
for (int k = 0; k < 4; ++k)
{
float uu2 = cu[k], vv2 = cv[k];
if (p.uvVariant & 1) uu2 = 1.0f - uu2;
if (p.uvVariant & 2) vv2 = 1.0f - vv2;
if (p.uvVariant & 4) { float t = uu2; uu2 = vv2; vv2 = t; }
cu[k] = uu2; cv[k] = vv2;
}
V q[6]; V q[6];
q[0].x = p.pos.x - rv.x - uv.x; q[0].y = p.pos.y - rv.y - uv.y; q[0].z = p.pos.z - rv.z - uv.z; q[0].u = 0; q[0].v = 1; q[0].x = p.pos.x - rv.x - uv.x; q[0].y = p.pos.y - rv.y - uv.y; q[0].z = p.pos.z - rv.z - uv.z; q[0].u = cu[0]; q[0].v = cv[0];
q[1].x = p.pos.x - rv.x + uv.x; q[1].y = p.pos.y - rv.y + uv.y; q[1].z = p.pos.z - rv.z + uv.z; q[1].u = 0; q[1].v = 0; q[1].x = p.pos.x - rv.x + uv.x; q[1].y = p.pos.y - rv.y + uv.y; q[1].z = p.pos.z - rv.z + uv.z; q[1].u = cu[1]; q[1].v = cv[1];
q[2].x = p.pos.x + rv.x + uv.x; q[2].y = p.pos.y + rv.y + uv.y; q[2].z = p.pos.z + rv.z + uv.z; q[2].u = 1; q[2].v = 0; q[2].x = p.pos.x + rv.x + uv.x; q[2].y = p.pos.y + rv.y + uv.y; q[2].z = p.pos.z + rv.z + uv.z; q[2].u = cu[2]; q[2].v = cv[2];
q[3] = q[0]; q[3] = q[0];
q[4] = q[2]; q[4] = q[2];
q[5].x = p.pos.x + rv.x - uv.x; q[5].y = p.pos.y + rv.y - uv.y; q[5].z = p.pos.z + rv.z - uv.z; q[5].u = 1; q[5].v = 1; q[5].x = p.pos.x + rv.x - uv.x; q[5].y = p.pos.y + rv.y - uv.y; q[5].z = p.pos.z + rv.z - uv.z; q[5].u = cu[3]; q[5].v = cv[3];
for (int k = 0; k < 6; ++k) { q[k].c = c; verts.push_back(q[k]); } std::vector<V> &sink = p.sfxMode ? sfxVerts : verts;
for (int k = 0; k < 6; ++k) { q[k].c = c; sink.push_back(q[k]); }
} }
} }
// DIAG (BT_FX_TEST): prove the draw reaches pixels -- particle count, the
// vertex count, the first particle's colour/alpha/pos, texture presence.
if (getenv("BT_FX_TEST") != 0 && !gBTPfxParticles.empty())
{
static float s_drawLog = 0.0f;
s_drawLog += dt;
if (s_drawLog >= 2.0f)
{
s_drawLog = 0.0f;
const BTPfxParticle &p0 = gBTPfxParticles[0];
DEBUG_STREAM << "[pfx-draw] n=" << (int)gBTPfxParticles.size()
<< " verts=" << (int)verts.size()
<< " tex=" << (s_pfxNoiseGray ? "gray" : (s_pfxNoise ? "fiery" : (s_pfxTex ? "grit" : "NONE")))
<< " p0(sfx=" << p0.sfxMode << " pos=" << p0.pos.x << "," << p0.pos.y << "," << p0.pos.z
<< " rad=" << p0.rad << " heat=" << p0.heat << " fade=" << p0.fade
<< ")\n" << std::flush;
if (!verts.empty())
DEBUG_STREAM << "[pfx-draw] v0=(" << verts[0].x << "," << verts[0].y << "," << verts[0].z
<< ") c=" << std::hex << (unsigned)verts[0].c << std::dec << "\n" << std::flush;
}
}
if (!verts.empty()) if (!verts.empty())
dev->DrawPrimitiveUP(D3DPT_TRIANGLELIST, (UINT)verts.size() / 3, dev->DrawPrimitiveUP(D3DPT_TRIANGLELIST, (UINT)verts.size() / 3,
&verts[0], sizeof(V)); &verts[0], sizeof(V));
if (!sfxVerts.empty())
{
// the fiery-ramped sheet for the heat-model bursts (see above)
if (s_pfxNoise)
dev->SetTexture(0, s_pfxNoise);
dev->DrawPrimitiveUP(D3DPT_TRIANGLELIST, (UINT)sfxVerts.size() / 3,
&sfxVerts[0], sizeof(V));
}
dev->SetTexture(0, NULL); dev->SetTexture(0, NULL);
if (s_pfxNoise) if (s_pfxNoise)
@@ -4503,6 +4837,63 @@ void
<< pfx_loaded << " effect definitions loaded\n" << std::flush; << pfx_loaded << " effect definitions loaded\n" << std::flush;
delete psfx_namelist; delete psfx_namelist;
} }
//
// OLD-STYLE SPECIALFX ("specialfxN=Name" + [Name] descriptor pages) -- the
// per-weapon impact effects (PPCHit/LaserHit/MissileHit/Chunks/Sparks/
// Fireball...). Field names per the 1995 binary's parser (part_008.c:1546:
// type/size/velocity/v_bias/y_off/variance/gravity/count/repeats +
// "cook" "%f %f %f" + "cool" "%f %f" + texture). NOTE the binary reads
// "variance" but the INI authors "varience" -- the lookup MISSED in the
// shipping pod, so variance is authentically dead (not parsed here).
// The 2007 loader's version-2-only walk below skips all of these
// (version < 2 -> continue), which is why the layer never rendered.
//
{
NameList *sfx_namelist =
master_notation_file->MakeEntryList(starting_page_name, "specialfx");
if (sfx_namelist != NULL)
{
extern int BTInstallSfx(int slot, int type, int no_tex, float size,
float velocity, float v_bias, float y_off, const float cook[3],
float gravity, float cool_mul, float cool_sub, int count, int repeats);
int sfx_loaded = 0;
for (entry = sfx_namelist->GetFirstEntry();
entry;
entry = entry->GetNextEntry())
{
int sfx_number = atoi(entry->GetName() + 9); // "specialfxN"
const char *page = entry->GetChar();
if (page == NULL || page[0] == '\0')
continue;
int type = 0, count = 1, repeats = 1;
float size = 0, velocity = 0, v_bias = 0, y_off = 0, gravity = 0;
float cook[3] = { 1, 1, 1 };
float cool_mul = 0.9f, cool_sub = 0.02f;
const char *sp = NULL;
master_notation_file->GetEntry(page, "type", &type);
master_notation_file->GetEntry(page, "size", &size);
master_notation_file->GetEntry(page, "velocity", &velocity);
master_notation_file->GetEntry(page, "v_bias", &v_bias);
master_notation_file->GetEntry(page, "y_off", &y_off);
master_notation_file->GetEntry(page, "gravity", &gravity);
master_notation_file->GetEntry(page, "count", &count);
master_notation_file->GetEntry(page, "repeats", &repeats);
if (master_notation_file->GetEntry(page, "cook", &sp) && sp != NULL)
sscanf(sp, "%f %f %f", &cook[0], &cook[1], &cook[2]);
if (master_notation_file->GetEntry(page, "cool", &sp) && sp != NULL)
sscanf(sp, "%f %f", &cool_mul, &cool_sub);
int no_tex = 0;
if (master_notation_file->GetEntry(page, "texture", &sp) && sp != NULL)
no_tex = (strncmp(sp, "NOTEX", 5) == 0); // NOTEX / NOTEXT
sfx_loaded += BTInstallSfx(sfx_number, type, no_tex, size, velocity,
v_bias, y_off, cook, gravity, cool_mul, cool_sub, count, repeats);
}
DEBUG_STREAM << "[sfx] page '" << starting_page_name << "': "
<< sfx_loaded << " old-style specialfx installed\n" << std::flush;
delete sfx_namelist;
}
}
//// ////
//// (original stubbed loader retained for provenance) //// (original stubbed loader retained for provenance)
//// ////
@@ -6840,29 +7231,40 @@ void
<< entity->localOrigin.linearPosition.x << entity->localOrigin.linearPosition.x
<< ", " << entity->localOrigin.linearPosition.y << ", " << entity->localOrigin.linearPosition.y
<< ", " << entity->localOrigin.linearPosition.z << "], " << effect_number << ");" << std::endl << std::flush; << ", " << entity->localOrigin.linearPosition.z << "], " << effect_number << ");" << std::endl << std::flush;
// Both effect-number ENCODINGS resolve to the same psfx slot: // TWO DISTINCT ENCODINGS (routing corrected 2026-07-12):
// <100 = the raw dpl board number; >=1000 = the WinTesla-era // < 100 = the dpl EFFECT-TABLE number = an OLD-STYLE
// "1000+slot" INDIE id carried by the damage-band resources // specialfx descriptor (BTDPL.INI [effects_to_load]
// (1002-1005 = ddam1-4 light..critical damage smoke, 1008 = // "specialfxN": 8=PPCHit, 9=LaserHit, 10=MissileHit,
// ddam5 zone-destroyed -- the SAME [pfx_day] mapping). BT // 3=Chunks1, 5=Sparks, 15=Fireball -- the 1996
// ships no INDIE descriptors (the version-2 specialfx pages // script's own comments match) -> the firmware-
// don't exist), so all slots route to the BT .PFX layer, with // decoded heat-model burst (BTStartSfx);
// the Explosion entity's ORIENTATION (the victim's frame) -- // >= 1000 = the "1000+slot" psfx id (1002-1005 = ddam1-4,
// the .PFX offsets/velocities are authored mech-local. // 1008 = ddam5, 1009-1013 = idam1-5) -> the .PFX
// layer, oriented in the victim's frame.
// (The old "<100 -> psfx slot" mapping rendered PPCHit as
// ddam5 SMOKE and Chunks as damage smoke -- the "hits are
// just a little smoke" report.)
if (effect_number >= 1000)
{ {
int pfx_slot = (effect_number >= 1000)
? effect_number - 1000 : effect_number;
extern void BTStartPfxFrame(int, float, float, float, extern void BTStartPfxFrame(int, float, float, float,
const float *, const float *, const float *); const float *, const float *, const float *);
float xr[3] = { (float)entity->localToWorld(0,0), (float)entity->localToWorld(0,1), (float)entity->localToWorld(0,2) }; float xr[3] = { (float)entity->localToWorld(0,0), (float)entity->localToWorld(0,1), (float)entity->localToWorld(0,2) };
float yr[3] = { (float)entity->localToWorld(1,0), (float)entity->localToWorld(1,1), (float)entity->localToWorld(1,2) }; float yr[3] = { (float)entity->localToWorld(1,0), (float)entity->localToWorld(1,1), (float)entity->localToWorld(1,2) };
float zr[3] = { (float)entity->localToWorld(2,0), (float)entity->localToWorld(2,1), (float)entity->localToWorld(2,2) }; float zr[3] = { (float)entity->localToWorld(2,0), (float)entity->localToWorld(2,1), (float)entity->localToWorld(2,2) };
BTStartPfxFrame(pfx_slot, BTStartPfxFrame(effect_number - 1000,
(float)entity->localOrigin.linearPosition.x, (float)entity->localOrigin.linearPosition.x,
(float)entity->localOrigin.linearPosition.y, (float)entity->localOrigin.linearPosition.y,
(float)entity->localOrigin.linearPosition.z, (float)entity->localOrigin.linearPosition.z,
xr, yr, zr); xr, yr, zr);
} }
else
{
extern void BTStartSfx(int, float, float, float);
BTStartSfx(effect_number,
(float)entity->localOrigin.linearPosition.x,
(float)entity->localOrigin.linearPosition.y,
(float)entity->localOrigin.linearPosition.z);
}
} }
else else
{ {
@@ -8630,17 +9032,14 @@ void
} }
// //
// Board effect numbers (<100) -- the 1995 dpl explosion/damage effects. // Board effect numbers (<100) -- the 1995 dpl EFFECT TABLE = the old-style
// RECONSTRUCTED: routed to the BT .PFX particle layer (the [pfx_day]/ // specialfx descriptors (BTDPL.INI [effects_to_load]), NOT psfx slots
// [pfx_night] psfxN mapping loads each number's authentic .PFX definition; // (routing corrected 2026-07-12; the psfx encoding is "1000+slot"). The
// see the layer banner at the top of this file). This is what makes every // firmware-decoded heat-model burst renders them (BTStartSfx).
// weapon-hit / damage-band / mech-death explosion actually VISIBLE -- the
// original dpl_Effect(dpl_effect_type_explosion, ...) below was IG-board
// hardware and was never ported.
// //
{ {
extern void BTStartPfx(int effect_number, float x, float y, float z); extern void BTStartSfx(int effect_number, float x, float y, float z);
BTStartPfx(effect_number, location.x, location.y, location.z); BTStartSfx(effect_number, location.x, location.y, location.z);
} }
//dpl_EXPLOSION_EFFECT_INFO my_explosion; //dpl_EXPLOSION_EFFECT_INFO my_explosion;
+20
View File
@@ -189,6 +189,26 @@ class NotationFile;
// beam from this live sim state -- the authentic per-weapon fire look). // beam from this live sim state -- the authentic per-weapon fire look).
// //
int BeamOn() const { return beamFlag; } // 0x46c int BeamOn() const { return beamFlag; } // 0x46c
Scalar DischargeTimer() const { return dischargeTimer; } // forensics
// REPLICANT-side beam aging (port, 2026-07-13): replicated discharge
// state arrives via update records, but replicant subsystems never
// Execute -- a lost/undelivered beam-END record left the peer's beam
// ON forever, drawn at its stale fire-time endpoint (the phantom
// "4th gold beam", user-reported). Run the master's deterministic
// countdown locally: no alarms, no upstream records; a live master
// refreshes the state with every real record anyway.
void ReplicantServiceBeam(Scalar dt)
{
if (beamFlag == 0)
return;
dischargeTimer -= dt;
if (dischargeTimer <= 0.0f)
{
beamFlag = 0;
firingActive = 0;
dischargeTimer = dischargeTime;
}
}
const Point3D &BeamEndpoint() const { return beamEndpoint; } // 0x460 const Point3D &BeamEndpoint() const { return beamEndpoint; } // 0x460
public: public:
+2
View File
@@ -1390,6 +1390,8 @@ Mech::Mech(
duckedTemplateMaxY = 0.0f; duckedTemplateMaxY = 0.0f;
templateBottomLift = 0.0f; templateBottomLift = 0.0f;
frameEntryWorldVelocity = Vector3D(0.0f, 0.0f, 0.0f); // collision-damage guard snapshot frameEntryWorldVelocity = Vector3D(0.0f, 0.0f, 0.0f); // collision-damage guard snapshot
ramLastVictim = 0; // ram contact-edge state
ramContactLinger = 0.0f;
if (GroundReal() && GetCollisionVolumeCount() > 0 if (GroundReal() && GetCollisionVolumeCount() > 0
&& collisionTemplate != 0 && collisionVolume != 0) && collisionTemplate != 0 && collisionVolume != 0)
{ {
+10
View File
@@ -639,6 +639,16 @@ public:
// approach speed -- which is all the binary's StaticBounce ever saw. // approach speed -- which is all the binary's StaticBounce ever saw.
// By-name access only; declared after the layout-locked fields. // By-name access only; declared after the layout-locked fields.
Vector3D frameEntryWorldVelocity; Vector3D frameEntryWorldVelocity;
// RAM CONTACT EDGE (port analog of the binary's bounce-separation): in the
// 1995 mover, StaticBounce REVERSED worldLinearVelocity, so the frame after
// a bump read as separating and the damage gate stopped repeat dispatch.
// Our gait re-derives velocity every frame (still closing while the stick
// is held / while respawn-overlapped), so sustained contact re-priced a
// fresh full-speed ram EVERY frame (~60 dmg x 60fps = melt; the respawn
// explode-loop). One bump = one damage event; re-arms after the contact
// breaks for ramContactLinger seconds.
Entity *ramLastVictim;
Scalar ramContactLinger;
// AUTHENTIC TARGETING (task #36): the engine Reticle struct (MUNGA/RETICLE.h // AUTHENTIC TARGETING (task #36): the engine Reticle struct (MUNGA/RETICLE.h
// [T0]) -- the mech's "TargetReticle" attribute (id 0x1d) binds to it, per the // [T0]) -- the mech's "TargetReticle" attribute (id 0x1d) binds to it, per the
+180 -22
View File
@@ -153,6 +153,7 @@
#include <cultural.hpp> // CulturalIcon::IsStoppingCollisionVolume / GetClassDerivations #include <cultural.hpp> // CulturalIcon::IsStoppingCollisionVolume / GetClassDerivations
#include <hostmgr.hpp> // HostManager::GetEntityPointer (band-effect attacker resolve) #include <hostmgr.hpp> // HostManager::GetEntityPointer (band-effect attacker resolve)
#include <messmgr.hpp> // SubsystemMessageManager (task #7 consolidated delivery) #include <messmgr.hpp> // SubsystemMessageManager (task #7 consolidated delivery)
#include <mechweap.hpp> // MechWeapon::GetExplosionResourceID (per-round detonation)
#if !defined(PLAYER_HPP) #if !defined(PLAYER_HPP)
# include <player.hpp> // Player::VehicleDeadMessage -- the death->respawn notification (task #52) # include <player.hpp> // Player::VehicleDeadMessage -- the death->respawn notification (task #52)
#endif #endif
@@ -774,18 +775,60 @@ struct BTProjectile {
Scalar aimOffsetY; // vertical aim offset vs the target's origin (live re-lead) Scalar aimOffsetY; // vertical aim offset vs the target's origin (live re-lead)
Scalar damage; // <= 0 -> VISUAL-ONLY round (replicant-side salvo mirror) Scalar damage; // <= 0 -> VISUAL-ONLY round (replicant-side salvo mirror)
int guided; // 1 = missile (seeker loft + steering); 0 = ballistic (straight) int guided; // 1 = missile (seeker loft + steering); 0 = ballistic (straight)
Entity *shooter; // the firing mech (messmgr explosion bundling at impact)
int weaponSubsys; // firing weapon's roster index (-1 = unthreaded) --
// resolves the weapon's ExplosionModelFile (mslhit/
// acanhit) in the messmgr, task #7 bundling
int active; int active;
}; };
static BTProjectile gProjectiles[64]; static BTProjectile gProjectiles[64];
extern void BTPushBeam(float,float,float, float,float,float, unsigned, float, float); extern void BTPushBeam(float,float,float, float,float,float, unsigned, float, float);
//###########################################################################
// PER-ROUND DETONATION (the binary's Missile::MoveAndCollide @004bef78: every
// round spawns ITS OWN ExplosionModelFile at its impact point, resource
// missile+0x33c -- a volley RIPPLES fireballs across its arrival frames, the
// demo look). Resolved from the firing weapon's roster entry (weapon+0x3E4);
// runs on BOTH nodes (the replicant visual salvo passes its launcher's index
// too). The messmgr's one bundled explosion remains (direct-fire semantics +
// the cross-pod damage stream); among a rippled volley it is invisible.
//###########################################################################
static void
BTSpawnRoundDetonation(Entity *shooter, int weapon_subsys, const Point3D &at)
{
extern int BTIsRegisteredMech(Entity *e);
if (shooter == 0 || weapon_subsys < 0 || !BTIsRegisteredMech(shooter))
return;
Mech *sm = (Mech *)shooter;
if (weapon_subsys >= sm->GetSubsystemCount())
return;
Subsystem *w = sm->GetSubsystem(weapon_subsys);
if (w == 0 || !w->IsDerivedFrom(*MechWeapon::GetClassDerivations()))
return;
ResourceDescription::ResourceID res = ((MechWeapon *)w)->GetExplosionResourceID();
if (res <= 0)
return;
Origin o;
o.linearPosition = at;
o.angularPosition = EulerAngles(Radian(0.0f), Radian(0.0f), Radian(0.0f));
Explosion::MakeMessage m(
Explosion::MakeMessageID, sizeof(Explosion::MakeMessage),
(Entity::ClassID)RegisteredClass::ExplosionClassID, EntityID::Null,
res, Explosion::DefaultFlags, o,
sm->GetEntityID(), sm->GetEntityID());
Explosion *e = Explosion::Make(&m);
if (e)
Register_Object(e);
}
// Called from ProjectileWeapon / MissileLauncher::FireWeapon (via the extern below) // Called from ProjectileWeapon / MissileLauncher::FireWeapon (via the extern below)
// with the live muzzle, the SHOOTER mech (to resolve the real launch port), the owner's // with the live muzzle, the SHOOTER mech (to resolve the real launch port), the owner's
// locked target entity + point, the launch speed (|muzzleVelocity|), and per-shot damage. // locked target entity + point, the launch speed (|muzzleVelocity|), and per-shot damage.
void void
BTPushProjectile(const Point3D &muzzle, void *shooter, void *target, const Point3D &targetPos, BTPushProjectile(const Point3D &muzzle, void *shooter, void *target, const Point3D &targetPos,
Scalar speed, Scalar damage, const Vector3D *launch_velocity, int guided) Scalar speed, Scalar damage, const Vector3D *launch_velocity, int guided,
int weapon_subsys)
{ {
// MUZZLE (muzzle wave, 2026-07-12): the passed muzzle is now the weapon's // MUZZLE (muzzle wave, 2026-07-12): the passed muzzle is now the weapon's
// AUTHENTIC mount segment (GetMuzzlePoint reads the real segmentIndex -- // AUTHENTIC mount segment (GetMuzzlePoint reads the real segmentIndex --
@@ -880,6 +923,34 @@ void
? (tpos.y - ((Mech *)target)->localOrigin.linearPosition.y) : 0.0f; ? (tpos.y - ((Mech *)target)->localOrigin.linearPosition.y) : 0.0f;
p.damage = damage; p.damage = damage;
p.guided = guided; // autocannon shells fly straight (no seeker in the binary's plain Projectile) p.guided = guided; // autocannon shells fly straight (no seeker in the binary's plain Projectile)
p.shooter = (Entity *)shooter;
p.weaponSubsys = weapon_subsys;
// RACK-TUBE SPREAD [T3, physically grounded]: the binary's Missile
// entities each launch from their own rack tube (per-tube authored
// MuzzlePosition); our single muzzle superimposed a whole salvo onto
// ONE trajectory -- 12 rounds read as one round, and their 12
// detonations stacked into one frame. Approximate the tube offsets
// with a small deterministic per-slot cone (+-~2.5 deg) + lateral
// muzzle offset; the seeker re-converges them onto the target so the
// volley arrives as the demo's RIPPLE of impacts.
// GUIDED ROUNDS ONLY: a ballistic shell has no seeker to re-converge
// it -- the slot-0 pattern deflected every AFC100 shell a fixed
// 3.4deg left / 2.5deg down (the phantom "4th gold beam" burying its
// tracer beside the target, user-reported). The binary's plain
// Projectile flies straight at the pick.
if (guided)
{
float ja = ((i % 5) - 2) * 0.030f; // yaw +-0.06 rad
float jb = (((i / 5) % 5) - 2) * 0.022f; // pitch +-0.044 rad
float vx = p.vel.x, vy = p.vel.y, vz = p.vel.z;
float ca = cosf(ja), sa = sinf(ja);
p.vel.x = vx * ca - vz * sa;
p.vel.z = vx * sa + vz * ca;
p.vel.y = vy + jb * p.speed;
p.pos.x += ((i % 3) - 1) * 0.6f;
p.pos.z += (((i / 3) % 3) - 1) * 0.6f;
}
p.active = 1; p.active = 1;
if (getenv("BT_PROJ_LOG")) if (getenv("BT_PROJ_LOG"))
DEBUG_STREAM << "[projectile] vel=(" << p.vel.x << "," << p.vel.y DEBUG_STREAM << "[projectile] vel=(" << p.vel.x << "," << p.vel.y
@@ -888,7 +959,7 @@ void
? " authored=(" : " none=(") ? " authored=(" : " none=(")
<< (launch_velocity ? launch_velocity->x : 0.0f) << "," << (launch_velocity ? launch_velocity->x : 0.0f) << ","
<< (launch_velocity ? launch_velocity->y : 0.0f) << "," << (launch_velocity ? launch_velocity->y : 0.0f) << ","
<< (launch_velocity ? launch_velocity->z : 0.0f) << ")\n" << std::flush; << (launch_velocity ? launch_velocity->z : 0.0f) << ")" << std::endl;
return; return;
} }
} }
@@ -973,13 +1044,19 @@ static void
if (BTGroundRayHit(prev.x, prev.y, prev.z, rd.x, rd.y, rd.z, if (BTGroundRayHit(prev.x, prev.y, prev.z, rd.x, rd.y, rd.z,
step + 1.0f, &hx, &hy, &hz)) step + 1.0f, &hx, &hy, &hz))
{ {
// burst on the rock: a tight puff cluster at the hit, no damage // burst on the rock: the round's own DETONATION (the binary
// missile detonates on ANY geometry, @004bef78) + a tight
// puff cluster, no damage
{
Point3D hp; hp.x = hx; hp.y = hy; hp.z = hz;
BTSpawnRoundDetonation(p.shooter, p.weaponSubsys, hp);
}
extern void BTPfxTrailPuff(int, float, float, float, float, float, float, int); extern void BTPfxTrailPuff(int, float, float, float, float, float, float, int);
for (int pf = 0; pf < 4; ++pf) for (int pf = 0; pf < 4; ++pf)
BTPfxTrailPuff(0, hx, hy, hz, -rd.x, -rd.y, -rd.z, 2); BTPfxTrailPuff(0, hx, hy, hz, -rd.x, -rd.y, -rd.z, 2);
if (getenv("BT_PROJ_LOG")) if (getenv("BT_PROJ_LOG"))
DEBUG_STREAM << "[projectile] WORLD burst at(" << hx << "," DEBUG_STREAM << "[projectile] WORLD burst at(" << hx << ","
<< hy << "," << hz << ")\n" << std::flush; << hy << "," << hz << ")" << std::endl;
p.active = 0; p.active = 0;
continue; continue;
} }
@@ -1014,6 +1091,7 @@ static void
} }
if (contact) if (contact)
{ {
BTSpawnRoundDetonation(p.shooter, p.weaponSubsys, p.pos);
Entity *tgt = p.target; Entity *tgt = p.target;
// Deliver to the projectile's target mech -- the launcher set p.target // Deliver to the projectile's target mech -- the launcher set p.target
// from the shooter's 0x388 slot (the picked victim; any peer mech in // from the shooter's 0x388 slot (the picked victim; any peer mech in
@@ -1033,15 +1111,38 @@ static void
dmg.damageAmount = p.damage; dmg.damageAmount = p.damage;
dmg.burstCount = 1; dmg.burstCount = 1;
dmg.impactPoint = p.pos; dmg.impactPoint = p.pos;
// Route through the SHOOTER's SubsystemMessageManager with the
// firing launcher's roster index (task #7 bundling): the
// consolidation resolves roster[id]+0x3E4 = the weapon's
// ExplosionModelFile (mslhit/acanhit) and fires it AT the
// impact point -- the missing missile-hit explosion (the
// laser path always did this via SendDamageMessage; the
// projectile path bypassed the manager entirely).
SubsystemMessageManager *mgr = 0;
if (p.shooter != 0 && p.weaponSubsys >= 0 && BTIsRegisteredMech(p.shooter))
mgr = (SubsystemMessageManager *)((Mech *)p.shooter)->GetMessageManager();
if (mgr != 0)
{
Entity::TakeDamageMessage take_damage(
Entity::TakeDamageMessageID, sizeof(Entity::TakeDamageMessage),
p.shooter->GetEntityID(), -1 /*unaimed -> cylinder resolves*/,
dmg, p.weaponSubsys);
mgr->AddDamageMessage(tgt, &take_damage);
}
else
{
Entity::TakeDamageMessage take_damage( Entity::TakeDamageMessage take_damage(
Entity::TakeDamageMessageID, sizeof(Entity::TakeDamageMessage), Entity::TakeDamageMessageID, sizeof(Entity::TakeDamageMessage),
0 /*inflictor id: bring-up*/, -1 /*unaimed -> cylinder resolves*/, dmg); 0 /*inflictor id: bring-up*/, -1 /*unaimed -> cylinder resolves*/, dmg);
tgt->Dispatch(&take_damage); tgt->Dispatch(&take_damage);
}
// gauge scoring wave (Step 6): a projectile hit credits SCORE too // gauge scoring wave (Step 6): a projectile hit credits SCORE too
// (tgt == gEnemyMech here; local player is the viewpoint shooter). // (tgt == gEnemyMech here; local player is the viewpoint shooter).
BTPostDamageScore((Entity *)tgt, p.damage); BTPostDamageScore((Entity *)tgt, p.damage);
DEBUG_STREAM << "[projectile] IMPACT damage=" << p.damage DEBUG_STREAM << "[projectile] IMPACT damage=" << p.damage
<< " zone=" << take_damage.damageZone << " (cyl-resolved)\n" << std::flush; << " subsys=" << p.weaponSubsys
<< (mgr ? " (msgmgr bundled)" : " (direct)")
<< " (zone cyl-resolved)\n" << std::flush;
} }
} }
p.active = 0; p.active = 0;
@@ -1196,6 +1297,8 @@ void
worldLinearVelocity = Vector3D(0.0f, 0.0f, 0.0f); worldLinearVelocity = Vector3D(0.0f, 0.0f, 0.0f);
localVelocity = Motion::Identity; localVelocity = Motion::Identity;
frameEntryWorldVelocity = Vector3D(0.0f, 0.0f, 0.0f); frameEntryWorldVelocity = Vector3D(0.0f, 0.0f, 0.0f);
ramLastVictim = 0;
ramContactLinger = 0.0f;
// --- our RELOCATED gait/motion accumulators -> identity (the 1995 offsets // --- our RELOCATED gait/motion accumulators -> identity (the 1995 offsets
// the decomp zeroes map to these named members in our layout) --- // the decomp zeroes map to these named members in our layout) ---
@@ -1363,7 +1466,7 @@ void
if (getenv("BT_DEATH_LOG")) if (getenv("BT_DEATH_LOG"))
DEBUG_STREAM << "[death] mech " << GetEntityID() DEBUG_STREAM << "[death] mech " << GetEntityID()
<< " destroyed -> subsystem shutdown + frozen wreck (IsDisabled=" << " destroyed -> subsystem shutdown + frozen wreck (IsDisabled="
<< (int)IsDisabled() << ")\n" << std::flush; << (int)IsDisabled() << ")" << std::endl;
// --- DEATH EFFECTS (task #42): dispatched HERE, at the VICTIM's own // --- DEATH EFFECTS (task #42): dispatched HERE, at the VICTIM's own
// once-per-death transition, so they fire regardless of WHAT killed it // once-per-death transition, so they fire regardless of WHAT killed it
@@ -1516,7 +1619,7 @@ void
DEBUG_STREAM << "[mech-exec] entity " << GetEntityID() DEBUG_STREAM << "[mech-exec] entity " << GetEntityID()
<< " instance=" << (int)GetInstance() << " instance=" << (int)GetInstance()
<< " pos=(" << localOrigin.linearPosition.x << ", " << " pos=(" << localOrigin.linearPosition.x << ", "
<< localOrigin.linearPosition.z << ")\n" << std::flush; << localOrigin.linearPosition.z << ")" << std::endl;
} }
} }
@@ -2023,7 +2126,7 @@ void
<< " err=" << err << " turn=" << gBTGotoTurn << " err=" << err << " turn=" << gBTGotoTurn
<< " thr=" << gBTGotoThrottle << " arr=" << arrived << " thr=" << gBTGotoThrottle << " arr=" << arrived
<< " fwd=(" << fwdX << "," << fwdZ << ")" << " fwd=(" << fwdX << "," << fwdZ << ")"
<< " tgt=(" << s_gx << "," << s_gz << ")\n" << std::flush; } << " tgt=(" << s_gx << "," << s_gz << ")" << std::endl; }
} }
// SELF-DRIVE (task #48): a "beeline" harness must supply its OWN // SELF-DRIVE (task #48): a "beeline" harness must supply its OWN
// forward throttle, not only steering -- otherwise it just turns // forward throttle, not only steering -- otherwise it just turns
@@ -2357,7 +2460,7 @@ void
gCurrentGait = wantGait; gCurrentGait = wantGait;
DEBUG_STREAM << "[gait] SequenceController -> blh" << suffix DEBUG_STREAM << "[gait] SequenceController -> blh" << suffix
<< " id=" << (long)*clip << " (" << (wantGait ? "run" : "walk") << " id=" << (long)*clip << " (" << (wantGait ? "run" : "walk")
<< ")\n" << std::flush; << ")" << std::endl;
} }
} }
adv = bodyAnimation.Advance(dt, 1); // inline clip-select path adv = bodyAnimation.Advance(dt, 1); // inline clip-select path
@@ -2427,7 +2530,7 @@ void
DEBUG_STREAM << "[gait] adv=" << travelAdv << " (proj=" << adv DEBUG_STREAM << "[gait] adv=" << travelAdv << " (proj=" << adv
<< ") pos=(" << ") pos=("
<< localOrigin.linearPosition.x << ", " << localOrigin.linearPosition.x << ", "
<< localOrigin.linearPosition.z << ")\n" << std::flush; << localOrigin.linearPosition.z << ")" << std::endl;
} }
} }
else else
@@ -2446,7 +2549,7 @@ void
gCurrentGait = wantGait; gCurrentGait = wantGait;
gBodyAnimReady = 1; gBodyAnimReady = 1;
DEBUG_STREAM << "[anim] gait -> blh" << suffix << " id=" << (long)*clip DEBUG_STREAM << "[anim] gait -> blh" << suffix << " id=" << (long)*clip
<< " (" << (wantGait ? "run" : "walk") << ")\n" << std::flush; << " (" << (wantGait ? "run" : "walk") << ")" << std::endl;
} }
else else
{ {
@@ -2481,7 +2584,7 @@ void
<< (dir < 0 ? " (reverse)" : "") << " adv=" << adv << (dir < 0 ? " (reverse)" : "") << " adv=" << adv
<< " loops=" << gBodyAnimLoops << " loops=" << gBodyAnimLoops
<< " pos=(" << localOrigin.linearPosition.x << ", " << " pos=(" << localOrigin.linearPosition.x << ", "
<< localOrigin.linearPosition.z << ")\n" << std::flush; << localOrigin.linearPosition.z << ")" << std::endl;
} }
} }
} // close the BT_GAIT_CUTOVER else (STEP 1-2 path) } // close the BT_GAIT_CUTOVER else (STEP 1-2 path)
@@ -2591,7 +2694,7 @@ void
DEBUG_STREAM << "[collide] pushed out by (" << dx << ", " DEBUG_STREAM << "[collide] pushed out by (" << dx << ", "
<< (localOrigin.linearPosition.y - before.y) << ", " << dz << (localOrigin.linearPosition.y - before.y) << ", " << dz
<< ") pos=(" << localOrigin.linearPosition.x << ", " << ") pos=(" << localOrigin.linearPosition.x << ", "
<< localOrigin.linearPosition.z << ")\n" << std::flush; << localOrigin.linearPosition.z << ")" << std::endl;
} }
// Floor clamp: never sink below the base ground (safety on solid-less maps). // Floor clamp: never sink below the base ground (safety on solid-less maps).
if (localOrigin.linearPosition.y < s_groundY) if (localOrigin.linearPosition.y < s_groundY)
@@ -2716,7 +2819,7 @@ void
DEBUG_STREAM << "[visgnd] lift=" << vLift DEBUG_STREAM << "[visgnd] lift=" << vLift
<< " at (" << localOrigin.linearPosition.x << ", " << " at (" << localOrigin.linearPosition.x << ", "
<< localOrigin.linearPosition.y << ", " << localOrigin.linearPosition.y << ", "
<< localOrigin.linearPosition.z << ")\n" << std::flush; << localOrigin.linearPosition.z << ")" << std::endl;
} }
} }
} }
@@ -3804,6 +3907,14 @@ void
// and a multi-solid frame compounds those reflections into a garbage // and a multi-solid frame compounds those reflections into a garbage
// velocity that priced mech-vs-mech damage 4x-40x too high (mp_a.log:32651). // velocity that priced mech-vs-mech damage 4x-40x too high (mp_a.log:32651).
frameEntryWorldVelocity = savedWorldVel; frameEntryWorldVelocity = savedWorldVel;
// ram contact-edge decay: once contact with the last victim has been
// broken for the linger window, re-arm the one-bump-one-hit gate
if (ramContactLinger > 0.0f)
{
ramContactLinger -= dt;
if (ramContactLinger <= 0.0f)
ramLastVictim = 0;
}
Damage dmg; // ctor zeroes damageAmount Damage dmg; // ctor zeroes damageAmount
if (cols != 0) if (cols != 0)
ProcessCollisionList(cols, dt, old_position, &dmg); ProcessCollisionList(cols, dt, old_position, &dmg);
@@ -3831,7 +3942,7 @@ void
} }
if (GroundLog()) if (GroundLog())
DEBUG_STREAM << "[ground] CRUNCH (crushable icon) at (" DEBUG_STREAM << "[ground] CRUNCH (crushable icon) at ("
<< savedPos.x << ", " << savedPos.z << ")\n" << std::flush; << savedPos.x << ", " << savedPos.z << ")" << std::endl;
} }
else if (dmg.damageAmount > 0.0f) // blocking hit: FULL FRAME REJECTION else if (dmg.damageAmount > 0.0f) // blocking hit: FULL FRAME REJECTION
{ {
@@ -3893,12 +4004,12 @@ void
} }
if (GroundLog()) if (GroundLog())
DEBUG_STREAM << "[ground] BLOCK + CRASH (iv2=" << iv2 DEBUG_STREAM << "[ground] BLOCK + CRASH (iv2=" << iv2
<< " clip=" << animationClips[0x20] << ")\n" << std::flush; << " clip=" << animationClips[0x20] << ")" << std::endl;
} }
else if (GroundLog()) else if (GroundLog())
DEBUG_STREAM << "[ground] BLOCK dmg=" << dmg.damageAmount DEBUG_STREAM << "[ground] BLOCK dmg=" << dmg.damageAmount
<< " at (" << old_position.x << ", " << old_position.z << " at (" << old_position.x << ", " << old_position.z
<< ")\n" << std::flush; << ")" << std::endl;
} }
// 1-Hz telemetry (verification: h / y / hit-miss); INSIDE-SOLID detector: // 1-Hz telemetry (verification: h / y / hit-miss); INSIDE-SOLID detector:
@@ -3915,7 +4026,7 @@ void
DEBUG_STREAM << "[ground] INSIDE-SOLID streak at (" DEBUG_STREAM << "[ground] INSIDE-SOLID streak at ("
<< localOrigin.linearPosition.x << ", " << localOrigin.linearPosition.x << ", "
<< localOrigin.linearPosition.y << ", " << localOrigin.linearPosition.y << ", "
<< localOrigin.linearPosition.z << ")\n" << std::flush; << localOrigin.linearPosition.z << ")" << std::endl;
} }
else else
s_zeroStreak = 0; s_zeroStreak = 0;
@@ -3928,7 +4039,7 @@ void
<< " lift=" << collisionTemplate->minY << " lift=" << collisionTemplate->minY
<< " pos=(" << localOrigin.linearPosition.x << ", " << " pos=(" << localOrigin.linearPosition.x << ", "
<< localOrigin.linearPosition.y << ", " << localOrigin.linearPosition.y << ", "
<< localOrigin.linearPosition.z << ")\n" << std::flush; << localOrigin.linearPosition.z << ")" << std::endl;
} }
} }
} }
@@ -3989,7 +4100,18 @@ static void
{ {
Damage dmg; Damage dmg;
dmg.damageType = Damage::CollisionDamageType; dmg.damageType = Damage::CollisionDamageType;
dmg.damageAmount = resolved->damageAmount; // RAM ECONOMY NORMALIZATION [T3, evidence-forced 2026-07-12]: StaticBounce
// prices the kinetic loss with the AUTHORED moverMass (~1.3e6 units) --
// ~59,000 "points" for a 10 m/s ram, against zones whose armor absorbs
// 1 point per point (scale = 1/armorPoints, armor 50-140: the [zone-armor]
// dump). The BINARY dispatches this raw number [T1 @part_012:15324] --
// but on the pod network it only ever hit the local REPLICANT of the
// victim, where it evaporated (the master never heard it; MECH.CPP:986
// merely warns). Our MP forwards replicant damage (task #47, required
// for weapons), which surfaced the dormant 59K as a one-shot ram-kill.
// Normalize by 1e-3 (the mass-unit/armor-point scale): a walking bump =
// a few points, a full charge = tens -- the armor-table economy.
dmg.damageAmount = resolved->damageAmount * 0.001f;
dmg.surfaceNormal = resolved->surfaceNormal; dmg.surfaceNormal = resolved->surfaceNormal;
dmg.impactPoint = Point3D( dmg.impactPoint = Point3D(
(collision.collisionSlice.minX + collision.collisionSlice.maxX) * 0.5f, (collision.collisionSlice.minX + collision.collisionSlice.maxX) * 0.5f,
@@ -4016,9 +4138,11 @@ static void
DEBUG_STREAM << "[collide-tx] " << inflictor->GetEntityID() DEBUG_STREAM << "[collide-tx] " << inflictor->GetEntityID()
<< " rams " << victim->GetEntityID() << " rams " << victim->GetEntityID()
<< " dmg=" << resolved->damageAmount << " dmg=" << resolved->damageAmount
<< " mass=" << inflictor->moverMass
<< " e=" << inflictor->elasticityCoefficient
<< " |v|=" << (Scalar)sqrtf(v.x*v.x + v.y*v.y + v.z*v.z) << " |v|=" << (Scalar)sqrtf(v.x*v.x + v.y*v.y + v.z*v.z)
<< " at(" << dmg.impactPoint.x << "," << dmg.impactPoint.y << " at(" << dmg.impactPoint.x << "," << dmg.impactPoint.y
<< "," << dmg.impactPoint.z << ")\n" << std::flush; << "," << dmg.impactPoint.z << ")" << std::endl;
} }
} }
@@ -4100,9 +4224,23 @@ void
// is UNBLOCKED (was deferred: zone==-1 used to be dropped). // is UNBLOCKED (was deferred: zone==-1 used to be dropped).
if (owner->IsDerivedFrom(*Mech::GetClassDerivations())) if (owner->IsDerivedFrom(*Mech::GetClassDerivations()))
{ {
// RAM CONTACT EDGE (see mech.hpp): dispatch damage only on a FRESH
// contact with this victim; while pressed, refresh the linger so
// sustained contact (held stick / respawn overlap) stays a BLOCK,
// not a 60Hz damage grinder -- the binary's bounce-separation made
// this edge implicit; our gait-derived velocity needs it explicit.
if (owner == ramLastVictim && ramContactLinger > 0.0f)
{
ramContactLinger = 0.35f; // still pressed: refresh, no damage
}
else
{
ramLastVictim = owner;
ramContactLinger = 0.35f;
BTDispatchCollisionDamage(this, owner, damage, collision); BTDispatchCollisionDamage(this, owner, damage, collision);
} }
} }
}
// --- CulturalIcon owner (buildings/trees/props) (:15361-15404) ---------- // --- CulturalIcon owner (buildings/trees/props) (:15361-15404) ----------
if (owner != 0 && owner->IsDerivedFrom(*CulturalIcon::GetClassDerivations())) if (owner != 0 && owner->IsDerivedFrom(*CulturalIcon::GetClassDerivations()))
@@ -4234,6 +4372,11 @@ void
continue; continue;
++energyOrdinal; ++energyOrdinal;
Emitter *em = (Emitter *)ws; Emitter *em = (Emitter *)ws;
// replicant beams: age the replicated discharge locally (a lost
// beam-END record otherwise pins the beam on forever -- see
// Emitter::ReplicantServiceBeam)
if (GetInstance() == Entity::ReplicantInstance)
em->ReplicantServiceBeam(ttl1);
if (!em->BeamOn()) if (!em->BeamOn())
continue; continue;
Point3D mz; Point3D mz;
@@ -4293,6 +4436,21 @@ void
(((unsigned)(40.0f + r * 215.0f) & 0xFF) << 16) | (((unsigned)(40.0f + r * 215.0f) & 0xFF) << 16) |
(((unsigned)(40.0f + g * 215.0f) & 0xFF) << 8) | (((unsigned)(40.0f + g * 215.0f) & 0xFF) << 8) |
((unsigned)(40.0f + b * 215.0f) & 0xFF); ((unsigned)(40.0f + b * 215.0f) & 0xFF);
// PHANTOM-BEAM FORENSICS (BT_FIRE_LOG): identify every drawn beam --
// owner/instance, roster slot, class, discharge state, colour, ends.
if (getenv("BT_FIRE_LOG"))
{
static int s_bd = 0;
if ((++s_bd % 30) == 1) // ~2Hz per sustained beam
DEBUG_STREAM << "[beam-draw] mech=" << GetEntityID()
<< " inst=" << (int)GetInstance()
<< " slot=" << wi << " cid=" << wcid
<< " timer=" << em->DischargeTimer()
<< " pip=(" << r << "," << g << "," << b << ")"
<< " mz=(" << mz.x << "," << mz.y << "," << mz.z << ")"
<< " end=(" << bend.x << "," << bend.y << "," << bend.z
<< ")" << std::endl;
}
BTPushBeamKind(mz.x, mz.y, mz.z, bend.x, bend.y, bend.z, BTPushBeamKind(mz.x, mz.y, mz.z, bend.x, bend.y, bend.z,
tint, ttl1, 1.0f /* natural model width */, tint, ttl1, 1.0f /* natural model width */,
isPPC ? 1 : 0 /* ppc.bgf : ermlaser.bgf */); isPPC ? 1 : 0 /* ppc.bgf : ermlaser.bgf */);
@@ -4300,6 +4458,6 @@ void
DEBUG_STREAM << "[beam] " << (isPPC ? "PPC" : "laser") << " #" << wi DEBUG_STREAM << "[beam] " << (isPPC ? "PPC" : "laser") << " #" << wi
<< " mz=(" << mz.x << "," << mz.y << "," << mz.z << " mz=(" << mz.x << "," << mz.y << "," << mz.z
<< ") end=(" << bend.x << "," << bend.y << "," << bend.z << ") end=(" << bend.x << "," << bend.y << "," << bend.z
<< ") rgb=(" << r << "," << g << "," << b << ")\n" << std::flush; << ") rgb=(" << r << "," << g << "," << b << ")" << std::endl;
} }
} }
+26
View File
@@ -240,6 +240,9 @@ Mech__DamageZone::Mech__DamageZone(
// members (read raw from the stream by the base ctor, DAMAGE.cpp:301-307); we // members (read raw from the stream by the base ctor, DAMAGE.cpp:301-307); we
// normalise them in place here -- not a raw 0x144/0x140 offset (which would be the // normalise them in place here -- not a raw 0x144/0x140 offset (which would be the
// binary layout, wrong when compiled against the engine base). // binary layout, wrong when compiled against the engine base).
Scalar rawScale[Damage::DamageTypeCount];
for (int type = 0; type < Damage::DamageTypeCount; ++type)
rawScale[type] = damageScale[type];
for (int type = 0; type < Damage::DamageTypeCount; ++type) // 5 damage types for (int type = 0; type < Damage::DamageTypeCount; ++type) // 5 damage types
{ {
Scalar even = 1.0f / defaultArmorPoints; // _DAT_0049d1c4 / count Scalar even = 1.0f / defaultArmorPoints; // _DAT_0049d1c4 / count
@@ -248,6 +251,19 @@ Mech__DamageZone::Mech__DamageZone(
if (rightLeg || leftLeg) if (rightLeg || leftLeg)
damageScale[type] *= 0.5f; // _DAT_0049d1cc damageScale[type] *= 0.5f; // _DAT_0049d1cc
} }
// ARMOR ECONOMY DUMP (the ram-kill forensics): the authored per-type
// absorption -- damageLevel += amount * scale[type]; level 1.0 = zone
// destroyed. scale[0] (Collision) x the StaticBounce kinetic number is
// the authored ram economy.
if (getenv("BT_DMG_LOG"))
DEBUG_STREAM << "[zone-armor] zone=" << damageZoneIndex
<< " armorPts=" << defaultArmorPoints
<< " raw={" << rawScale[0] << "," << rawScale[1] << "," << rawScale[2]
<< "," << rawScale[3] << "," << rawScale[4] << "}"
<< " scale={" << damageScale[0] << "," << damageScale[1] << ","
<< damageScale[2] << "," << damageScale[3] << "," << damageScale[4] << "}"
<< " leg=" << (int)(rightLeg || leftLeg)
<< " vital=" << (int)vitalDamageZone << std::endl;
// //
// Critical-subsystem table. // Critical-subsystem table.
@@ -1055,6 +1071,16 @@ void
continue; continue;
lastLevel[i] = level; lastLevel[i] = level;
// AUTHENTIC GATE (decomp re-verified 2026-07-12, workflow): the binary
// fires the CURRENT band descriptor's effect whenever the zone's
// damage-CHANGED flag is set (+0xb8 & 4, set by DamageZone::TakeDamage,
// coalesced per tick) -- i.e. EVERY hit on a damaged mech re-puffs its
// band effect. Bands 3/4 are authored ember/bright-FIRE plumes, so a
// nearly-destroyed mech under fire burns continuously (the demo look).
// A level RISE this tick IS the flag analog -- no crossing test. (The
// earlier metronome was SHKWAVE mispacing, not this path; an observer
// node sees a few replication-step puffs per volley -- smoke, fine.)
const Mech__DamageZone::DamageDescriptor *d = zone->DescriptorForLevel(level); const Mech__DamageZone::DamageDescriptor *d = zone->DescriptorForLevel(level);
if (level >= 1.0f && prev < 1.0f) // just destroyed -> the Destroyed descriptor if (level >= 1.0f && prev < 1.0f) // just destroyed -> the Destroyed descriptor
{ {
+6 -3
View File
@@ -72,7 +72,8 @@
// shooter's frame (up-tilted rack launch); damage <= 0 = a VISUAL-ONLY round. // shooter's frame (up-tilted rack launch); damage <= 0 = a VISUAL-ONLY round.
extern void BTPushProjectile(const Point3D &muzzle, void *shooter, void *target, extern void BTPushProjectile(const Point3D &muzzle, void *shooter, void *target,
const Point3D &targetPos, Scalar speed, Scalar damage, const Point3D &targetPos, Scalar speed, Scalar damage,
const Vector3D *launch_velocity = 0, int guided = 1); const Vector3D *launch_velocity = 0, int guided = 1,
int weapon_subsys = -1);
//########################################################################### //###########################################################################
// Port-side salvo replication state (missile-visibility wave). // Port-side salvo replication state (missile-visibility wave).
@@ -305,7 +306,8 @@ void MissileLauncher::FireWeapon()
int nmiss = (missileCount > 0 && missileCount < 40) ? missileCount : 1; int nmiss = (missileCount > 0 && missileCount < 40) ? missileCount : 1;
for (int i = 0; i < nmiss; ++i) for (int i = 0; i < nmiss; ++i)
BTPushProjectile(muzzle, o, target, targetPos, speed, damageData.damageAmount, BTPushProjectile(muzzle, o, target, targetPos, speed, damageData.damageAmount,
&launchVelocity); &launchVelocity, 1,
subsystemID /*messmgr explosion bundling at impact (task #7)*/);
// Salvo replication (missile-visibility wave): bump the fire counter + stamp // Salvo replication (missile-visibility wave): bump the fire counter + stamp
// the aim point; the extended update record carries both to peer nodes. // the aim point; the extended update record carries both to peer nodes.
@@ -376,7 +378,8 @@ void
n = (missileCount > 0 && missileCount < 40) ? missileCount : 1; n = (missileCount > 0 && missileCount < 40) ? missileCount : 1;
for (int i = 0; i < n; ++i) for (int i = 0; i < n; ++i)
BTPushProjectile(mz, owner, 0 /*no entity: aim point only*/, BTPushProjectile(mz, owner, 0 /*no entity: aim point only*/,
rec->salvoTarget, spd, 0.0f /*VISUAL*/, &launchVelocity); rec->salvoTarget, spd, 0.0f /*VISUAL*/, &launchVelocity, 1,
subsystemID /*per-round detonation resolve on this node*/);
if (getenv("BT_PROJ_LOG")) if (getenv("BT_PROJ_LOG"))
DEBUG_STREAM << "[projectile] REPLICANT salvo x" << n DEBUG_STREAM << "[projectile] REPLICANT salvo x" << n
<< " at(" << rec->salvoTarget.x << "," << rec->salvoTarget.y << " at(" << rec->salvoTarget.x << "," << rec->salvoTarget.y
+4 -2
View File
@@ -94,7 +94,8 @@
// crash, 2026-07-12 -- gotcha #3). // crash, 2026-07-12 -- gotcha #3).
extern void BTPushProjectile(const Point3D &muzzle, void *shooter, void *target, extern void BTPushProjectile(const Point3D &muzzle, void *shooter, void *target,
const Point3D &targetPos, Scalar speed, Scalar damage, const Point3D &targetPos, Scalar speed, Scalar damage,
const Vector3D *launch_velocity = 0, int guided = 1); const Vector3D *launch_velocity = 0, int guided = 1,
int weapon_subsys = -1);
//############################################################################# //#############################################################################
@@ -697,7 +698,8 @@ void
+ launchVelocity.y*launchVelocity.y + launchVelocity.y*launchVelocity.y
+ launchVelocity.z*launchVelocity.z); + launchVelocity.z*launchVelocity.z);
BTPushProjectile(muzzle, o, target, targetPos, speed, damageData.damageAmount, BTPushProjectile(muzzle, o, target, targetPos, speed, damageData.damageAmount,
0 /*aim straight at the pick*/, 0 /*BALLISTIC: no seeker on a shell*/); 0 /*aim straight at the pick*/, 0 /*BALLISTIC: no seeker on a shell*/,
subsystemID /*messmgr explosion bundling at impact (task #7)*/);
simulationFlags |= 0x1; // replication-dirty simulationFlags |= 0x1; // replication-dirty
Check_Fpu(); Check_Fpu();
+56
View File
@@ -0,0 +1,56 @@
"""Capture timed screenshots of a btl4 window via PrintWindow (works occluded).
Usage: python fxshot.py [count] [interval_s] [outprefix] [nodesubstr]
"""
import ctypes, ctypes.wintypes as wt, sys, time
user32 = ctypes.windll.user32
gdi32 = ctypes.windll.gdi32
count = int(sys.argv[1]) if len(sys.argv) > 1 else 8
interval = float(sys.argv[2]) if len(sys.argv) > 2 else 0.4
prefix = sys.argv[3] if len(sys.argv) > 3 else 'fx'
want = sys.argv[4] if len(sys.argv) > 4 else '1501'
titles = []
def enum_cb(hwnd, lparam):
n = user32.GetWindowTextLengthW(hwnd)
if n > 0:
buf = ctypes.create_unicode_buffer(n + 1)
user32.GetWindowTextW(hwnd, buf, n + 1)
if buf.value.startswith('BattleTech') and want in buf.value:
titles.append((hwnd, buf.value))
return True
WNDENUMPROC = ctypes.WINFUNCTYPE(ctypes.c_bool, wt.HWND, wt.LPARAM)
user32.EnumWindows(WNDENUMPROC(enum_cb), 0)
if not titles:
print('no matching BattleTech window'); sys.exit(1)
hwnd, title = titles[0]
print('capturing:', title)
rect = wt.RECT()
user32.GetClientRect(hwnd, ctypes.byref(rect))
w, h = rect.right, rect.bottom
hdc = user32.GetDC(0)
mem = gdi32.CreateCompatibleDC(hdc)
bmp = gdi32.CreateCompatibleBitmap(hdc, w, h)
gdi32.SelectObject(mem, bmp)
class BMIH(ctypes.Structure):
_fields_ = [('biSize', wt.DWORD), ('biWidth', wt.LONG), ('biHeight', wt.LONG),
('biPlanes', wt.WORD), ('biBitCount', wt.WORD), ('biCompression', wt.DWORD),
('biSizeImage', wt.DWORD), ('biXPelsPerMeter', wt.LONG), ('biYPelsPerMeter', wt.LONG),
('biClrUsed', wt.DWORD), ('biClrImportant', wt.DWORD)]
from PIL import Image
for i in range(count):
# PW_RENDERFULLCONTENT (2) captures D3D content
user32.PrintWindow(hwnd, mem, 2 | 1) # +PW_CLIENTONLY
bmi = BMIH(ctypes.sizeof(BMIH), w, -h, 1, 32, 0, 0, 0, 0, 0, 0)
buf = ctypes.create_string_buffer(w * h * 4)
gdi32.GetDIBits(mem, bmp, 0, h, buf, ctypes.byref(bmi), 0)
img = Image.frombuffer('RGB', (w, h), buf.raw, 'raw', 'BGRX', 0, 1)
img.save(r'C:\git\bt411\scratchpad\%s_%d.png' % (prefix, i))
time.sleep(interval)
print('saved %d shots %dx%d' % (count, w, h))
+507
View File
@@ -0,0 +1,507 @@
/* Table of opcodes for the i860.
Copyright 1989, 1991, 2000, 2002, 2003 Free Software Foundation, Inc.
This file is part of GAS, the GNU Assembler, and GDB, the GNU disassembler.
GAS/GDB is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 1, or (at your option)
any later version.
GAS/GDB is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with GAS or GDB; see the file COPYING. If not, write to
the Free Software Foundation, 51 Franklin Street - Fifth Floor,
Boston, MA 02110-1301, USA. */
/* Structure of an opcode table entry. */
struct i860_opcode
{
/* The opcode name. */
const char *name;
/* Bits that must be set. */
unsigned long match;
/* Bits that must not be set. */
unsigned long lose;
const char *args;
/* Nonzero if this is a possible expand-instruction. */
char expand;
};
enum expand_type
{
E_MOV = 1, E_ADDR, E_U32, E_AND, E_S32, E_DELAY, XP_ONLY
};
/* All i860 opcodes are 32 bits, except for the pseudo-instructions
and the operations utilizing a 32-bit address expression, an
unsigned 32-bit constant, or a signed 32-bit constant.
These opcodes are expanded into a two-instruction sequence for
any situation where the immediate operand does not fit in 32 bits.
In the case of the add and subtract operations the expansion is
to a three-instruction sequence (ex: orh, or, adds). In cases
where the address is to be relocated, the instruction is
expanded to handle the worse case, this could be optimized at
the final link if the actual address were known.
The pseudoinstructions are: mov, fmov, pmov, nop, and fnop.
These instructions are implemented as a one or two instruction
sequence of other operations.
The match component is a mask saying which bits must match a
particular opcode in order for an instruction to be an instance
of that opcode.
The args component is a string containing one character
for each operand of the instruction.
Kinds of operands:
# Number used by optimizer. It is ignored.
1 src1 integer register.
2 src2 integer register.
d dest register.
c ctrlreg control register.
i 16 bit immediate.
I 16 bit immediate, aligned 2^0. (ld.b)
J 16 bit immediate, aligned 2^1. (ld.s)
K 16 bit immediate, aligned 2^2. (ld.l, {p}fld.l, fst.l)
L 16 bit immediate, aligned 2^3. ({p}fld.d, fst.d)
M 16 bit immediate, aligned 2^4. ({p}fld.q, fst.q)
5 5 bit immediate.
l lbroff 26 bit PC relative immediate.
r sbroff 16 bit PC relative immediate.
s split 16 bit immediate.
S split 16 bit immediate, aligned 2^0. (st.b)
T split 16 bit immediate, aligned 2^1. (st.s)
U split 16 bit immediate, aligned 2^2. (st.l)
e src1 floating point register.
f src2 floating point register.
g dest floating point register. */
/* The order of the opcodes in this table is significant. The assembler
requires that all instances of the same mnemonic must be consecutive.
If they aren't, the assembler will not function properly.
The order of opcodes does not affect the disassembler. */
static const struct i860_opcode i860_opcodes[] =
{
/* REG-Format Instructions. */
{ "ld.c", 0x30000000, 0xcc000000, "c,d", 0 }, /* ld.c csrc2,idest */
{ "ld.b", 0x00000000, 0xfc000000, "1(2),d", 0 }, /* ld.b isrc1(isrc2),idest */
{ "ld.b", 0x04000000, 0xf8000000, "I(2),d", E_ADDR }, /* ld.b #const(isrc2),idest */
{ "ld.s", 0x10000000, 0xec000001, "1(2),d", 0 }, /* ld.s isrc1(isrc2),idest */
{ "ld.s", 0x14000000, 0xe8000001, "J(2),d", E_ADDR }, /* ld.s #const(isrc2),idest */
{ "ld.l", 0x10000001, 0xec000000, "1(2),d", 0 }, /* ld.l isrc1(isrc2),idest */
{ "ld.l", 0x14000001, 0xe8000000, "K(2),d", E_ADDR }, /* ld.l #const(isrc2),idest */
{ "st.c", 0x38000000, 0xc4000000, "1,c", 0 }, /* st.c isrc1ni,csrc2 */
{ "st.b", 0x0c000000, 0xf0000000, "1,S(2)", E_ADDR }, /* st.b isrc1ni,#const(isrc2) */
{ "st.s", 0x1c000000, 0xe0000001, "1,T(2)", E_ADDR }, /* st.s isrc1ni,#const(isrc2) */
{ "st.l", 0x1c000001, 0xe0000000, "1,U(2)", E_ADDR }, /* st.l isrc1ni,#const(isrc2) */
{ "ixfr", 0x08000000, 0xf4000000, "1,g", 0 }, /* ixfr isrc1ni,fdest */
{ "fld.l", 0x20000002, 0xdc000001, "1(2),g", 0 }, /* fld.l isrc1(isrc2),fdest */
{ "fld.l", 0x24000002, 0xd8000001, "K(2),g", E_ADDR }, /* fld.l #const(isrc2),fdest */
{ "fld.l", 0x20000003, 0xdc000000, "1(2)++,g", 0 }, /* fld.l isrc1(isrc2)++,fdest */
{ "fld.l", 0x24000003, 0xd8000000, "K(2)++,g", E_ADDR }, /* fld.l #const(isrc2)++,fdest */
{ "fld.d", 0x20000000, 0xdc000007, "1(2),g", 0 }, /* fld.d isrc1(isrc2),fdest */
{ "fld.d", 0x24000000, 0xd8000007, "L(2),g", E_ADDR }, /* fld.d #const(isrc2),fdest */
{ "fld.d", 0x20000001, 0xdc000006, "1(2)++,g", 0 }, /* fld.d isrc1(isrc2)++,fdest */
{ "fld.d", 0x24000001, 0xd8000006, "L(2)++,g", E_ADDR }, /* fld.d #const(isrc2)++,fdest */
{ "fld.q", 0x20000004, 0xdc000003, "1(2),g", 0 }, /* fld.q isrc1(isrc2),fdest */
{ "fld.q", 0x24000004, 0xd8000003, "M(2),g", E_ADDR }, /* fld.q #const(isrc2),fdest */
{ "fld.q", 0x20000005, 0xdc000002, "1(2)++,g", 0 }, /* fld.q isrc1(isrc2)++,fdest */
{ "fld.q", 0x24000005, 0xd8000002, "M(2)++,g", E_ADDR }, /* fld.q #const(isrc2)++,fdest */
{ "pfld.l", 0x60000002, 0x9c000001, "1(2),g", 0 }, /* pfld.l isrc1(isrc2),fdest */
{ "pfld.l", 0x64000002, 0x98000001, "K(2),g", E_ADDR }, /* pfld.l #const(isrc2),fdest */
{ "pfld.l", 0x60000003, 0x9c000000, "1(2)++,g", 0 }, /* pfld.l isrc1(isrc2)++,fdest */
{ "pfld.l", 0x64000003, 0x98000000, "K(2)++,g", E_ADDR }, /* pfld.l #const(isrc2)++,fdest */
{ "pfld.d", 0x60000000, 0x9c000007, "1(2),g", 0 }, /* pfld.d isrc1(isrc2),fdest */
{ "pfld.d", 0x64000000, 0x98000007, "L(2),g", E_ADDR }, /* pfld.d #const(isrc2),fdest */
{ "pfld.d", 0x60000001, 0x9c000006, "1(2)++,g", 0 }, /* pfld.d isrc1(isrc2)++,fdest */
{ "pfld.d", 0x64000001, 0x98000006, "L(2)++,g", E_ADDR }, /* pfld.d #const(isrc2)++,fdest */
{ "pfld.q", 0x60000004, 0x9c000003, "1(2),g", XP_ONLY }, /* pfld.q isrc1(isrc2),fdest */
{ "pfld.q", 0x64000004, 0x98000003, "L(2),g", XP_ONLY }, /* pfld.q #const(isrc2),fdest */
{ "pfld.q", 0x60000005, 0x9c000002, "1(2)++,g", XP_ONLY }, /* pfld.q isrc1(isrc2)++,fdest */
{ "pfld.q", 0x64000005, 0x98000002, "L(2)++,g", XP_ONLY }, /* pfld.q #const(isrc2)++,fdest */
{ "fst.l", 0x28000002, 0xd4000001, "g,1(2)", 0 }, /* fst.l fdest,isrc1(isrc2) */
{ "fst.l", 0x2c000002, 0xd0000001, "g,K(2)", E_ADDR }, /* fst.l fdest,#const(isrc2) */
{ "fst.l", 0x28000003, 0xd4000000, "g,1(2)++", 0 }, /* fst.l fdest,isrc1(isrc2)++ */
{ "fst.l", 0x2c000003, 0xd0000000, "g,K(2)++", E_ADDR }, /* fst.l fdest,#const(isrc2)++ */
{ "fst.d", 0x28000000, 0xd4000007, "g,1(2)", 0 }, /* fst.d fdest,isrc1(isrc2) */
{ "fst.d", 0x2c000000, 0xd0000007, "g,L(2)", E_ADDR }, /* fst.d fdest,#const(isrc2) */
{ "fst.d", 0x28000001, 0xd4000006, "g,1(2)++", 0 }, /* fst.d fdest,isrc1(isrc2)++ */
{ "fst.d", 0x2c000001, 0xd0000006, "g,L(2)++", E_ADDR }, /* fst.d fdest,#const(isrc2)++ */
{ "fst.q", 0x28000004, 0xd4000003, "g,1(2)", 0 }, /* fst.d fdest,isrc1(isrc2) */
{ "fst.q", 0x2c000004, 0xd0000003, "g,M(2)", E_ADDR }, /* fst.d fdest,#const(isrc2) */
{ "fst.q", 0x28000005, 0xd4000002, "g,1(2)++", 0 }, /* fst.d fdest,isrc1(isrc2)++ */
{ "fst.q", 0x2c000005, 0xd0000002, "g,M(2)++", E_ADDR }, /* fst.d fdest,#const(isrc2)++ */
{ "pst.d", 0x3c000000, 0xc0000007, "g,L(2)", E_ADDR }, /* pst.d fdest,#const(isrc2) */
{ "pst.d", 0x3c000001, 0xc0000006, "g,L(2)++", E_ADDR }, /* pst.d fdest,#const(isrc2)++ */
{ "addu", 0x80000000, 0x7c000000, "1,2,d", 0 }, /* addu isrc1,isrc2,idest */
{ "addu", 0x84000000, 0x78000000, "i,2,d", E_S32 }, /* addu #const,isrc2,idest */
{ "adds", 0x90000000, 0x6c000000, "1,2,d", 0 }, /* adds isrc1,isrc2,idest */
{ "adds", 0x94000000, 0x68000000, "i,2,d", E_S32 }, /* adds #const,isrc2,idest */
{ "subu", 0x88000000, 0x74000000, "1,2,d", 0 }, /* subu isrc1,isrc2,idest */
{ "subu", 0x8c000000, 0x70000000, "i,2,d", E_S32 }, /* subu #const,isrc2,idest */
{ "subs", 0x98000000, 0x64000000, "1,2,d", 0 }, /* subs isrc1,isrc2,idest */
{ "subs", 0x9c000000, 0x60000000, "i,2,d", E_S32 }, /* subs #const,isrc2,idest */
{ "shl", 0xa0000000, 0x5c000000, "1,2,d", 0 }, /* shl isrc1,isrc2,idest */
{ "shl", 0xa4000000, 0x58000000, "i,2,d", 0 }, /* shl #const,isrc2,idest */
{ "shr", 0xa8000000, 0x54000000, "1,2,d", 0 }, /* shr isrc1,isrc2,idest */
{ "shr", 0xac000000, 0x50000000, "i,2,d", 0 }, /* shr #const,isrc2,idest */
{ "shrd", 0xb0000000, 0x4c000000, "1,2,d", 0 }, /* shrd isrc1,isrc2,idest */
{ "shra", 0xb8000000, 0x44000000, "1,2,d", 0 }, /* shra isrc1,isrc2,idest */
{ "shra", 0xbc000000, 0x40000000, "i,2,d", 0 }, /* shra #const,isrc2,idest */
{ "mov", 0xa0000000, 0x5c00f800, "2,d", 0 }, /* shl r0,isrc2,idest */
{ "mov", 0x94000000, 0x69e00000, "i,d", E_MOV }, /* adds #const,r0,idest */
{ "nop", 0xa0000000, 0x5ffff800, "", 0 }, /* shl r0,r0,r0 */
{ "fnop", 0xb0000000, 0x4ffff800, "", 0 }, /* shrd r0,r0,r0 */
{ "trap", 0x44000000, 0xb8000000, "1,2,d", 0 }, /* trap isrc1ni,isrc2,idest */
{ "flush", 0x34000004, 0xc81f0003, "L(2)", E_ADDR }, /* flush #const(isrc2) */
{ "flush", 0x34000005, 0xc81f0002, "L(2)++", E_ADDR }, /* flush #const(isrc2)++ */
{ "and", 0xc0000000, 0x3c000000, "1,2,d", 0 }, /* and isrc1,isrc2,idest */
{ "and", 0xc4000000, 0x38000000, "i,2,d", E_AND }, /* and #const,isrc2,idest */
{ "andh", 0xcc000000, 0x30000000, "i,2,d", 0 }, /* andh #const,isrc2,idest */
{ "andnot", 0xd0000000, 0x2c000000, "1,2,d", 0 }, /* andnot isrc1,isrc2,idest */
{ "andnot", 0xd4000000, 0x28000000, "i,2,d", E_U32 }, /* andnot #const,isrc2,idest */
{ "andnoth", 0xdc000000, 0x20000000, "i,2,d", 0 }, /* andnoth #const,isrc2,idest */
{ "or", 0xe0000000, 0x1c000000, "1,2,d", 0 }, /* or isrc1,isrc2,idest */
{ "or", 0xe4000000, 0x18000000, "i,2,d", E_U32 }, /* or #const,isrc2,idest */
{ "orh", 0xec000000, 0x10000000, "i,2,d", 0 }, /* orh #const,isrc2,idest */
{ "xor", 0xf0000000, 0x0c000000, "1,2,d", 0 }, /* xor isrc1,isrc2,idest */
{ "xor", 0xf4000000, 0x08000000, "i,2,d", E_U32 }, /* xor #const,isrc2,idest */
{ "xorh", 0xfc000000, 0x00000000, "i,2,d", 0 }, /* xorh #const,isrc2,idest */
{ "bte", 0x58000000, 0xa4000000, "1,2,r", 0 }, /* bte isrc1s,isrc2,sbroff */
{ "bte", 0x5c000000, 0xa0000000, "5,2,r", 0 }, /* bte #const5,isrc2,sbroff */
{ "btne", 0x50000000, 0xac000000, "1,2,r", 0 }, /* btne isrc1s,isrc2,sbroff */
{ "btne", 0x54000000, 0xa8000000, "5,2,r", 0 }, /* btne #const5,isrc2,sbroff */
{ "bla", 0xb4000000, 0x48000000, "1,2,r", E_DELAY }, /* bla isrc1s,isrc2,sbroff */
{ "bri", 0x40000000, 0xbc000000, "1", E_DELAY }, /* bri isrc1ni */
/* Core Escape Instruction Format */
{ "lock", 0x4c000001, 0xb000001e, "", 0 }, /* lock set BL in dirbase */
{ "calli", 0x4c000002, 0xb000001d, "1", E_DELAY }, /* calli isrc1ni */
{ "intovr", 0x4c000004, 0xb000001b, "", 0 }, /* intovr trap on integer overflow */
{ "unlock", 0x4c000007, 0xb0000018, "", 0 }, /* unlock clear BL in dirbase */
{ "ldio.l", 0x4c000408, 0xb00003f7, "2,d", XP_ONLY }, /* ldio.l isrc2,idest */
{ "ldio.s", 0x4c000208, 0xb00005f7, "2,d", XP_ONLY }, /* ldio.s isrc2,idest */
{ "ldio.b", 0x4c000008, 0xb00007f7, "2,d", XP_ONLY }, /* ldio.b isrc2,idest */
{ "stio.l", 0x4c000409, 0xb00003f6, "1,2", XP_ONLY }, /* stio.l isrc1ni,isrc2 */
{ "stio.s", 0x4c000209, 0xb00005f6, "1,2", XP_ONLY }, /* stio.s isrc1ni,isrc2 */
{ "stio.b", 0x4c000009, 0xb00007f6, "1,2", XP_ONLY }, /* stio.b isrc1ni,isrc2 */
{ "ldint.l", 0x4c00040a, 0xb00003f5, "2,d", XP_ONLY }, /* ldint.l isrc2,idest */
{ "ldint.s", 0x4c00020a, 0xb00005f5, "2,d", XP_ONLY }, /* ldint.s isrc2,idest */
{ "ldint.b", 0x4c00000a, 0xb00007f5, "2,d", XP_ONLY }, /* ldint.b isrc2,idest */
{ "scyc.b", 0x4c00000b, 0xb00007f4, "2", XP_ONLY }, /* scyc.b isrc2 */
/* CTRL-Format Instructions */
{ "br", 0x68000000, 0x94000000, "l", E_DELAY }, /* br lbroff */
{ "call", 0x6c000000, 0x90000000, "l", E_DELAY }, /* call lbroff */
{ "bc", 0x70000000, 0x8c000000, "l", 0 }, /* bc lbroff */
{ "bc.t", 0x74000000, 0x88000000, "l", E_DELAY }, /* bc.t lbroff */
{ "bnc", 0x78000000, 0x84000000, "l", 0 }, /* bnc lbroff */
{ "bnc.t", 0x7c000000, 0x80000000, "l", E_DELAY }, /* bnc.t lbroff */
/* Floating Point Escape Instruction Format - pfam.p fsrc1,fsrc2,fdest. */
{ "r2p1.ss", 0x48000400, 0xb40001ff, "e,f,g", 0 },
{ "r2p1.sd", 0x48000480, 0xb400017f, "e,f,g", 0 },
{ "r2p1.dd", 0x48000580, 0xb400007f, "e,f,g", 0 },
{ "r2pt.ss", 0x48000401, 0xb40001fe, "e,f,g", 0 },
{ "r2pt.sd", 0x48000481, 0xb400017e, "e,f,g", 0 },
{ "r2pt.dd", 0x48000581, 0xb400007e, "e,f,g", 0 },
{ "r2ap1.ss", 0x48000402, 0xb40001fd, "e,f,g", 0 },
{ "r2ap1.sd", 0x48000482, 0xb400017d, "e,f,g", 0 },
{ "r2ap1.dd", 0x48000582, 0xb400007d, "e,f,g", 0 },
{ "r2apt.ss", 0x48000403, 0xb40001fc, "e,f,g", 0 },
{ "r2apt.sd", 0x48000483, 0xb400017c, "e,f,g", 0 },
{ "r2apt.dd", 0x48000583, 0xb400007c, "e,f,g", 0 },
{ "i2p1.ss", 0x48000404, 0xb40001fb, "e,f,g", 0 },
{ "i2p1.sd", 0x48000484, 0xb400017b, "e,f,g", 0 },
{ "i2p1.dd", 0x48000584, 0xb400007b, "e,f,g", 0 },
{ "i2pt.ss", 0x48000405, 0xb40001fa, "e,f,g", 0 },
{ "i2pt.sd", 0x48000485, 0xb400017a, "e,f,g", 0 },
{ "i2pt.dd", 0x48000585, 0xb400007a, "e,f,g", 0 },
{ "i2ap1.ss", 0x48000406, 0xb40001f9, "e,f,g", 0 },
{ "i2ap1.sd", 0x48000486, 0xb4000179, "e,f,g", 0 },
{ "i2ap1.dd", 0x48000586, 0xb4000079, "e,f,g", 0 },
{ "i2apt.ss", 0x48000407, 0xb40001f8, "e,f,g", 0 },
{ "i2apt.sd", 0x48000487, 0xb4000178, "e,f,g", 0 },
{ "i2apt.dd", 0x48000587, 0xb4000078, "e,f,g", 0 },
{ "rat1p2.ss", 0x48000408, 0xb40001f7, "e,f,g", 0 },
{ "rat1p2.sd", 0x48000488, 0xb4000177, "e,f,g", 0 },
{ "rat1p2.dd", 0x48000588, 0xb4000077, "e,f,g", 0 },
{ "m12apm.ss", 0x48000409, 0xb40001f6, "e,f,g", 0 },
{ "m12apm.sd", 0x48000489, 0xb4000176, "e,f,g", 0 },
{ "m12apm.dd", 0x48000589, 0xb4000076, "e,f,g", 0 },
{ "ra1p2.ss", 0x4800040a, 0xb40001f5, "e,f,g", 0 },
{ "ra1p2.sd", 0x4800048a, 0xb4000175, "e,f,g", 0 },
{ "ra1p2.dd", 0x4800058a, 0xb4000075, "e,f,g", 0 },
{ "m12ttpa.ss", 0x4800040b, 0xb40001f4, "e,f,g", 0 },
{ "m12ttpa.sd", 0x4800048b, 0xb4000174, "e,f,g", 0 },
{ "m12ttpa.dd", 0x4800058b, 0xb4000074, "e,f,g", 0 },
{ "iat1p2.ss", 0x4800040c, 0xb40001f3, "e,f,g", 0 },
{ "iat1p2.sd", 0x4800048c, 0xb4000173, "e,f,g", 0 },
{ "iat1p2.dd", 0x4800058c, 0xb4000073, "e,f,g", 0 },
{ "m12tpm.ss", 0x4800040d, 0xb40001f2, "e,f,g", 0 },
{ "m12tpm.sd", 0x4800048d, 0xb4000172, "e,f,g", 0 },
{ "m12tpm.dd", 0x4800058d, 0xb4000072, "e,f,g", 0 },
{ "ia1p2.ss", 0x4800040e, 0xb40001f1, "e,f,g", 0 },
{ "ia1p2.sd", 0x4800048e, 0xb4000171, "e,f,g", 0 },
{ "ia1p2.dd", 0x4800058e, 0xb4000071, "e,f,g", 0 },
{ "m12tpa.ss", 0x4800040f, 0xb40001f0, "e,f,g", 0 },
{ "m12tpa.sd", 0x4800048f, 0xb4000170, "e,f,g", 0 },
{ "m12tpa.dd", 0x4800058f, 0xb4000070, "e,f,g", 0 },
/* Floating Point Escape Instruction Format - pfsm.p fsrc1,fsrc2,fdest. */
{ "r2s1.ss", 0x48000410, 0xb40001ef, "e,f,g", 0 },
{ "r2s1.sd", 0x48000490, 0xb400016f, "e,f,g", 0 },
{ "r2s1.dd", 0x48000590, 0xb400006f, "e,f,g", 0 },
{ "r2st.ss", 0x48000411, 0xb40001ee, "e,f,g", 0 },
{ "r2st.sd", 0x48000491, 0xb400016e, "e,f,g", 0 },
{ "r2st.dd", 0x48000591, 0xb400006e, "e,f,g", 0 },
{ "r2as1.ss", 0x48000412, 0xb40001ed, "e,f,g", 0 },
{ "r2as1.sd", 0x48000492, 0xb400016d, "e,f,g", 0 },
{ "r2as1.dd", 0x48000592, 0xb400006d, "e,f,g", 0 },
{ "r2ast.ss", 0x48000413, 0xb40001ec, "e,f,g", 0 },
{ "r2ast.sd", 0x48000493, 0xb400016c, "e,f,g", 0 },
{ "r2ast.dd", 0x48000593, 0xb400006c, "e,f,g", 0 },
{ "i2s1.ss", 0x48000414, 0xb40001eb, "e,f,g", 0 },
{ "i2s1.sd", 0x48000494, 0xb400016b, "e,f,g", 0 },
{ "i2s1.dd", 0x48000594, 0xb400006b, "e,f,g", 0 },
{ "i2st.ss", 0x48000415, 0xb40001ea, "e,f,g", 0 },
{ "i2st.sd", 0x48000495, 0xb400016a, "e,f,g", 0 },
{ "i2st.dd", 0x48000595, 0xb400006a, "e,f,g", 0 },
{ "i2as1.ss", 0x48000416, 0xb40001e9, "e,f,g", 0 },
{ "i2as1.sd", 0x48000496, 0xb4000169, "e,f,g", 0 },
{ "i2as1.dd", 0x48000596, 0xb4000069, "e,f,g", 0 },
{ "i2ast.ss", 0x48000417, 0xb40001e8, "e,f,g", 0 },
{ "i2ast.sd", 0x48000497, 0xb4000168, "e,f,g", 0 },
{ "i2ast.dd", 0x48000597, 0xb4000068, "e,f,g", 0 },
{ "rat1s2.ss", 0x48000418, 0xb40001e7, "e,f,g", 0 },
{ "rat1s2.sd", 0x48000498, 0xb4000167, "e,f,g", 0 },
{ "rat1s2.dd", 0x48000598, 0xb4000067, "e,f,g", 0 },
{ "m12asm.ss", 0x48000419, 0xb40001e6, "e,f,g", 0 },
{ "m12asm.sd", 0x48000499, 0xb4000166, "e,f,g", 0 },
{ "m12asm.dd", 0x48000599, 0xb4000066, "e,f,g", 0 },
{ "ra1s2.ss", 0x4800041a, 0xb40001e5, "e,f,g", 0 },
{ "ra1s2.sd", 0x4800049a, 0xb4000165, "e,f,g", 0 },
{ "ra1s2.dd", 0x4800059a, 0xb4000065, "e,f,g", 0 },
{ "m12ttsa.ss", 0x4800041b, 0xb40001e4, "e,f,g", 0 },
{ "m12ttsa.sd", 0x4800049b, 0xb4000164, "e,f,g", 0 },
{ "m12ttsa.dd", 0x4800059b, 0xb4000064, "e,f,g", 0 },
{ "iat1s2.ss", 0x4800041c, 0xb40001e3, "e,f,g", 0 },
{ "iat1s2.sd", 0x4800049c, 0xb4000163, "e,f,g", 0 },
{ "iat1s2.dd", 0x4800059c, 0xb4000063, "e,f,g", 0 },
{ "m12tsm.ss", 0x4800041d, 0xb40001e2, "e,f,g", 0 },
{ "m12tsm.sd", 0x4800049d, 0xb4000162, "e,f,g", 0 },
{ "m12tsm.dd", 0x4800059d, 0xb4000062, "e,f,g", 0 },
{ "ia1s2.ss", 0x4800041e, 0xb40001e1, "e,f,g", 0 },
{ "ia1s2.sd", 0x4800049e, 0xb4000161, "e,f,g", 0 },
{ "ia1s2.dd", 0x4800059e, 0xb4000061, "e,f,g", 0 },
{ "m12tsa.ss", 0x4800041f, 0xb40001e0, "e,f,g", 0 },
{ "m12tsa.sd", 0x4800049f, 0xb4000160, "e,f,g", 0 },
{ "m12tsa.dd", 0x4800059f, 0xb4000060, "e,f,g", 0 },
/* Floating Point Escape Instruction Format - pfmam.p fsrc1,fsrc2,fdest. */
{ "mr2p1.ss", 0x48000000, 0xb40005ff, "e,f,g", 0 },
{ "mr2p1.sd", 0x48000080, 0xb400057f, "e,f,g", 0 },
{ "mr2p1.dd", 0x48000180, 0xb400047f, "e,f,g", 0 },
{ "mr2pt.ss", 0x48000001, 0xb40005fe, "e,f,g", 0 },
{ "mr2pt.sd", 0x48000081, 0xb400057e, "e,f,g", 0 },
{ "mr2pt.dd", 0x48000181, 0xb400047e, "e,f,g", 0 },
{ "mr2mp1.ss", 0x48000002, 0xb40005fd, "e,f,g", 0 },
{ "mr2mp1.sd", 0x48000082, 0xb400057d, "e,f,g", 0 },
{ "mr2mp1.dd", 0x48000182, 0xb400047d, "e,f,g", 0 },
{ "mr2mpt.ss", 0x48000003, 0xb40005fc, "e,f,g", 0 },
{ "mr2mpt.sd", 0x48000083, 0xb400057c, "e,f,g", 0 },
{ "mr2mpt.dd", 0x48000183, 0xb400047c, "e,f,g", 0 },
{ "mi2p1.ss", 0x48000004, 0xb40005fb, "e,f,g", 0 },
{ "mi2p1.sd", 0x48000084, 0xb400057b, "e,f,g", 0 },
{ "mi2p1.dd", 0x48000184, 0xb400047b, "e,f,g", 0 },
{ "mi2pt.ss", 0x48000005, 0xb40005fa, "e,f,g", 0 },
{ "mi2pt.sd", 0x48000085, 0xb400057a, "e,f,g", 0 },
{ "mi2pt.dd", 0x48000185, 0xb400047a, "e,f,g", 0 },
{ "mi2mp1.ss", 0x48000006, 0xb40005f9, "e,f,g", 0 },
{ "mi2mp1.sd", 0x48000086, 0xb4000579, "e,f,g", 0 },
{ "mi2mp1.dd", 0x48000186, 0xb4000479, "e,f,g", 0 },
{ "mi2mpt.ss", 0x48000007, 0xb40005f8, "e,f,g", 0 },
{ "mi2mpt.sd", 0x48000087, 0xb4000578, "e,f,g", 0 },
{ "mi2mpt.dd", 0x48000187, 0xb4000478, "e,f,g", 0 },
{ "mrmt1p2.ss", 0x48000008, 0xb40005f7, "e,f,g", 0 },
{ "mrmt1p2.sd", 0x48000088, 0xb4000577, "e,f,g", 0 },
{ "mrmt1p2.dd", 0x48000188, 0xb4000477, "e,f,g", 0 },
{ "mm12mpm.ss", 0x48000009, 0xb40005f6, "e,f,g", 0 },
{ "mm12mpm.sd", 0x48000089, 0xb4000576, "e,f,g", 0 },
{ "mm12mpm.dd", 0x48000189, 0xb4000476, "e,f,g", 0 },
{ "mrm1p2.ss", 0x4800000a, 0xb40005f5, "e,f,g", 0 },
{ "mrm1p2.sd", 0x4800008a, 0xb4000575, "e,f,g", 0 },
{ "mrm1p2.dd", 0x4800018a, 0xb4000475, "e,f,g", 0 },
{ "mm12ttpm.ss",0x4800000b, 0xb40005f4, "e,f,g", 0 },
{ "mm12ttpm.sd",0x4800008b, 0xb4000574, "e,f,g", 0 },
{ "mm12ttpm.dd",0x4800018b, 0xb4000474, "e,f,g", 0 },
{ "mimt1p2.ss", 0x4800000c, 0xb40005f3, "e,f,g", 0 },
{ "mimt1p2.sd", 0x4800008c, 0xb4000573, "e,f,g", 0 },
{ "mimt1p2.dd", 0x4800018c, 0xb4000473, "e,f,g", 0 },
{ "mm12tpm.ss", 0x4800000d, 0xb40005f2, "e,f,g", 0 },
{ "mm12tpm.sd", 0x4800008d, 0xb4000572, "e,f,g", 0 },
{ "mm12tpm.dd", 0x4800018d, 0xb4000472, "e,f,g", 0 },
{ "mim1p2.ss", 0x4800000e, 0xb40005f1, "e,f,g", 0 },
{ "mim1p2.sd", 0x4800008e, 0xb4000571, "e,f,g", 0 },
{ "mim1p2.dd", 0x4800018e, 0xb4000471, "e,f,g", 0 },
/* Floating Point Escape Instruction Format - pfmsm.p fsrc1,fsrc2,fdest. */
{ "mr2s1.ss", 0x48000010, 0xb40005ef, "e,f,g", 0 },
{ "mr2s1.sd", 0x48000090, 0xb400056f, "e,f,g", 0 },
{ "mr2s1.dd", 0x48000190, 0xb400046f, "e,f,g", 0 },
{ "mr2st.ss", 0x48000011, 0xb40005ee, "e,f,g", 0 },
{ "mr2st.sd", 0x48000091, 0xb400056e, "e,f,g", 0 },
{ "mr2st.dd", 0x48000191, 0xb400046e, "e,f,g", 0 },
{ "mr2ms1.ss", 0x48000012, 0xb40005ed, "e,f,g", 0 },
{ "mr2ms1.sd", 0x48000092, 0xb400056d, "e,f,g", 0 },
{ "mr2ms1.dd", 0x48000192, 0xb400046d, "e,f,g", 0 },
{ "mr2mst.ss", 0x48000013, 0xb40005ec, "e,f,g", 0 },
{ "mr2mst.sd", 0x48000093, 0xb400056c, "e,f,g", 0 },
{ "mr2mst.dd", 0x48000193, 0xb400046c, "e,f,g", 0 },
{ "mi2s1.ss", 0x48000014, 0xb40005eb, "e,f,g", 0 },
{ "mi2s1.sd", 0x48000094, 0xb400056b, "e,f,g", 0 },
{ "mi2s1.dd", 0x48000194, 0xb400046b, "e,f,g", 0 },
{ "mi2st.ss", 0x48000015, 0xb40005ea, "e,f,g", 0 },
{ "mi2st.sd", 0x48000095, 0xb400056a, "e,f,g", 0 },
{ "mi2st.dd", 0x48000195, 0xb400046a, "e,f,g", 0 },
{ "mi2ms1.ss", 0x48000016, 0xb40005e9, "e,f,g", 0 },
{ "mi2ms1.sd", 0x48000096, 0xb4000569, "e,f,g", 0 },
{ "mi2ms1.dd", 0x48000196, 0xb4000469, "e,f,g", 0 },
{ "mi2mst.ss", 0x48000017, 0xb40005e8, "e,f,g", 0 },
{ "mi2mst.sd", 0x48000097, 0xb4000568, "e,f,g", 0 },
{ "mi2mst.dd", 0x48000197, 0xb4000468, "e,f,g", 0 },
{ "mrmt1s2.ss", 0x48000018, 0xb40005e7, "e,f,g", 0 },
{ "mrmt1s2.sd", 0x48000098, 0xb4000567, "e,f,g", 0 },
{ "mrmt1s2.dd", 0x48000198, 0xb4000467, "e,f,g", 0 },
{ "mm12msm.ss", 0x48000019, 0xb40005e6, "e,f,g", 0 },
{ "mm12msm.sd", 0x48000099, 0xb4000566, "e,f,g", 0 },
{ "mm12msm.dd", 0x48000199, 0xb4000466, "e,f,g", 0 },
{ "mrm1s2.ss", 0x4800001a, 0xb40005e5, "e,f,g", 0 },
{ "mrm1s2.sd", 0x4800009a, 0xb4000565, "e,f,g", 0 },
{ "mrm1s2.dd", 0x4800019a, 0xb4000465, "e,f,g", 0 },
{ "mm12ttsm.ss",0x4800001b, 0xb40005e4, "e,f,g", 0 },
{ "mm12ttsm.sd",0x4800009b, 0xb4000564, "e,f,g", 0 },
{ "mm12ttsm.dd",0x4800019b, 0xb4000464, "e,f,g", 0 },
{ "mimt1s2.ss", 0x4800001c, 0xb40005e3, "e,f,g", 0 },
{ "mimt1s2.sd", 0x4800009c, 0xb4000563, "e,f,g", 0 },
{ "mimt1s2.dd", 0x4800019c, 0xb4000463, "e,f,g", 0 },
{ "mm12tsm.ss", 0x4800001d, 0xb40005e2, "e,f,g", 0 },
{ "mm12tsm.sd", 0x4800009d, 0xb4000562, "e,f,g", 0 },
{ "mm12tsm.dd", 0x4800019d, 0xb4000462, "e,f,g", 0 },
{ "mim1s2.ss", 0x4800001e, 0xb40005e1, "e,f,g", 0 },
{ "mim1s2.sd", 0x4800009e, 0xb4000561, "e,f,g", 0 },
{ "mim1s2.dd", 0x4800019e, 0xb4000461, "e,f,g", 0 },
{ "fmul.ss", 0x48000020, 0xb40005df, "e,f,g", 0 }, /* fmul.p fsrc1,fsrc2,fdest */
{ "fmul.sd", 0x480000a0, 0xb400055f, "e,f,g", 0 }, /* fmul.p fsrc1,fsrc2,fdest */
{ "fmul.dd", 0x480001a0, 0xb400045f, "e,f,g", 0 }, /* fmul.p fsrc1,fsrc2,fdest */
{ "pfmul.ss", 0x48000420, 0xb40001df, "e,f,g", 0 }, /* pfmul.p fsrc1,fsrc2,fdest */
{ "pfmul.sd", 0x480004a0, 0xb400015f, "e,f,g", 0 }, /* pfmul.p fsrc1,fsrc2,fdest */
{ "pfmul.dd", 0x480005a0, 0xb400005f, "e,f,g", 0 }, /* pfmul.p fsrc1,fsrc2,fdest */
{ "pfmul3.dd", 0x480005a4, 0xb400005b, "e,f,g", 0 }, /* pfmul3.p fsrc1,fsrc2,fdest */
{ "fmlow.dd", 0x480001a1, 0xb400045e, "e,f,g", 0 }, /* fmlow.dd fsrc1,fsrc2,fdest */
{ "frcp.ss", 0x48000022, 0xb40005dd, "f,g", 0 }, /* frcp.p fsrc2,fdest */
{ "frcp.sd", 0x480000a2, 0xb400055d, "f,g", 0 }, /* frcp.p fsrc2,fdest */
{ "frcp.dd", 0x480001a2, 0xb400045d, "f,g", 0 }, /* frcp.p fsrc2,fdest */
{ "frsqr.ss", 0x48000023, 0xb40005dc, "f,g", 0 }, /* frsqr.p fsrc2,fdest */
{ "frsqr.sd", 0x480000a3, 0xb400055c, "f,g", 0 }, /* frsqr.p fsrc2,fdest */
{ "frsqr.dd", 0x480001a3, 0xb400045c, "f,g", 0 }, /* frsqr.p fsrc2,fdest */
{ "fadd.ss", 0x48000030, 0xb40005cf, "e,f,g", 0 }, /* fadd.p fsrc1,fsrc2,fdest */
{ "fadd.sd", 0x480000b0, 0xb400054f, "e,f,g", 0 }, /* fadd.p fsrc1,fsrc2,fdest */
{ "fadd.dd", 0x480001b0, 0xb400044f, "e,f,g", 0 }, /* fadd.p fsrc1,fsrc2,fdest */
{ "pfadd.ss", 0x48000430, 0xb40001cf, "e,f,g", 0 }, /* pfadd.p fsrc1,fsrc2,fdest */
{ "pfadd.sd", 0x480004b0, 0xb400014f, "e,f,g", 0 }, /* pfadd.p fsrc1,fsrc2,fdest */
{ "pfadd.dd", 0x480005b0, 0xb400004f, "e,f,g", 0 }, /* pfadd.p fsrc1,fsrc2,fdest */
{ "fsub.ss", 0x48000031, 0xb40005ce, "e,f,g", 0 }, /* fsub.p fsrc1,fsrc2,fdest */
{ "fsub.sd", 0x480000b1, 0xb400054e, "e,f,g", 0 }, /* fsub.p fsrc1,fsrc2,fdest */
{ "fsub.dd", 0x480001b1, 0xb400044e, "e,f,g", 0 }, /* fsub.p fsrc1,fsrc2,fdest */
{ "pfsub.ss", 0x48000431, 0xb40001ce, "e,f,g", 0 }, /* pfsub.p fsrc1,fsrc2,fdest */
{ "pfsub.sd", 0x480004b1, 0xb400014e, "e,f,g", 0 }, /* pfsub.p fsrc1,fsrc2,fdest */
{ "pfsub.dd", 0x480005b1, 0xb400004e, "e,f,g", 0 }, /* pfsub.p fsrc1,fsrc2,fdest */
{ "fix.sd", 0x480000b2, 0xb400054d, "e,g", 0 }, /* fix.p fsrc1,fdest */
{ "fix.dd", 0x480001b2, 0xb400044d, "e,g", 0 }, /* fix.p fsrc1,fdest */
{ "pfix.sd", 0x480004b2, 0xb400014d, "e,g", 0 }, /* pfix.p fsrc1,fdest */
{ "pfix.dd", 0x480005b2, 0xb400004d, "e,g", 0 }, /* pfix.p fsrc1,fdest */
{ "famov.ss", 0x48000033, 0xb40005cc, "e,g", 0 }, /* famov.p fsrc1,fdest */
{ "famov.ds", 0x48000133, 0xb40004cc, "e,g", 0 }, /* famov.p fsrc1,fdest */
{ "famov.sd", 0x480000b3, 0xb400054c, "e,g", 0 }, /* famov.p fsrc1,fdest */
{ "famov.dd", 0x480001b3, 0xb400044c, "e,g", 0 }, /* famov.p fsrc1,fdest */
{ "pfamov.ss", 0x48000433, 0xb40001cc, "e,g", 0 }, /* pfamov.p fsrc1,fdest */
{ "pfamov.ds", 0x48000533, 0xb40000cc, "e,g", 0 }, /* pfamov.p fsrc1,fdest */
{ "pfamov.sd", 0x480004b3, 0xb400014c, "e,g", 0 }, /* pfamov.p fsrc1,fdest */
{ "pfamov.dd", 0x480005b3, 0xb400004c, "e,g", 0 }, /* pfamov.p fsrc1,fdest */
/* Opcode pfgt has R bit cleared; pfle has R bit set. */
{ "pfgt.ss", 0x48000434, 0xb40001cb, "e,f,g", 0 }, /* pfgt.p fsrc1,fsrc2,fdest */
{ "pfgt.dd", 0x48000534, 0xb40000cb, "e,f,g", 0 }, /* pfgt.p fsrc1,fsrc2,fdest */
/* Opcode pfgt has R bit cleared; pfle has R bit set. */
{ "pfle.ss", 0x480004b4, 0xb400014b, "e,f,g", 0 }, /* pfle.p fsrc1,fsrc2,fdest */
{ "pfle.dd", 0x480005b4, 0xb400004b, "e,f,g", 0 }, /* pfle.p fsrc1,fsrc2,fdest */
{ "pfeq.ss", 0x48000435, 0xb40001ca, "e,f,g", 0 }, /* pfeq.p fsrc1,fsrc2,fdest */
{ "pfeq.dd", 0x48000535, 0xb40000ca, "e,f,g", 0 }, /* pfeq.p fsrc1,fsrc2,fdest */
{ "ftrunc.sd", 0x480000ba, 0xb4000545, "e,g", 0 }, /* ftrunc.p fsrc1,fdest */
{ "ftrunc.dd", 0x480001ba, 0xb4000445, "e,g", 0 }, /* ftrunc.p fsrc1,fdest */
{ "pftrunc.sd", 0x480004ba, 0xb4000145, "e,g", 0 }, /* pftrunc.p fsrc1,fdest */
{ "pftrunc.dd", 0x480005ba, 0xb4000045, "e,g", 0 }, /* pftrunc.p fsrc1,fdest */
{ "fxfr", 0x48000040, 0xb40005bf, "e,d", 0 }, /* fxfr fsrc1,idest */
{ "fiadd.ss", 0x48000049, 0xb40005b6, "e,f,g", 0 }, /* fiadd.w fsrc1,fsrc2,fdest */
{ "fiadd.dd", 0x480001c9, 0xb4000436, "e,f,g", 0 }, /* fiadd.w fsrc1,fsrc2,fdest */
{ "pfiadd.ss", 0x48000449, 0xb40001b6, "e,f,g", 0 }, /* pfiadd.w fsrc1,fsrc2,fdest */
{ "pfiadd.dd", 0x480005c9, 0xb4000036, "e,f,g", 0 }, /* pfiadd.w fsrc1,fsrc2,fdest */
{ "fisub.ss", 0x4800004d, 0xb40005b2, "e,f,g", 0 }, /* fisub.w fsrc1,fsrc2,fdest */
{ "fisub.dd", 0x480001cd, 0xb4000432, "e,f,g", 0 }, /* fisub.w fsrc1,fsrc2,fdest */
{ "pfisub.ss", 0x4800044d, 0xb40001b2, "e,f,g", 0 }, /* pfisub.w fsrc1,fsrc2,fdest */
{ "pfisub.dd", 0x480005cd, 0xb4000032, "e,f,g", 0 }, /* pfisub.w fsrc1,fsrc2,fdest */
{ "fzchkl", 0x480001d7, 0xb4000428, "e,f,g", 0 }, /* fzchkl fsrc1,fsrc2,fdest */
{ "pfzchkl", 0x480005d7, 0xb4000028, "e,f,g", 0 }, /* pfzchkl fsrc1,fsrc2,fdest */
{ "fzchks", 0x480001df, 0xb4000420, "e,f,g", 0 }, /* fzchks fsrc1,fsrc2,fdest */
{ "pfzchks", 0x480005df, 0xb4000020, "e,f,g", 0 }, /* pfzchks fsrc1,fsrc2,fdest */
{ "faddp", 0x480001d0, 0xb400042f, "e,f,g", 0 }, /* faddp fsrc1,fsrc2,fdest */
{ "pfaddp", 0x480005d0, 0xb400002f, "e,f,g", 0 }, /* pfaddp fsrc1,fsrc2,fdest */
{ "faddz", 0x480001d1, 0xb400042e, "e,f,g", 0 }, /* faddz fsrc1,fsrc2,fdest */
{ "pfaddz", 0x480005d1, 0xb400002e, "e,f,g", 0 }, /* pfaddz fsrc1,fsrc2,fdest */
{ "form", 0x480001da, 0xb4000425, "e,g", 0 }, /* form fsrc1,fdest */
{ "pform", 0x480005da, 0xb4000025, "e,g", 0 }, /* pform fsrc1,fdest */
/* Floating point pseudo-instructions. */
{ "fmov.ss", 0x48000049, 0xb7e005b6, "e,g", 0 }, /* fiadd.ss fsrc1,f0,fdest */
{ "fmov.dd", 0x480001c9, 0xb7e00436, "e,g", 0 }, /* fiadd.dd fsrc1,f0,fdest */
{ "fmov.sd", 0x480000b3, 0xb400054c, "e,g", 0 }, /* famov.sd fsrc1,fdest */
{ "fmov.ds", 0x48000133, 0xb40004cc, "e,g", 0 }, /* famov.ds fsrc1,fdest */
{ "pfmov.ds", 0x48000533, 0xb40000cc, "e,g", 0 }, /* pfamov.ds fsrc1,fdest */
{ "pfmov.dd", 0x480005c9, 0xb7e00036, "e,g", 0 }, /* pfiadd.dd fsrc1,f0,fdest */
{ 0, 0, 0, 0, 0 },
};
#define NUMOPCODES ((sizeof i860_opcodes)/(sizeof i860_opcodes[0]))
+99
View File
@@ -0,0 +1,99 @@
"""Minimal i860 XR/XP disassembler driven by the binutils opcode table.
Usage: python i860dis.py <va_hex> [len_hex]
Disassembles VREND.MNG code (loads at 0xf0400000; file header 0x1C).
Rebuilt 2026-07-12 (the task-#55 original didn't survive); opcode truth =
scratchpad/i860_opcodes.h (binutils include/opcode/i860.h, haiku legacy
mirror). Operand syntax per the header comment.
"""
import re, struct, sys, os
HERE = os.path.dirname(os.path.abspath(__file__))
MNG = os.path.join(HERE, '..', 'content', 'VREND.MNG')
HDR = 0x1C
CODEBASE = 0xf0400000
CODESZ = 0x3ac80
DATASZ = 0x1e9c0
def load_ops():
src = open(os.path.join(HERE, 'i860_opcodes.h')).read()
ops = []
for m in re.finditer(r'\{\s*"([^"]+)",\s*(0x[0-9a-fA-F]+),\s*(0x[0-9a-fA-F]+),\s*"([^"]*)",\s*(\w+)\s*\}', src):
name, match, lose, args, expand = m.groups()
ops.append((name, int(match, 16), int(lose, 16), args))
return ops
OPS = load_ops()
def fields(w):
return {
'src2': (w >> 21) & 0x1F,
'dest': (w >> 16) & 0x1F,
'src1': (w >> 11) & 0x1F,
'imm16': w & 0xFFFF,
'imm5': (w >> 11) & 0x1F,
'lbroff': w & 0x03FFFFFF,
'sbroff': ((w >> 5) & 0xF800) | (w & 0x07FF), # split 16-bit branch offset
'split16': ((w >> 5) & 0xF800) | (w & 0x07FF), # split 16-bit (st) immediate
'creg': (w >> 21) & 0x1F,
}
def sext(v, bits):
return v - (1 << bits) if v & (1 << (bits - 1)) else v
def decode(w, pc):
for name, match, lose, args in OPS:
if (w & match) == match and (w & lose) == 0:
# binutils order guarantees no false positive only if we check
# match as EXACT on the fixed bits: (w & ~varmask) == match.
# Reconstruct: fixed bits = match | lose; variable = operands.
f = fields(w)
out = []
for a in args:
if a in '#,()': continue
if a == '1': out.append('r%d' % f['src1'])
elif a == '2': out.append('r%d' % f['src2'])
elif a == 'd': out.append('r%d' % f['dest'])
elif a in 'iIJKLM': out.append('0x%x' % f['imm16'])
elif a == '5': out.append('%d' % f['imm5'])
elif a == 'l':
tgt = pc + 4 + (sext(f['lbroff'], 26) << 2)
out.append('0x%08x' % tgt)
elif a == 'r':
tgt = pc + 4 + (sext(f['sbroff'], 16) << 2)
out.append('0x%08x' % tgt)
elif a in 'sSTU': out.append('0x%x' % f['split16'])
elif a == 'e': out.append('f%d' % f['src1'])
elif a == 'f': out.append('f%d' % f['src2'])
elif a == 'g': out.append('f%d' % f['dest'])
elif a == 'c': out.append('creg%d' % f['creg'])
else: out.append('?%s' % a)
return name, out
return None, None
def candidates(w):
"""All table rows consistent with word w (the table has overlaps; the
first (most specific) is printed, rest shown when ambiguous)."""
res = []
for name, match, lose, args in OPS:
if (w & match) == match and (w & lose) == 0:
res.append(name)
return res
def main():
data = open(MNG, 'rb').read()
code = data[HDR:HDR + CODESZ]
va = int(sys.argv[1], 16)
ln = int(sys.argv[2], 16) if len(sys.argv) > 2 else 0x100
off = va - CODEBASE
for pc in range(va, va + ln, 4):
w = struct.unpack_from('<I', code, pc - CODEBASE)[0]
name, ops = decode(w, pc)
if name is None:
print('0x%08x: %08x ???' % (pc, w))
else:
print('0x%08x: %08x %-10s %s' % (pc, w, name, ','.join(ops)))
if __name__ == '__main__':
main()
File diff suppressed because it is too large Load Diff