Compare commits
17
Commits
@@ -85,7 +85,8 @@ precise than anything you can infer.
|
|||||||
| Multiplayer, replication, netcode | `context/multiplayer.md` |
|
| Multiplayer, replication, netcode | `context/multiplayer.md` |
|
||||||
| **The OPERATOR CONSOLE + RELAY** — how to launch it, ports, seats, the round/launch lifecycle, re-arm | `context/operator-console.md` |
|
| **The OPERATOR CONSOLE + RELAY** — how to launch it, ports, seats, the round/launch lifecycle, re-arm | `context/operator-console.md` |
|
||||||
| Running a session as sysop (operator-facing how-to) | `docs/OPERATOR_GUIDE.md` |
|
| Running a session as sysop (operator-facing how-to) | `docs/OPERATOR_GUIDE.md` |
|
||||||
| The tester-facing OPEN-ISSUES handout (for Discord) | `docs/OPEN_ISSUES_FOR_TESTERS.txt` — regenerate from the tracker + update the header date before each playtest |
|
| **The PLAYTEST TRACKER SHEET** — the testers' issue board (primary feedback channel since 2026-08-14): tabs, status vocabulary, the Dev-Comments contract, how to read/write it | `context/playtest-tracker.md` |
|
||||||
|
| The tester-facing OPEN-ISSUES handout (for Discord) | `docs/OPEN_ISSUES_FOR_TESTERS.txt` — SECONDARY since the tracker sheet; regenerate from the tracker + update the header date before each playtest |
|
||||||
| Pod hardware, monitors, RIO, MFD surfaces, input remap (CONTROLS.MAP/XInput) | `context/pod-hardware.md` |
|
| Pod hardware, monitors, RIO, MFD surfaces, input remap (CONTROLS.MAP/XInput) | `context/pod-hardware.md` |
|
||||||
| Glass cockpit desktop dev layer (BT_GLASS/BT_STEAM gates, PadRIO, miniconsole, Steam) | `context/glass-cockpit.md` |
|
| Glass cockpit desktop dev layer (BT_GLASS/BT_STEAM gates, PadRIO, miniconsole, Steam) | `context/glass-cockpit.md` |
|
||||||
| Steam internet MP (the wire seam, identity tokens, lobby) | `context/steam-networking.md` |
|
| Steam internet MP (the wire seam, identity tokens, lobby) | `context/steam-networking.md` |
|
||||||
|
|||||||
@@ -199,6 +199,28 @@ default (`-DBT_STEAM=ON` is the documented dev-checkout state). `BT_EXPIRE=ON` (
|
|||||||
14-day tester window; an expire-OFF zip is renamed `-noexpire` and warns. Verify by extracting the
|
14-day tester window; an expire-OFF zip is renamed `-noexpire` and warns. Verify by extracting the
|
||||||
zip somewhere clean and booting it with no repo present — that is what catches a missing runtime DLL.
|
zip somewhere clean and booting it with no repo present — that is what catches a missing runtime DLL.
|
||||||
|
|
||||||
|
⚠ **ANTIVIRUS SMOKE TEST — do this before handing a zip to anyone (#190, 2026-08-15).** Windows
|
||||||
|
Defender's ML classifier flagged `btl4.exe` as `Trojan:Win32/Bearfoos.A!ml` and DELETED it. False
|
||||||
|
positive, and **it corrected itself within hours** — the verdict lived in definition set
|
||||||
|
1.457.172.0 (8/14 19:34) and was gone by 1.457.177.0 (8/15 03:41), same binary passing cleanly;
|
||||||
|
a second tester on midnight-updated definitions never saw it at all. **The lesson is the response,
|
||||||
|
not the incident: an ML verdict is a time-varying opinion about a file, not a fact about it — update
|
||||||
|
definitions and retest BEFORE warning testers, resigning, or rebuilding.** It is also
|
||||||
|
**per-binary, not per-project**: of 17 archived builds extracted side by
|
||||||
|
side, only **4.11.957** was condemned — 622 through 913 all survived — plus that evening's fresh
|
||||||
|
build. Our exe fits the profile that trips these classifiers (unsigned, rebuilt every session so it
|
||||||
|
never accrues reputation, opens raw sockets, enumerates processes, writes beside itself), so any
|
||||||
|
given build is a coin flip. It also turns without warning: the same binary ran clean across benches
|
||||||
|
all evening, then a signature update (8/14 19:34, 1.457.172.0) condemned it hours later.
|
||||||
|
**The check:** extract the new exe to a normal folder that is NOT inside a Defender exclusion (a
|
||||||
|
Desktop subfolder is realistic), wait ~60 s, and confirm the file still exists. Deletion is
|
||||||
|
asynchronous — an immediate `Test-Path` can pass and the file vanish a minute later. If it is
|
||||||
|
condemned, do not ship it: submit to https://www.microsoft.com/en-us/wdsi/filesubmission (category
|
||||||
|
"software developer / incorrectly detected", 1-3 day turnaround, clears it for EVERY player) and
|
||||||
|
rebuild. A rename does not help — the classifier scores content, and the exe name is constant
|
||||||
|
anyway (only the zip is versioned). Long-term fix is code signing.
|
||||||
|
⚠ A Defender exclusion on the repo makes this test silently pass — test outside it.
|
||||||
|
|
||||||
## Build ritual — the stale-link flake (bit 4+ times; MANDATORY for bench work)
|
## Build ritual — the stale-link flake (bit 4+ times; MANDATORY for bench work)
|
||||||
MSBuild often does NOT relink `btl4.exe` when only static-lib members changed — the bench then
|
MSBuild often does NOT relink `btl4.exe` when only static-lib members changed — the bench then
|
||||||
runs a STALE exe and the session burns hours on phantom results. Ritual for every code-change build:
|
runs a STALE exe and the session burns hours on phantom results. Ritual for every code-change build:
|
||||||
|
|||||||
@@ -977,14 +977,33 @@ Full decomp map of the death path. Two tiers:
|
|||||||
`ApplyDamageAndMeasure` @4ac07c. Diag: `BT_CRIT_PROBE=<zone>`. Detail: [[open-questions]]
|
`ApplyDamageAndMeasure` @4ac07c. Diag: `BT_CRIT_PROBE=<zone>`. Detail: [[open-questions]]
|
||||||
(the resolved entry) + mechdmg.cpp/mechsub.cpp.
|
(the resolved entry) + mechdmg.cpp/mechsub.cpp.
|
||||||
- **`MechDeathHandler`** (ctor `FUN_0042a984` + Performance `FUN_0042aa2c`, cached mech+0x850 / `mech[0x214]`) —
|
- **`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
|
the per-ZONE **effect watcher**. ⚠ **Corrected reading (#125, 2026-08-14 [T1 raw
|
||||||
(mech+0x120[mech+0x11c]); as a subsystem's damage crosses a descriptor-table threshold (`FUN_0042a5f4`
|
part_003.c:10963-93]; supersedes the 07-12 note that had it "applying the GraphicState"):**
|
||||||
detects the crossing, `FUN_0042a664` looks up the state) it applies the `GraphicState` ("Destroyed" = enum 1,
|
each tick walks the zones (mech+0x120[mech+0x11c]); two changed-flag branches SELECT an
|
||||||
parsed @part_003.c:9099) to the subsystem graphic (`sub+0xd4`) AND dispatches an effect-creation message
|
effect descriptor — level moved (`+0xb8 & 4`) → `DescriptorForLevel` (`FUN_0042a664`; band
|
||||||
(`FUN_0043663c`→`FUN_004364e4` to the effect mgr `app+0x38`). ✅ Fully exported; was a no-op stub
|
crossing `FUN_0042a5f4` also dirties the zone record on a master), gstate moved (`& 8`) →
|
||||||
(mech.cpp:221) — now fully RECONSTRUCTED (mechdmg.cpp — ctor @0042a984 / Tick @0042aa2c; wired in the
|
`DescriptorForGraphicState(the zone's CURRENT gstate @+0x78)` (`FUN_0042a6c4`), the gstate
|
||||||
Mech ctor, mech.cpp:1469) [T2]. Gamedata `DeathSplashDamage`/`Radius`/`Effect` load into a descriptor @ +0x74/78/7c
|
pick OVERWRITING the level pick — then dispatches the effect message
|
||||||
(`FUN_004a2da8`). Destroyed-skin variant set (`destroyed`, `destroyeds`…`destroyedd`) @part_003.c:5720.
|
(`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]:**
|
**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 `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
|
- The `Mech::DeathShutdown` roster loop (RP `VTV::DeathShutdown` analog — loop subsystems calling the
|
||||||
|
|||||||
@@ -796,6 +796,10 @@ default-ON (`'0'` disables).
|
|||||||
| `BT_INCOMING_LOG` | `[incoming]` missile-alarm receipt (#83): LOCK/clear edges + ~1Hz held lines with distance (mech4.cpp PerformAndWatch latch) |
|
| `BT_INCOMING_LOG` | `[incoming]` missile-alarm receipt (#83): LOCK/clear edges + ~1Hz held lines with distance (mech4.cpp PerformAndWatch latch) |
|
||||||
| `BT_DESIGNATE=enemy` | bench designator (#83 rig): stamps the production target slots (+0x388/+0x37c/+0x38c) with the nearest live mech on frames where the boresight pick found no mech — standoff locks; a real pick still wins; HUD ring untouched |
|
| `BT_DESIGNATE=enemy` | bench designator (#83 rig): stamps the production target slots (+0x388/+0x37c/+0x38c) with the nearest live mech on frames where the boresight pick found no mech — standoff locks; a real pick still wins; HUD ring untouched |
|
||||||
| `BT_WORLDHIT=0` | disables the missile flight sweep's static-solid-tree half (#175) — restores the heightfield-only world test (A/B lever; default on) |
|
| `BT_WORLDHIT=0` | disables the missile flight sweep's static-solid-tree half (#175) — restores the heightfield-only world test (A/B lever; default on) |
|
||||||
|
| `BT_KEYSIM="rate:sec,..."` | scripted key-hold schedule (#102 bench): overrides `gBTInput.leverRate` segment by segment so a headless bench drives the REAL lever integrator + zero detent + gait SM (the forcedThrottle harnesses bypass the lever); after the last segment the lever rests (mech4.cpp, after BTInputPoll) |
|
||||||
|
| `BT_KEYSIM_REV="t:dur"` | hold the pod's REVERSE button (0x3F) in [t,t+dur) on the BT_KEYSIM clock — drives `gBTReverseHeld`, the chokepoint every rig funnels through (the authentic sign-flip shape) |
|
||||||
|
| `BT_KEYSIM_TURN="t:dur"` | full-right turn pulse in [t,t+dur) on the same clock (the #102 twist-rescue leg) |
|
||||||
|
| (receipt) `[brake]` | hard-brake slew engage/release under BT_GAIT_TRACE (#102: forward cycle + negative demand → forwardCycleRate=superStopAcceleration + gyro lurch/judder; mech4.cpp drive block) |
|
||||||
| `BT_CRIT_PROBE=<zone>` | hammer one own-mech zone every 4s (crit-propagation diag, task #2; mech4.cpp) |
|
| `BT_CRIT_PROBE=<zone>` | hammer one own-mech zone every 4s (crit-propagation diag, task #2; mech4.cpp) |
|
||||||
| `BT_REPL_LOG` | replicant/MP replication log (mech4.cpp) |
|
| `BT_REPL_LOG` | replicant/MP replication log (mech4.cpp) |
|
||||||
| `BT_GOTO="enemy"\|"x z"` / `BT_GOTO_LOG` | self-driving beeline (to the enemy or a map coordinate) + its log |
|
| `BT_GOTO="enemy"\|"x z"` / `BT_GOTO_LOG` | self-driving beeline (to the enemy or a map coordinate) + its log |
|
||||||
|
|||||||
@@ -864,3 +864,17 @@ were three different presentation facts:
|
|||||||
- Authored no-lamp facts (from the mechalrm item census, [galm-item]): conditions 1 (Damaged)
|
- Authored no-lamp facts (from the mechalrm item census, [galm-item]): conditions 1 (Damaged)
|
||||||
and 3 (Overheating) have NO lamp items for ANY subsystem; cond 0 (Destroyed) resolves lamps
|
and 3 (Overheating) have NO lamp items for ANY subsystem; cond 0 (Destroyed) resolves lamps
|
||||||
only for weapon-slot subsystems. A heat alarm never flashes a cockpit button -- authored.
|
only for weapon-slot subsystems. A heat alarm never flashes a cockpit button -- authored.
|
||||||
|
|
||||||
|
## RADAR: the static world draws again (#49 CLOSED, 2026-08-14) [T1 audit + T2 operator-verified]
|
||||||
|
Full subsystem audit + fix ledger: `docs/RADAR_MAP_AUDIT.md` (agent reports:
|
||||||
|
`scratchpad/night17/radar_audit_*.txt`). Headlines: the machine's scope drew every
|
||||||
|
pip-bearing Terrain-GRAPH entity (walls/buttes/hills/ridges/buildings/trees -- 110
|
||||||
|
authored type-0x12 vector pips) as dim staticColor outlines under the bright mover
|
||||||
|
blips; the port's RebuildEntityGrid fed only DYNAMIC entities, so the whole static
|
||||||
|
world was absent. Fixed with a non-dynamic Terrain-graph pass (GAUGREND.cpp,
|
||||||
|
c2193e2). AUTHENTIC ABSENCES to defend in the field: the wall10k outer boundary
|
||||||
|
was invisible on the pods too (no pip -- never draw a boundary ring); floors/sky/
|
||||||
|
overhead pieces/walkways are unpipped by design; range rings don't exist (baked
|
||||||
|
tick ring only); the wedge is 45 deg total (hardcoded pi/8 -- the manual's "60
|
||||||
|
degrees" is print drift); radar shadowing (@004c228c) compiled empty. Receipts:
|
||||||
|
[map-grid] census + capped [map] static dumps under BT_MAP_LOG.
|
||||||
|
|||||||
@@ -115,25 +115,6 @@ each, so they cover the foot band too, and the hit test takes the first match
|
|||||||
what keeps 0x16/0x17/0x1F/0x1E reachable. That class of bug is why the verification below tests
|
what keeps 0x16/0x17/0x1F/0x1E reachable. That class of bug is why the verification below tests
|
||||||
first-hit reachability, not mere presence.
|
first-hit reachability, not mere presence.
|
||||||
|
|
||||||
**Protrusion bonus — `BT_RIO_PROTRUDE` (2026-08-14, player request) [T2 receipt+screenshot].**
|
|
||||||
Players asked for more of the RIO buttons to clear the glass so a FLASHING lamp is easier to
|
|
||||||
notice. `BTRioBankMetricsFor` now adds a flat **+10px** to `strip` AFTER the scale, so the bar
|
|
||||||
reaches that much further out on every renderer; `BT_RIO_PROTRUDE=<px>` tunes it (`0` restores
|
|
||||||
the pre-2026-08-14 geometry). Deliberately FLAT, not scaled: the surround runs the glass at half
|
|
||||||
scale where the strip sits on the 6px floor — a scaled bonus would help least exactly where the
|
|
||||||
lamps are smallest — and that floor already trades proportionality for legibility. Because only
|
|
||||||
`strip` moves, this touches exactly the **red MFD** and **yellow radar** buttons (+ the radar
|
|
||||||
foot row) and grows them **outward only**: the layout anchors the inboard edge, so under-glass
|
|
||||||
depth, button WIDTH and the press targets are unchanged. The **blue flight cells** are laid out
|
|
||||||
from `cellW/cellH` and never see it (receipt: their bank bounds are byte-identical). Verified by
|
|
||||||
`BT_RIOBANK_LOG` A/B — e.g. Heat MFD top button `y 21→11, h 126→136` with its bottom edge fixed
|
|
||||||
at 147, bank bounds `252→272` tall, no negative coordinates — plus a surround screenshot pair.
|
|
||||||
Relation to **#172**: the alert RING (`BT_ALERT_RING`, default off) answered the same night-16
|
|
||||||
complaint after a *"+8px grown-face bloom"* was rejected on the bench as "the button got fat";
|
|
||||||
this is a different shape — the lamp keeps its width and simply shows more of its own bar — so
|
|
||||||
the ring stays optional. Desktop-glass affordance only; the pod's buttons are physical and
|
|
||||||
backlit and never had this problem.
|
|
||||||
|
|
||||||
## Sticky window placement — BT_GLASS_LAYOUT (2026-07-28) [T2 round-trip-verified]
|
## Sticky window placement — BT_GLASS_LAYOUT (2026-07-28) [T2 round-trip-verified]
|
||||||
|
|
||||||
The `BT_GLASS_PANELS` windows self-place in a pod-faithful ring around the main window
|
The `BT_GLASS_PANELS` windows self-place in a pod-faithful ring around the main window
|
||||||
|
|||||||
+55
-1
@@ -31,7 +31,8 @@ to a steady pose that the cycle clips never rebind, so it HOLDS.) Decoded from B
|
|||||||
`[hip]` probe [T2]:
|
`[hip]` probe [T2]:
|
||||||
- `<pfx>swr` ramps `jointhip` 0 → **−8°** (stand→walk), `<pfx>wrl/wrr` −8 → **−11.1°** (walk→run),
|
- `<pfx>swr` ramps `jointhip` 0 → **−8°** (stand→walk), `<pfx>wrl/wrr` −8 → **−11.1°** (walk→run),
|
||||||
`<pfx>wsl/wsr` −8 → 0 (walk→stand). The dedicated return clip only exists because the lean
|
`<pfx>wsl/wsr` −8 → 0 (walk→stand). The dedicated return clip only exists because the lean
|
||||||
persists. The BLH has NO `jointhip` channel in any clip → it stays level (both views).
|
persists. The BLH **and the OWENS** have NO `jointhip` channel in any locomotion clip → they
|
||||||
|
stay level (both views; Owens jc=9 = root + 8 leg hinges, #102 content audit 2026-08-14 [T1]).
|
||||||
- **Two clip sets per mech.** The ctor gate (@part_012.c:10308-10320) binds the **INTERIOR** set
|
- **Two clip sets per mech.** The ctor gate (@part_012.c:10308-10320) binds the **INTERIOR** set
|
||||||
(4-char `'i'` suffix, `LoadLocomotionClipsExt` @004a86c8) to the **local cockpit master** — those
|
(4-char `'i'` suffix, `LoadLocomotionClipsExt` @004a86c8) to the **local cockpit master** — those
|
||||||
clips animate `jointshakey` (cockpit shake, a BALL joint) and OMIT `jointhip`, so the PILOT's own
|
clips animate `jointshakey` (cockpit shake, a BALL joint) and OMIT `jointhip`, so the PILOT's own
|
||||||
@@ -129,6 +130,59 @@ Port: keyboard lever snaps out of the band at rest (mech4.cpp "GAIT DETENT" acco
|
|||||||
the glass PadRIO throttle slew applies the SAME at-rest snap (L4PADRIO.cpp, band published in
|
the glass PadRIO throttle slew applies the SAME at-rest snap (L4PADRIO.cpp, band published in
|
||||||
lever units via the mech4.cpp `gBTGaitDetentLo/Hi` seam — 2026-07-20 glass re-test) [T2].
|
lever units via the mech4.cpp `gBTGaitDetentLo/Hi` seam — 2026-07-20 glass re-test) [T2].
|
||||||
|
|
||||||
|
**REVERSE is a BUTTON, never a lever sweep (2026-08-14, #102 audit) [T1]:** the pod's throttle
|
||||||
|
lever is 0..800 ADC → [0,1] forward-only; reverse = HOLD the red button on the throttle handle
|
||||||
|
(RIO unit 0x3F `Throttle1`, keyboard LALT; manual p7 "hold in the red button while
|
||||||
|
accelerating"). The mapper formula (@004afd10, part_013.c:7318-23) is a flag sign-flip:
|
||||||
|
forward `demand = runSpeed(0x34c)·throttle·fwdScale(0x5c0)`; reverse
|
||||||
|
`demand = −runSpeed·throttle` — **NO forwardThrottleScale in reverse** and no separate reverse
|
||||||
|
scale (full-lever reverse demands the full run top speed; the back cycle's clip-derived clamps
|
||||||
|
bound the actual backing pace). Port: PadRIO EmitButton(0x3F) → `gBTReverseHeld` →
|
||||||
|
`reverseThrust` (glass rigs; keyboard-bridge rigs also honor `key_throttle<0`). ⚠ The real
|
||||||
|
CAB's hardware red button is currently DEAD (streamed button mappings unconsumed — AddOrErase
|
||||||
|
still a Fail stub; the BTPadRIOActive accommodation excludes hardware RIO) — open-questions.
|
||||||
|
⚠ A held brake key can NEVER produce reverse: glass clamps the throttle channel to [0,1]; the
|
||||||
|
DEV keyboard-bridge zero detent latches a held sweep at exactly 0 (taps DO cross — the latch
|
||||||
|
needs prev>0). Player education: "hold ALT + keep the throttle open", exactly like the pod.
|
||||||
|
|
||||||
|
**HARD-BRAKE slew + gyro lurch/judder (#102 root, ported 2026-08-14) [T1 decode / T2 bench]:**
|
||||||
|
the master perf (@0x4aa158-0x4aa365; part_013.c:3353-95) watches for a FORWARD leg cycle
|
||||||
|
meeting a NEGATIVE demand (the red-button throw at speed): engage-edge latches `+0x3f4` (the
|
||||||
|
recon's `airborneSelect` — **another jump-jet-family misread; it is the BRAKE flag**, and it
|
||||||
|
replicates via the type-8 record field so peers pick the rate too), `ForceUpdate(0x100)`,
|
||||||
|
swaps `forwardCycleRate(0x344)` to `+0x5bc` = the model's **SuperStopAcceleration** (the recon's
|
||||||
|
"airborneCycleRate"), and kicks the gyro with a random-sign Z torque+impulse (0.4). While
|
||||||
|
braking above `walkStrideLength`, the gyro takes a vertical 0.2 impulse every 0.4 s
|
||||||
|
(`gyroRumbleTimer`@0x5c4) — the pod's brake JUDDER. Release-edge restores `+0x5b8` ground
|
||||||
|
rate. −1.1f = authored-off sentinel; every shipped chassis is LIVE: heavies brake HARDER than
|
||||||
|
they accelerate (thor/vul/ava/snd 10→20), madcat 20→20, loki 15→15, but **owens 50→25 and
|
||||||
|
blkhawk 30→10 brake at half/a third of their ground slew** — the light sprinter's long coast
|
||||||
|
is authored. The pod communicated that coast with the cab shake; the un-ported port coasted
|
||||||
|
SILENTLY, which is the felt "hang" of #102. Port: mech4.cpp drive block, before the channel
|
||||||
|
advances; `[brake]` receipts under BT_GAIT_TRACE.
|
||||||
|
|
||||||
|
**Gait-SM PARK facts (2026-08-14 bench matrix, night18/gaithang_bench.sh) [T1/T2]:** every
|
||||||
|
moving state's slew clamps are NONZERO clip-derived speeds (walk floor `standSpeed`, run floor
|
||||||
|
`reverseSpeedMax`, back-cycle ceiling `gimpSpeedMax` = sbr exit, −5.83 owens), so clips always
|
||||||
|
reach their finished-callbacks — the machine has no mid-cycle stall by construction, and a
|
||||||
|
144-clip sweep found NO mid-clip zero/negative root beats anywhere. Standing is the ONLY
|
||||||
|
rest, it never reposes the skeleton (authentic — the pose stays where the last clip left it;
|
||||||
|
wind-downs end in the authored stand pose so normal stops look clean), and the trn arm is the
|
||||||
|
one input-driven rebinder (SetLegAnimation resets the playhead — why "twisting unsticks it").
|
||||||
|
A trn cut by the turn-stop exit parks MID-PIVOT (authentic; the next input re-drives it) —
|
||||||
|
**this is the #102 "leg in the air" statue**: bench D reproduced it deterministically (turn
|
||||||
|
pulse ends mid-trn → parked at legFrm=10, leg raised; any twist/throttle re-drives — Lynx's
|
||||||
|
exact rescue). The machine had the same state but NOBODY COULD SEE IT: the pod has no
|
||||||
|
external view of your own mech (the chase camera is a port/dev addition), and peers' mechs
|
||||||
|
are continuously re-driven by their own pilots. Owens reads worst because its mid-swing
|
||||||
|
amplitude is the largest of the 8 chassis (thigh −43° vs MadCat −27°). NOT-A-BUG verdict;
|
||||||
|
the felt "hang" half was the missing brake system above.
|
||||||
|
⚠ Recon split-cell corollary: the binary's SM switch reads the LIVE alarm cell after the
|
||||||
|
wind-down-gate/death-latch preamble; the port's split alarm/int pair must RE-SYNC before the
|
||||||
|
switch or the gate frame dispatches the stale state (fixed 2026-08-14, both channels).
|
||||||
|
Bench hooks: `BT_KEYSIM="rate:sec,..."` (scripted lever keys through the real
|
||||||
|
integrator+detent), `BT_KEYSIM_REV="t:dur"` (the 0x3F button), `BT_KEYSIM_TURN="t:dur"`.
|
||||||
|
|
||||||
## Turn-in-place (trn, state 4) — AUTHENTIC dispatcher decoded (task #64, 2026-07-14)
|
## Turn-in-place (trn, state 4) — AUTHENTIC dispatcher decoded (task #64, 2026-07-14)
|
||||||
**The entry dispatcher is NOT in the Ghidra decomp** — it lives in the master-perf gap
|
**The entry dispatcher is NOT in the Ghidra decomp** — it lives in the master-perf gap
|
||||||
`0x4a9b5c–0x4ab188` (functions_index.tsv jumps FUN_004a9770→FUN_004ab188; the SOLE reader of
|
`0x4a9b5c–0x4ab188` (functions_index.tsv jumps FUN_004a9770→FUN_004ab188; the SOLE reader of
|
||||||
|
|||||||
@@ -100,6 +100,14 @@ status vs the pod's 7-display / RGB-splitter / dual-sound-card rig:
|
|||||||
3. Serial protocol timing/handshake vs a real RIO board (and the intercom path) — unverified.
|
3. Serial protocol timing/handshake vs a real RIO board (and the intercom path) — unverified.
|
||||||
4. `-platform pod` deliberately does NOT auto-enable `L4GAUGE` (each surface needs its own
|
4. `-platform pod` deliberately does NOT auto-enable `L4GAUGE` (each surface needs its own
|
||||||
device); the pod bring-up procedure itself is undocumented → the Nick items above.
|
device); the pod bring-up procedure itself is undocumented → the Nick items above.
|
||||||
|
5. **REVERSE is DEAD on the hardware-RIO cab (#102 audit side-finding, 2026-08-14) [T3
|
||||||
|
code-read].** The real red button (0x3F) reaches `buttonGroup` but nothing consumes it:
|
||||||
|
the streamed record [2] route (`Button Throttle1 → attr 6 ReverseThrust@0x124`) dead-ends
|
||||||
|
on the unreconstructed `MechControlsMapper::AddOrErase` Fail stubs, and the
|
||||||
|
`BTPadRIOActive()`-gated `gBTReverseHeld` accommodation deliberately excludes hardware
|
||||||
|
RIO. Fix lane when cab work resumes: either reconstruct AddOrErase (the streamed button
|
||||||
|
mappings then drive attr 6 themselves — the authentic path) or widen the accommodation's
|
||||||
|
gate. Decide the three-rig matrix (DEV bridge / glass PadRIO / hardware RIO) explicitly.
|
||||||
|
|
||||||
## BINARY-COVERAGE AUDIT discoveries (2026-07-13, 6-agent decomp census) [T1 verified-uncited]
|
## BINARY-COVERAGE AUDIT discoveries (2026-07-13, 6-agent decomp census) [T1 verified-uncited]
|
||||||
New unaccounted functionality no prior list knew (addresses verified absent from game/+context/+docs):
|
New unaccounted functionality no prior list knew (addresses verified absent from game/+context/+docs):
|
||||||
@@ -310,8 +318,14 @@ register. ⚠ The audit also flags the damage-economy item as SELF-CONTRADICTOR
|
|||||||
(the databinding trap). DORMANT in-game (the review context no longer survives the viewpoint
|
(the databinding trap). DORMANT in-game (the review context no longer survives the viewpoint
|
||||||
swap -- the #48 teardown fix) but LIVE the moment the mission-review screen actually runs at
|
swap -- the #48 teardown fix) but LIVE the moment the mission-review screen actually runs at
|
||||||
round end. Bridge all three through btplayer.cpp accessors before enabling the review screen.
|
round end. Bridge all three through btplayer.cpp accessors before enabling the review screen.
|
||||||
- **`PoweredSubsystem` AutoConnect is DEAD, and vtable slot +0x40 is misattributed --
|
- **✅ FIXED (#62) — `PoweredSubsystem` AutoConnect WAS dead, and vtable slot +0x40 was
|
||||||
FILED AS GITEA #62 (root cause of the #21 field report)
|
misattributed.** (Entry kept for the reasoning; the "deliberately NOT fixed" line below is
|
||||||
|
STALE — `HasVoltage(Subsystem *source = 0)` now exists and both call shapes are restored,
|
||||||
|
powersub.cpp:399/414, with a `BT_POWER_DETACH_TEST=1` hook and a `[power] AutoConnect
|
||||||
|
RE-ATTACHED` receipt. ⚠ 2026-08-15: Lynx reports an AUTO weapon still not recovering after a
|
||||||
|
GENERATOR OVERHEAT specifically — tracked in #189; the suspect there is the generator's own
|
||||||
|
stale state, not AutoConnect.)
|
||||||
|
ORIGINAL ENTRY: FILED AS GITEA #62 (root cause of the #21 field report)
|
||||||
(2026-07-25, found while auditing #47's GetStatusFlags change) [T1 decomp].** The binary
|
(2026-07-25, found while auditing #47's GetStatusFlags change) [T1 decomp].** The binary
|
||||||
(@004b0bd0 tail) calls slot +0x40 with TWO shapes: `(this, 0)` in the outer gate (require
|
(@004b0bd0 tail) calls slot +0x40 with TWO shapes: `(this, 0)` in the outer gate (require
|
||||||
`== 0`) and `(this, candidateGenerator)` inside the roster walk (require `!= 0`). That is a
|
`== 0`) and `(this, candidateGenerator)` inside the roster walk (require `!= 0`). That is a
|
||||||
|
|||||||
@@ -444,5 +444,6 @@ doing nothing:
|
|||||||
|
|
||||||
- Uses: [[content-archives]] (the EGG is the mission + roster) · [[multiplayer]] (what the relayed
|
- Uses: [[content-archives]] (the EGG is the mission + roster) · [[multiplayer]] (what the relayed
|
||||||
frames mean: master/replicant, dead reckoning)
|
frames mean: master/replicant, dead reckoning)
|
||||||
- Feeds: [[multiplayer]] · [[steam-networking]] (the alternate wire seam) · [[pod-hardware]]
|
- Feeds: [[multiplayer]] · [[steam-networking]] (the alternate wire seam) · [[pod-hardware]] ·
|
||||||
|
[[playtest-tracker]] (where the session's findings land afterwards)
|
||||||
- Sysop instructions: `docs/OPERATOR_GUIDE.md`
|
- Sysop instructions: `docs/OPERATOR_GUIDE.md`
|
||||||
|
|||||||
@@ -0,0 +1,144 @@
|
|||||||
|
---
|
||||||
|
id: playtest-tracker
|
||||||
|
title: "The playtest tracker sheet — the tester-facing issue board"
|
||||||
|
status: living
|
||||||
|
source_sections: "Established 2026-08-14/15 (session housekeeping); supersedes the .txt handout as the primary feedback channel"
|
||||||
|
related_topics: [operator-console, test-harness, project-overview]
|
||||||
|
key_terms: [gitea, tracker, playtest]
|
||||||
|
open_questions:
|
||||||
|
- "Should the OPEN tab auto-sync from gitea on a schedule, or stay a manual reconciliation before/after each playtest?"
|
||||||
|
---
|
||||||
|
|
||||||
|
# The playtest tracker sheet
|
||||||
|
|
||||||
|
Since **2026-08-14** the testers track issues in a shared Google Sheet instead of the annotated
|
||||||
|
`docs/OPEN_ISSUES_FOR_TESTERS.txt` handout. It is now the **primary feedback channel**; edited
|
||||||
|
.txt docs and pasted Discord logs are secondary ([[operator-console]] covers the session tooling
|
||||||
|
itself). The sheet is the testers' INPUT surface; **gitea remains the system of record.**
|
||||||
|
|
||||||
|
## Shape
|
||||||
|
|
||||||
|
Two tabs. Sheet ID and the write-bridge credentials live in the operator's local Claude memory
|
||||||
|
(`feedback-channels`), **never in this repo** — the bridge URL + token together grant write
|
||||||
|
access to the sheet.
|
||||||
|
|
||||||
|
- **OPEN** — `# | Priority | Test Status | Dev Status | Dev Comments | Tester Comments`,
|
||||||
|
ONE row per ticket, sorted by priority then ticket number.
|
||||||
|
- **CLOSED** — the same minus Priority, ascending by number. Also holds non-ticket records
|
||||||
|
(completed audits) under a text key like `AUDIT`.
|
||||||
|
|
||||||
|
**The split is driven by GITEA STATE, not by the Dev Status column** — the two drift constantly,
|
||||||
|
and the sheet's own column is the stale one.
|
||||||
|
|
||||||
|
## Dev Status vocabulary (dev-owned column)
|
||||||
|
|
||||||
|
The colour is **embedded in the label**, not applied as a cell fill:
|
||||||
|
|
||||||
|
| Label | Meaning |
|
||||||
|
|---|---|
|
||||||
|
| `🟡 PATCHED - RETEST` | code landed, needs a playtest to confirm — the testers' retest list |
|
||||||
|
| `🔴 FAILED RETEST` | a shipped fix did **not** hold; back on the bench (never collapse this into QUEUED — it understates a regression as "not started") |
|
||||||
|
| `🟠 NEED INFO` | blocked on testers for a repro, detail, or log |
|
||||||
|
| `🔵 YOUR CALL` | not a code question; needs their opinion or era memory |
|
||||||
|
| `⚪ QUEUED` | logged and understood, work not started |
|
||||||
|
| `🟢 WORKING` | actively being worked right now |
|
||||||
|
| `✅ DONE` | verified — **only ever appears on CLOSED** |
|
||||||
|
|
||||||
|
Priority: **P1** blocks a session / hits everyone · **P2** gameplay correctness confirmed in the
|
||||||
|
field · **P3** presentation and polish · **P4** era question or opinion call · **P5** latent or
|
||||||
|
internal with no field impact.
|
||||||
|
|
||||||
|
Test Status is TESTER-owned (`Pass` / `Fail` / `Test` = not yet retested / `Rework` / `Clarify`).
|
||||||
|
A **Fail on a ticket gitea has closed** is the highest-value signal in the whole sheet — it means
|
||||||
|
a shipped fix did not hold, or the tested build predated it.
|
||||||
|
|
||||||
|
## The Dev Comments contract
|
||||||
|
|
||||||
|
That column is what a player reads, so it is written for a player, in three lines:
|
||||||
|
|
||||||
|
```
|
||||||
|
<one sentence: what is wrong, or what changed if patched>
|
||||||
|
STATUS: <where it actually stands>
|
||||||
|
NEED FROM YOU: <a concrete, specific ask> <- omit for QUEUED
|
||||||
|
```
|
||||||
|
|
||||||
|
Under ~55 words, no addresses, no engine jargon. The ask must name what to capture (which mech,
|
||||||
|
which map, what time, what you expected vs saw, the boot-screen build number) — never "let us
|
||||||
|
know if it happens again". If the thread already answers it, do not ask again.
|
||||||
|
|
||||||
|
## Working rules (learned the hard way)
|
||||||
|
|
||||||
|
- **Read the gitea COMMENT THREAD, never the body alone.** Bodies are original reports and are
|
||||||
|
routinely superseded, retracted, or already fixed by comment 4. Triaging 53 tickets from
|
||||||
|
threads moved a third of the statuses and dropped P1 from 5 to 2. See
|
||||||
|
[[reconstruction-gotchas]] for the general form of this mistake.
|
||||||
|
- **DATE EVERY TESTER ENTRY: `M/D - ` at the front of each line.** The build is cut per session,
|
||||||
|
so the date implies the build — which is what makes a Pass or Fail interpretable at all.
|
||||||
|
Entries carried over from before this convention are marked **`(undated)`**, meaning
|
||||||
|
*provenance unknown — do not treat as fresh evidence*. That marker is deliberate: the sheet was
|
||||||
|
seeded from earlier tracking and nobody can now say which rows are original, so stamping them
|
||||||
|
all with the session date would manufacture precision. Reading undated rows as fresh already
|
||||||
|
produced a false "still crashing after the fix" claim on #170 that had to be retracted across
|
||||||
|
seven tickets — #170's row carries the same three timestamps and players as its own 2026-08-12
|
||||||
|
body, and whether that is a copy-forward or a genuine recurrence is STILL UNRESOLVED.
|
||||||
|
One entry per line, each with its own stamp; comment cells grow over time and undated text
|
||||||
|
running together is unreadable within two sessions.
|
||||||
|
An undated entry CAN often be dated from gitea: if its text is quoted in a ticket body, the
|
||||||
|
report existed by that ticket's creation date (this is how #170's three crash lines were pinned
|
||||||
|
to 2026-08-12 — its body says `FIELD 2026-08-12` and lists those exact times).
|
||||||
|
⚠ **SELF-REFERENCE TRAP:** do not date an entry from a comment YOU wrote while harvesting the
|
||||||
|
sheet — those quote the tester text verbatim, so a naive search dates the report to your own
|
||||||
|
housekeeping. Exclude same-day comments and take the EARLIEST match. This bit once already: a
|
||||||
|
#170 line was briefly stamped with the harvest date instead of the event date.
|
||||||
|
- **Never write `DONE` on OPEN.** A row that is done belongs on CLOSED; a `DONE` on the open page
|
||||||
|
is a contradiction that confuses everyone reading it.
|
||||||
|
- **Merge duplicates to one row per ticket** — linking loose reports to existing tickets creates
|
||||||
|
two rows for the same number otherwise.
|
||||||
|
- **Never use cell fills for status.** Fills belong to cells, labels to text; every re-sort moves
|
||||||
|
the text and leaves the paint, so they desync. This was re-fixed three times before the colour
|
||||||
|
was embedded in the label.
|
||||||
|
- **A leftover FILTER hides rows after a rewrite** — if rows appear to vanish and the row numbers
|
||||||
|
in the gutter SKIP, that is hidden rows, not lost data (Data → Remove filter).
|
||||||
|
- **Row heights**: autofit makes wrapped comment rows hundreds of pixels tall (≈4 visible rows).
|
||||||
|
Pin to ~63 px.
|
||||||
|
|
||||||
|
## The cycle
|
||||||
|
|
||||||
|
1. **Before a playtest** — regenerate/refresh: reconcile every row against gitea, clear stale
|
||||||
|
statuses, make sure each `NEED INFO` carries an ask the testers can act on that night.
|
||||||
|
2. **After a playtest — THE SWEEP.** Read the sheet, file the unfiled `New` rows as tickets, link
|
||||||
|
duplicates to existing numbers, post the field evidence onto the tickets, then resolve every
|
||||||
|
`🟡 PATCHED - RETEST` row by what the tester wrote in Test Status:
|
||||||
|
|
||||||
|
| Test Status | Meaning | Action |
|
||||||
|
|---|---|---|
|
||||||
|
| `Pass` | fix confirmed | close in gitea, move the row to CLOSED |
|
||||||
|
| `Fail` + detail | the fix did not hold | `🔴 FAILED RETEST`, reopen in gitea, raise to ≥P2, post their evidence |
|
||||||
|
| `Fail`, no detail | cannot act yet | `🟠 NEED INFO` with ONE discriminating question |
|
||||||
|
| blank | nobody tested it | leave `🟡`, roll to the next session |
|
||||||
|
|
||||||
|
⚠ **A Pass or Fail with no BUILD NUMBER is uninterpretable** — a Pass on an older build cannot
|
||||||
|
verify a fix that shipped later. Before treating a `Fail` as a regression, establish that the
|
||||||
|
tested build actually contained the fix; that ambiguity is why #27 and #38 sat at `NEED INFO`
|
||||||
|
(asking for the boot-screen build) rather than going straight to `🔴`. Stamp "shipped in build
|
||||||
|
NNN" into every `🟡` Dev Comment so the answer is checkable later.
|
||||||
|
3. Reconciling ~50 tickets against their threads is a fan-out job — one agent per batch of ~7,
|
||||||
|
returning `{num, dev_status, priority, dev_comment}`.
|
||||||
|
|
||||||
|
## Access
|
||||||
|
|
||||||
|
- **Read** — the sheet is public, so the CSV export works with no auth:
|
||||||
|
`https://docs.google.com/spreadsheets/d/<ID>/export?format=csv&gid=0` (307-redirects to a
|
||||||
|
`googleusercontent.com` host; follow it). The Claude **Google Drive connector cannot see it** —
|
||||||
|
that connector is scoped to files it created itself, and it has no cell-write capability for any
|
||||||
|
file, so sharing the sheet to the account does not help.
|
||||||
|
- **Write** — a Google Apps Script web app bound to the sheet, called with `curl -sL -d` (never
|
||||||
|
`-X POST`). Actions: `ping`, `tabs`, `get`, `set`, `append`, `note`, `width`, `height`,
|
||||||
|
`autofit`, `format`. Extending it requires re-pasting the script AND
|
||||||
|
**Deploy → Manage deployments → pencil → Version: New version** — clicking Deploy alone keeps
|
||||||
|
the old code live on the same URL. Treat further script edits as expensive: the operator has
|
||||||
|
said the redeploy loop is not worth repeating for cosmetics.
|
||||||
|
|
||||||
|
## Key Relationships
|
||||||
|
- Feeds: the gitea tracker (system of record) · `docs/OPEN_ISSUES_FOR_TESTERS.txt` (now secondary)
|
||||||
|
- Uses: [[operator-console]] (session tooling) · [[test-harness]] (what "verified" means here)
|
||||||
@@ -541,6 +541,20 @@ test); (3) swap in one of the spare Prolific adapters as cheap elimination; (4)
|
|||||||
port + ghost-COM cleanup regardless. ⚠ My STALL/RECOVER edge detector in 881 keys on
|
port + ghost-COM cleanup regardless. ⚠ My STALL/RECOVER edge detector in 881 keys on
|
||||||
request-age, which the 50 ms poll keeps resetting — it never fires; the `age=` field carries the
|
request-age, which the 50 ms poll keeps resetting — it never fires; the `age=` field carries the
|
||||||
real signal (fix the keying to reply-age at the next build).
|
real signal (fix the keying to reply-age at the next build).
|
||||||
|
**✅ RESOLVED 2026-08-15 (#160) — it was the FTDI adapter's BUFFER SIZE, set too high [T2,
|
||||||
|
Nick on the cab].** Lowering it fixed BOTH halves: the mid-play axis dropouts and the frozen
|
||||||
|
boots. An oversized receive buffer makes the driver BATCH incoming serial data instead of
|
||||||
|
delivering it promptly, so RIO analog samples arrive late and in clumps (the "encoder dropouts")
|
||||||
|
and the boot-time handshake can miss its window outright (the wedge). **No port code was ever at
|
||||||
|
fault** — every software theory in this section (power rail sag, board-side wedge, print
|
||||||
|
deadlock) is superseded; the surviving value of those notes is the diagnostic procedure, not the
|
||||||
|
hypotheses. Set on every future cab build or driver reinstall, since a fresh FTDI install comes
|
||||||
|
up with defaults that reproduce it: Device Manager → the COM port → Properties → Port Settings →
|
||||||
|
**Advanced** → lower **Receive (Bytes)**; check **Latency Timer** in the same panel at the same
|
||||||
|
time (1 ms is the usual value for responsive serial devices) [T4 — the adjacent standard knob,
|
||||||
|
not confirmed as part of Nick's fix]. Lesson for the ledger: a fault that reproduces on ONE
|
||||||
|
machine and survives every code change is a DRIVER/CONFIG suspect before it is a code suspect.
|
||||||
|
|
||||||
**Remote minidump procedure (worked over SSH, no tools install):** 32-bit dump of the wedged pid
|
**Remote minidump procedure (worked over SSH, no tools install):** 32-bit dump of the wedged pid
|
||||||
via `C:\Windows\SysWOW64\rundll32.exe C:\Windows\SysWOW64\comsvcs.dll, MiniDump <pid>
|
via `C:\Windows\SysWOW64\rundll32.exe C:\Windows\SysWOW64\comsvcs.dll, MiniDump <pid>
|
||||||
C:\bt411\w.dmp full` → scp home → local x86 cdb (`where.exe cdb` resolves the bt411-tools copy)
|
C:\bt411\w.dmp full` → scp home → local x86 cdb (`where.exe cdb` resolves the bt411-tools copy)
|
||||||
|
|||||||
@@ -1106,3 +1106,71 @@ THIS NEVER RUNS *** banner naming the live site (missile.cpp now does). (3) A d
|
|||||||
FIXED is a claim, not evidence -- a fix without a FIELD receipt ([incoming], [seqloop], etc.)
|
FIXED is a claim, not evidence -- a fix without a FIELD receipt ([incoming], [seqloop], etc.)
|
||||||
is T3 at best. (4) The fix's own bench must fail first: the first #83 bench (solo) produced
|
is T3 at best. (4) The fix's own bench must fail first: the first #83 bench (solo) produced
|
||||||
zero receipts because solo seats never fill -- proving the rig, then the fix, in that order.
|
zero receipts because solo seats never fill -- proving the rig, then the fix, in that order.
|
||||||
|
|
||||||
|
## 34. CHANGE DETECTION AGAINST A SHARED MULTI-WRITER SCRATCH: previousModeMask (#155, 2026-08-14)
|
||||||
|
The engine ModeManager keeps ONE previousModeMask cell, overwritten by EVERY
|
||||||
|
Add/Remove/ReplaceModeMask call. Any consumer computing `changed = current ^
|
||||||
|
manager->GetPreviousModeMask()` only sees the delta of the LAST write -- fine
|
||||||
|
while exactly one writer touches the mode per frame, silently lossy the moment
|
||||||
|
a second writer appears. The authentic panic-arm chatter (the no-hysteresis
|
||||||
|
eject evaluator flapping ModeEject per frame) made the gauge renderer's page
|
||||||
|
XOR permanently blind to SetPresetMode's page bits: MFD page EXITS died while
|
||||||
|
the PANIC lamp flickered (entries survived via !alreadyActivatedFlag; the
|
||||||
|
always-active radar/score/coolant never consult the XOR -- the field's exact
|
||||||
|
alive/dead split). The renderer ALREADY OWNED a correctly-maintained
|
||||||
|
previousModeMask member; reading the manager's scratch instead looks like
|
||||||
|
lineage drift.
|
||||||
|
**Rules:** (1) a change-detector must own its OWN baseline snapshot, sampled
|
||||||
|
and advanced at ITS consumption rate -- never a shared scratch another writer
|
||||||
|
can stomp between samples. (2) When an authentic high-frequency toggler
|
||||||
|
exists (chatter, flap, blink), audit every `GetPreviousModeMask`-shaped read
|
||||||
|
it can starve (LAMP.cpp's read survives because lamp flicker IS the intended
|
||||||
|
output). (3) The live-human receipt loop cracked what two headless rigs
|
||||||
|
could not: the injection path was clean, so the defect HAD to be presentation
|
||||||
|
-- receipts proving delivery while the screen disagrees convict the renderer.
|
||||||
|
|
||||||
|
## 35. THE SPLIT ALARM/INT CELL DISPATCHES STALE ON PREAMBLE TRANSITIONS (#102, 2026-08-14)
|
||||||
|
|
||||||
|
In the binary, a gait SM's "state variable" and its AlarmIndicator LEVEL are
|
||||||
|
ONE cell (`+0x3b0` == `legStateAlarm.level`, `+0x728` == body's) -- the switch
|
||||||
|
reads it LIVE, so a preamble transition (the wind-down gate cutting 6-9 ->
|
||||||
|
Standing, a death latch binding 0x1c-0x1f) dispatches its NEW state the same
|
||||||
|
frame: with a reverse demand held, the gate frame falls straight into case 0
|
||||||
|
-> SetLegAnimation(0x10), which REBINDS AND REWINDS the clip -- a parked pose
|
||||||
|
structurally cannot linger. The recon split the cell into an alarm + a
|
||||||
|
mirror int synced once at function top; every mid-preamble SetLevel left the
|
||||||
|
switch dispatching the STALE pre-transition case for one frame -- and any
|
||||||
|
read of the mirror between preamble steps (the gate's own state test after a
|
||||||
|
same-frame death latch) judged a dead value.
|
||||||
|
**Rules:** (1) when a recon splits a binary cell into alarm + mirror, RE-SYNC
|
||||||
|
the mirror after EVERY preamble step that can write the alarm, not just at
|
||||||
|
entry -- or read the alarm directly at each decision. (2) The binary's
|
||||||
|
same-frame fallthrough semantics (gate -> case 0 -> rebind -> advance) are
|
||||||
|
part of the state machine's correctness, not an optimization: the one-frame
|
||||||
|
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.
|
||||||
|
|||||||
@@ -7,9 +7,12 @@
|
|||||||
=============================================================================
|
=============================================================================
|
||||||
HOW TO REPORT: drop a line in Discord with the issue number and PASS /
|
HOW TO REPORT: drop a line in Discord with the issue number and PASS /
|
||||||
FAIL / what you saw. Screenshots and rough times help.
|
FAIL / what you saw. Screenshots and rough times help.
|
||||||
LOGS -- ONE CLICK: double-click SENDLOGS.bat in the game folder after the
|
LOGS -- ONE CLICK: CLOSE THE GAME, then double-click SENDLOGS.bat in the
|
||||||
session. ONE small zip lands on your Desktop -- attach that. This build
|
game folder. ONE small zip lands on your Desktop -- attach that. It now
|
||||||
also names every shot fired in the log, so reports can name weapons.
|
tells you the exact file it wrote; if it says SKIPPED, the game was still
|
||||||
|
running and holding that log. (Two of you hit errors last time -- both
|
||||||
|
causes are fixed, and it no longer claims success when it failed.) This
|
||||||
|
build also names every shot fired in the log, so reports can name weapons.
|
||||||
SOUNDS: use the SOUNDBOARD zip's COPY button to reference an exact sound
|
SOUNDS: use the SOUNDBOARD zip's COPY button to reference an exact sound
|
||||||
(that is how Oracle handed us the missile-alarm sample -- it worked).
|
(that is how Oracle handed us the missile-alarm sample -- it worked).
|
||||||
FIELD GUIDE: mark up your copy and send the file back. Oracle's return
|
FIELD GUIDE: mark up your copy and send the file back. Oracle's return
|
||||||
@@ -72,13 +75,16 @@ A. FIXED THIS BUILD -- PLEASE VERIFY (headliners first)
|
|||||||
The Nova spawned showing its REAR laser's range pips in the forward
|
The Nova spawned showing its REAR laser's range pips in the forward
|
||||||
view. The HUD now commits the forward view at spawn. (Only the pips
|
view. The HUD now commits the forward view at spawn. (Only the pips
|
||||||
were wrong -- rear weapons never actually fired forward.)
|
were wrong -- rear weapons never actually fired forward.)
|
||||||
#151 (Oracle) Panic eject scored -499
|
#151 (Oracle) Panic eject scored -499 -- CORRECTION: -499 IS the machine
|
||||||
Solved, and the machine agrees with YOUR math: eject = your mech
|
Our line here was wrong (we mis-read our own ticket history; the
|
||||||
self-destructs (scored as self-damage, ~-500) + the death cost
|
correct decode was already on the ticket). Bench-verified: eject
|
||||||
(-500) = about -1000, i.e. 1000 points -> roughly ZERO. The extra
|
is scored as SUICIDE (about -999, the kill value of your own
|
||||||
-500 you saw was the #162 double-death bug, fixed on testing night.
|
mech) PLUS the death cost (-500). 1000 - 999 - 500 = -499
|
||||||
TEST: repeat the maneuver -- you should land within a few points
|
exactly -- the original's own arithmetic, reproduced three times
|
||||||
of zero, not hundreds below.
|
on the bench, never doubled. If you eject tonight and land at
|
||||||
|
-499 again, the machine is RIGHT, not broken.
|
||||||
|
DESIGN CALL (restored): keep the authentic ~-1500 swing, or
|
||||||
|
house-rule a flat eject cost? Opinions to the thread.
|
||||||
FEEL Three more fixes on the original's clock (same bug class as #173):
|
FEEL Three more fixes on the original's clock (same bug class as #173):
|
||||||
the cockpit HIT-SHAKE (was bouncing ~2x too fast -- hits now rock
|
the cockpit HIT-SHAKE (was bouncing ~2x too fast -- hits now rock
|
||||||
you at the pod's pace with less rebound), the FOOTSTEP loudness
|
you at the pod's pace with less rebound), the FOOTSTEP loudness
|
||||||
@@ -200,7 +206,7 @@ G. INTERNAL ENGINEERING BACKLOG (nothing to test)
|
|||||||
|
|
||||||
=============================================================================
|
=============================================================================
|
||||||
End of list. 57 open issues total:
|
End of list. 57 open issues total:
|
||||||
17 verify (3 headliners), 12 reproduce, 6 era questions, 2 design calls,
|
16 verify (3 headliners), 12 reproduce, 6 era questions, 3 design calls,
|
||||||
10 known gaps, 11 internal -- plus 4 "not bugs" that are the real machine.
|
10 known gaps, 11 internal -- plus 4 "not bugs" that are the real machine.
|
||||||
Since Wednesday: 15 fixes fielded, 4 tickets closed by evidence alone, and
|
Since Wednesday: 15 fixes fielded, 4 tickets closed by evidence alone, and
|
||||||
Oracle's questionnaire + soundboard find settled five more. Your reports
|
Oracle's questionnaire + soundboard find settled five more. Your reports
|
||||||
|
|||||||
@@ -223,6 +223,7 @@ raise the seat count if you're simply out of seats.
|
|||||||
| A player gets **ROSTER FULL** | no free seat in the current round | Re-arm between rounds so the roster re-opens, then let them join |
|
| A player gets **ROSTER FULL** | no free seat in the current round | Re-arm between rounds so the roster re-opens, then let them join |
|
||||||
| Console window stops responding | the relay stalled on a dead connection | Stop Session, Start Session; please keep the log (below) |
|
| Console window stops responding | the relay stalled on a dead connection | Stop Session, Start Session; please keep the log (below) |
|
||||||
| A player says they cannot connect at all | ports, or wrong address | confirm 1500/1501 TCP + 1501 UDP reach this machine |
|
| A player says they cannot connect at all | ports, or wrong address | confirm 1500/1501 TCP + 1501 UDP reach this machine |
|
||||||
|
| **Pod cab only:** stick/throttle/pedals go dead mid-round, or the cab freezes during boot | the USB-serial (FTDI) adapter's **buffer size is too high**, so cockpit data arrives in late clumps | Device Manager → the RIO's COM port → Properties → **Port Settings → Advanced** → lower **Receive (Bytes)**. Re-check after any driver reinstall or new cab build — FTDI defaults bring it back. |
|
||||||
|
|
||||||
### The one thing to do before restarting a session
|
### The one thing to do before restarting a session
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
# Radar/Map Display Drift Audit — binary vs port vs content (2026-08-14)
|
||||||
|
|
||||||
|
**What this is:** the AI-facing ledger of the radar/map subsystem audit (3-agent,
|
||||||
|
full reports preserved at `scratchpad/night17/radar_audit_{bin,port,content}.txt`;
|
||||||
|
~800k tokens of reads). Trigger: #49 (arena walls missing from radar) + the field
|
||||||
|
report "many map items aren't showing on radar, like map boundaries."
|
||||||
|
Companion fix: the static world feed, commit `c2193e2` (#49 CLOSED, operator-verified).
|
||||||
|
|
||||||
|
## The architecture, as the machine built it [T1 unless noted]
|
||||||
|
|
||||||
|
- ONE radar picture for every map/mech: the 480×640 portrait `BTSEC1.PCX` +
|
||||||
|
`BTSEC1OV.PCX` baked art (tick ring + crosshair — **no range rings exist**),
|
||||||
|
with the live `map(` widget in the (366,367) scope rect at (58,229). There is
|
||||||
|
**no per-map radar art and no map page** — the ModeMapping bit is the
|
||||||
|
weapon-button CONFIG session, not cartography (aliased ModeNonConfig [T0]).
|
||||||
|
- **Entity membership** (@004cbfa0, the interest feed): an entity reaches the
|
||||||
|
scope iff (a) a type-0x12 GaugeImageStream pip exists for its resourceID AND
|
||||||
|
(b) Mover-derived → moving list, else Terrain-GRAPH-derived → static list.
|
||||||
|
The derivation graph parents UnscalableTerrain (0x2A), CulturalIcon (0x5E)
|
||||||
|
and Landmark (0x56) onto Terrain [T0 TERRAIN.cpp:183, CULTURAL.cpp:27] even
|
||||||
|
though their C++ bases differ.
|
||||||
|
- **Draw order** (@004c22c4): wedge → static pips → moving pips → name labels,
|
||||||
|
inside a stroke recorder (erase = replay last frame in bg color).
|
||||||
|
- **DrawStatic** (@004c25b4): each in-capability static's pip drawn with the
|
||||||
|
entity's FULL transform — walls/buttes/hills/ridges/buildings/trees rendered
|
||||||
|
as world-rotated, metre-scaled staticColor(3) outlines. 110 authored GIMs
|
||||||
|
ship (wall1-3, aw01-05, ab*/ar*/bld*, buttea-e, hillg/dhillg 652-byte
|
||||||
|
contours, sr1e/n/s/w polar rails, tree, dish, …).
|
||||||
|
- **DrawMoving** (@004c2788): every mover except the operator, color 7, target
|
||||||
|
boxed (+4px, hotbox color 1). NO self-blip; NO pipless fallback (every
|
||||||
|
shipped mech has a pip).
|
||||||
|
- **DrawNames** (@004c28c4): ≤16 callsign labels (64×16 egg rasters via
|
||||||
|
playerBitmapIndex); the target's label BLINKS at frame rate.
|
||||||
|
- **Zoom**: 6-step exponential, RadarRange = 250·2^level (250…8000 m scope
|
||||||
|
width), SLEWED smoothly; SCALE numeric shows it live. Capability radius =
|
||||||
|
RadarPercent × 4000 m — radar damage shrinks detection (#105/#121 pending).
|
||||||
|
- **Wedge**: hardcoded π/8 half-angle (45° total); the config's "180" param
|
||||||
|
only sizes the DEAD shadow table. (Manual says "60° field of vision" —
|
||||||
|
print-vs-code drift, the code wins for 4.10 [T1 vs manual].)
|
||||||
|
|
||||||
|
## Authenticity verdicts the port must respect
|
||||||
|
|
||||||
|
1. **The pure map boundary was INVISIBLE on the shipped machine.** Every
|
||||||
|
outdoor map places one `wall10k` (collision solids only, ±4100 m ring —
|
||||||
|
no pip, no video model). Players saw a "boundary" only where visible
|
||||||
|
wall/ridge ENTITIES formed the perimeter (arena aw*, polar sr1* rails, rav
|
||||||
|
buttes). Do NOT invent a boundary rectangle. (ExistanceBox type-26 extents:
|
||||||
|
arena ±700, rav ±1000, others ±7000 — available if a design call ever
|
||||||
|
wants one.)
|
||||||
|
2. **BuildRadarShadow (@004c228c) compiled EMPTY** — radar occlusion shading
|
||||||
|
was cut before ship. The port's empty body is faithful.
|
||||||
|
3. **Authored pip omissions are design**: floors/sky, wall10k, overhead butte
|
||||||
|
tops (butteeu), walkways/banners, snow banks have NO pips on purpose.
|
||||||
|
Never force-draw them.
|
||||||
|
4. **Missile pips are authored dead content**: lrm/srm/strk/nrk GIMs ship, but
|
||||||
|
Projectile/Missile are Entity-derived in the graph — they fail both class
|
||||||
|
tests and never listed on the machine either.
|
||||||
|
5. **Landmark labels + DropZone blips: dormant/absent** (zero Landmark
|
||||||
|
instances; DropZones carry rid −1).
|
||||||
|
|
||||||
|
## The #49 root cause + fix (2026-08-14, c2193e2)
|
||||||
|
|
||||||
|
The port dropped the engine's per-entity interest feed and replaced it with
|
||||||
|
`GaugeRenderer::RebuildEntityGrid` — which iterated DYNAMIC masters/replicants
|
||||||
|
only. The 1008 statically-registered UnscalableTerrain instances (every wall,
|
||||||
|
butte, hill, ridge, big building) never entered the static grid; DrawStatic
|
||||||
|
(fully transcribed and working) had nothing to draw. Dynamic-registering
|
||||||
|
CulturalIcon props (e.g. grass's 313 trees) were the only statics that ever
|
||||||
|
showed. Fix: a third RebuildEntityGrid pass files non-dynamic Terrain-graph
|
||||||
|
entities from `HostManager::AllEntityIterator`; pipless ones no-op at the pip
|
||||||
|
lookup exactly as on the machine. Receipts: `[map-grid] worldStatics=` census +
|
||||||
|
capped per-static `[map] static ent/cls/rid/pip/dsq` dump (BT_MAP_LOG).
|
||||||
|
Benched grass (hill cls=42 rid=2190 pip=1 drawing + trees) and arena1
|
||||||
|
(126 world statics, aw walls rid 1918/1915 drawing, unpipped props skipped);
|
||||||
|
operator-verified visually on arena1.
|
||||||
|
|
||||||
|
## Known port deviations (deliberate, inventoried)
|
||||||
|
|
||||||
|
- Three visible-radius culls (DrawStatic/DrawMoving/DrawNames) drop contacts
|
||||||
|
between scope radius and capability radius instead of the binary's
|
||||||
|
draw-and-clip — justified by the shared gauge surface (out-of-port draws
|
||||||
|
land on neighbors). Revisit only with a real GraphicsView clip.
|
||||||
|
- Cross-blip fallback for pipless movers (dev content insurance; dead in the
|
||||||
|
field — every shipped mech has a pip).
|
||||||
|
- Dead-mover (state 2/9) radar drop: the wreck-blip removal. The binary's
|
||||||
|
uninteresting-notification presumably did the same [T4 — unverified whether
|
||||||
|
a pod wreck kept its blip].
|
||||||
|
- Per-frame O(all-entities) rebuild vs the authentic event-driven feed —
|
||||||
|
acceptable at shipped populations; restoring the notify feed retires it.
|
||||||
|
|
||||||
|
## Stale notes corrected by this audit
|
||||||
|
|
||||||
|
- `mech.cpp` "SetTargetRange is stubbed" — FALSE since the zoom work
|
||||||
|
(btstubs.cpp body + the mapper slew); comment swept 2026-08-14.
|
||||||
|
- `btl4rdr.cpp` header "Make has no exported caller" — stale; @004c1b4c IS in
|
||||||
|
the 2026-08-06 re-export.
|
||||||
|
- `docs/GAUGE_COMPOSITE.md` row 24 "cross-blip stand-in" — stale; pips are the
|
||||||
|
live path since dd27238.
|
||||||
|
|
||||||
|
## Open follow-ups
|
||||||
|
|
||||||
|
- ERA QUESTION (for the group): what did the ARENA EDGE look like on the pod's
|
||||||
|
scope — just the wall outlines, or anything more? (Guards against inventing
|
||||||
|
a boundary ring; the code says wall outlines only.)
|
||||||
|
- #105/#121: RadarPercent is pinned 1.0 until sensor damage lands — the
|
||||||
|
capability-shrink lane is authored and waiting.
|
||||||
|
- The 45°-wedge vs the manual's "60° field of vision" — flag to testers
|
||||||
|
comparing against memory; the 4.10 code hardcodes π/8·2.
|
||||||
|
- Overlay-plane compositing into the dev sec cell (SECTOR/SCALE readouts) —
|
||||||
|
existing polish item, unchanged.
|
||||||
@@ -3675,6 +3675,49 @@ void
|
|||||||
staticEntities.Add(entity);
|
staticEntities.Add(entity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// (BT411 #49, 2026-08-14) THE STATIC WORLD FEED -- the missing half of the
|
||||||
|
// dropped interest feed. The original filed every pip-bearing
|
||||||
|
// Terrain-GRAPH entity into staticEntities via NotifyOfNewInterestingEntity
|
||||||
|
// (@004cbfa0: pip-resource gate, then Mover -> moving / Terrain -> static);
|
||||||
|
// the derivation graph parents UnscalableTerrain (0x2A), CulturalIcon
|
||||||
|
// (0x5E) and Landmark (0x56) onto Terrain [T0 TERRAIN.cpp:183,
|
||||||
|
// CULTURAL.cpp:27], so the map streams' walls / arena wall sections /
|
||||||
|
// buttes / hills / perimeter ridges / buildings / trees (1008
|
||||||
|
// UnscalableTerrain + 789 CulturalIcon instances across the shipped maps)
|
||||||
|
// were all radar-visible as authored vector-outline pips (110 type-0x12
|
||||||
|
// GIMs ship). The dynamic iterators above never see statically-registered
|
||||||
|
// entities (Host::AddEntity files them into allEntitySocket only), so the
|
||||||
|
// port's scope drew NO static world at all -- the #49 walls and the field's
|
||||||
|
// 'map items not showing'. File non-dynamic Terrain-graph entities here;
|
||||||
|
// pipless ones (floors, sky, the wall10k outer fence -- authentically
|
||||||
|
// invisible on the shipped radar too) no-op at DrawStatic's pip lookup
|
||||||
|
// exactly as they did on the machine.
|
||||||
|
int world_statics = 0;
|
||||||
|
{
|
||||||
|
HostManager::AllEntityIterator all_iterator(host_manager);
|
||||||
|
all_iterator.First();
|
||||||
|
Entity *entity;
|
||||||
|
while ((entity = all_iterator.ReadAndNext()) != NULL)
|
||||||
|
{
|
||||||
|
if (entity->IsDynamic()) // dynamics already sorted above
|
||||||
|
continue;
|
||||||
|
if (entity->IsDerivedFrom(*Terrain::GetClassDerivations()))
|
||||||
|
{
|
||||||
|
staticEntities.Add(entity);
|
||||||
|
++world_statics;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// census receipt (#49 bench): an empty static scope must be
|
||||||
|
// distinguishable from a culled one. ~1 Hz under BT_MAP_LOG.
|
||||||
|
if (getenv("BT_MAP_LOG"))
|
||||||
|
{
|
||||||
|
static int s_census = 0;
|
||||||
|
if ((s_census++ % 60) == 0)
|
||||||
|
DEBUG_STREAM << "[map-grid] worldStatics=" << world_statics
|
||||||
|
<< "\n" << std::flush;
|
||||||
|
}
|
||||||
Check_Fpu();
|
Check_Fpu();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3702,8 +3745,19 @@ void
|
|||||||
// like the pod -- and the working SetPresetMode page switching NEEDS the
|
// like the pod -- and the working SetPresetMode page switching NEEDS the
|
||||||
// authentic gating (the force made every eng screen paint over the shared
|
// authentic gating (the force made every eng screen paint over the shared
|
||||||
// Eng plane, pinning it on the highest screen and ignoring page flips).
|
// Eng plane, pinning it on the highest screen and ignoring page flips).
|
||||||
|
// (BT411 #155, 2026-08-14) READ OUR OWN LAST-FRAME MASK, not the mode
|
||||||
|
// manager's previousModeMask: that cell is a shared scratch overwritten by
|
||||||
|
// EVERY Add/RemoveModeMask call, and the authentic panic-arm chatter (the
|
||||||
|
// no-hysteresis eject evaluator) writes it per frame -- stomping the page
|
||||||
|
// bits out of this XOR before we sample it. Result in the field: MFD page
|
||||||
|
// EXITS went dead while PANIC flickered (first entries still worked via
|
||||||
|
// !alreadyActivatedFlag), radar/score/coolant stayed live (always-active).
|
||||||
|
// The renderer already maintains its own previousModeMask member (updated
|
||||||
|
// below at the activate/deactivate guard); sampling it here makes the
|
||||||
|
// change mask accumulate everything since the LAST RENDER FRAME, correct
|
||||||
|
// under any number of mode writers. Single-transition frames: identical.
|
||||||
ModeMask
|
ModeMask
|
||||||
previous_mode_mask = application->GetModeManager()->GetPreviousModeMask();
|
previous_mode_mask = previousModeMask;
|
||||||
ModeMask
|
ModeMask
|
||||||
change_mode_mask = current_mode_mask ^ previous_mode_mask; // xor tells me what has changed
|
change_mode_mask = current_mode_mask ^ previous_mode_mask; // xor tells me what has changed
|
||||||
//-------------------------------------------------------------------
|
//-------------------------------------------------------------------
|
||||||
|
|||||||
@@ -25,46 +25,6 @@ namespace
|
|||||||
const int minimumStrip = 6;
|
const int minimumStrip = 6;
|
||||||
const int defaultGap = 4;
|
const int defaultGap = 4;
|
||||||
|
|
||||||
//-------------------------------------------------------------------
|
|
||||||
// PROTRUSION BONUS (2026-08-14, player request). Flat pixels added to
|
|
||||||
// the lamp strip AFTER the scale, so the visible bar reaches this much
|
|
||||||
// further out of the glass on EVERY renderer.
|
|
||||||
//
|
|
||||||
// WHY FLAT, not scaled: the ask is on-screen noticeability of a
|
|
||||||
// FLASHING lamp, and the surround runs the glass at half scale, where
|
|
||||||
// the strip lands on the `minimumStrip` floor (~6px) -- a scaled bonus
|
|
||||||
// would give the surround half the help exactly where the lamps are
|
|
||||||
// smallest. The floor above already establishes legibility over strict
|
|
||||||
// proportionality for this field; this is the same trade, sized by the
|
|
||||||
// players. It is a DESKTOP-GLASS affordance: the pod's buttons are
|
|
||||||
// physical and backlit and have never had this problem.
|
|
||||||
//
|
|
||||||
// Scope: only the strip -- so only the RED MFD buttons and the YELLOW
|
|
||||||
// radar buttons/foot row grow, and they grow OUTWARD ONLY (the layout
|
|
||||||
// anchors the inboard edge; under-glass depth is unchanged). The BLUE
|
|
||||||
// flight cells are laid out from cellW/cellH and never see this.
|
|
||||||
//
|
|
||||||
// Relation to #172: the alert RING (BT_ALERT_RING, default off) was the
|
|
||||||
// answer to the same night-16 complaint after a "+8px grown-face bloom"
|
|
||||||
// was rejected on the bench as "the button got fat". This is a
|
|
||||||
// different shape -- the lamp keeps its width and simply shows more of
|
|
||||||
// its own bar -- and it is unconditional, so the ring stays optional.
|
|
||||||
//-------------------------------------------------------------------
|
|
||||||
const int defaultProtrude = 10;
|
|
||||||
|
|
||||||
int ProtrudeBonus()
|
|
||||||
{
|
|
||||||
static int cached = -1;
|
|
||||||
if (cached < 0)
|
|
||||||
{
|
|
||||||
const char *e = getenv("BT_RIO_PROTRUDE"); // tune by eye; 0 = pre-2026-08-14
|
|
||||||
cached = (e != 0) ? atoi(e) : defaultProtrude;
|
|
||||||
if (cached < 0) cached = 0;
|
|
||||||
if (cached > 200) cached = 200;
|
|
||||||
}
|
|
||||||
return cached;
|
|
||||||
}
|
|
||||||
|
|
||||||
//-------------------------------------------------------------------
|
//-------------------------------------------------------------------
|
||||||
// Per-column horizontal nudge for the 4 MFD buttons, left->right, so
|
// Per-column horizontal nudge for the 4 MFD buttons, left->right, so
|
||||||
// they line up with the (unevenly spaced) DISPLAY / PROGRAM / ...
|
// they line up with the (unevenly spaced) DISPLAY / PROGRAM / ...
|
||||||
@@ -151,7 +111,6 @@ void
|
|||||||
|
|
||||||
int strip = Scaled(nativeStrip, shortAxis, nativeShortAxis);
|
int strip = Scaled(nativeStrip, shortAxis, nativeShortAxis);
|
||||||
if (strip < minimumStrip) strip = minimumStrip;
|
if (strip < minimumStrip) strip = minimumStrip;
|
||||||
strip += ProtrudeBonus(); // player-requested reach (see above)
|
|
||||||
|
|
||||||
out->strip = strip;
|
out->strip = strip;
|
||||||
out->gap = defaultGap;
|
out->gap = defaultGap;
|
||||||
|
|||||||
@@ -394,20 +394,6 @@ static const char *kEnvironIniDefault =
|
|||||||
"# Phosphor colour of the mono MFDs, RRGGBB. Default is a green tube.\n"
|
"# Phosphor colour of the mono MFDs, RRGGBB. Default is a green tube.\n"
|
||||||
"#BT_COCKPIT_TINT=21FF42\n"
|
"#BT_COCKPIT_TINT=21FF42\n"
|
||||||
"\n"
|
"\n"
|
||||||
"# How far the lit RIO buttons reach out past the edge of a display, in pixels.\n"
|
|
||||||
"# The pod's buttons were physical and backlit; on glass they are a thin bar\n"
|
|
||||||
"# along the display edge, and a FLASHING one is easy to miss. This is how much\n"
|
|
||||||
"# more of that bar shows -- a LONGER bar, not a wider button: the button keeps\n"
|
|
||||||
"# its width, the display picture still covers everything it did before, and the\n"
|
|
||||||
"# whole display stays clickable. Red (MFD) and yellow (map) buttons only; the\n"
|
|
||||||
"# blue flight block has no glass to hide behind, so it never changes.\n"
|
|
||||||
"# 10 the default\n"
|
|
||||||
"# 0 back to the original thin bar\n"
|
|
||||||
"# 1-200 anything in between\n"
|
|
||||||
"# Give it a NUMBER -- a word like 'on' counts as 0 and turns it off. In the\n"
|
|
||||||
"# exploded view (BT_GLASS_PANELS) the windows grow to fit the longer buttons.\n"
|
|
||||||
"#BT_RIO_PROTRUDE=10\n"
|
|
||||||
"\n"
|
|
||||||
"# ---- Display ----------------------------------------------------------------\n"
|
"# ---- Display ----------------------------------------------------------------\n"
|
||||||
"\n"
|
"\n"
|
||||||
"# The cockpit is fitted into the window at ONE uniform scale, centred, with\n"
|
"# The cockpit is fitted into the window at ONE uniform scale, centred, with\n"
|
||||||
|
|||||||
@@ -867,6 +867,20 @@ void
|
|||||||
Pip *pip = (pips != NULL)
|
Pip *pip = (pips != NULL)
|
||||||
? pips->LookUpPip(entity->GetResourceID()) // FUN_004688ce(pips+0x58,entity+0x1bc,0)
|
? pips->LookUpPip(entity->GetResourceID()) // FUN_004688ce(pips+0x58,entity+0x1bc,0)
|
||||||
: NULL;
|
: NULL;
|
||||||
|
// (#49 bench receipt) per-static dump, symmetric to DrawMoving's --
|
||||||
|
// answers "is the list empty, the pip lookup missing, or the cull
|
||||||
|
// eating it" straight from a field log. Capped; BT_MAP_LOG.
|
||||||
|
if (getenv("BT_MAP_LOG"))
|
||||||
|
{
|
||||||
|
static int s_sd = 0;
|
||||||
|
if (s_sd++ < 400)
|
||||||
|
DEBUG_STREAM << "[map] static ent=" << (void *)entity
|
||||||
|
<< " cls=" << (int)entity->GetClassID()
|
||||||
|
<< " rid=" << entity->GetResourceID()
|
||||||
|
<< " pip=" << (pip != NULL ? 1 : 0)
|
||||||
|
<< " dsq=" << (float)(delta.x*delta.x + delta.z*delta.z)
|
||||||
|
<< ((s_sd == 400) ? " (cap)" : "") << "\n" << std::flush;
|
||||||
|
}
|
||||||
if (pip != NULL)
|
if (pip != NULL)
|
||||||
{
|
{
|
||||||
AffineMatrix blipXform;
|
AffineMatrix blipXform;
|
||||||
|
|||||||
@@ -603,6 +603,26 @@ int
|
|||||||
|| (liveGenerators == 0)
|
|| (liveGenerators == 0)
|
||||||
|| (coolantFrac < 0.05f) // _DAT_0049fb50
|
|| (coolantFrac < 0.05f) // _DAT_0049fb50
|
||||||
|| (gimped && noviceSim);
|
|| (gimped && noviceSim);
|
||||||
|
// (#155 rig) BT_FORCE_EJECT_ARM: pin the panic-arm state without damage
|
||||||
|
// side-effects, so the dead-MFD-bank repro isolates the ModeEject
|
||||||
|
// interaction. =1 steady ON; =2 CHATTER at the evaluator's own rate
|
||||||
|
// (the authentic no-hysteresis flap, e.g. a coolant fraction hovering at
|
||||||
|
// the 0.05 threshold). Env-gated bench scaffolding, off by default.
|
||||||
|
{
|
||||||
|
static int s_forceArm = -1;
|
||||||
|
if (s_forceArm < 0)
|
||||||
|
{
|
||||||
|
const char *fa = getenv("BT_FORCE_EJECT_ARM");
|
||||||
|
s_forceArm = (fa != 0) ? atoi(fa) : 0;
|
||||||
|
}
|
||||||
|
if (s_forceArm == 1)
|
||||||
|
ejectPermitted = 1;
|
||||||
|
else if (s_forceArm == 2)
|
||||||
|
{
|
||||||
|
static unsigned s_flap = 0;
|
||||||
|
ejectPermitted = (int)(++s_flap & 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
return ejectPermitted;
|
return ejectPermitted;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1688,9 +1708,12 @@ Mech::Mech(
|
|||||||
vertSpeedFiltered = 0.0f;
|
vertSpeedFiltered = 0.0f;
|
||||||
{ extern void *g_btFootStepAddr; g_btFootStepAddr = &footStep; } // DIAG: watcher-poll tracer target
|
{ extern void *g_btFootStepAddr; g_btFootStepAddr = &footStep; } // DIAG: watcher-poll tracer target
|
||||||
{ extern void *g_btAccelAddr; g_btAccelAddr = &localAcceleration; } // DIAG: accel poll/change tracer
|
{ extern void *g_btAccelAddr; g_btAccelAddr = &localAcceleration; } // DIAG: accel poll/change tracer
|
||||||
radarRange = 1000.0f; // radar display scale (SetTargetRange is stubbed;
|
radarRange = 1000.0f; // radar ZOOM default (level 2 of the 250*2^n ladder;
|
||||||
// 1km default zoom so contacts within ~500m show on
|
// scope shows currentScale/2 = 500m radius). The zoom
|
||||||
// the radar, vs the config maximum_range=4000 edge)
|
// buttons slew it live via SetTargetRange (btstubs.cpp
|
||||||
|
// body + the mapper slew -- the old 'stubbed' note was
|
||||||
|
// stale, swept 2026-08-14 radar audit). The config's
|
||||||
|
// 4000 is the CAPABILITY radius, a different axis.
|
||||||
radarLinearPosition = &localOrigin.linearPosition; // map reads the mech's live world position...
|
radarLinearPosition = &localOrigin.linearPosition; // map reads the mech's live world position...
|
||||||
radarAngularPosition= &localOrigin.angularPosition; // ...and orientation (pointers into the base origin)
|
radarAngularPosition= &localOrigin.angularPosition; // ...and orientation (pointers into the base origin)
|
||||||
duckState = 0; // not crouching
|
duckState = 0; // not crouching
|
||||||
|
|||||||
@@ -923,7 +923,9 @@ Scalar
|
|||||||
// run/run-to-walk transition, drop the gait alarm to "standing".
|
// run/run-to-walk transition, drop the gait alarm to "standing".
|
||||||
//
|
//
|
||||||
{
|
{
|
||||||
int state = legAnimationState; // this+0x3b0
|
// live-cell read (binary: one field @0x3b0; a same-frame death latch
|
||||||
|
// above must not leave a stale 6-9 here for the gate to stomp)
|
||||||
|
int state = (int)legStateAlarm.GetLevel();
|
||||||
if (legCycleSpeed <= ZeroSpeed // this+0x348
|
if (legCycleSpeed <= ZeroSpeed // this+0x348
|
||||||
&& (state == 6 || state == 7 || state == 8 || state == 9))
|
&& (state == 6 || state == 7 || state == 8 || state == 9))
|
||||||
{
|
{
|
||||||
@@ -932,6 +934,16 @@ Scalar
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SAME-FRAME RE-DISPATCH (#102, 2026-08-14): the binary's switch re-reads
|
||||||
|
// the state cell AFTER the gate/death-latch preamble (part_012.c:17523
|
||||||
|
// 'switch(*(param_1 + 0x3b0))' -- +0x3b0 IS the alarm level, one cell), so
|
||||||
|
// the frame the wind-down gate cuts to Standing dispatches case 0 at once
|
||||||
|
// and, with a reverse demand held, rebinds the reverse-entry clip the SAME
|
||||||
|
// frame (SetLegAnimation resets the playhead -- the pose can never linger).
|
||||||
|
// The recon's split alarm/int pair made the switch read the STALE pre-gate
|
||||||
|
// state for one frame; re-sync here restores the binary's dispatch.
|
||||||
|
legAnimationState = (int)legStateAlarm.GetLevel();
|
||||||
|
|
||||||
switch (legAnimationState) // this+0x3b0
|
switch (legAnimationState) // this+0x3b0
|
||||||
{
|
{
|
||||||
case StandingAnimation: // 0
|
case StandingAnimation: // 0
|
||||||
@@ -1268,6 +1280,12 @@ Scalar
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SAME-FRAME RE-DISPATCH (#102 parity twin): the binary's switch reads the
|
||||||
|
// live cell +0x728 after the death-latch preamble (FUN_004a5678 has no
|
||||||
|
// wind-down gate [T1 part_013.c:1-33]); re-sync so a just-latched crash
|
||||||
|
// clip dispatches this frame, not next.
|
||||||
|
bodyAnimationState = (int)bodyStateAlarm.GetLevel();
|
||||||
|
|
||||||
switch (bodyAnimationState) // this+0x728
|
switch (bodyAnimationState) // this+0x728
|
||||||
{
|
{
|
||||||
case StandingAnimation: // 0
|
case StandingAnimation: // 0
|
||||||
|
|||||||
@@ -3279,6 +3279,15 @@ void
|
|||||||
{
|
{
|
||||||
if (dt > 0.0001f && dt < 0.5f)
|
if (dt > 0.0001f && dt < 0.5f)
|
||||||
{
|
{
|
||||||
|
// BRAKE-RATE SELECT (#102, 2026-08-14): the binary's replicant ran
|
||||||
|
// IntegrateMotion (@004ab1c8) per frame, whose first line picks the
|
||||||
|
// slew from the replicated brake flag (mech4.cpp:298 -- that TU path
|
||||||
|
// is DEAD in the port, Mech::Simulate bypassed). The flag itself
|
||||||
|
// already replicates (type-8 record -> airborneSelect, mech.cpp
|
||||||
|
// ReadUpdateRecord); without this line a peer watched the master
|
||||||
|
// brake at superStopAcceleration while its own slew stayed at the
|
||||||
|
// ground rate.
|
||||||
|
forwardCycleRate = airborneSelect ? airborneCycleRate : groundCycleRate;
|
||||||
// AUTHENTIC PEER HEADING (decomp FUN_004ab188/FUN_00409f58 via FUN_004ab1c8):
|
// AUTHENTIC PEER HEADING (decomp FUN_004ab188/FUN_00409f58 via FUN_004ab1c8):
|
||||||
// the original replicant integrates its heading INCREMENTALLY from the
|
// the original replicant integrates its heading INCREMENTALLY from the
|
||||||
// CURRENT pose -- compose an exact rotation of (replicated yaw rate * dt)
|
// CURRENT pose -- compose an exact rotation of (replicated yaw rate * dt)
|
||||||
@@ -3925,6 +3934,73 @@ void
|
|||||||
// gBTInput. Everything below the reads (the lever/stick/
|
// gBTInput. Everything below the reads (the lever/stick/
|
||||||
// twist integrators, detents, edge latches) is UNCHANGED.
|
// twist integrators, detents, edge latches) is UNCHANGED.
|
||||||
BTInputPoll(dt);
|
BTInputPoll(dt);
|
||||||
|
// BT_KEYSIM (issue #102 bench, 2026-08-14): scripted key-hold
|
||||||
|
// schedule -- overrides the polled lever/turn channels so a
|
||||||
|
// headless bench can drive the REAL virtual-controls chain
|
||||||
|
// (lever integrator + zero detent + gait SM), which the
|
||||||
|
// forcedThrottle harnesses bypass. Spec: comma-separated
|
||||||
|
// "rate:seconds" segments consumed in order; rate is written
|
||||||
|
// to gBTInput.leverRate verbatim (the key-hold rate is ~0.7;
|
||||||
|
// sign = key direction; 0 = keys at rest). After the last
|
||||||
|
// segment the lever stays at rest (a bench never mixes real
|
||||||
|
// keys in). BT_KEYSIM_TURN="start:dur" adds a full-right
|
||||||
|
// turn pulse in [start, start+dur) on the same clock (the
|
||||||
|
// twist-rescue leg of the #102 repro).
|
||||||
|
{
|
||||||
|
static const char *s_ksSpec = getenv("BT_KEYSIM");
|
||||||
|
if (s_ksSpec && *s_ksSpec)
|
||||||
|
{
|
||||||
|
static float s_ksClock = 0.0f;
|
||||||
|
s_ksClock += dt;
|
||||||
|
float segEnd = 0.0f; int applied = 0;
|
||||||
|
for (const char *p = s_ksSpec; p && *p; )
|
||||||
|
{
|
||||||
|
const float rate = (float)atof(p);
|
||||||
|
const char *colon = strchr(p, ':');
|
||||||
|
if (!colon) break;
|
||||||
|
segEnd += (float)atof(colon + 1);
|
||||||
|
if (s_ksClock < segEnd)
|
||||||
|
{
|
||||||
|
gBTInput.leverRate = rate;
|
||||||
|
applied = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const char *comma = strchr(p, ',');
|
||||||
|
p = comma ? comma + 1 : 0;
|
||||||
|
}
|
||||||
|
if (!applied)
|
||||||
|
gBTInput.leverRate = 0.0f; // schedule exhausted
|
||||||
|
static const char *s_ktSpec = getenv("BT_KEYSIM_TURN");
|
||||||
|
if (s_ktSpec && *s_ktSpec)
|
||||||
|
{
|
||||||
|
const float t0 = (float)atof(s_ktSpec);
|
||||||
|
const char *c2 = strchr(s_ktSpec, ':');
|
||||||
|
const float tdur = c2 ? (float)atof(c2 + 1) : 0.0f;
|
||||||
|
if (s_ksClock >= t0 && s_ksClock < t0 + tdur)
|
||||||
|
{
|
||||||
|
gBTInput.turnActive = 1;
|
||||||
|
gBTInput.turnTarget = 1.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// BT_KEYSIM_REV="start:dur": hold the pod's REVERSE
|
||||||
|
// THRUST button (unit 0x3F, LALT) in [start,start+dur)
|
||||||
|
// on the same clock -- drives gBTReverseHeld, the one
|
||||||
|
// chokepoint every rig funnels through (L4PADRIO
|
||||||
|
// EmitButton -> mechmppr reverseThrust). This is the
|
||||||
|
// AUTHENTIC reverse shape: throttle stays put, demand
|
||||||
|
// sign-flips (issue #102 repro).
|
||||||
|
static const char *s_krSpec = getenv("BT_KEYSIM_REV");
|
||||||
|
if (s_krSpec && *s_krSpec)
|
||||||
|
{
|
||||||
|
extern int gBTReverseHeld;
|
||||||
|
const float r0 = (float)atof(s_krSpec);
|
||||||
|
const char *c3 = strchr(s_krSpec, ':');
|
||||||
|
const float rdur = c3 ? (float)atof(c3 + 1) : 0.0f;
|
||||||
|
gBTReverseHeld =
|
||||||
|
(s_ksClock >= r0 && s_ksClock < r0 + rdur) ? 1 : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
gBTDrive.keyFwd = gBTInput.leverRate > 0.0001f ? 1 : 0;
|
gBTDrive.keyFwd = gBTInput.leverRate > 0.0001f ? 1 : 0;
|
||||||
gBTDrive.keyBack = gBTInput.leverRate < -0.0001f ? 1 : 0;
|
gBTDrive.keyBack = gBTInput.leverRate < -0.0001f ? 1 : 0;
|
||||||
gBTDrive.keyLeft = (gBTInput.turnActive && gBTInput.turnTarget < 0.0f) ? 1 : 0;
|
gBTDrive.keyLeft = (gBTInput.turnActive && gBTInput.turnTarget < 0.0f) ? 1 : 0;
|
||||||
@@ -5413,6 +5489,74 @@ void
|
|||||||
<< " reverseSpeedMax=" << reverseSpeedMax << " target=" << bodyTargetSpeed
|
<< " reverseSpeedMax=" << reverseSpeedMax << " target=" << bodyTargetSpeed
|
||||||
<< " split=" << s_realControls << "\n" << std::flush;
|
<< " split=" << s_realControls << "\n" << std::flush;
|
||||||
}
|
}
|
||||||
|
// HARD-BRAKE SLEW + GYRO LURCH/JUDDER (issue #102, 2026-08-14) --
|
||||||
|
// FUN_004a9b5c @0x4aa158-0x4aa365 (part_013.c:3353-3395 in the
|
||||||
|
// 2026-08-06 re-export; disasm scratchpad/night6/gap_4a9770.txt),
|
||||||
|
// transcribed field for field. When a FORWARD leg cycle meets a
|
||||||
|
// NEGATIVE demand (the red-button reverse throw at speed), the
|
||||||
|
// binary latches airborneSelect(+0x3f4) -- ANOTHER jump-jet-family
|
||||||
|
// misread: it is the BRAKE-ENGAGED flag, and +0x5bc
|
||||||
|
// ("airborneCycleRate") is the model's SuperStopAcceleration, the
|
||||||
|
// hard-brake slew -- swaps forwardCycleRate(+0x344) to it, ships
|
||||||
|
// the flag (ForceUpdate(0x100); the type-8 record field carries it
|
||||||
|
// so peers pick the brake rate too), and kicks the gyro with a
|
||||||
|
// random-sign Z lurch. While braking above the walk cap the gyro
|
||||||
|
// takes a vertical 0.2 impulse every 0.4 s (gyroRumbleTimer@0x5c4,
|
||||||
|
// zeroed by the clip loader) -- the pod's brake JUDDER, the cab
|
||||||
|
// shake that told the pilot "braking!" through the long coast.
|
||||||
|
// -1.1f = authored-off sentinel (no shipped chassis uses it;
|
||||||
|
// content: Owens brakes at HALF its ground rate 50->25, Blackhawk
|
||||||
|
// 30->10, the heavies 10->20). Without this block the port
|
||||||
|
// silently coasted at the ground slew with zero feedback -- the
|
||||||
|
// felt "hang" of #102.
|
||||||
|
if (airborneCycleRate != -1.1f)
|
||||||
|
{
|
||||||
|
MechControlsMapper *bmppr = MappingMapper();
|
||||||
|
const Scalar bdmd = (bmppr != 0) ? bmppr->speedDemand : 0.0f;
|
||||||
|
if (legCycleSpeed > 0.0f && bdmd < 0.0f)
|
||||||
|
{
|
||||||
|
if (airborneSelect == 0)
|
||||||
|
{
|
||||||
|
airborneSelect = 1; // brake ENGAGE edge
|
||||||
|
ForceUpdate(0x100);
|
||||||
|
forwardCycleRate = airborneCycleRate; // 0x344 <- 0x5bc
|
||||||
|
if (gyroSubsystem != 0)
|
||||||
|
{
|
||||||
|
extern void GyroApplyDamageImpulse(Subsystem *, Scalar, Scalar, Scalar, Scalar);
|
||||||
|
extern void GyroApplyDamageTorque (Subsystem *, Scalar, Scalar, Scalar, Scalar);
|
||||||
|
Scalar r = RandomUnit();
|
||||||
|
if (RandomUnit() > 0.5f) // _DAT_004ab170
|
||||||
|
r = -r;
|
||||||
|
GyroApplyDamageTorque (gyroSubsystem, 0.0f, 0.0f, r, 0.4f);
|
||||||
|
GyroApplyDamageImpulse(gyroSubsystem, 0.0f, 0.0f, r, 0.4f);
|
||||||
|
}
|
||||||
|
if (getenv("BT_GAIT_TRACE"))
|
||||||
|
DEBUG_STREAM << "[brake] ENGAGE cyc=" << legCycleSpeed
|
||||||
|
<< " dmd=" << bdmd << " rate=" << airborneCycleRate
|
||||||
|
<< " (ground " << groundCycleRate << ")\n" << std::flush;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (airborneSelect != 0)
|
||||||
|
{
|
||||||
|
airborneSelect = 0; // brake RELEASE edge
|
||||||
|
ForceUpdate(0x100);
|
||||||
|
forwardCycleRate = groundCycleRate; // 0x344 <- 0x5b8
|
||||||
|
if (getenv("BT_GAIT_TRACE"))
|
||||||
|
DEBUG_STREAM << "[brake] release\n" << std::flush;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (airborneSelect != 0 && walkStrideLength < legCycleSpeed
|
||||||
|
&& gyroSubsystem != 0)
|
||||||
|
{
|
||||||
|
if (gyroRumbleTimer > 0.0f)
|
||||||
|
gyroRumbleTimer -= dt; // +0x5c4 countdown
|
||||||
|
else
|
||||||
|
{
|
||||||
|
extern void GyroApplyDamageImpulse(Subsystem *, Scalar, Scalar, Scalar, Scalar);
|
||||||
|
GyroApplyDamageImpulse(gyroSubsystem, 0.0f, 1.0f, 0.0f, 0.2f);
|
||||||
|
gyroRumbleTimer = 0.4f; // the 0.4s judder period
|
||||||
|
}
|
||||||
|
}
|
||||||
if (s_realControls)
|
if (s_realControls)
|
||||||
{
|
{
|
||||||
// CHANNEL ROLES (task #49, disasm-corrected [T1]): in the binary
|
// CHANNEL ROLES (task #49, disasm-corrected [T1]): in the binary
|
||||||
|
|||||||
+121
-66
@@ -1106,9 +1106,19 @@ void
|
|||||||
damageDescriptors.push_back(d);
|
damageDescriptors.push_back(d);
|
||||||
}
|
}
|
||||||
if (count != 0 && getenv("BT_DEATH_LOG"))
|
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)
|
// #125: full table (the summary-only line hid the question that
|
||||||
<< " effect=" << (count > 0 ? damageDescriptors[0].effectResource : -1) << ")\n" << std::flush;
|
// 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).
|
// per-zone last-damage cache, zeroed (binary this[0x10], size mech+0x11c).
|
||||||
int count = (mech != 0 && mech->damageZoneCount > 0) ? mech->damageZoneCount : 0;
|
int count = (mech != 0 && mech->damageZoneCount > 0) ? mech->damageZoneCount : 0;
|
||||||
lastLevel.assign(count, 0.0f);
|
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
|
MechDeathHandler::~MechDeathHandler() // @0042a9f4
|
||||||
@@ -1219,10 +1238,16 @@ MechDeathHandler::~MechDeathHandler() // @0042a9f4
|
|||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// @0042aa2c -- each tick, walk the zones; where a zone's damageLevel rose since
|
// @0042aa2c -- each tick, walk the zones (raw: part_003.c:10963-93). Two
|
||||||
// last tick, fire the current damage-band descriptor's explosion (binary +0xb8 &
|
// changed-flag branches select an effect descriptor: level moved (+0xb8 & 4)
|
||||||
// 4) and, on destruction, the Destroyed-graphic descriptor's explosion (+0xb8 &
|
// -> DescriptorForLevel; gstate moved (& 8) -> DescriptorForGraphicState(the
|
||||||
// 8), applying that descriptor's GraphicState (the destroyed skin) to the zone.
|
// 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
|
void
|
||||||
MechDeathHandler::Tick() // @0042aa2c
|
MechDeathHandler::Tick() // @0042aa2c
|
||||||
@@ -1417,12 +1442,40 @@ void
|
|||||||
}
|
}
|
||||||
|
|
||||||
int n = owner->damageZoneCount;
|
int n = owner->damageZoneCount;
|
||||||
|
int needRemake = 0; // #125: one coalesced remake per tick
|
||||||
for (int i = 0; i < n && i < (int)lastLevel.size(); ++i)
|
for (int i = 0; i < n && i < (int)lastLevel.size(); ++i)
|
||||||
{
|
{
|
||||||
Mech__DamageZone *zone = owner->Zone(i);
|
Mech__DamageZone *zone = owner->Zone(i);
|
||||||
if (zone == 0)
|
if (zone == 0)
|
||||||
continue;
|
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 level = zone->GetStructureDamageLevel();
|
||||||
Scalar prev = lastLevel[i];
|
Scalar prev = lastLevel[i];
|
||||||
|
|
||||||
@@ -1430,47 +1483,64 @@ void
|
|||||||
// (ForceUpdate(DamageZoneUpdateModelFlag)). Just refresh the cache: the
|
// (ForceUpdate(DamageZoneUpdateModelFlag)). Just refresh the cache: the
|
||||||
// respawn un-wreck no longer rides this edge -- it fires from the
|
// respawn un-wreck no longer rides this edge -- it fires from the
|
||||||
// death-state exit watcher above, which also covers deaths that never
|
// 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)
|
if (level < prev)
|
||||||
{
|
{
|
||||||
lastLevel[i] = level;
|
lastLevel[i] = level;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (level <= prev) // unchanged -- no new damage
|
const int levelRose = (level > prev) ? 1 : 0; // the flag-4 (& 4) analog
|
||||||
continue;
|
if (levelRose)
|
||||||
lastLevel[i] = level;
|
lastLevel[i] = level;
|
||||||
|
if (!levelRose && !gstateEdged)
|
||||||
|
continue; // nothing moved this tick
|
||||||
|
|
||||||
// AUTHENTIC (FUN_0042aa2c @0042aa2c, the binary's effect-watcher level
|
// AUTHENTIC (FUN_0042aa2c raw, part_003.c:10963-10993 -- re-read
|
||||||
// branch): when the rise CROSSES a band-descriptor threshold
|
// 2026-08-14 for #125; this REPLACES the 07-12 reading, which had
|
||||||
// (FUN_0042a5f4) and this node owns the master, raise the entity's
|
// invented a descriptor->gstate WRITE, see below):
|
||||||
// damage-zone update flag (*(entity+0x18) |= 2, master-gated on
|
// flag & 4 (level moved): descriptor = DescriptorForLevel(level)
|
||||||
// (entity+0x28 & 0xc) == 0) -- the zone update records ship this
|
// [FUN_0042a664]; if the rise CROSSED a band threshold
|
||||||
// zone's damageLevel to every observer, so replicant dolls/hull tint
|
// [FUN_0042a5f4] and this node owns the master, dirty |= 2
|
||||||
// track the fight at band granularity. This send was MISSING from
|
// (the zone update record ships) -- every hit on a damaged
|
||||||
// the port (only the graphic-state branch below raised it): masters
|
// mech re-puffs its band effect (bands 3/4 are authored
|
||||||
// consumed damage correctly while every observer's copy sat at 0.0
|
// ember/fire plumes: a nearly-dead mech burns continuously).
|
||||||
// until destruction (the "no discoloration in MP" report, #87).
|
if (levelRose)
|
||||||
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
|
|
||||||
{
|
{
|
||||||
const Mech__DamageZone::DamageDescriptor *dd =
|
if (zone->DescriptorCrossed(prev, level)
|
||||||
zone->DescriptorForGraphicState(DamageZone::DestroyedGraphicState); // enum 1
|
&& owner->GetInstance() == Entity::MasterInstance)
|
||||||
if (dd != 0)
|
owner->ForceUpdate(Entity::DamageZoneUpdateModelFlag);
|
||||||
d = dd;
|
}
|
||||||
|
// 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)
|
if (d != 0)
|
||||||
{
|
{
|
||||||
@@ -1489,36 +1559,21 @@ void
|
|||||||
BTSpawnDamageEffect(owner, d->effectResource, // explosion AT the zone's
|
BTSpawnDamageEffect(owner, d->effectResource, // explosion AT the zone's
|
||||||
zone->segmentIndex); // segment (world position)
|
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"))
|
if (getenv("BT_DEATH_LOG"))
|
||||||
DEBUG_STREAM << "[deathfx] zone " << i << " level " << level
|
DEBUG_STREAM << "[deathfx] zone " << i << " level " << level
|
||||||
<< " -> effect " << d->effectResource << " gstate " << d->graphicState
|
<< " -> effect " << d->effectResource
|
||||||
|
<< " (zone gstate " << (int)zone->GetGraphicState() << ")"
|
||||||
<< "\n" << std::flush;
|
<< "\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);
|
||||||
}
|
}
|
||||||
|
|
||||||
//#############################################################################
|
//#############################################################################
|
||||||
|
|||||||
@@ -372,6 +372,17 @@ class MechDeathHandler
|
|||||||
private:
|
private:
|
||||||
Mech *owner; // @0x14
|
Mech *owner; // @0x14
|
||||||
std::vector<Scalar> lastLevel; // @0x10 per-zone last damageLevel cache
|
std::vector<Scalar> 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<int> lastGState;
|
||||||
// PORT (respawn replication): last-tick MovementMode, so a REPLICANT
|
// PORT (respawn replication): last-tick MovementMode, so a REPLICANT
|
||||||
// observer can reverse the one-way wreck swap on the DEATH-STATE EXIT
|
// 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
|
// edge (9/2 -> 0/1, only Mech::Reset ever leaves the death modes). The
|
||||||
|
|||||||
+51
-6
@@ -5,15 +5,60 @@ rem Double-click me after a playtest. The zip lands on your DESKTOP as
|
|||||||
rem BTLOGS_<machine>_<date>.zip -- attach that one file in Discord.
|
rem BTLOGS_<machine>_<date>.zip -- attach that one file in Discord.
|
||||||
rem Collects: every session log, matchlog, and launch breadcrumb. Nothing
|
rem Collects: every session log, matchlog, and launch breadcrumb. Nothing
|
||||||
rem is deleted or modified; this only makes a copy.
|
rem is deleted or modified; this only makes a copy.
|
||||||
|
rem
|
||||||
|
rem Field-hardened 2026-08-15 after two testers hit different failures:
|
||||||
|
rem * ConstrainedLanguage mode (locked-down Windows) blocks .NET calls, so
|
||||||
|
rem [Environment]::GetFolderPath and [math]::Round are gone -- the Desktop
|
||||||
|
rem is resolved from environment variables and the size via -f formatting.
|
||||||
|
rem Compress-Archive ALSO fails there (it is a script module that calls
|
||||||
|
rem .NET internally), so the zip is made with Windows' native tar.exe and
|
||||||
|
rem Compress-Archive is only the fallback if tar is missing.
|
||||||
|
rem * A log still held open by a running BattleTech made Compress-Archive
|
||||||
|
rem abort the WHOLE zip. Files are now staged with Copy-Item first, so an
|
||||||
|
rem unreadable one is skipped and named instead of killing the run.
|
||||||
|
rem * The old script announced success unconditionally. It now only says
|
||||||
|
rem the zip is ready if the file actually exists.
|
||||||
|
rem ABOUT.txt (machine, date, folder, exe version) rides inside the zip so we
|
||||||
|
rem can tell WHICH BUILD a report came from.
|
||||||
rem ==========================================================================
|
rem ==========================================================================
|
||||||
cd /d "%~dp0"
|
cd /d "%~dp0"
|
||||||
powershell -NoProfile -ExecutionPolicy Bypass -Command ^
|
powershell -NoProfile -ExecutionPolicy Bypass -Command ^
|
||||||
|
"$ErrorActionPreference = 'SilentlyContinue';" ^
|
||||||
"$d = Get-Date -Format yyyyMMdd_HHmm;" ^
|
"$d = Get-Date -Format yyyyMMdd_HHmm;" ^
|
||||||
"$out = Join-Path ([Environment]::GetFolderPath('Desktop')) (\"BTLOGS_{0}_{1}.zip\" -f $env:COMPUTERNAME, $d);" ^
|
"$desk = $null;" ^
|
||||||
"$files = Get-ChildItem -Path 'content\*.log','content\lastrun_*.txt','content\matchlog_*.txt','content\cdb_*.txt' -ErrorAction SilentlyContinue | Where-Object { $_.LastWriteTime -gt (Get-Date).AddHours(-36) };" ^
|
"if ($env:OneDrive -and (Test-Path (Join-Path $env:OneDrive 'Desktop'))) { $desk = Join-Path $env:OneDrive 'Desktop' }" ^
|
||||||
"if (-not $files) { Write-Host 'No logs found -- run me from the BT411 folder (next to the content directory).'; exit 1 }" ^
|
"elseif (Test-Path (Join-Path $env:USERPROFILE 'Desktop')) { $desk = Join-Path $env:USERPROFILE 'Desktop' }" ^
|
||||||
"Compress-Archive -Force -Path $files.FullName -DestinationPath $out;" ^
|
"else { $desk = $PWD.Path };" ^
|
||||||
"Write-Host ('Wrote ' + $out + ' (' + $files.Count + ' files, ' + [math]::Round((Get-Item $out).Length/1MB,1) + ' MB)');"
|
"$out = Join-Path $desk ('BTLOGS_{0}_{1}.zip' -f $env:COMPUTERNAME, $d);" ^
|
||||||
|
"$files = Get-ChildItem -Path 'content\*.log','content\lastrun_*.txt','content\matchlog_*.txt','content\cdb_*.txt' | Where-Object { $_.LastWriteTime -gt (Get-Date).AddHours(-36) };" ^
|
||||||
|
"if (-not $files) { Write-Host ''; Write-Host ' No logs found -- run me from the BT411 folder (the one containing content).' -ForegroundColor Yellow; exit 1 };" ^
|
||||||
|
"$stage = Join-Path $env:TEMP ('BTLOGS_' + $d);" ^
|
||||||
|
"Remove-Item -Recurse -Force $stage;" ^
|
||||||
|
"New-Item -ItemType Directory -Path $stage -Force | Out-Null;" ^
|
||||||
|
"$kept = 0; $skip = @();" ^
|
||||||
|
"foreach ($f in $files) { Copy-Item -LiteralPath $f.FullName -Destination $stage -Force; if (Test-Path (Join-Path $stage $f.Name)) { $kept = $kept + 1 } else { $skip += $f.Name } };" ^
|
||||||
|
"$exe = Get-Item 'btl4.exe';" ^
|
||||||
|
"$about = Join-Path $stage 'ABOUT.txt';" ^
|
||||||
|
"Set-Content -Path $about -Value ('machine=' + $env:COMPUTERNAME);" ^
|
||||||
|
"Add-Content -Path $about -Value ('packed=' + $d);" ^
|
||||||
|
"Add-Content -Path $about -Value ('folder=' + $PWD.Path);" ^
|
||||||
|
"if ($exe) { Add-Content -Path $about -Value ('btl4.exe built ' + $exe.LastWriteTime); if ($exe.VersionInfo.FileVersion) { Add-Content -Path $about -Value ('btl4.exe version ' + $exe.VersionInfo.FileVersion) } };" ^
|
||||||
|
"if ($kept -eq 0) { Write-Host ''; Write-Host ' Could not read any log files -- close BattleTech, then run me again.' -ForegroundColor Red; exit 1 };" ^
|
||||||
|
"Remove-Item -Force $out;" ^
|
||||||
|
"Push-Location $stage;" ^
|
||||||
|
"if (Get-Command tar.exe) { tar.exe -a -c -f $out * } ;" ^
|
||||||
|
"Pop-Location;" ^
|
||||||
|
"if (-not (Test-Path $out)) { Compress-Archive -Force -Path (Join-Path $stage '*') -DestinationPath $out };" ^
|
||||||
|
"Remove-Item -Recurse -Force $stage;" ^
|
||||||
|
"Write-Host '';" ^
|
||||||
|
"if (Test-Path $out) { Write-Host (' ZIP READY: ' + $out) -ForegroundColor Green; Write-Host (' ' + $kept + ' files, ' + ('{0:N1}' -f ((Get-Item $out).Length/1MB)) + ' MB'); if ($skip.Count -gt 0) { Write-Host (' SKIPPED (still in use): ' + ($skip -join ', ')) -ForegroundColor Yellow; Write-Host ' Close BattleTech and run me again to include those.' -ForegroundColor Yellow } }" ^
|
||||||
|
"else { Write-Host ' FAILED -- no zip was created.' -ForegroundColor Red; Write-Host ' Screenshot this window and send it to us.' -ForegroundColor Red; exit 1 }"
|
||||||
|
if errorlevel 1 goto :failed
|
||||||
echo.
|
echo.
|
||||||
echo Attach the BTLOGS zip from your Desktop in Discord. Thanks!
|
echo Attach that BTLOGS zip in Discord. Thanks!
|
||||||
|
goto :done
|
||||||
|
:failed
|
||||||
|
echo.
|
||||||
|
echo Nothing was created -- see the message above.
|
||||||
|
:done
|
||||||
pause
|
pause
|
||||||
|
|||||||
@@ -32,3 +32,85 @@ received); Elengil's pre-rotation steam_20260812.log; era answers (valve-boost l
|
|||||||
450/800 pip, PPC-hit visuals for Cyd's scramble); Cyd PPC-scramble merge = post-Friday
|
450/800 pip, PPC-hit visuals for Cyd's scramble); Cyd PPC-scramble merge = post-Friday
|
||||||
(rebase over palette work + dirty-skip token fix + 2-node bench). NEXT WEEK: BTSimClock
|
(rebase over palette work + dirty-skip token fix + 2-node bench). NEXT WEEK: BTSimClock
|
||||||
migration per the census checklist (gyro item may land today instead).
|
migration per the census checklist (gyro item may land today instead).
|
||||||
|
|
||||||
|
LATE-NIGHT TRACKER SWEEP (post-midnight): #151 resolved-by-#162 (eject -499 = self-blast -499
|
||||||
|
+ death -500 + DUPLICATE death -500; now expects ~0 -- Oracle re-test Friday); #112 CLOSED
|
||||||
|
(fixed by #171, sweep receipts burst=1 universal); #147 NaN-trap receipt promoted ALWAYS-ON
|
||||||
|
(65b30d4) so Friday can name the NaN source; #153 CLOSED-AUTHENTIC (binary clamps elevation
|
||||||
|
only on input [T1 @004b5cf0]; snap-on-next-input = the machine; Recenter is twist-only).
|
||||||
|
HANDOUT REGEN TOMORROW must add the known-authentic-quirks lines: veteran heat is real in
|
||||||
|
4.10; Basic-mode elevation snaps on next pitch input (authentic); eject now costs ~all your
|
||||||
|
points (self-blast + death cost, the manual's own math); plus reword the old 'settles the
|
||||||
|
HUD horizon' phrasing (Oracle's question).
|
||||||
|
|
||||||
|
FRIDAY DAYTIME (car session): full comment-thread sweep DONE (new memory:
|
||||||
|
read-ticket-threads-not-bodies). #151 CORRECTED (-499 IS authentic: suicide + death cost,
|
||||||
|
bench-proven on-ticket 08-10; my ~zero claim retracted, handout fixed, design call restored).
|
||||||
|
#104 rename HELD (the 360dps receipts say the NARC label may be truthful). #113 closed-faithful
|
||||||
|
+ the authored TURN RATES wired same day (cec6458: model+0x40 deg/s caps the steering; A/B
|
||||||
|
benched 43/69 CAP receipts vs zero; mad2 'LRM15' = nrk profile). #155 bank-capture half
|
||||||
|
ANSWERED: the machine never captured any bank (121 streamed rows zero eject-gated [T1];
|
||||||
|
MechRIOMapper 0x200000 = PANIC+lamp only [T1]; engine dispatch = AND [T0]) -> the dead
|
||||||
|
eng/weapon MFDs are OUR bug; repro rig designed (BT_BTNTEST + [group-upd] walked/matched
|
||||||
|
while armed). SUNDAY SLATE: #155 mechanism hunt + fix; #102 forward/reverse detent; #125
|
||||||
|
zone-edge; #27 clean retest note; #130 retest-likely-fixed.
|
||||||
|
|
||||||
|
#155 CLOSED-OUT FRIDAY AFTERNOON (caa50ef + gotcha 34, 7b641f9): the dead banks were the
|
||||||
|
gauge renderer's change-XOR reading the mode MANAGER's shared previousModeMask scratch (the
|
||||||
|
authentic panic chatter stomps it per frame, eating SetPresetMode's page bits); one-line fix
|
||||||
|
(sample the renderer's OWN member); diagnosed via the operator's live mouse session (receipts
|
||||||
|
proved delivery while the screen held = presentation convicted), fix live-verified same
|
||||||
|
session (8 transitions / 4 exits / 2833 flap edges). Flicker stays authentic. SUNDAY SLATE
|
||||||
|
REMAINING: #102 forward/reverse detent, #125 first-destroyed-zone edge; plus tonight's field
|
||||||
|
returns. Rides the Sunday build with the turn rates.
|
||||||
|
|
||||||
|
RADAR AUDIT (Friday evening, 3-agent, 800k tokens): #49 CLOSED operator-verified (c2193e2)
|
||||||
|
-- the static world feed restored (non-dynamic Terrain-graph pass in RebuildEntityGrid);
|
||||||
|
walls/buttes/hills/ridges/trees/buildings draw as authored pips on every map, zero new art.
|
||||||
|
Ledger: docs/RADAR_MAP_AUDIT.md. Authentic absences documented (wall10k boundary invisible
|
||||||
|
by authorship; unpipped props; no range rings; 45deg wedge). SUNDAY NOTES must carry: the
|
||||||
|
radar restoration headline + the two not-bugs (boundary, declutter) + the arena-edge era
|
||||||
|
question. Sunday build now carries: turn rates + #155 banks + #49 radar world.
|
||||||
|
|
||||||
|
NIGHT18 (Friday evening, post-compaction): #102 OWENS RUN<->REVERSE. The #102 audit
|
||||||
|
(3-agent: binary/input/content + bench matrix night18/gaithang_bench.sh) found the port
|
||||||
|
was missing the machine's whole HARD-BRAKE system: master perf @0x4aa158-0x4aa365 swaps
|
||||||
|
the gait slew to the model's SuperStopAcceleration when a forward cycle meets a reverse
|
||||||
|
demand (+0x3f4 "airborneSelect" = the BRAKE latch, another jump-jet-family misread;
|
||||||
|
+0x5bc "airborneCycleRate" = the brake rate) + gyro random-sign lurch on engage + 0.4s
|
||||||
|
vertical judder above walk speed (gyroRumbleTimer@0x5c4). Content: heavies brake harder
|
||||||
|
than they accelerate (10->20), OWENS brakes at HALF (50->25) -- the sprinter's long
|
||||||
|
coast is authored; the pod communicated it through the cab shake, the port coasted
|
||||||
|
SILENTLY = the felt hang. PORTED (mech4 drive block, [brake] receipts) + the
|
||||||
|
same-frame re-dispatch parity fix (gotcha 35: split alarm/int cell dispatched stale on
|
||||||
|
gate/death-latch frames; both channels re-synced). Bench: ALT sign-flip at full run =
|
||||||
|
clean chain into reverse w/ brake receipts; held-brake-key = clean authored stand, zero
|
||||||
|
brake (authentic), twist arms trn from the park (the rescue). REVERSE INPUT TRUTH: red
|
||||||
|
button 0x3F/LALT sign-flip, demand = -runSpeed*throttle (NO fwdScale); brake keys can
|
||||||
|
NEVER reverse (throttle [0,1] like the pod lever) -> HANDOUT education line. Side-find:
|
||||||
|
hardware-RIO cab reverse is DEAD (AddOrErase stubs; open-questions row). KEYSIM bench
|
||||||
|
hooks added (BT_KEYSIM/_REV/_TURN). 674 worktree forensic pending -> ticket close.
|
||||||
|
SUNDAY build now carries: turn rates + #155 banks + #49 radar + #102 brake system.
|
||||||
|
NEXT: #125 first-destroyed-zone edge.
|
||||||
|
NIGHT18 addendum: 674 forensic RESOLVED without a repro run -- the 674 build boots the
|
||||||
|
GLASS profile by default (log: '[boot] platform profile: GLASS'), so PadRIO owned
|
||||||
|
reverseThrust and the BT_FORCE_FLIP negative-throttle path was stomped (rev=0 all run);
|
||||||
|
i.e. reverse at 674 was exclusively LALT on every rig. The statue itself = the trn
|
||||||
|
turn-stop MID-PIVOT park (authentic, pod-invisible: no external self-view existed);
|
||||||
|
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'.
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# =========================================================================
|
||||||
|
# ISSUE #102 FORENSICS -- the 4.11.674 build (commit 137c151, the build
|
||||||
|
# Lynx reported against). Worktree C:/git/bt411-674. No BT_KEYSIM on
|
||||||
|
# 674, so the ALT demand shape rides the forced harness instead:
|
||||||
|
# BT_AUTODRIVE=1.0 + BT_FORCE_FLIP=14 (key_throttle sign-flips at t=14 ->
|
||||||
|
# reverseThrust=1 via the key_throttle<0 term). BT_PLATFORM deliberately
|
||||||
|
# UNSET: under glass the PadRIO-active accommodation (mechmppr ~1036)
|
||||||
|
# stomps reverseThrust back to gBTReverseHeld=0 and the flip dies.
|
||||||
|
# usage: gaithang674.sh <tag> [extra env pairs...]
|
||||||
|
# =========================================================================
|
||||||
|
set -x
|
||||||
|
EXE=/c/git/bt411-674/build/Release/btl4.exe
|
||||||
|
cd /c/git/bt411-674/content || exit 1
|
||||||
|
|
||||||
|
TAG=$1; shift
|
||||||
|
sed 's/^experience=.*/experience=expert/' MP.EGG > GH674.EGG
|
||||||
|
sed -i "s/^map=.*/map=grass/; s/^time=.*/time=day/; 0,/^vehicle=.*/s//vehicle=owens/" GH674.EGG
|
||||||
|
LOG=gh674_${TAG}.log
|
||||||
|
rm -f "$LOG"
|
||||||
|
|
||||||
|
( export BT_START_INSIDE=1 BT_DEV_GAUGES=1 BT_LOG="$LOG" BT_AFFINITY=0x03
|
||||||
|
export BT_GAIT_TRACE=1 BT_AUTODRIVE=1.0 BT_FORCE_FLIP=14
|
||||||
|
for kv in "$@"; do export "$kv"; done
|
||||||
|
"$EXE" -egg GH674.EGG &
|
||||||
|
echo $! > /tmp/gh674.pid
|
||||||
|
cat /proc/$!/winpid > /tmp/gh674.winpid 2>/dev/null )
|
||||||
|
sleep 55
|
||||||
|
[ -f /tmp/gh674.winpid ] && taskkill //F //PID "$(cat /tmp/gh674.winpid)" > /dev/null 2>&1
|
||||||
|
rm -f /tmp/gh674.pid /tmp/gh674.winpid
|
||||||
|
sleep 2
|
||||||
|
echo "--- $LOG"
|
||||||
|
grep -ac "gaitSM" "$LOG" || true
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# =========================================================================
|
||||||
|
# ISSUE #102 -- OWENS RUN->REVERSE GAIT HANG (night18)
|
||||||
|
# Solo expert Owens, grass/day. BT_KEYSIM drives the REAL virtual-controls
|
||||||
|
# chain (lever integrator + ZERO DETENT + gait SM) on a schedule -- the
|
||||||
|
# forcedThrottle harnesses bypass the lever, which is where the suspect
|
||||||
|
# lives (mech4.cpp zero detent: a held sweep that crosses 0 latches at 0
|
||||||
|
# until BOTH keys rest -> demand pinned to exactly 0.0 while reverse held).
|
||||||
|
#
|
||||||
|
# run A (repro): 3s rest, 10s full fwd (run), 20s HOLD BACK -- the
|
||||||
|
# Lynx shape. Twist pulse at t=38 (the rescue leg).
|
||||||
|
# run B (control): same but RELEASE 1.5s then re-press back -- the
|
||||||
|
# documented detent recovery; reverse must engage.
|
||||||
|
#
|
||||||
|
# Receipts: [gaitSM] per frame (legState/legCycle/legFrm/spd), [vctl] 1Hz
|
||||||
|
# (keys + lever). Park signature: legState=0 with legFrm frozen mid-clip,
|
||||||
|
# spd=0 while back key held; reverse states (16-19) never entered.
|
||||||
|
# usage: gaithang_bench.sh <A|B> [extra env pairs...]
|
||||||
|
# =========================================================================
|
||||||
|
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
|
||||||
|
|
||||||
|
VARIANT=$1; shift
|
||||||
|
bt_expert_egg MP.EGG GH102.EGG
|
||||||
|
sed -i "s/^map=.*/map=grass/; s/^time=.*/time=day/; 0,/^vehicle=.*/s//vehicle=owens/" GH102.EGG
|
||||||
|
LOG=gaithang_${VARIANT}.log
|
||||||
|
rm -f "$LOG"
|
||||||
|
|
||||||
|
if [ "$VARIANT" = "A" ]; then
|
||||||
|
# S-key sweep shape (keyboard-bridge rigs only; dead channel on glass)
|
||||||
|
KS="0:3,0.7:10,-0.7:20,0:8"
|
||||||
|
KT="38:2.5"
|
||||||
|
KR=""
|
||||||
|
XTRA=""
|
||||||
|
DUR=55
|
||||||
|
elif [ "$VARIANT" = "C" ]; then
|
||||||
|
# THE AUTHENTIC LALT SHAPE (glass rig = what Lynx flies): full forced
|
||||||
|
# throttle, REVERSE BUTTON held t=14..34 (gBTReverseHeld -> reverseThrust
|
||||||
|
# sign-flip), release at 34 = the recovery probe (a Standing-class park
|
||||||
|
# recovers on the demand flip; a frozen-clip park stays parked).
|
||||||
|
KS="0:1"
|
||||||
|
KT=""
|
||||||
|
KR="14:20"
|
||||||
|
XTRA="BT_AUTODRIVE=1.0"
|
||||||
|
DUR=55
|
||||||
|
elif [ "$VARIANT" = "D" ]; then
|
||||||
|
# THE GLASS S-KEY SHAPE: demand pinned at EXACTLY 0 after a full run
|
||||||
|
# (PadRIO throttle clamps [0,1] -- S can never reverse; the keyboard-
|
||||||
|
# bridge detent pins the same way). BT_KEY_BRIDGE=1 forces the bridge
|
||||||
|
# so BT_KEYSIM's lever drives the mapper. Twist pulse at t=38 = the
|
||||||
|
# Lynx rescue. Park signature: legState=0, legFrm frozen != 0, spd=0.
|
||||||
|
KS="0:3,0.7:10,-0.7:20,0:10"
|
||||||
|
KT="38:2.5"
|
||||||
|
KR=""
|
||||||
|
XTRA="BT_KEY_BRIDGE=1"
|
||||||
|
DUR=55
|
||||||
|
else
|
||||||
|
KS="0:3,0.7:10,-0.7:2,0:1.5,-0.7:15"
|
||||||
|
KT=""
|
||||||
|
KR=""
|
||||||
|
XTRA=""
|
||||||
|
DUR=45
|
||||||
|
fi
|
||||||
|
|
||||||
|
( export BT_GAIT_TRACE=1 BT_KEY_LOG=1 BT_KEYSIM="$KS"
|
||||||
|
[ -n "$KT" ] && export BT_KEYSIM_TURN="$KT"
|
||||||
|
[ -n "$KR" ] && export BT_KEYSIM_REV="$KR"
|
||||||
|
[ -n "$XTRA" ] && export $XTRA
|
||||||
|
for kv in "$@"; do export "$kv"; done
|
||||||
|
bt_launch "$LOG" GH102.EGG 0x03 )
|
||||||
|
sleep $DUR
|
||||||
|
bt_kill_ours
|
||||||
|
sleep 3
|
||||||
|
taskkill //F //IM btl4.exe > /dev/null 2>&1
|
||||||
|
echo "--- $LOG"
|
||||||
|
grep -ac "gaitSM" "$LOG"
|
||||||
|
grep -a "vctl" "$LOG" | head -40
|
||||||
@@ -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=<A's mech> (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 <tag>
|
||||||
|
# =========================================================================
|
||||||
|
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
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# =========================================================================
|
||||||
|
# ISSUE #125 -- authored zone-descriptor dump (solo, any chassis)
|
||||||
|
# usage: zonedump.sh <vehicle>
|
||||||
|
# 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
|
||||||
Reference in New Issue
Block a user