diff --git a/context/combat-damage.md b/context/combat-damage.md index 9d71cc2..4f91fd0 100644 --- a/context/combat-damage.md +++ b/context/combat-damage.md @@ -977,14 +977,33 @@ Full decomp map of the death path. Two tiers: `ApplyDamageAndMeasure` @4ac07c. Diag: `BT_CRIT_PROBE=`. Detail: [[open-questions]] (the resolved entry) + mechdmg.cpp/mechsub.cpp. - **`MechDeathHandler`** (ctor `FUN_0042a984` + Performance `FUN_0042aa2c`, cached mech+0x850 / `mech[0x214]`) — - the **per-subsystem destroyed-skin + explosion engine**. Each tick walks the damage subsystems - (mech+0x120[mech+0x11c]); as a subsystem's damage crosses a descriptor-table threshold (`FUN_0042a5f4` - detects the crossing, `FUN_0042a664` looks up the state) it applies the `GraphicState` ("Destroyed" = enum 1, - parsed @part_003.c:9099) to the subsystem graphic (`sub+0xd4`) AND dispatches an effect-creation message - (`FUN_0043663c`→`FUN_004364e4` to the effect mgr `app+0x38`). ✅ Fully exported; was a no-op stub - (mech.cpp:221) — now fully RECONSTRUCTED (mechdmg.cpp — ctor @0042a984 / Tick @0042aa2c; wired in the - Mech ctor, mech.cpp:1469) [T2]. Gamedata `DeathSplashDamage`/`Radius`/`Effect` load into a descriptor @ +0x74/78/7c - (`FUN_004a2da8`). Destroyed-skin variant set (`destroyed`, `destroyeds`…`destroyedd`) @part_003.c:5720. + the per-ZONE **effect watcher**. ⚠ **Corrected reading (#125, 2026-08-14 [T1 raw + part_003.c:10963-93]; supersedes the 07-12 note that had it "applying the GraphicState"):** + each tick walks the zones (mech+0x120[mech+0x11c]); two changed-flag branches SELECT an + effect descriptor — level moved (`+0xb8 & 4`) → `DescriptorForLevel` (`FUN_0042a664`; band + crossing `FUN_0042a5f4` also dirties the zone record on a master), gstate moved (`& 8`) → + `DescriptorForGraphicState(the zone's CURRENT gstate @+0x78)` (`FUN_0042a6c4`), the gstate + pick OVERWRITING the level pick — then dispatches the effect message + (`FUN_0043663c`→`FUN_004364e4` to `app+0x38`). **The descriptor picks the EFFECT only — + the binary NEVER writes gstate here.** The gstate writers are the destruction cascade + (`RecurseSegmentTable`: the dying zone → Destroyed(1), children/siblings → Gone(2)), + replication, and Reset. The port's former `ApplyDamageGraphicState(d->graphicState)` was an + INVENTION, and with its pick hardcoded to Destroyed it stomped every cascade child's + authored Gone(2) back to 1 the same tick, master-side, pre-send — the gun pod never dropped + off a killed arm on ANY node (#125). **The authored two-stage arm story (restored):** a + directly-killed GUN zone shows its wrecked variant mesh (`thrdrgun.bgf`-family, EXTERIOR + skeleton set — every zone's table carries `lvl=-1 {fx gs=1}` + `{fx gs=2}` terminals, + unreachable by level, existing for exactly these gstate picks); a killed ARM swaps to its + wrecked variant AND its pod segment VANISHES (Gone). Port mesh coupling: the binary's + renderer re-picked segment meshes from gstate per frame; the port's built-once tree gets the + event equivalent — per-zone gstate caches in Tick fire ONE coalesced `RemakeEntityRenderables` + on any edge (also the peer's only trigger: replicated gstate-only beats have no level edge). + Effects are suppressed on →Exists edges (the binary's heal writes the cell raw, no flag — + no respawn puff). 2-node benched both ways (night18/mp_zone125.sh): peer receives + `dz_rgun gstate 0->2` and hides the pod on the FIRST beat; legacy A/B receipt shows the + stomp (`jointrgun → gstate 2` then re-remade `→ 1`). [T1/T2] Gamedata + `DeathSplashDamage`/`Radius`/`Effect` load into a descriptor @ +0x74/78/7c (`FUN_004a2da8`). + Destroyed-skin variant set (`destroyed`, `destroyeds`…`destroyedd`) @part_003.c:5720. **NOT EXPORTED (master-perf gap 0x4a9770-0x4ab188 + the un-exported TakeDamage) [T4]:** - The `movementMode` WRITER (sets 5-8 then 2/9 on death) + the fall-direction (5/6/7/8) selection. - The `Mech::DeathShutdown` roster loop (RP `VTV::DeathShutdown` analog — loop subsystems calling the diff --git a/context/reconstruction-gotchas.md b/context/reconstruction-gotchas.md index d86778e..7fd301c 100644 --- a/context/reconstruction-gotchas.md +++ b/context/reconstruction-gotchas.md @@ -1151,3 +1151,26 @@ stale dispatch is exactly one frame of "no exit condition tested" -- the #102 class. (3) Sibling of gotcha 30 (one cell, many port stores) and the namedClip aliasing class -- grep for `= (int)` alarm syncs when auditing any split-cell machine. + +## 36. AN INVENTED WRITE INSIDE A FAITHFUL TRANSCRIPTION: descriptor-as-authority (#125, 2026-08-14) + +The MechDeathHandler tick was transcribed with one line the binary does not +have: `zone->ApplyDamageGraphicState(d->graphicState)` -- the effect +DESCRIPTOR (a lookup result) written back into the zone's graphic state (the +authoritative cell). The raw @0042aa2c (part_003.c:10963-93) uses the +descriptor ONLY to pick an effect resource; the gstate writers are the +cascade, replication, and Reset. With the pick hardcoded to Destroyed(1), +the invented write STOMPED every cascade child's authored Gone(2) back to 1 +in the same tick, master-side, before the record shipped -- so the wire +itself carried the corruption and no peer could ever render the authored +state. An added write is worse than a stub: a stub leaves the authentic +writers alone; an invention actively fights them, and it fights them on +every node that runs the transcription. +**Rules:** (1) in a transcription, every WRITE to authoritative state must +have a byte-level citation -- effects/lookups reading state is safe, state +consuming a lookup result is the red flag; (2) convict with a before/after +receipt AROUND the suspect pass (the [BTrender] seg lines caught gstate 2 +becoming 1 between two remakes -- one receipt, whole conviction); (3) when a +lookup table carries entries UNREACHABLE by its primary key (the lvl=-1 +gs=1/gs=2 terminals), those entries name the binary's OTHER selector -- find +it before assuming the reachable subset is the whole contract. diff --git a/game/reconstructed/mechdmg.cpp b/game/reconstructed/mechdmg.cpp index 34b3e71..7f7901f 100644 --- a/game/reconstructed/mechdmg.cpp +++ b/game/reconstructed/mechdmg.cpp @@ -1106,9 +1106,19 @@ void damageDescriptors.push_back(d); } if (count != 0 && getenv("BT_DEATH_LOG")) - DEBUG_STREAM << "[deathfx] zone descriptor table loaded: " << count - << " entries (first threshold=" << (count > 0 ? damageDescriptors[0].damageLevel : -1.0f) - << " effect=" << (count > 0 ? damageDescriptors[0].effectResource : -1) << ")\n" << std::flush; + { + // #125: full table (the summary-only line hid the question that + // mattered -- whether a Destroyed-gstate entry exists per zone). + DEBUG_STREAM << "[deathfx] zone " << damageZoneIndex + << " '" << (const char *)damageZoneName + << "' descriptors (" << count << "):"; + for (int di = 0; di < (int)damageDescriptors.size(); ++di) + DEBUG_STREAM << " {lvl=" << damageDescriptors[di].damageLevel + << " fx=" << damageDescriptors[di].effectResource + << " gs=" << damageDescriptors[di].graphicState + << " dly=" << damageDescriptors[di].timeDelay << "}"; + DEBUG_STREAM << "\n" << std::flush; + } } // @@ -1212,6 +1222,15 @@ MechDeathHandler::MechDeathHandler(Mech *mech) // @0042a984 // per-zone last-damage cache, zeroed (binary this[0x10], size mech+0x11c). int count = (mech != 0 && mech->damageZoneCount > 0) ? mech->damageZoneCount : 0; lastLevel.assign(count, 0.0f); + // #125: gstate cache seeded from the zones' CURRENT states (a handler + // built on an already-damaged mech must not fire a spurious remake). + lastGState.assign(count, (int)DamageZone::ExistsGraphicState); + for (int i = 0; i < count; ++i) + { + Mech__DamageZone *z = (mech != 0) ? mech->Zone(i) : 0; + if (z != 0) + lastGState[i] = (int)z->GetGraphicState(); + } } MechDeathHandler::~MechDeathHandler() // @0042a9f4 @@ -1219,10 +1238,16 @@ MechDeathHandler::~MechDeathHandler() // @0042a9f4 } // -// @0042aa2c -- each tick, walk the zones; where a zone's damageLevel rose since -// last tick, fire the current damage-band descriptor's explosion (binary +0xb8 & -// 4) and, on destruction, the Destroyed-graphic descriptor's explosion (+0xb8 & -// 8), applying that descriptor's GraphicState (the destroyed skin) to the zone. +// @0042aa2c -- each tick, walk the zones (raw: part_003.c:10963-93). Two +// changed-flag branches select an effect descriptor: level moved (+0xb8 & 4) +// -> DescriptorForLevel; gstate moved (& 8) -> DescriptorForGraphicState(the +// zone's CURRENT gstate), overwriting the level pick. The descriptor picks +// the EFFECT only -- the binary never writes gstate here (#125 corrected the +// earlier reading, which had invented a descriptor->gstate apply). Port +// additions on top: the per-zone level/gstate caches stand in for the +// changed-flags (immune to consumption ordering), and a gstate edge fires +// ONE coalesced RemakeEntity -- the event equivalent of the binary renderer's +// per-frame mesh re-pick (the port render tree is built once). // void MechDeathHandler::Tick() // @0042aa2c @@ -1417,12 +1442,40 @@ void } int n = owner->damageZoneCount; + int needRemake = 0; // #125: one coalesced remake per tick for (int i = 0; i < n && i < (int)lastLevel.size(); ++i) { Mech__DamageZone *zone = owner->Zone(i); if (zone == 0) continue; + // #125: GRAPHIC-STATE EDGE watch (all instances) -- the mesh follows + // the zone's gstate, not the descriptor tables. Three writers move a + // gstate with NO level edge in this loop: a replicated record + // (DamageZone::ReadUpdateRecord -- the peer path), the master's + // destruction cascade (RecurseSegmentTable's SetGraphicState(1)/Gone(2) + // marks on the zone + its children/siblings), and the descriptor apply + // below. The binary needed no event -- its renderer re-picked every + // segment's mesh from gstate PER FRAME; the port's built-once render + // tree re-picks on this diff instead. The old trigger (the descriptor + // branch below, level-rise + Destroyed-descriptor gated) missed both + // the cascade children (gstate Gone, level unchanged) and any zone + // whose authored table lacks a gs=1 entry (DescriptorForLevel returns + // NULL past the last threshold) -- the #125 field shape: peers' arm + // intact until a SECOND destroying edge finally fired a remake, whose + // full-segment walk then applied the stale states too. + int gstateEdged = 0; // the flag-8 (& 8) analog, this tick + if (i < (int)lastGState.size()) + { + const int g = (int)zone->GetGraphicState(); + if (g != lastGState[i]) + { + lastGState[i] = g; + gstateEdged = 1; + needRemake = 1; + } + } + Scalar level = zone->GetStructureDamageLevel(); Scalar prev = lastLevel[i]; @@ -1430,47 +1483,64 @@ void // (ForceUpdate(DamageZoneUpdateModelFlag)). Just refresh the cache: the // respawn un-wreck no longer rides this edge -- it fires from the // death-state exit watcher above, which also covers deaths that never - // moved a zone (#94). + // moved a zone (#94). (The gstate cache above saw the heal edge too -- + // the coalesced remake restores the segment; no EFFECT fires for it, + // see the Exists suppression below.) if (level < prev) { lastLevel[i] = level; continue; } - if (level <= prev) // unchanged -- no new damage - continue; - lastLevel[i] = level; + const int levelRose = (level > prev) ? 1 : 0; // the flag-4 (& 4) analog + if (levelRose) + lastLevel[i] = level; + if (!levelRose && !gstateEdged) + continue; // nothing moved this tick - // AUTHENTIC (FUN_0042aa2c @0042aa2c, the binary's effect-watcher level - // branch): when the rise CROSSES a band-descriptor threshold - // (FUN_0042a5f4) and this node owns the master, raise the entity's - // damage-zone update flag (*(entity+0x18) |= 2, master-gated on - // (entity+0x28 & 0xc) == 0) -- the zone update records ship this - // zone's damageLevel to every observer, so replicant dolls/hull tint - // track the fight at band granularity. This send was MISSING from - // the port (only the graphic-state branch below raised it): masters - // consumed damage correctly while every observer's copy sat at 0.0 - // until destruction (the "no discoloration in MP" report, #87). - if (zone->DescriptorCrossed(prev, level) - && owner->GetInstance() == Entity::MasterInstance) - owner->ForceUpdate(Entity::DamageZoneUpdateModelFlag); - - // 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); - if (level >= 1.0f && prev < 1.0f) // just destroyed -> the Destroyed descriptor + // AUTHENTIC (FUN_0042aa2c raw, part_003.c:10963-10993 -- re-read + // 2026-08-14 for #125; this REPLACES the 07-12 reading, which had + // invented a descriptor->gstate WRITE, see below): + // flag & 4 (level moved): descriptor = DescriptorForLevel(level) + // [FUN_0042a664]; if the rise CROSSED a band threshold + // [FUN_0042a5f4] and this node owns the master, dirty |= 2 + // (the zone update record ships) -- every hit on a damaged + // mech re-puffs its band effect (bands 3/4 are authored + // ember/fire plumes: a nearly-dead mech burns continuously). + if (levelRose) { - const Mech__DamageZone::DamageDescriptor *dd = - zone->DescriptorForGraphicState(DamageZone::DestroyedGraphicState); // enum 1 - if (dd != 0) - d = dd; + if (zone->DescriptorCrossed(prev, level) + && owner->GetInstance() == Entity::MasterInstance) + owner->ForceUpdate(Entity::DamageZoneUpdateModelFlag); + } + // flag & 8 (gstate moved): descriptor = DescriptorForGraphicState( + // the zone's CURRENT graphic state, zone+0x78) [FUN_0042a6c4] -- + // OVERWRITING the level pick (the raw stores into the same + // local_c, null included) -- and dirty |= 2 unconditionally on + // a master. The binary NEVER writes gstate here: the descriptor + // chooses the EFFECT only. The gstate writers are the + // destruction cascade (RecurseSegmentTable: self 1, children/ + // siblings 2), replication (ReadUpdateRecord) and Reset. The + // port's former `ApplyDamageGraphicState(d->graphicState)` was + // an INVENTION -- and with the pick hardcoded to Destroyed it + // STOMPED every cascade child's authored Gone(2) back to 1 the + // same tick, master-side, before the record shipped: the gun + // pod never dropped off a killed arm on ANY node. Receipt that + // convicted it: `[BTrender] seg 'jointrgun' -> gstate 1` on a + // solo master right after `[cascade] zone 17 DESTROYED` (#125). + const Mech__DamageZone::DamageDescriptor *d = + levelRose ? zone->DescriptorForLevel(level) : 0; + if (gstateEdged) + { + const int g = (int)zone->GetGraphicState(); + // Exists(0) suppression: the binary's heal path (Reset) writes the + // gstate cell RAW (no changed-flag), so a heal never fires flag 8 + // -- our cache diff sees the ->Exists edge anyway (wanted, it + // drives the mesh restore) but must not fire the gs=0 BAND entry + // as a respawn "damage" puff. + d = (g != (int)DamageZone::ExistsGraphicState) + ? zone->DescriptorForGraphicState(g) : 0; + if (owner->GetInstance() == Entity::MasterInstance) + owner->ForceUpdate(Entity::DamageZoneUpdateModelFlag); } if (d != 0) { @@ -1489,36 +1559,21 @@ void BTSpawnDamageEffect(owner, d->effectResource, // explosion AT the zone's zone->segmentIndex); // segment (world position) } - zone->ApplyDamageGraphicState(d->graphicState); // destroyed skin (graphic state) - // A graphic-state change means the segment's MODEL changed (intact -> - // destroyed variant, keyed by GetVideoObjectName(skl, gstate)). Fire - // the engine's model-rebuild flag so the renderer re-runs - // MakeMechRenderables and loads the destroyed segment geometry -- the - // same mechanism CulturalIcons use to visibly deform on damage - // (CULTURAL.cpp:91; EXPTBL.cpp:549 does this per zone in the engine's - // ExplosionTable). Without this the tree stays as built at spawn (intact). - // The render's "RemakeEntity" state (which consumes this flag) was - // NOT ported -- the mech tree is built once at spawn -- so we drive the - // equivalent directly: set the flag (authentic signal; also flags the - // zone for net damage replication) AND call the render bridge, which - // swaps the wrecked segment mesh onto the already-built tree in place. - // The single-threaded frame loop makes the immediate swap safe. - if (d->graphicState != DamageZone::ExistsGraphicState) - { - // Master-gated like the binary's gstate branch (@0042aa2c: - // (entity+0x28 & 0xc) == 0 guards the |= 2); the mesh swap - // runs on EVERY instance -- an observer node executes this - // same path when the replicated level/gstate lands. - if (owner->GetInstance() == Entity::MasterInstance) - owner->ForceUpdate(Entity::DamageZoneUpdateModelFlag); - BTRemakeMechModel(owner); // RemakeEntity: swap in the destroyed mesh - } if (getenv("BT_DEATH_LOG")) DEBUG_STREAM << "[deathfx] zone " << i << " level " << level - << " -> effect " << d->effectResource << " gstate " << d->graphicState + << " -> effect " << d->effectResource + << " (zone gstate " << (int)zone->GetGraphicState() << ")" << "\n" << std::flush; } } + + // #125: ONE remake per tick, after every zone's state has settled -- + // RemakeEntityRenderables walks ALL segments and re-picks each by its + // zone's LIVE gstate, so a single call applies the destroying zone and + // its whole cascade (children Gone, siblings) together, and the render + // tree's own segGState diff makes repeats free. + if (needRemake) + BTRemakeMechModel(owner); } //############################################################################# diff --git a/game/reconstructed/mechdmg.hpp b/game/reconstructed/mechdmg.hpp index d0bc33f..ea9f942 100644 --- a/game/reconstructed/mechdmg.hpp +++ b/game/reconstructed/mechdmg.hpp @@ -372,6 +372,17 @@ class MechDeathHandler private: Mech *owner; // @0x14 std::vector lastLevel; // @0x10 per-zone last damageLevel cache + // PORT (#125): per-zone last GRAPHIC STATE cache. The binary's + // renderer re-picked every segment's mesh from its zone's gstate PER + // FRAME, so any gstate write (descriptor apply, the destruction + // cascade's child/sibling Gone marks, a replicated record) showed the + // next frame with no event plumbing. The port's built-once render + // tree needs the event: Tick diffs each zone's gstate against this + // cache and fires ONE coalesced RemakeEntity when anything moved -- + // the port equivalent of the per-frame re-pick, independent of the + // descriptor tables (whose ForLevel lookup returns NULL past the last + // threshold -- the #125 first-destroying-hit gap). + std::vector lastGState; // PORT (respawn replication): last-tick MovementMode, so a REPLICANT // observer can reverse the one-way wreck swap on the DEATH-STATE EXIT // edge (9/2 -> 0/1, only Mech::Reset ever leaves the death modes). The diff --git a/scratchpad/night17/DAY_STATE.md b/scratchpad/night17/DAY_STATE.md index 13e3ac5..4ccaf44 100644 --- a/scratchpad/night17/DAY_STATE.md +++ b/scratchpad/night17/DAY_STATE.md @@ -100,3 +100,17 @@ turn-stop MID-PIVOT park (authentic, pod-invisible: no external self-view existe bench D reproduced it at trn legFrm=10 + twist rescue. Peer brake-rate select restored in the replicant motion path (IntegrateMotion:298 is dead code -- Simulate bypassed). Final build: 20 benign LNK2019s exact. +NIGHT18 (cont): #125 FIXED + 2-node benched. Root pair: (1) the Tick transcription +carried an INVENTED write -- ApplyDamageGraphicState(descriptor) -- the raw @0042aa2c +(part_003.c:10963-93) only PICKS an effect (level flag -> ForLevel; gstate flag -> +ForGraphicState(CURRENT gstate), overwriting); the hardcoded-Destroyed pick stomped +every cascade child's authored Gone(2) -> 1 master-side pre-send: the gun pod NEVER +dropped off a killed arm on any node (gotcha 36). (2) the mesh swap was descriptor- +gated; now per-zone gstate caches fire ONE coalesced RemakeEntity on any edge (the +binary re-picked meshes from gstate per frame). Restored authored story: killed gun += wrecked thrdrgun mesh hangs; killed arm = wrecked thrdrarm (exterior skel) + pod +VANISHES -- peers see all of it on the FIRST beat ([zone-repl] dz_rgun gstate 0->2 + +peer remake, mp_zone125.sh fix/legacy receipts). Heal edges fire no effects (Exists +suppression; binary Reset writes raw). Respawn regression clean (0 heal puffs). +SUNDAY build now: turn rates + #155 banks + #49 radar + #102 brake + #125 zone edge. +Handout candidates: 'arms now visibly wreck + pods drop off on everyone's screen'. diff --git a/scratchpad/night18/mp_zone125.sh b/scratchpad/night18/mp_zone125.sh new file mode 100644 index 0000000..f0781dd --- /dev/null +++ b/scratchpad/night18/mp_zone125.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +# ========================================================================= +# ISSUE #125 -- peers' first destroyed-zone graphic (2-node, thor) +# A (master, thor) self-destroys dz_rarm with ONE 120-pt hit at t=25. +# B observes. PASS receipts on B (the peer): +# [zone-repl] zone 10 'dz_rarm' ... gstate 0->1 (arm Destroyed arrives) +# [zone-repl] zone 17 'dz_rgun' ... gstate 0->2 (pod GONE arrives -- the +# pre-fix master STOMPED this to 1 before the record shipped) +# [BTrender] seg 'jointrgun' slot 6 -> gstate 2 (peer hides the pod) +# [BTrender] RemakeEntity: ... entity= (the peer-side remake +# fired on the FIRST beat -- the pre-fix peer had no trigger at all +# when descriptors misfired, and could never see gstate 2) +# usage: mp_zone125.sh +# ========================================================================= +set -x +cd /c/git/bt411/content || exit 1 +taskkill //F //IM btl4.exe > /dev/null 2>&1 +sleep 2 +TAG=${1:-fix} +rm -f z125_a_${TAG}.log z125_b_${TAG}.log +sed 's/^experience=.*/experience=expert/' MP.EGG > Z125.EGG +sed -i "s/^map=.*/map=grass/; s/^time=.*/time=day/; 0,/^vehicle=.*/s//vehicle=thor/" Z125.EGG + +BT_PLATFORM=glass BT_START_INSIDE=1 BT_DEV_GAUGES=1 \ +BT_LOG=z125_b_${TAG}.log BT_AFFINITY=0x0C BT_MP_LOG=1 BT_DEATH_LOG=1 \ + ../build/Release/btl4.exe -egg Z125.EGG -net 1601 & +sleep 2 +BT_PLATFORM=glass BT_START_INSIDE=1 BT_DEV_GAUGES=1 \ +BT_LOG=z125_a_${TAG}.log BT_AFFINITY=0x03 BT_MP_LOG=1 BT_DEATH_LOG=1 BT_DMG_LOG=1 \ + BT_SELF_DAMAGE=120 BT_SELF_DAMAGE_ZONE=dz_rarm BT_SELF_DAMAGE_DELAY=25 BT_SELF_DAMAGE_TICKS=1 \ + ../build/Release/btl4.exe -egg Z125.EGG -net 1501 & +sleep 4 +python ../tools/btconsole.py Z125.EGG 127.0.0.1:1501 127.0.0.1:1601 & +PC=$! + +sleep 75 +kill $PC 2>/dev/null +taskkill //F //IM btl4.exe > /dev/null 2>&1 +echo "=== A (master) ===" +grep -a "dmghit\|cascade\|RemakeEntity\|seg 'jointr" z125_a_${TAG}.log | head -12 +echo "=== B (peer) ===" +grep -a "zone-repl\|RemakeEntity\|seg 'jointr\|deathfx\] zone 1[07] level" z125_b_${TAG}.log | head -16 diff --git a/scratchpad/night18/zonedump.sh b/scratchpad/night18/zonedump.sh new file mode 100644 index 0000000..de04420 --- /dev/null +++ b/scratchpad/night18/zonedump.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# ========================================================================= +# ISSUE #125 -- authored zone-descriptor dump (solo, any chassis) +# usage: zonedump.sh +# Prints each zone's [zone-armor] row + the full [deathfx] descriptor +# table (lvl/fx/gs/dly per entry) at load. The #125 question: which zones +# carry a gs=1 (Destroyed) entry -- the ONLY trigger the old remake had. +# ========================================================================= +set -x +. /c/git/bt411/scratchpad/night6/bench_common.sh +cd /c/git/bt411/content || exit 1 +taskkill //F //IM btl4.exe > /dev/null 2>&1 +sleep 2 + +VEH=${1:-thor} +bt_expert_egg MP.EGG ZD125.EGG +sed -i "s/^map=.*/map=grass/; s/^time=.*/time=day/; 0,/^vehicle=.*/s//vehicle=$VEH/" ZD125.EGG +LOG=zonedump_${VEH}.log +rm -f "$LOG" +( export BT_DEATH_LOG=1 BT_DMG_LOG=1 + bt_launch "$LOG" ZD125.EGG 0x03 ) +sleep 30 +bt_kill_ours +sleep 2 +taskkill //F //IM btl4.exe > /dev/null 2>&1 +echo "--- $LOG" +grep -ac "deathfx" "$LOG" || true