Compare commits
28
Commits
@@ -71,3 +71,6 @@ scratchpad/**/*.dll
|
|||||||
scratchpad/**/SESSION_HANDOFF*.md
|
scratchpad/**/SESSION_HANDOFF*.md
|
||||||
scratchpad/**/AGENT_FINDINGS*.md
|
scratchpad/**/AGENT_FINDINGS*.md
|
||||||
scratchpad/**/message*.txt
|
scratchpad/**/message*.txt
|
||||||
|
|
||||||
|
# tracker bridge credentials (URL + token = write access to the shared sheet) -- NEVER commit
|
||||||
|
scratchpad/tracker_bridge.local
|
||||||
|
|||||||
@@ -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, and the committed CSV snapshots (`tools/tracker_snapshot.py` → `docs/tracker/`) that make tester edits a `git diff` | `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
|
||||||
|
|||||||
@@ -766,6 +766,9 @@ default-ON (`'0'` disables).
|
|||||||
| `BT_GAUGE_SKIP_LOG` | `[gskip] <primitive>` per unregistered gauge widget the parse skips |
|
| `BT_GAUGE_SKIP_LOG` | `[gskip] <primitive>` per unregistered gauge widget the parse skips |
|
||||||
| `BT_VALVE_LOG` | condenser valve flow distribution |
|
| `BT_VALVE_LOG` | condenser valve flow distribution |
|
||||||
| `BT_BAYTEST=<frame>` | #46 rig: send message 1 (crit-induced cook-off arm) to the first AmmoBin at the given sim frame — the 10 s fuse then runs live (`scratchpad/baytest.py` / `baypurge.py`) |
|
| `BT_BAYTEST=<frame>` | #46 rig: send message 1 (crit-induced cook-off arm) to the first AmmoBin at the given sim frame — the 10 s fuse then runs live (`scratchpad/baytest.py` / `baypurge.py`) |
|
||||||
|
| `BT_BAYFIRE_TEST=<sec\|key>` | #183 rig: drive the AUTHENTIC bay-fire arming gate (heat alarm→FAILURE on a non-empty bin). Numeric = seconds of BIN sim time (⚠ starts at mission LOAD, not control); `key` = F9 arms EVERY bin ticking within 2 ticks of the press (per-bin cookOffArmed dedups) — the arm-all form exists because the ENG panel shows ONE weapon group's page at a time, so a single-bin arm is invisible unless the panel is on that weapon (`scratchpad/night18/bayfire183.sh`) |
|
||||||
|
| `BT_BAYFIRE_LOG` | #183 receipts: `[gau-bind]` cluster ctor bin/digit-pointer/warehouse-bitmap binds, `[gau-fire]` edge-only cookOffArmed+jammed as the CLUSTER sees them, `[twostate]` edge census of every TwoState draw + capped `MISS` when a bitmap fails to load (BitMap::Make NULL = silent no-draw) |
|
||||||
|
| `BT_JAMTEST=<frame\|key>` | #183 rig: drive the AUTHENTIC jam latch (message 2 @004bcabc → weaponAlarm=Jammed) on EVERY ballistic/missile weapon at once (arm-all, same page-at-a-time reasoning as BT_BAYFIRE_TEST). `key` = F11 (F10 is the Windows system-menu key; F9 = bay fire). Bridge: `BTWeaponForceJam` (projweap.cpp) |
|
||||||
| `BT_PLANE_AUDIT` | #48 forensics -- **DEFAULT-ON** (`=0` opts out): logs any gauge draw whose color carries bits outside its port's plane mask (`[plane] LEAK/PORT`) AND any out-of-bounds draw start (`[plane] OOB`, clamped). First detection of each also writes a `GLITCH` **matchlog** record, so playtest evidence arrives with the round logs even unattended |
|
| `BT_PLANE_AUDIT` | #48 forensics -- **DEFAULT-ON** (`=0` opts out): logs any gauge draw whose color carries bits outside its port's plane mask (`[plane] LEAK/PORT`) AND any out-of-bounds draw start (`[plane] OOB`, clamped). First detection of each also writes a `GLITCH` **matchlog** record, so playtest evidence arrives with the round logs even unattended |
|
||||||
| `BT_LOOP_AUDIT` | `[loop-audit]` per-sound `sample-flag × source-render-type -> AL_LOOPING` (Gitea #51; rig `scratchpad/loopaudit.py`) |
|
| `BT_LOOP_AUDIT` | `[loop-audit]` per-sound `sample-flag × source-render-type -> AL_LOOPING` (Gitea #51; rig `scratchpad/loopaudit.py`) |
|
||||||
| `BT_LOOP_LEGACY=1` | restore the OLD always-loop rule (`AL_LOOPING = sample != ForceStatic`) for a field A/B — see the loop-flag note in [[wintesla-port]] |
|
| `BT_LOOP_LEGACY=1` | restore the OLD always-loop rule (`AL_LOOPING = sample != ForceStatic`) for a field A/B — see the loop-flag note in [[wintesla-port]] |
|
||||||
@@ -796,6 +799,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 |
|
||||||
|
|||||||
@@ -212,6 +212,29 @@ the base non-virtual `DrawWarningLamp` existed, so the count stayed green-on-bla
|
|||||||
dark box in the green dot. Fix: made `DrawWarningLamp` virtual + added the ballistic override.
|
dark box in the green dot. Fix: made `DrawWarningLamp` virtual + added the ballistic override.
|
||||||
Energy weapons (`EnergyWeaponCluster`, no ammo count) use the base only — unaffected.
|
Energy weapons (`EnergyWeaponCluster`, no ammo count) use the base only — unaffected.
|
||||||
|
|
||||||
|
## The ENG panel is ONE weapon-group page at a time + the bay-fire/jam icons (#183, 2026-08-15) [T2 pixel-verified]
|
||||||
|
All four weapon groups share the SAME `eng1` graphics port with **absolute** widget coordinates;
|
||||||
|
only the `subBit` mode bit (0x2/0x4/0x8/0x10, `kAuxGeom`) selects WHICH group's children execute.
|
||||||
|
So the ENG panel is a per-weapon DETAIL page, and a state icon lights **only while the panel shows
|
||||||
|
that weapon's page** — a bay fire on a weapon you aren't viewing draws nothing, correctly. On the
|
||||||
|
machine the pilot was steered there by the FLASHING ENG BUTTON (authored `GaugeAlarmStream`); that
|
||||||
|
flash is the still-unbuilt half of #47 and the remaining discoverability gap.
|
||||||
|
Icon geometry (byte-faithful, part_014.c:6510-6522): jam `btejam.pcc` @(0xa0,0xb2), bay-fire
|
||||||
|
`btefire.pcc` @(0x121,0xb6) — **different places**, ~0x81 px apart; the countdown numeric
|
||||||
|
(`NumericDisplayScalarTwoState`, seconds-to-detonation) sits beside the fire icon.
|
||||||
|
**#183's actual break** was never the icon: the cluster resolved its bin with the BINARY's raw
|
||||||
|
walk (`ResolveLink(subsystem+0x43c)` → NULL on our layout — the trap lived in the 1995
|
||||||
|
connection-object INTERNALS) so the TwoState input read 0 forever. The jam icon's raw read
|
||||||
|
(`+0x364==5`) was NOT broken — that offset is `static_assert`-locked (weaponAlarm @0x350 + the
|
||||||
|
alarm's internal +0x14), a sweep false-positive corrected the same day; its typed bridge
|
||||||
|
(`BTWeaponJammed`) is equivalent. **BOTH icons pixel-verified live 2026-08-15** (F9 bay-fire /
|
||||||
|
F11 jam rigs, lit side by side). Sweep census + the refutation test for suspected raw reads:
|
||||||
|
[[reconstruction-gotchas]] §8. Rigs + receipts (`BT_BAYFIRE_TEST`, `BT_JAMTEST`,
|
||||||
|
`BT_BAYFIRE_LOG`) in [[decomp-reference]] §6. Diagnostic anatomy of a silent icon: warehouse
|
||||||
|
miss (`BitMap::Make` NULL → TwoState draws NOTHING, silently), value never pumped
|
||||||
|
(`GaugeConnectionDirectOf` from a dead pointer), or widget never executes (mode bit not
|
||||||
|
selected) — the `[twostate]` census separates all three.
|
||||||
|
|
||||||
## TEMP/STATUS bar (HorizTwoPartBar) tiles a striped pattern from x=0 (2026-07-21) [T1 decomp + render-verified]
|
## TEMP/STATUS bar (HorizTwoPartBar) tiles a striped pattern from x=0 (2026-07-21) [T1 decomp + render-verified]
|
||||||
The per-weapon TEMP/STATUS bar is a `HorizTwoPartBar` (@004c4170, Execute @004c4340). It renders
|
The per-weapon TEMP/STATUS bar is a `HorizTwoPartBar` (@004c4170, Execute @004c4340). It renders
|
||||||
**three zones along X** from the interned `tileImage` + two colours (`fillColor`, `backgroundColor`):
|
**three zones along X** from the interned `tileImage` + two colours (`fillColor`, `backgroundColor`):
|
||||||
@@ -864,3 +887,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.
|
||||||
|
|||||||
+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
|
||||||
|
|||||||
@@ -70,6 +70,14 @@ authentic path scoped.
|
|||||||
Director's flash logic (10s on/15s off, solid final 30s), live `playerRank` pointers re-sort
|
Director's flash logic (10s on/15s off, solid final 30s), live `playerRank` pointers re-sort
|
||||||
rows as scores change. **Each 128×32 ordinal bitmap packs TWO ordinals side by side**
|
rows as scores change. **Each 128×32 ordinal bitmap packs TWO ordinals side by side**
|
||||||
("1st|2nd", "3rd|4th" — why 4 bitmaps serve 8 players): texture rank/2, u-half by parity.
|
("1st|2nd", "3rd|4th" — why 4 bitmaps serve 8 players): texture rank/2, u-half by parity.
|
||||||
|
⚠ **Latent, not a filed bug (2026-08-15):** `CameraHUDDrawQuad` (L4VIDRND.cpp ~3088) sets a
|
||||||
|
texture and draws WITHOUT the texture-stage ops that #58 had to add to the reticle plate's
|
||||||
|
draw. D3D9 stage state is global and sticky, so this renders correctly only as long as whatever
|
||||||
|
draws before it leaves stage 0 in a texture-friendly state — it was screenshot-verified working
|
||||||
|
when written, so it does today. If the banner or ranking rows ever appear as SOLID BLOCKS, that
|
||||||
|
is this: fix is the same save/set(`SELECTARG1`)/restore pair. Filed as #192 and closed without a
|
||||||
|
fix — no symptom has ever been observed and the seat has not been used in a session since it was
|
||||||
|
built.
|
||||||
BT_SHOT now captures AFTER the 2D pass (it used to dump pre-HUD — overlays were on screen
|
BT_SHOT now captures AFTER the 2D pass (it used to dump pre-HUD — overlays were on screen
|
||||||
but invisible to screenshots). Screenshot-verified: "1st MAVERICK" + banner over live
|
but invisible to screenshots). Screenshot-verified: "1st MAVERICK" + banner over live
|
||||||
coverage. Remaining: operator-app camera-seat row helper (hostType not in the roster UI),
|
coverage. Remaining: operator-app camera-seat row helper (hostType not in the roster UI),
|
||||||
@@ -100,6 +108,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 +326,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,163 @@
|
|||||||
|
---
|
||||||
|
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}`.
|
||||||
|
|
||||||
|
## Snapshots — knowing what the testers changed
|
||||||
|
|
||||||
|
`python tools/tracker_snapshot.py` writes `docs/tracker/OPEN.csv` + `CLOSED.csv` and they are
|
||||||
|
COMMITTED, so every tester edit becomes a plain `git diff`. `--diff` refreshes and prints the
|
||||||
|
change against the committed copy without you having to remember what it used to say.
|
||||||
|
|
||||||
|
Rows are written sorted by TICKET NUMBER rather than sheet order, so re-prioritising the OPEN tab
|
||||||
|
produces no diff noise — only real content changes appear. Snapshot at the END of a working
|
||||||
|
session and again BEFORE the next one; the gap between those two commits is exactly what the
|
||||||
|
testers did.
|
||||||
|
|
||||||
|
⚠ Credentials never live in the repo: the tool reads `BT_TRACKER_URL` / `BT_TRACKER_TOKEN` from
|
||||||
|
the environment, or from `scratchpad/tracker_bridge.local` (gitignored).
|
||||||
|
|
||||||
|
Why it matters: without a snapshot, separating a tester's edit from your own formatting pass is
|
||||||
|
guesswork. The first time this came up, a diff against a stale snapshot buried three genuine
|
||||||
|
tester edits under ~60 lines of my own date-stamping — and one of those three (#187, narrowed
|
||||||
|
from ballistics to missiles) changed what the ticket was about.
|
||||||
|
|
||||||
|
## 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)
|
||||||
|
|||||||
@@ -165,6 +165,33 @@ A. Examples: `BTResolveWeaponMuzzle` (mech4.cpp — a complete-Mech TU with the
|
|||||||
(btplayer.cpp — complete BTPlayer), `BTGetSubsystemAuxScreen` (powersub.cpp — casts through the
|
(btplayer.cpp — complete BTPlayer), `BTGetSubsystemAuxScreen` (powersub.cpp — casts through the
|
||||||
real PoweredSubsystem). Keep the alloc SIZE + special-cache when swapping a factory case. [T2]
|
real PoweredSubsystem). Keep the alloc SIZE + special-cache when swapping a factory case. [T2]
|
||||||
|
|
||||||
|
**2026-08-15 gauge-layer sweep (#183) + its correction the same day: a raw read is a SUSPECT,
|
||||||
|
not a conviction.** Grepping `*(int *)((char *)` in `btl4gau*.cpp` found five sites; the
|
||||||
|
evidence sorted them three ways, and the sorting is the lesson:
|
||||||
|
- **PROVEN BUG (had a receipt):** the bay-fire bin link — `ResolveLink(subsystem+0x43c)` walked
|
||||||
|
the 1995 *connection-object internals* and returned NULL (`bin=00000000` receipt). The #183
|
||||||
|
field defect. Fixed via `BTWeaponAmmoBin`; pixel-verified.
|
||||||
|
- **FALSE POSITIVES (the layout was LOCKED):** the jam read (`+0x364` = weaponAlarm level;
|
||||||
|
`static_assert @0x350` + the 0x54-alarm's internal `+0x14`) and `StateConnection::Update`
|
||||||
|
(`source+0x14` — source is a **GaugeAlarm54's OWN ADDRESS**, e.g. modeAlarm@+0x2b8, and +0x14
|
||||||
|
is its LEVEL cell; reservoirAlarm shows the same internal offset). Both were correct all
|
||||||
|
along. The jam "fix" happened to be equivalent (same cell by name — now pixel-verified); the
|
||||||
|
mode-lamp "fix" cast the WRONG CLASS (`Simulation` vs `GaugeAlarm54`), froze the AUTO/OFF lamp
|
||||||
|
at frame 0, was field-caught within the hour and reverted. **No tester build carried it.**
|
||||||
|
- **SAFE EITHER WAY:** fail lamp (`+0x40` → `GetSimulationState()==1`) and rear tile (`+0x334`
|
||||||
|
→ `BTWeaponIsRearFiring`) — right class, same cell by name; cannot regress even if the raw
|
||||||
|
form was aliased-correct.
|
||||||
|
|
||||||
|
**The refutation test, before replacing ANY raw read:** (1) identify the pointed-at object's
|
||||||
|
CLASS — a member-internal offset into a layout-locked object (GaugeAlarm54's level at +0x14) is
|
||||||
|
binary-faithful BY CONSTRUCTION; (2) grep for a `static_assert` locking the offset; (3) demand a
|
||||||
|
RECEIPT that the site is actually broken (the one real bug had `bin=00000000`; the one that
|
||||||
|
broke a working widget had no receipt and field testimony AGAINST it — "the lamp has been
|
||||||
|
working"). Sweeping the file is still right — the real one and the deferred one (coolant-loop
|
||||||
|
heat-sink digit, `hs+0x1d4`, no port member yet) were both found that way — but sweep means
|
||||||
|
AUDIT each site against this test, not blanket-replace. Era/field testimony outranks
|
||||||
|
code-reading here, same as everywhere else in this project. [T2]
|
||||||
|
|
||||||
## 9. Message-handler chaining + entity validity
|
## 9. Message-handler chaining + entity validity
|
||||||
|
|
||||||
- A reconstructed class's `MessageHandlers` set must be built **chained to the parent's**
|
- A reconstructed class's `MessageHandlers` set must be built **chained to the parent's**
|
||||||
@@ -1106,3 +1133,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.
|
||||||
@@ -619,3 +619,55 @@ per-frame gravity/drag adds land on an accumulator that PerformAndWatch
|
|||||||
zeroes every frame — they are per-frame *acceleration definitions*, not
|
zeroes every frame — they are per-frame *acceleration definitions*, not
|
||||||
accumulating increments, and integrate ×dt correctly at ANY frame rate.
|
accumulating increments, and integrate ×dt correctly at ANY frame rate.
|
||||||
(The port's separate "environment gravity reads 0" gap stays open.)
|
(The port's separate "environment gravity reads 0" gap stays open.)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Round-two returns (2026-08-15): TWO annotated copies of the second-edition guide
|
||||||
|
|
||||||
|
Staged raw in `scratchpad/night18/fieldlogs/` (uncommitted, field-log rule): **Oracle**
|
||||||
|
(`WEAPONS_FIELD_GUIDE 20260814 Oracle.txt`, 18 fresh notes — his round-one copy is already
|
||||||
|
harvested above) and a NEW witness, **NANOOK** (`WEAPONS_FIELD_GUIDE_957.txt`, 15 substantive).
|
||||||
|
Scoreboard context: pilots 9-and-1 vs our code-reading.
|
||||||
|
|
||||||
|
**SETTLED / CONFIRMED [era-witness]:**
|
||||||
|
- **Self-splash tiebreaker ANSWERED — the machine splashed you.** The guide posed it
|
||||||
|
explicitly ("our port says no; only your memory can settle it"); Nanook: *"Missile splash
|
||||||
|
damage self."* Oracle's v2 testing independently shows the PORT has no splash at all
|
||||||
|
("mechs against walls, each other, fired at walls/ground/hills, point blank — only the mech
|
||||||
|
struck recorded damage") — which also FAILS 957's own "hug the cover and the blast splashes
|
||||||
|
you" claim. → Ticket filed (see tracker); #175's cover-interception half stays CONFIRMED.
|
||||||
|
- Myomer/coolant seek-4 behavior MATCHES ("sustain higher speed on seek 4 with loop 5
|
||||||
|
boosted... no mech could sprint at max indefinitely... no longer overheating to immobile").
|
||||||
|
- Drag governor direction endorsed pre-flight ("MATCHES the description"); AC sections MATCH
|
||||||
|
both witnesses (Nanook: AFC100 kick vs AFC25 barely; per-shot trigger rhythm; ~1 s
|
||||||
|
damage-at-max-range); generator-hit static MATCHES (Nanook: "gauges/MFDs briefly garbage,
|
||||||
|
2-5 seconds").
|
||||||
|
- Seek-4 era meta recorded (Oracle): VWE-boards tactic — Thor/Denkou all-energy seek 4 +
|
||||||
|
myomers, 3-shot kills, drop seek after the second shot. Tuning-validation reference.
|
||||||
|
|
||||||
|
**CONTRADICTIONS OPEN (each needs a decomp re-read or a decision):**
|
||||||
|
1. **Missile pursuit law** — guide says the seeker LEADS (decomp reading); Oracle:
|
||||||
|
*"PURE pursuit, not LEAD... I would be surprised [otherwise] as it didn't appear that way."*
|
||||||
|
→ Re-read the seeker steering (mislanch/misthrst @004be474 family) before the v3 guide.
|
||||||
|
2. **"Lock required; no dumb-fire path exists"** (guide's machine claim) — Oracle: trigger
|
||||||
|
ALWAYS fired a ready weapon; dumb-firing missiles was a known skill move *specifically to
|
||||||
|
avoid tripping the incoming alarm*. The PORT already always-launches (mislanch.cpp:325
|
||||||
|
"Always launch", Null target = point fire) so no port defect — the GUIDE's machine claim
|
||||||
|
is what needs the decomp re-check + v3 correction.
|
||||||
|
3. **Incoming-alarm tempo** — 957 ramps (slow far / frantic close); Nanook: *"Tempo does not
|
||||||
|
ramp up"* (era memory). Oracle's sheet note liked the ramp but liking ≠ era-authentic and
|
||||||
|
he holds REFERENCE VIDEOS — check them before touching #83.
|
||||||
|
4. **Energy weapons X-ing out from heat** — inter-witness dispute Oracle reports from the
|
||||||
|
floor: some players say a fused energy weapon could PERMANENTLY X out; Oracle says it
|
||||||
|
always recovered after cooling. Decide with the decomp (weapon heat-fail path) and fold
|
||||||
|
into the #133-family NOT-BUGS/handout answer.
|
||||||
|
5. **Missile speed residual** — "may still be flying a little too fast"; plus a testable era
|
||||||
|
detail: out-of-fuel LRMs visibly SLOWED and lost altitude ("guided ballistic") on return
|
||||||
|
passes. Check the coast phase's drag vs that description (cross-ref #168 ttl).
|
||||||
|
6. **Nanook, unverified era claim**: short-range front-CT missile hits registering on
|
||||||
|
CRITICAL REAR armor ("damage wrap-around", not yet reproduced by him in 957). Park until
|
||||||
|
he reproduces or the zone fan-out decomp says impossible.
|
||||||
|
7. **Generator voltage gauge** (Oracle): pods' gauge never visibly changed output voltage —
|
||||||
|
generators just recharged slower as they heated. Cross-ref the #189 family before anyone
|
||||||
|
"fixes" a static-looking voltage bar.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
#,Test Status,Dev Status,Dev Comments,Tester Comments
|
||||||
|
18,Pass,✅ DONE,"Engineering aux buttons crashed the game (glass windows)
|
||||||
|
TEST: click every Engineering panel button, incl. the aux row. No crash.",
|
||||||
|
20,Pass,✅ DONE,"Auto cooling BALANCE did nothing
|
||||||
|
TEST: ADV control mode, press BALANCE -- coolant redistributes.",
|
||||||
|
21,Pass,✅ DONE,"Gear cycling is safe (#21 — and a retraction): we told you the machine bricked lasers on a gear wrap. Several of you said ""nah."" You were right — our reading of ONE number in the original's code was off by a byte-width. Cycle your gears as hard as you like; also fixes the dark recharge arc / dark ready-dot after gear play.",8/14 Oracle - OK
|
||||||
|
22,Pass,✅ DONE,"#22 Ammo + destroyed weapons did not reset on respawn
|
||||||
|
TEST: die with empty bins / dead weapons -> respawn full and repaired.",
|
||||||
|
24,Pass,✅ DONE,Controller hat/look view sticks (recovers after wiggling),"(undated) Testers haven't been seeing this lately, but there was one instance a couple of nights ago that self resolved by moving the hat around a few times."
|
||||||
|
25,Pass,✅ DONE,"Gamepad turn profile (stick X could not turn the mech)
|
||||||
|
TEST: RajelAran -- bind an axis to ""Turn"" in content\bindings.txt.",
|
||||||
|
32,Pass,✅ DONE,"Audio cutting in/out late in the match
|
||||||
|
TEST: long drop -- audio should stay clean to the end.",
|
||||||
|
35,Pass,✅ DONE,"Owens crashed when firing lasers (Conn Man x2, long ago)
|
||||||
|
TEST: Conn Man -- Owens, spam lasers. Believed fixed for a while.",
|
||||||
|
36,Pass,✅ DONE,"Turning continued after releasing the pad/rudder axis
|
||||||
|
TEST: RajelAran -- release/center the turn axis, turning stops.",
|
||||||
|
37,Pass,✅ DONE,"A mech can look twisted backwards - it's temporary, and it also happened to another player's mech.
|
||||||
|
STATUS: Model data is clean; suspect a mirrored pad twist axis or a bad reading sent to others.
|
||||||
|
NEED FROM YOU: a clip of it happening, and does your torso twist the way you push the stick?",(undated) No occurences in a long time
|
||||||
|
39,Pass,✅ DONE,"Controls were live before the game screen appeared
|
||||||
|
TEST: mash fire during load -- nothing happens until the scene shows.",
|
||||||
|
40,Pass,✅ DONE,"Waiting/join screen burned 45-50% GPU
|
||||||
|
TEST: GPU near idle while waiting in the join screen.",
|
||||||
|
43,Pass,✅ DONE,"Comm display: pilot list with KILLS/DEATHS
|
||||||
|
TEST: all pilots listed, kills/deaths update through the round.",
|
||||||
|
47,Pass,✅ DONE,"Jam / ammo-bay-fire annunciation
|
||||||
|
TEST: bay fire alarm looks OK now. Still watching: does the ENG page
|
||||||
|
button FLASH on a weapon JAM? (run ballistics hot to force jams)",
|
||||||
|
48,Pass,✅ DONE,"The filled boxes around inactive CONTROL MODE entries and the phantom block between the heading dial and the armor rosette are gone (#48) — stray art was landing on a live armor-damage color slot; the shipped machine drew those regions black and now we do too.
|
||||||
|
Armor rosette / damage-flash colors no longer go stale on the desktop glass panels — they now track live like the in-pod displays.
|
||||||
|
The desktop plasma marquee no longer shows garbage between boot and the first callsign draw.",(undated) Radar display looks much better (text)
|
||||||
|
49,Queued,✅ DONE,"RADAR DRAWS THE WORLD AGAIN (#49): walls, buttes, hills, ridges, buildings and trees now appear on the scope, on every map, using art that shipped with the game. The original filed every pip-bearing terrain object into the radar; our feed was passing only MOVING things, so the scope had nothing static to draw. NOT bugs: the 10 km outer boundary has no radar art (authentic -- the walls ARE the boundary you see), and pips thin out as you zoom out.",
|
||||||
|
51,Pass,✅ DONE,"Coolant flush sound looped forever
|
||||||
|
TEST: flush coolant -- the sound ends when the flush ends.",
|
||||||
|
53,Pass,✅ DONE,"Generators could not be switched OFF
|
||||||
|
TEST: ENG page -- toggle each generator off and back on.",
|
||||||
|
54,Pass,✅ DONE,"David) all three energy weapons died for a whole round
|
||||||
|
TEST: after your first death, weapons must still recharge normally
|
||||||
|
for the rest of the round.",
|
||||||
|
56,Pass,✅ DONE,"MFD buttons drift away from where they are drawn
|
||||||
|
TEST: alt-tab / focus changes mid-round -- buttons stay aligned.",
|
||||||
|
58,Pass,✅ DONE,"Reticle callsign plate drew as a solid rectangle
|
||||||
|
TEST: target names under the reticle render as text on every map.",
|
||||||
|
59,Pass,✅ DONE,"First death permanently froze parts of that player's simulation
|
||||||
|
TEST: nothing specific -- covered by normal play after dying.",
|
||||||
|
61,Pass,✅ DONE,"Searchlight toggle was dead
|
||||||
|
TEST: searchlight button toggles + lamp tracks. (The visible beam and
|
||||||
|
night-fog change are separate, still to come -- #123.)",
|
||||||
|
62,Pass,✅ DONE,"Weapon knocked off the power bus stayed dark forever
|
||||||
|
TEST: fiddle bus/generator selection on the ENG page -- weapons
|
||||||
|
reconnect on their own (AUTO) instead of staying dead.",
|
||||||
|
69,Pass,✅ DONE,"""Sunder V1"" renamed to DENKOU
|
||||||
|
TEST: vehicle picker shows Denkou -- era witnesses, confirm spelling.","(undated) Denkou is correct spelling, confirmed by Ronin who was the one who gave that mech it's name"
|
||||||
|
70,Pass,✅ DONE,"(Draco) torso twist lost after a death/respawn
|
||||||
|
TEST: die, respawn, twist. Fixed a while ago but never field-confirmed.",
|
||||||
|
71,Pass,✅ DONE,"Closing the Plasma window lost it until restart
|
||||||
|
TEST: X the plasma window -- it minimizes to the taskbar; click restores.",
|
||||||
|
100,Pass,✅ DONE,"Main coolant reservoir drains with NO leak shown anywhere
|
||||||
|
NOTE: what you were doing; screenshot the ENG page when you catch it.",
|
||||||
|
101,Queued,✅ DONE,"Round ended with 18 s still on the clock
|
||||||
|
NOTE: exact clock value + which drop; has not repeated since.",(undated) No observations
|
||||||
|
102,Queued,✅ DONE,"OWENS RUN -> REVERSE (#102): the port was missing the machine's HARD-BRAKE system. On the real machine, throwing reverse at speed swaps to the chassis's authored brake rate, kicks the gyro and judders the cab. The Owens brakes at HALF its drive rate (50 -> 25), so its long coast into reverse is AUTHORED -- the pod told you through the seat; our silent coast read as a hang. Two more things: reverse is the RED BUTTON (hold LALT with the throttle OPEN, manual p7) and brake keys can never reverse; and the 'leg in the air until you twist' is the machine's own mid-pivot park, which was invisible on the pod because you could never see your own mech from outside.
|
||||||
|
|
||||||
|
#102 FIXED 8/14. The port was missing the machine's whole HARD-BRAKE system, so a full-run Owens meeting reverse just coasted silently -- that silence was the 'hang'. Also worth knowing: reverse is the RED BUTTON (hold LALT with the throttle OPEN); brake/throttle-down keys can never produce reverse. Retest: Owens, exterior cam, full run, hold LALT.","(undated) Unable to repro
|
||||||
|
|
||||||
|
(undated) Lynx - Possible issue getting out of reverse. Monitor"
|
||||||
|
104,Queued,✅ DONE,"Closed, NO rename. Published VWE material carries the NARC 15 loadout, so the label is correct and our decomp-only reading would have introduced an error. Thanks -- that is the second time this month era material has corrected a binary-only inference.","8/14 - Per published info on VWE site, this is correct"
|
||||||
|
107,Queued,✅ DONE,Destroyed HeatSink annunciates nothing (no lamp for condition 0),
|
||||||
|
111,Pass,✅ DONE,"No more blue bubble on other players (#111): the translocation warp sphere is YOURS alone now — it plays only in the dying pilot's own cockpit, as the machine intended. From everyone else's seat, a mech death is the big boom and a respawn is the mech appearing — no bubble. (Lynx called this one on night 9; the bubble was our addition, not the machine's.)",8/14 Oracle - OK
|
||||||
|
112,Queued,✅ DONE,AFC rounds shotgun across zones (should land as one projectile),
|
||||||
|
114,Pass,✅ DONE,"#114 Missile impact smoke was a wall
|
||||||
|
TEST: LRM exchanges -- smoke is present but no longer a smoke screen.",
|
||||||
|
119,Pass,✅ DONE,"Coolant-warning voice ""clipping"" at empty reservoir = authentic
|
||||||
|
(every leak-status edge restarts the phrase; the pod did this too).",
|
||||||
|
122,Pass,✅ DONE,"Throttle: normal-vs-seek-4 speeds crammed into last sliver of travel
|
||||||
|
NOTE: which mech; era witnesses -- how did the pod lever feel?","8/10 - I changed the throttle axis mapping to remove the defaulted ""deadzone 2"" argument and it fixed my issue"
|
||||||
|
125,Queued,✅ DONE,"PEERS SEE THE FIRST DESTROYED-ZONE GRAPHIC (#125): kill an arm and everyone watching sees the arm wreck AND its gun pod drop off, on the killing hit. Two faults: our code overwrote the destruction cascade's authored 'gone' state before the update shipped (so the wire itself carried the wrong thing), and peers had no mesh-swap trigger for state-only updates.",
|
||||||
|
127,Pass,✅ DONE,Weapon MFD EJECT graphic -- need a reference image/description.,(undated) Working as expected
|
||||||
|
128,Pass,✅ DONE,"terrain rams no longer spring instant coolant leaks (#128), death blasts are back in Steam matches (authentic — advanced damage was silently off for Steam clients before; don't report the boom as a bug).","(undated) Couldn't cause a leak by ramming yet, crits of gyro and torso components occurred and worsened with each collision. Many players tested no one could get a myomer crit."
|
||||||
|
130,Pass,✅ DONE,"A mech slid across the ground with its legs not walking (Vulture, 6-player round). Three root causes were found and fixed in this family � the wall-grind knockdown storm, the peer turn-lock, and the Standing-lock where a peer holding steady speed never started its walk cycle (Aug 7). Closed on operator verdict; reopen on sight, the log now names the stuck state.",8/14 Oracle - No reports for a couple of builds
|
||||||
|
131,Pass,✅ DONE,"#131 Reticle showed LOCK off-target / no-reg on target
|
||||||
|
TEST: stationary targets -- lock ring matches where the reticle is.",
|
||||||
|
133,Queued,✅ DONE,"Generator X-out on the MFD: Oracle recalls an X ""under certain
|
||||||
|
circumstances"" -- WHICH circumstances? (shutdown / overheat / destroyed)",(undated) Shared reference from manual pg 23. Red X when destroyed
|
||||||
|
135,Pass,✅ DONE,"The generator warning lamp now flashes the right lamp (#135) — a coolant leak was lighting the wrong annunciator and could leave a lamp flashing forever. Both fixed. (The leak lamp flashing continuously while the alarm sounds once IS authentic — edge-triggered sound, level-triggered lamp.)",(undated) Working properly for everyone
|
||||||
|
137,Pass,✅ DONE,"#137 Respawned with myomers heat MAXED (mech frozen at spawn)
|
||||||
|
TEST: respawn repeatedly -- heat always starts normal. Oracle already
|
||||||
|
confirmed one clean session; more eyes welcome, this one eluded us.",
|
||||||
|
138,Pass,✅ DONE,"138 Glass buttons flashed at the wrong rate unless a window was dragged
|
||||||
|
TEST: leave the MFD windows unfocused -- lamps flash at the real rate.",
|
||||||
|
139,Pass,✅ DONE,"(Oracle, once) silent exit between ""lobby up"" and the room screen
|
||||||
|
NOTE: if hosting dies silently again, say so and we pull the log.",8/10 - Issue was due to comment made in L4GAUGE.cfg file
|
||||||
|
143,Pass,✅ DONE,"Hitting one panel can make smoke pour from many panels at once, often after a big alpha strike.
|
||||||
|
STATUS: reported several times, cause not yet chased.
|
||||||
|
NEED FROM YOU: next time it happens, say whether the smoking panels were already damaged or still undamaged, and which mech was hit.",8/14 Oracle - Players aren't reporting this anymore
|
||||||
|
146,Pass,✅ DONE,"Respawned already running (virtual throttle survived death)
|
||||||
|
TEST: respawn -- throttle idle until YOU move it; a physical throttle
|
||||||
|
re-syncs on first movement. Oracle confirmed the mechanic; keep watching.",
|
||||||
|
149,Pass,✅ DONE,"Glass cockpit stutter / hitching ** HEADLINER **
|
||||||
|
First fielded build with the fix. Glass users -- especially Oracle:
|
||||||
|
mission load and play should feel smooth again (repaints now skip
|
||||||
|
windows whose gauges did not change).",
|
||||||
|
150,Pass,✅ DONE,"Double kill credit (one kill counted twice) ** HEADLINER **
|
||||||
|
First fielded build with the fix (868's double-kills were pre-fix).
|
||||||
|
TEST: kill someone with a missile volley -- scoreboard +1, not +2.",8/10 - We tested with missiles exclusively as well as direct fire
|
||||||
|
151,Queued,✅ DONE,Closed on your verdict: -499 stays. It is the machine's own scoring -- your eject scores as a suicide plus the standard death cost -- and it was bench-verified on the ticket.,(undated) Leave as-is
|
||||||
|
152,Pass,✅ DONE,"Torso auto-recentre / CENTER button
|
||||||
|
TEST: CENTER (pod button / F7) recentres the torso in every control
|
||||||
|
mode, on glass and RIO rigs too. (Era question below still open.)
|
||||||
|
(#152 rider): did Mid/Adv auto-recentre the torso in the pod, or only
|
||||||
|
Basic? (Oracle: don't know. Binary says: no auto-recentre.)",(undated) I think Basic was the only mode that centered
|
||||||
|
153,Queued,✅ DONE,Basic-mode elevation limit only applies after cycling modes (fix pending),
|
||||||
|
154,Pass,✅ DONE,"""Generator out"" now speaks when the generator trips, not when it comes back (#154). The original content had Yip's voice line wired to the recovery edge — the only backwards fault alarm in the whole game, and the manual says it belongs at the loss. This is the port's one deliberate content fix; everything else about generators (trip at full red, back online at half-cooled after a spin-up) was already exactly the manual's rule and is untouched. Also gone: the ghost ""generator out"" you could hear while respawning.","(undated) Generator out annunciation is working properly, but only able to test by overheating gen. Never got a crit on a gen no matter how much we tried."
|
||||||
|
155,Queued,✅ DONE,MFD BANKS UNDER PANIC (#155): Engineering and Weapon buttons respond again while the panic lamp is lit. The flicker itself stays -- that half is authentic (no hysteresis in the evaluator; the pod's incandescent lamp was the low-pass filter).,
|
||||||
|
156,Pass,✅ DONE,"Mission end fades to black; mission start flashes white and fades in. The window slamming shut at round end was the bug — the original fade sequence was missing entirely and is now reconstructed from the binary. If the end still feels abrupt on your machine, say so.",(undated) Works
|
||||||
|
157,Queued,✅ DONE,"""Crash at drop end"" (Conn Man) = NOT a crash. Full forensics on the
|
||||||
|
ticket: every machine that night shows clean drops, and the round-end
|
||||||
|
exit-to-menu is the arcade's normal between-rounds flow. His separate
|
||||||
|
lag / ""missiles hit empty space"" report will get its own ticket.
|
||||||
|
FYI: the console window's ""closed unexpectedly"" text shows after ANY
|
||||||
|
exit -- do not let it scare you.",
|
||||||
|
160,Pass,✅ DONE,"Pod cab: controls went dead at random and older builds froze at startup. ROOT CAUSE (Nick, on the cab): the FTDI USB-serial adapter had its BUFFER SIZE set too high, so cockpit data arrived in late clumps � analog dropouts in play, and a missed handshake window at boot. Never a software fault. Re-check after any driver reinstall or new cab build; FTDI defaults bring it back.",
|
||||||
|
162,Pass,✅ DONE,"Kills/deaths now count exactly. A death that landed as two simultaneous hits (direct + splash on the same frame) credited the killer TWICE and double-counted the death. Found by stress-testing at 4 and 8 nodes until it broke; verified after the fix with 26-kill
|
||||||
|
melees balancing to the round. Scoring is −500 per death; if your K/D looks wrong Wednesday iday, screenshot it — every kill is now receipted in the log by name.","(undated) K/D worked well, losing points on death"
|
||||||
|
168,Pass,✅ DONE,"The original decides a cannon hit AT THE TRIGGER — locked target, in range, single panel, done. The round you watch fly is a tracer drawn for show. Our ""lead the target"" behavior was a porting mistake, now reversed: no leading, ever. Ballistic damage also respects the range scale again (energy always did), and cannon damage lands on ONE panel (#112 closed).
|
||||||
|
Missiles still genuinely fly — that part was always real — but now at the machine's true speeds: missiles are slower and heavier this build (we were flying them 5× too fast). The seeker does your leading. Missiles can now also strike a mech that walks into the flight path.","8/14 Oracle - AFCs are hitting instantly, tracer animation arrives seconds later and looks like an incoming missile which looks very weird.
|
||||||
|
8/14 Oracle - AFCs are rocking the cockpit structure in the main view. but no rock on HUD and no visible rocking to other players (which I think is correct).
|
||||||
|
8/14 Oracle - Multiple instances of mechs intercepting missiles fired at another target"
|
||||||
|
169,Pass,✅ DONE,"the LEAVE LOBBY button is no longer half-cut at the window edge, and an 8-player host roster no longer piles into the LAUNCH button.
|
||||||
|
|
||||||
|
#169 -- already fixed; this row predates the fix. Please confirm on the current build.",8/10 - On lobby screen the leave lobby button is cut off at the bottom edge of the window (half shown)
|
||||||
|
171,Pass,✅ DONE,"The original decides a cannon hit AT THE TRIGGER — locked target, in range, single panel, done. The round you watch fly is a tracer drawn for show. Our ""lead the target"" behavior was a porting mistake, now reversed: no leading, ever. Ballistic damage also respects the range scale again (energy always did), and cannon damage lands on ONE panel (#112 closed).
|
||||||
|
Missiles still genuinely fly — that part was always real — but now at the machine's true speeds: missiles are slower and heavier this build (we were flying them 5× too fast). The seeker does your leading. Missiles can now also strike a mech that walks into the flight path.",8/14 Oracle - See comments in 168
|
||||||
|
173,Pass,✅ DONE,"Big walkers overheated in seek-4 almost instantly and would not cool; a timing bug was doubling running heat. Fixed � heat now samples on the machine's own clock, and walkers hold supercharge as the manual promised. Gotcha: the loop-5 boost is TWO valve presses; a third press wraps the valve CLOSED. Peer-walk sound warble spun out to #188.",8/14 - Oracle - OK
|
||||||
|
176,Pass,✅ DONE,"Did changing seek gears really swing damage as much as the guide claimed? Oracle said no. He was right, and the real ladder is now measured: Seek 1 = 56%, Seek 2 = 77%, Seek 3 = 100% (the published number), Seek 4 = 153% overcharge at ~3x the charge time and +53% heat. Doctrine: downshift for rate of fire, seek 4 for an alpha punch. No code change needed.",(undated) Oracle - CONTRADICTS the guide's claim; seek 3 agrees with published VWE data
|
||||||
|
177,Queued,✅ DONE,#177 CLOSED -- agrees with you. Era witness + the 4.0 manual both say heat is EXPERT-only; logged your note as independent confirmation.,
|
||||||
|
179,Pass,✅ DONE,"Small fix, fast turnaround (#179): Oracle reported the Nova spawning with its rear laser's range pips showing in the forward view (self-corrected after one rear-view toggle) — root-caused and fixed the same evening. The HUD now commits the forward view at spawn, as the machine did. Only the pips were wrong; rear weapons never actually fired forward.",8/14 Oracle - OK
|
||||||
|
190,Pass,✅ DONE,"Windows Defender briefly flagged the game as a trojan and deleted it. FALSE POSITIVE, and already fixed by Microsoft: the verdict existed in one definition set (8/14 evening) and was gone by the next (8/15 early hours) - the same file now passes cleanly. No action needed; a normal Windows update clears it. We also proved the binary was untampered by comparing it against a known-good build (same sections, no added imports, identical shipped DLLs).",8/15 - second tester on midnight-updated Defender: no problem with the same 957 build
|
||||||
|
192,Queued,✅ DONE,"Closed without a fix - no symptom was ever observed. A code audit found a missing graphics setting on the observer-seat callsign banner, but that screen was verified working with screenshots when it was built and nobody has used it since. Reopen if the observer seat ever shows solid blocks where names should be; the fix is about six lines.",
|
||||||
|
AUDIT,Pass,✅ DONE,Armor audit � crouched and standing on all chassis with energy weapons.,(undated) Completed by testers; no defects found.
|
||||||
|
@@ -0,0 +1,180 @@
|
|||||||
|
#,Priority,Test Status,Dev Status,Dev Comments,Tester Comments
|
||||||
|
14,P5,Queued,⚪ QUEUED,"A checklist of cockpit panel buttons that were wired to nothing - crouch, generator on/off, coolant balance, searchlight, thermal.
|
||||||
|
STATUS: Internal tracking only; clicking a dead button can no longer crash, and most of these buttons have since been built under their own tickets. Nothing to test.",
|
||||||
|
17,P5,Queued,⚪ QUEUED,"In the cavern, the upper halves of the tall rock towers are see-through to your range finder - aim up at one and the range reads past it.
|
||||||
|
STATUS: Found by us, not in the field; we're first checking whether the original arcade rocks were solid up there before adding anything.",
|
||||||
|
23,P4,Queued,🔵 YOUR CALL,"The corner MFD panels overlap the edges of the world view.
|
||||||
|
STATUS: Not a bug - the real pod's monitors clipped the eyeport the same way; kept as a design-review item.
|
||||||
|
NEED FROM YOU: On a desktop monitor, do you want a setting to shrink the overlap, or keep it pod-faithful? Say your screen size.",(undated) Gave my feedback
|
||||||
|
27,P2,Fail,🟠 NEED INFO,"Autocannon and Gauss hits still spread across several damage panels instead of one solid hit.
|
||||||
|
STATUS: single-panel hits shipped, but the field still reports spread.
|
||||||
|
NEED FROM YOU: fire one Gauss and one AFC at a parked mech, say how many panels changed, and read us the boot-screen build number.",8/10 - Still spreading damage to multiple panels with both Gauss and AFCs. Separately passed along observations about range and behavior seeming like a form of LRM.
|
||||||
|
28,P2,Queued,🟡 PATCHED - RETEST,"No mech has ever died from a critical hit to a vital internal part - only from sections blown apart.
|
||||||
|
STATUS: The missing machinery is now built; needs a live match to prove it works.
|
||||||
|
NEED FROM YOU: Note the mech, map and time whenever a mech dies with no section blown off.",
|
||||||
|
30,P5,Queued,⚪ QUEUED,"A jammed or heat-killed weapon looks like a healthy one on the default weapon view.
|
||||||
|
STATUS: Settled as authentic - the jam lamp on that weapon's ENG DATA page is confirmed working, an EJECT tap clears a jam, and only internal cleanup remains here.",
|
||||||
|
38,P3,Fail,🟠 NEED INFO,"Everyone except the host gets a red patch and VGL badge instead of their pick; colour is correct.
|
||||||
|
STATUS: a fix shipped to send badge and patch through the lobby; the field still fails.
|
||||||
|
NEED FROM YOU: have every player read out their boot-screen build number next session - an older build shows exactly this.","8/10 - Only host initially spawns with the correct mech, badge and patch color. Everyone else initially spawned with their chosen mech color but a red patch and VGL badge. Host appears correct to others and self in external view. Again, Non-host players had correct mech color but always red patches and VGL badge. Nothing changed when respawning."
|
||||||
|
41,P2,Mixed,🟠 NEED INFO,"Nothing to test here - this one is a trap we set, not a bug you can reproduce.
|
||||||
|
STATUS: David/Auric's launcher died before the game could write anything. We cannot make it happen on demand, so the build now records WHY at the moment it fails.
|
||||||
|
NEED FROM YOU: only if a launcher dies again - send content\lastrun_join.txt and it names the cause. Otherwise ignore this row.",8/14 Oracle - Clarify please
|
||||||
|
63,P5,Queued,⚪ QUEUED,"The end-of-mission review screen reads player info from the wrong place, so it could show junk numbers or crash.
|
||||||
|
STATUS: harmless right now - that screen never opens during play; it gets rewired before the screen is ever switched on.",
|
||||||
|
64,P5,Queued,⚪ QUEUED,"An internal mix-up left part of each mech's damage bookkeeping pointing at nothing, which had quietly broken the coolant-leak plumbing.
|
||||||
|
STATUS: the half that could affect play is fixed and shipped; the leftover is code tidy-up you will never see.",
|
||||||
|
65,P5,Queued,⚪ QUEUED,"Internal: several mech subsystems handle their messages in a way a future change could silently bypass.
|
||||||
|
STATUS: nothing is broken in play today; logged as a single cleanup pass.",
|
||||||
|
68,P3,Queued,⚪ QUEUED,"If you HOST and the lobby fails to create, the game just closes with no message.
|
||||||
|
STATUS: Reopened - this was closed after fixing the JOIN side; the host side still exits silently. Confirmed in the code.
|
||||||
|
NEED FROM YOU: nothing - but if hosting ever dies without a word, that is this.",
|
||||||
|
72,P5,Queued,⚪ QUEUED,"Closing the game while a mission is still loading can throw away the shutdown request.
|
||||||
|
STATUS: not a live problem - the game still exits in about a second; logged so a future change cannot turn it into a hang.",
|
||||||
|
74,P2,Pass,🟠 NEED INFO,"8BitDo gamepad works (partly) in solo but does nothing in Steam multiplayer.
|
||||||
|
STATUS: Logged; we need your setup details before we can chase it.
|
||||||
|
NEED FROM YOU: the exact 8BitDo model, how it connects (XInput/DInput/Switch mode), whether Steam is applying a controller layout, and which controls do work in solo.",
|
||||||
|
76,P3,Queued,🟡 PATCHED - RETEST,"The main cockpit and Plasma windows return to the wrong place after relaunch, even with layout saving on.
|
||||||
|
STATUS: the layout-wiping bug is fixed, so these two are ready to re-check.
|
||||||
|
NEED FROM YOU: move the cockpit full-screen and Plasma to your second monitor, save, relaunch, then say which came back wrong.",(undated) Pass this?
|
||||||
|
77,P3,Queued,⚪ QUEUED,"The Secondary/Radar window is locked to one size, so it cannot fill a tablet or touch panel used as an extra display.
|
||||||
|
STATUS: logged as a real feature request; making it resizable has not started.",(undated) Indifferent
|
||||||
|
83,P3,Mixed,🟠 NEED INFO,"The incoming-missile beeper is back and speeds up as a missile closes, but two testers say the tone sounds wrong (""clown horn"").
|
||||||
|
STATUS: alarm fixed and confirmed working; pitch under rework.
|
||||||
|
NEED FROM YOU: compare ours to the two pod videos (0:43, 1:10) - is ours too low, too high, or too slow?","8/14 Oracle - Alarm is back and sounds good with changing tempo based on missile proximity. NARCs and Streaks are orbiting mechs when they miss, possibly a little too much vs how I recall. All missiles are currently able to hit stationary targets out to maximmum possible range they can be seen.
|
||||||
|
8/14 Oracle - Can see that orbiting NARC missiles will tighten their orbit after about 10 seconds as their fuel is exhausted.
|
||||||
|
8/14 Oracle - Elengil reports alarm pitch sounds off
|
||||||
|
8/14 Oracle - Nanook reports larm sounds like a clown horn
|
||||||
|
8/14 Oracle - Multiple players reporting missiles orbiting too much (18 times on at least two occasions, 10 times is fairly common)
|
||||||
|
8/14 Oracle - Missiles tracking a mech that dies before they reach it will airburst. In the pods they used to continue flying, someone (Elengil?) recalled that they would fly toward the center of the map."
|
||||||
|
90,P3,Queued,🟠 NEED INFO,"Fire and smoke on badly damaged mechs look thinner and shorter than the arcade.
|
||||||
|
STATUS: Untuned; the report predates our particle fix, so we need a fresh look.
|
||||||
|
NEED FROM YOU: Name a mech you saw burn, roughly how many seconds it lasted, and whether flames start before the kill or only on the wreck.",
|
||||||
|
103,P4,Queued,🔵 YOUR CALL,"Ramming damages a mech's insides - gyro, heat sinks, torso - never its armor; grinding does nothing.
|
||||||
|
STATUS: Confirmed as the original's design; the myomer damage you saw was our bug and is fixed.
|
||||||
|
NEED FROM YOU: On the real cabinets, did a hard high-speed ram ever dent the target's armor, or only wreck internals?",(undated) Don't recall
|
||||||
|
105,P3,Queued,⚪ QUEUED,"Sensor damage currently does nothing - the arcade flickered or dropped the HUD and could kill the radar.
|
||||||
|
STATUS: The behavior is fully described by Lynx and Oracle and logged; the work simply has not started.",
|
||||||
|
113,P3,Queued,🟡 PATCHED - RETEST,"LRM burn looked too long; missiles now fly slower and shorter with air drag restored.
|
||||||
|
STATUS: Fixed in 4.11.957; flight timings re-checked against the original and found correct.
|
||||||
|
NEED FROM YOU: does it still read too long? Say which half - the flame on the missile, or the effect on the struck mech.",
|
||||||
|
115,P2,Queued,⚪ QUEUED,"The hot box target-select buttons do nothing, so you can't designate a specific enemy.
|
||||||
|
STATUS: Confirmed dead and understood; wiring the original selector buttons is queued and nothing is needed from you. Oracle's Standard-versus-Expert memory is logged alongside it.",
|
||||||
|
117,P3,Queued,⚪ QUEUED,"Ronin confirmed every chassis had its own cockpit bounce in the original, on purpose, so each mech felt different from the seat.
|
||||||
|
STATUS: Logged as design knowledge; we still have to check whether our build shakes per chassis or uses one shared bounce. Nothing to test.",
|
||||||
|
121,P3,Queued,⚪ QUEUED,"Sensor heat has no effect - the bar can peg full and your radar and targeting keep working perfectly.
|
||||||
|
STATUS: Known and logged; the heat penalty for sensors was never built, so nothing is needed from you.",
|
||||||
|
123,P3,Queued,🟡 PATCHED - RETEST,"The searchlight's visible half landed - night fog pulls back, plus a cone on your mech.
|
||||||
|
STATUS: Fixed weeks ago; never night-tested.
|
||||||
|
NEED FROM YOU: On a night map, toggle the searchlight (F5) and say if the murk pulls back at mid range (close walls stay dark) and whether teammates see your cone.",
|
||||||
|
126,P2,Queued,🟠 NEED INFO,"From certain positions another player's shots are both invisible and silent; moving makes them show again.
|
||||||
|
STATUS: one report with a log; cause unknown.
|
||||||
|
NEED FROM YOU: note both mechs, the map, your distance, whether terrain blocked the line, and how far you moved before shots returned.",
|
||||||
|
132,P3,Queued,🟠 NEED INFO,"A yellow line sits on the HUD in every mech; only one tester sees it.
|
||||||
|
STATUS: unreproduced; it may be the same stuck beam that this build's weapon fix cured.
|
||||||
|
NEED FROM YOU: Conn Man - is it still there in the latest build? If so, a full-screen photo plus your screen resolution.",
|
||||||
|
136,P3,Queued,🟡 PATCHED - RETEST,"Toggling components on a shut leaking loop seemed to stop and restart the alarm.
|
||||||
|
STATUS: ruled authentic - the sound loops while a leak lasts; the flashing button by the radar is the real indicator, now fixed.
|
||||||
|
NEED FROM YOU: next leak, check the button flashes the whole time, even when the alarm is silent.",
|
||||||
|
145,P3,Queued,⚪ QUEUED,"The IR / 'predator vision' button does nothing visible yet.
|
||||||
|
STATUS: Oracle's description - a random palette shift, not a heat image - is on file; building the display is queued.",
|
||||||
|
147,P3,Queued,🟡 PATCHED - RETEST,"The moving range marker can vanish for the rest of the session while the tick marks stay.
|
||||||
|
STATUS: likely cause fixed and the game now records it automatically; not seen since.
|
||||||
|
NEED FROM YOU: if it vanishes again in a multiplayer drop, note the map and time and send your log.",
|
||||||
|
158,P2,Queued,🟠 NEED INFO,"After respawning in Abandoned Arena you can be stuck until you cycle the throttle.
|
||||||
|
STATUS: two possible causes - the new respawn throttle release, or spawning against scenery - not yet separated.
|
||||||
|
NEED FROM YOU: when stuck, note whether your throttle lever was already pushed forward, whether cycling it frees you, and where you spawned.",
|
||||||
|
159,P3,Queued,🟠 NEED INFO,"Missiles and cannon fire seen exploding in empty space, away from the target, during laggy drops.
|
||||||
|
STATUS: logs show no dropouts, so this is lag; cannon fire is now instant-hit, which may have fixed half of it.
|
||||||
|
NEED FROM YOU: note shooter, target, clock time, and whether it was missiles or cannon.",
|
||||||
|
163,P2,Queued,🟡 PATCHED - RETEST,"Some players were left staring at a frozen picture of the finished round; quitting sometimes hung.
|
||||||
|
STATUS: fixed - the game now shuts down in a set order and always closes.
|
||||||
|
NEED FROM YOU: after your next few rounds, confirm the menu returns instantly - especially for the host - with no frozen screen or Task Manager.",
|
||||||
|
164,P3,Queued,🟡 PATCHED - RETEST,"A laser beam could stick on, hanging from your legs and swinging with your rudder until you died.
|
||||||
|
STATUS: Cause found and fixed; beams now only draw while a weapon is genuinely firing.
|
||||||
|
NEED FROM YOU: If a beam outlives its shot again, note the time and the beam's colour, and send your log.",
|
||||||
|
165,P2,Queued,🟡 PATCHED - RETEST,"Weapons X'd out as destroyed could still fire and recycle.
|
||||||
|
STATUS: Fixed - crit-killed weapons now stop firing the instant the X appears.
|
||||||
|
NEED FROM YOU: Shoot a weapon until it X's, hold the trigger, confirm nothing fires; name it if it does. A dial cycling under an X is normal.",8/14 Oracle - Needs more testing
|
||||||
|
166,P2,Queued,🟠 NEED INFO,"A weapon reads ready on the MFD but refuses to fire until you eject a round.
|
||||||
|
STATUS: One cause fixed; a new case right after respawn says another path remains.
|
||||||
|
NEED FROM YOU: Note the weapon, whether it followed a respawn, and what freed it - eject or repeated pulls - plus your log.","8/14 - Oracle - Sauron couldn't fire SRM in a Hellbringer after spawning after a minute into the game without any action except repeated attempts to fire, it fired again.Around 9:53 PM ET"
|
||||||
|
167,P2,Queued,🟠 NEED INFO,"For a stretch, the lock circle stops appearing on any target.
|
||||||
|
STATUS: Seen twice in the field, never on our test rig - cause unknown, no fix yet.
|
||||||
|
NEED FROM YOU: Note the time it starts and stops, whether your shots still tracked, and whether dying or picking a new target brought the circle back.",(undated) Oracle - At 10:32PM Oracle had no reticle circle appear on any targets mechs
|
||||||
|
170,P1,Fail,🟡 PATCHED - RETEST,"Crash when the game returns to the menu after a round; it looks like a crash joining the lobby.
|
||||||
|
STATUS: cause found and the shutdown rebuilt; the repeat report may be the original one re-filed.
|
||||||
|
NEED FROM YOU: play three rounds back-to-back; if it crashes, note the time and send that session's log.","8/12 - 9:02PM ET crash while joining lobby Oracle
|
||||||
|
8/12 - 9:43PM ET crash while joining lobby Oracle
|
||||||
|
8/12 - 9:57PM ET Sauron and Nanook has lobby join crash"
|
||||||
|
172,P4,Pass,🔵 YOUR CALL,"The leak lamp isn't late - weapon leaks light only on the engineering page; a generator leak lights only the button beside the radar.
|
||||||
|
STATUS: Ruled authentic; the voice loops on its own, so repeats aren't new leaks.
|
||||||
|
NEED FROM YOU: On the real pod, was the generator warning light big and bright, or subtle?","8/14 Oracle - OK
|
||||||
|
|
||||||
|
8/14 - Elengil reported coolant leak on AFC 50 in Zanin Neko, twice only started flashing only when the voice said coolant leak for the second time
|
||||||
|
(undated) 8:44PM ET Lynx reported a loop 6 leak that did not flash immediately."
|
||||||
|
174,P2,Mixed,🟡 PATCHED - RETEST,"Shooting a section that is ALREADY destroyed used to keep grinding fresh internal damage into whatever survived - and quietly kept scoring points for it.
|
||||||
|
STATUS: Fixed. A destroyed zone is now inert: no new crits, no points. This is also why ramming used to feel like it wrecked your internals progressively.
|
||||||
|
NEED FROM YOU: keep firing into an already-blown arm or torso - your damage readout should stop changing, and your score should not tick up.",8/14 Oracle - Clarify please
|
||||||
|
175,P2,Fail,🟠 NEED INFO,"Missiles used to fly through hills and buildings; a fix now bursts them on the first solid thing they hit.
|
||||||
|
STATUS: Fix shipped, but the 2026-08-14 sheet marked it Fail with no detail.
|
||||||
|
NEED FROM YOU: did a missile pass THROUGH cover, or burst early in open air? Note the map and time.",8/14 Oracle - We didn't see splash from missiles exploding near other mechs. We tried firing salvos at the ground or wall next a mech. We tried putting mechs against each other and firing missiles at one to see if the damage would spread to the mechs that weren't hit. Cover is intercepting the missiles properly.
|
||||||
|
178,P3,Queued,⚪ QUEUED,"PPC hits may briefly knock a generator offline - long enough that anything set to AUTO re-assigns itself to a different generator, too short to see the generator read as down.
|
||||||
|
STATUS: Oracle described the signature; we have the brownout machinery but have not confirmed an energy hit actually reaches the victim's electrical chain.","(undated) Oracle - saw sub-systems on auto mode switch generator assignment after PPC hits; never saw a generator actually go offline
|
||||||
|
8/14 - Tested multiple players firing PPCs at a single mech with all systems set to auto generator for several minutes, no systems changed their generator, inclonclusive result, need more testing."
|
||||||
|
180,P3,Queued,🟠 NEED INFO,"Coolant loop buttons keep flashing after the leak alarm goes quiet; loop 6 never stopped.
|
||||||
|
STATUS: Logged. Unclear whether the lamp is stuck or loop 6 was still genuinely leaking.
|
||||||
|
NEED FROM YOU: next leak, say which loop, and whether that loop still cooled you (heat dropping) while its button kept flashing.","8/14 - Loop 6 leak indication and alarm stopped after some time, but buttons on loops keep flashing even after alarms went silent. After further waiting (approx 1 minute) loop 3 button stopped flashing. Loop 6 never stopped flashing."
|
||||||
|
181,P3,Queued,⚪ QUEUED,"When another player kills you, their name is not appearing on the second screen.
|
||||||
|
STATUS: Logged as a display problem only; scoring and kill credit are already verified correct. Fix not started.",8/14 - Message showing name of player that killed you is not showing on secondary screen
|
||||||
|
182,P1,Queued,🔵 YOUR CALL,"If the host leaves the lobby, nobody gets a Launch button and the lobby is stuck.
|
||||||
|
STATUS: confirmed twice; we can build it either way but need a direction.
|
||||||
|
NEED FROM YOU: should another player automatically become host, or should the lobby close so everyone re-forms?","8/14 - Oracle - If host leaves a lobby, no one gets the Launch button. Confirmed twice, unclear if someone should become the host."
|
||||||
|
183,P4,Queued,🟡 PATCHED - RETEST,"No icon appeared when a weapon had an ammo bay fire.
|
||||||
|
STATUS: FIXED in 4.11.988 and verified on our machine - BOTH the bay-fire icon (with its countdown) and the jam icon light correctly now. They sit on the burning/jammed weapon's OWN page: jam icon left spot, bay-fire right spot.
|
||||||
|
NEED FROM YOU: next real bay fire or jam, switch the ENG panel to that weapon's page and confirm what you see matches the pods.","8/14 - Oracle - No icon in the display mode for a weapon that has an ammo bay fire (center of MFD, same area where the jammed icon is shown)"
|
||||||
|
184,P4,Queued,🔵 YOUR CALL,"Some testers recall energy weapons burning out and getting X'd from overheating alone.
|
||||||
|
STATUS: No heat-only burnout for lasers or PPCs found in the original code; only ammo weapons go dark.
|
||||||
|
NEED FROM YOU: If you saw it, name the weapon, whether you were also taking hits, and whether the X stayed all round.","8/14 - Oracle - Some debate about testers recalling that energy weapons could ""fuse"" and get X'd out if they overheated too much"
|
||||||
|
185,P3,Queued,🟠 NEED INFO,"In glass mode the coolant window vanished mid-mission; only a hand-edited config file brought it back.
|
||||||
|
STATUS: Logged; unclear whether it never opened or was dragged off during play.
|
||||||
|
NEED FROM YOU: Nanook, check the coolant window is on screen at mission start, then note what you were doing when it vanishes.","(undated) 9:40PM ET Nanook was running glass and lost coolant MFD, didn't know if it was there at the start of the mission, couldn't get it back. Wasn't sure if it was moved off by accident or moved off, so edited his config file to get it back."
|
||||||
|
186,P2,Queued,🟠 NEED INFO,"Missiles from the Vulture looked like they exploded partway to the target.
|
||||||
|
STATUS: Logged; may be the new cover-blocking behavior hitting an unseen object, or just lag.
|
||||||
|
NEED FROM YOU: was there a hill, building or tree in the line, or did it burst in open air? Note the map and your mech.",8/14 - Oracle - Missiles fired from Vulture by Draco - VGCorps appeared to explode halfway to target around 9:55 PM ET
|
||||||
|
187,P2,Queued,⚪ QUEUED,"Ballistic and missile weapons (Gauss, AFCs, Streaks, Narc) still damage targets past the rangefinder's max range; energy weapons correctly stop.
|
||||||
|
STATUS: Report logged with the weapon list; we're checking whether the damage code and the rangefinder read the same range number.","8/14 - Streak 2, Streak 4, Narc 15, LRM and SRM
|
||||||
|
Observed that these weapons are hitting and damaging targets beyond the maximum range of the rangefinder. Energy weapons were indepentdently tested and found to stop doing damage at their respective max ranges on the rangefinder scale. Gauss, AFC100, AFC50, AFC25 are stopping at their rangefinder limits. Oracle - I believe this is the correct behavior for the missiles as they can glide."
|
||||||
|
188,P3,Queued,🟠 NEED INFO,"A mech walking past you may make its engine or footstep sound waver in pitch.
|
||||||
|
STATUS: split off from the heat-timing fix; we could only measure this properly with a live ear.
|
||||||
|
NEED FROM YOU: stand still, have someone walk then run a mech past you - does the sound warble or stay steady? Say which chassis and how close.",
|
||||||
|
189,P2,Queued,🟠 NEED INFO,"After a generator overheats and shuts down, a weapon left on AUTO never comes back online - even once the generator has cooled. Forcing a reassignment revives it.
|
||||||
|
STATUS: Re-described from Lynx's full account (the earlier 'generator will not cool' wording was wrong - cooling works fine). Best suspect: the generator's state stays stale after it cools, so auto-assignment keeps thinking it cannot power anything.
|
||||||
|
NEED FROM YOU: does it also happen in EXPERT? Lynx saw it in veteran and says expert behaved normally - if that holds, it narrows this a lot.","8/15 - Lynx - Nova, both PPCs seeked to 4, coolant loops boosted, fire repeatedly (~8 min to force the overheat). Loops keep the PPCs cool but seek-4 energy drives generator heat up until it shuts down. PPC forced to manual; left on auto it does not return once cooled. Comes back on a forced reassignment. Flushing coolant DID bring generator temp down. First seen in veteran; expert behaved as expected. 'Deep mojo about heat transfer in the electrical subsystem' - wants more testing before it is documented."
|
||||||
|
191,P3,Queued,🟡 PATCHED - RETEST,"Other players could not see your coolant flush cloud - the manual says they should.
|
||||||
|
STATUS: FIXED in 4.11.983. The flush never left your machine; it now ships to everyone, tested across two machines.
|
||||||
|
NEED FROM YOU: have someone flush coolant while you watch their mech from outside - you should see bluish steam rise from THEIR mech.",
|
||||||
|
193,P1,Queued,⚪ QUEUED,"Once, back in July, everyone froze mid-round about a minute after one player's game died - the round ended early for all six.
|
||||||
|
STATUS: Never root-caused, logs lost, and it was never filed until now. Not seen since; several netcode fixes have landed since then.
|
||||||
|
NEED FROM YOU: if anyone drops mid-round, note whether the rest of you keep playing normally. One clean observation lets us close this.",
|
||||||
|
194,P2,Queued,⚪ QUEUED,"An ammo bay fire may be doing far more damage than it should - possibly enough that it always kills, no matter how little ammo is left.
|
||||||
|
STATUS: Found by audit. The maths matches the original game; what is unverified is whether one number means per-round or per-salvo.
|
||||||
|
NEED FROM YOU: if you survive (or die to) a bay fire when your bin was nearly EMPTY, tell us - a near-empty bin should not be fatal.","8/15 - Oracle, a nearly empty bin should not be fatal if the mech can absorb the damage. I have survived these, though there was still additional damage from the explosing ammo."
|
||||||
|
195,P3,Queued,⚪ QUEUED,"With 5 or more pilots, the DEATHS number on the Comm screen can run off the edge of the display.
|
||||||
|
STATUS: Measured during an earlier audit and never fixed; a safety clamp hides the worst of it.
|
||||||
|
NEED FROM YOU: a screenshot of the Comm pilot list in a full 6-8 player round would confirm how it looks now.",
|
||||||
|
196,P3,Queued,⚪ QUEUED,"Smoke trails may be thicker on faster PCs - the game emits particles per frame instead of per second.
|
||||||
|
STATUS: Same class as the heat-timing bug we fixed; found by audit, not yet fixed.
|
||||||
|
NEED FROM YOU: nothing specific. If smoke looks very different between two of you in the same fight, that is this.",
|
||||||
|
197,P3,Queued,⚪ QUEUED,"On the real pod cab, the red REVERSE button on the throttle does nothing.
|
||||||
|
STATUS: Found during the reverse-gait work. Desktop and glass rigs are fine - this is cab-only, and it needs a decision about which of two wiring routes to take.
|
||||||
|
NEED FROM YOU: nothing - cab-side work with Nick.",
|
||||||
|
198,P3,Queued,⚪ QUEUED,"A malformed mission file (the same pilot listed twice) can crash the game while loading.
|
||||||
|
STATUS: Seen once during a mass-rejoin last month, then never filed. Probably needs a guard rather than a hunt.
|
||||||
|
NEED FROM YOU: nothing.",
|
||||||
|
199,P4,Queued,⚪ QUEUED,"Heat may be shedding too fast across the board (myomers, weapons, generators) - so ammo jams and bay fires are rarer than they used to be.
|
||||||
|
STATUS: Filed from Draco's note. Plausible: a fix in the last build removed a lot of phantom walker heat, and this may have gone one step too far. Checking the authored cooling rates first.
|
||||||
|
NEED FROM YOU: from anyone who played the pods - roughly how often SHOULD a hot autocannon jam in a normal fight? That number is what we would calibrate against.","8/15 - Draco - recalls the Vulture running hotter; myomer, weapon and generator heat all cool too quickly now, and he is not getting the ammo jams/fires he remembers having to manage"
|
||||||
|
200,P2,Queued,⚪ QUEUED,"Missiles never splash - near misses do nothing, and you cannot hurt yourself firing point-blank into a wall, which the pods allowed.
|
||||||
|
STATUS: Confirmed by Oracle's systematic test (walls, ground, hills, adjacent mechs - only the struck mech takes damage) and Nanook's memory that self-splash was real. Cover interception itself works; this is only the missing blast radius.
|
||||||
|
NEED FROM YOU: nothing - two witnesses is enough. We dig into the detonation code.",
|
||||||
|
@@ -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
|
||||||
//-------------------------------------------------------------------
|
//-------------------------------------------------------------------
|
||||||
|
|||||||
@@ -4531,10 +4531,34 @@ void
|
|||||||
{
|
{
|
||||||
previousState = new_value;
|
previousState = new_value;
|
||||||
|
|
||||||
|
// #183 diag (BT_BAYFIRE_LOG, edge-only): the bay-fire icon died
|
||||||
|
// SILENTLY in the field -- this census shows which TwoState widgets
|
||||||
|
// actually execute and draw, so a bench can separate "value never
|
||||||
|
// arrives" / "bitmap missing" / "widget never runs".
|
||||||
|
{
|
||||||
|
static const char *s_bfDiag = getenv("BT_BAYFIRE_LOG");
|
||||||
|
if (s_bfDiag != 0)
|
||||||
|
DEBUG_STREAM << "[twostate] " << bitMapName << " -> "
|
||||||
|
<< new_value << "\n" << std::flush;
|
||||||
|
}
|
||||||
localView.SetColor(color[new_value]);
|
localView.SetColor(color[new_value]);
|
||||||
localView.DrawBitMap(0, bit_map);
|
localView.DrawBitMap(0, bit_map);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// #183 diag: the silent-miss branch. A NULL here means BitMap::Make
|
||||||
|
// could not produce the art -- the widget runs, the value may even be
|
||||||
|
// 1, and nothing is ever drawn. Capped; only interesting while lit.
|
||||||
|
static const char *s_bfDiag2 = getenv("BT_BAYFIRE_LOG");
|
||||||
|
static int s_bfMissCap = 0;
|
||||||
|
if (s_bfDiag2 != 0 && currentValue > 0 && s_bfMissCap < 20)
|
||||||
|
{
|
||||||
|
++s_bfMissCap;
|
||||||
|
DEBUG_STREAM << "[twostate] MISS " << bitMapName << " value="
|
||||||
|
<< currentValue << " (bitmap failed to load)\n" << std::flush;
|
||||||
|
}
|
||||||
|
}
|
||||||
//-----------------------------------------------------------
|
//-----------------------------------------------------------
|
||||||
// Unlock the image: we're done
|
// Unlock the image: we're done
|
||||||
//-----------------------------------------------------------
|
//-----------------------------------------------------------
|
||||||
@@ -4544,6 +4568,23 @@ void
|
|||||||
Check_Fpu();
|
Check_Fpu();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// #183 diag helper: can the warehouse produce this bitmap? Lives here because
|
||||||
|
// the WarehouseBinOf<BitMap> template needs complete types; callers pass the
|
||||||
|
// L4GaugeRenderer they already hold. Balanced Get/Release -- no refcount leak.
|
||||||
|
//
|
||||||
|
void *
|
||||||
|
BTGaugeBitmapProbe(void *renderer_v, const char *name)
|
||||||
|
{
|
||||||
|
L4GaugeRenderer *r = (L4GaugeRenderer *)renderer_v;
|
||||||
|
if (r == NULL || r->warehousePointer == NULL)
|
||||||
|
return NULL;
|
||||||
|
BitMap *bm = r->warehousePointer->bitMapBin.Get(name);
|
||||||
|
if (bm != NULL)
|
||||||
|
r->warehousePointer->bitMapBin.Release(name);
|
||||||
|
return bm;
|
||||||
|
}
|
||||||
|
|
||||||
//###########################################################################
|
//###########################################################################
|
||||||
// NumericDisplayScalarTwoState
|
// NumericDisplayScalarTwoState
|
||||||
//###########################################################################
|
//###########################################################################
|
||||||
|
|||||||
@@ -250,6 +250,13 @@ int BTAmmoBinCookOffTime(void *bin)
|
|||||||
{
|
{
|
||||||
return (bin != 0) ? ((AmmoBin *)bin)->cookOffTime : 0;
|
return (bin != 0) ? ((AmmoBin *)bin)->cookOffTime : 0;
|
||||||
}
|
}
|
||||||
|
// #183: the round count, for the gauge cluster's TestInstance gate. Same
|
||||||
|
// databinding rule as the two above -- the caller used to raw-read bin+0x180,
|
||||||
|
// which is the BINARY's offset and garbage on our compiled AmmoBin.
|
||||||
|
int BTAmmoBinRoundCount(void *bin)
|
||||||
|
{
|
||||||
|
return (bin != 0) ? ((AmmoBin *)bin)->GetAmmoCount() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
// The round's GameModel resource ID (ammoModelFile @0x1E8, word 0x7A). In the
|
// The round's GameModel resource ID (ammoModelFile @0x1E8, word 0x7A). In the
|
||||||
// arcade the MissileLauncher seeds each spawned Missile's model from AmmoBin+0x1e8
|
// arcade the MissileLauncher seeds each spawned Missile's model from AmmoBin+0x1e8
|
||||||
@@ -353,6 +360,63 @@ void AmmoBin::AmmoBinSimulation(Scalar time_slice)
|
|||||||
{
|
{
|
||||||
WatchSimulation(time_slice); // FUN_004aeac4(this) -- drive this+0x140
|
WatchSimulation(time_slice); // FUN_004aeac4(this) -- drive this+0x140
|
||||||
|
|
||||||
|
// #183 VERIFICATION HOOK (BT_BAYFIRE_TEST=<seconds>, off by default).
|
||||||
|
// #47 fixed the fire icon's DATA PATH and asked the field to "confirm the
|
||||||
|
// pixels"; Oracle reports no icon, so the pixels need a rig rather than a
|
||||||
|
// playtest. Forcing a real bay fire in play takes sustained ballistic heat
|
||||||
|
// on a specific bin -- this drives the AUTHENTIC arming condition instead
|
||||||
|
// (heat alarm -> FAILURE on a non-empty bin) at a scripted time, so
|
||||||
|
// everything downstream (cookOffArmed, the countdown, the icon, the
|
||||||
|
// detonation) runs exactly as it does in a match. Same hook family as
|
||||||
|
// BT_VALVE / BT_POWER_DETACH_TEST / BT_FLUSH_TEST.
|
||||||
|
{
|
||||||
|
static const char *s_bfEnv = getenv("BT_BAYFIRE_TEST");
|
||||||
|
if (s_bfEnv != 0 && *s_bfEnv != '\0' && ammoCount > 0)
|
||||||
|
{
|
||||||
|
static int s_bfFired = 0;
|
||||||
|
int fire = 0;
|
||||||
|
if (s_bfEnv[0] == 'k') // BT_BAYFIRE_TEST=key -> F9 ON DEMAND
|
||||||
|
{
|
||||||
|
// The clocked form below counts the BIN's simulation time, which
|
||||||
|
// starts during mission load -- so "90 seconds" expired before the
|
||||||
|
// operator had the Engineering page up. Watching a key lets a
|
||||||
|
// human arm it exactly when they are looking at the right cluster.
|
||||||
|
//
|
||||||
|
// The ENG panel shows ONE weapon group's detail page at a time
|
||||||
|
// (shared "eng1" port, subBit mode select), so a single-bin arm
|
||||||
|
// is invisible unless the panel happens to be on THAT weapon's
|
||||||
|
// page -- the first field attempt armed the AFC100 while the
|
||||||
|
// panel showed an LRM page, and "nothing happened". One press
|
||||||
|
// now arms EVERY bin that ticks within a 2-tick window, so
|
||||||
|
// whatever ammo weapon the panel is showing, its icon lights.
|
||||||
|
// (The statics are shared across bins by design; the per-bin
|
||||||
|
// cookOffArmed gate in the authentic arming below dedups.)
|
||||||
|
static int s_prevKey = 0;
|
||||||
|
static int s_edgeTick = -0x40000000;
|
||||||
|
const int down = (GetAsyncKeyState(VK_F9) & 0x8000) != 0;
|
||||||
|
const int now = BTGameClockNowTicks();
|
||||||
|
if (down && !s_prevKey)
|
||||||
|
s_edgeTick = now;
|
||||||
|
s_prevKey = down;
|
||||||
|
fire = (now - s_edgeTick) <= 2;
|
||||||
|
s_bfFired = 0; // re-armable: press F9 again
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
static Scalar s_bfClock = 0.0f;
|
||||||
|
s_bfClock += time_slice;
|
||||||
|
fire = (!s_bfFired && s_bfClock >= (Scalar)atof(s_bfEnv));
|
||||||
|
}
|
||||||
|
if (fire)
|
||||||
|
{
|
||||||
|
s_bfFired = 1;
|
||||||
|
heatAlarm.SetLevel(2); // FAILURE -- the authentic arm gate
|
||||||
|
DEBUG_STREAM << "[bayfire-test] forced heat FAILURE on " << GetName()
|
||||||
|
<< " (" << ammoCount << " rounds)\n" << std::flush;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (simulationState == 1) // this+0x40 (destroyed)
|
if (simulationState == 1) // this+0x40 (destroyed)
|
||||||
ammoAlarm.SetLevel(Empty);
|
ammoAlarm.SetLevel(Empty);
|
||||||
|
|
||||||
|
|||||||
@@ -1247,6 +1247,11 @@ void
|
|||||||
// ---- numeric #2 : linked heat-sink number, 1 digit @(0x15E,5) (:1315-1321)
|
// ---- numeric #2 : linked heat-sink number, 1 digit @(0x15E,5) (:1315-1321)
|
||||||
// BEST-EFFORT (marked): the resolved sink's +0x1D4 index is a raw offset on
|
// BEST-EFFORT (marked): the resolved sink's +0x1D4 index is a raw offset on
|
||||||
// a not-byte-exact object -> guarded (NULL link / read 0).
|
// a not-byte-exact object -> guarded (NULL link / read 0).
|
||||||
|
// ⚠ #183-sweep CONFIRMED TRAP, deferred: +0x1D4 is the binary Condenser's
|
||||||
|
// condenserNumber (word 0x75, last digit of the name) and the port class
|
||||||
|
// has NO such member yet -- this reads garbage and the digit is wrong.
|
||||||
|
// Proper fix = add condenserNumber to the streamed parse + a typed
|
||||||
|
// bridge; needs the heatfamily resource work, not a one-liner.
|
||||||
int hsNumber = 0;
|
int hsNumber = 0;
|
||||||
void *hsLink = AttributePointerOf(sub, "HeatSink"); // FUN_0041bfc0
|
void *hsLink = AttributePointerOf(sub, "HeatSink"); // FUN_0041bfc0
|
||||||
void *hs = (hsLink != NULL) ? ResolveLink(hsLink) : NULL; // FUN_00417ab4
|
void *hs = (hsLink != NULL) ? ResolveLink(hsLink) : NULL; // FUN_00417ab4
|
||||||
@@ -1726,7 +1731,11 @@ HeatSinkCluster::~HeatSinkCluster()
|
|||||||
void HeatSinkCluster::Execute()
|
void HeatSinkCluster::Execute()
|
||||||
{
|
{
|
||||||
if (failSubsystem != NULL)
|
if (failSubsystem != NULL)
|
||||||
failFlag = (*(int *)((char *)failSubsystem + 0x40) == 1); // BEST-EFFORT raw
|
// #183 sweep: was a raw `+0x40 == 1` read -- the BINARY's simulationState
|
||||||
|
// cell, garbage on our compiled Subsystem, so the ENG fail lamp
|
||||||
|
// (btehfail/btepfail) tracked noise. Typed: state 1 == Destroyed
|
||||||
|
// (same cell the MFD schematic tint reads, btl4gaug.cpp:427).
|
||||||
|
failFlag = (failSubsystem->GetSimulationState() == 1);
|
||||||
heatLoadScaled = heatLoad * HeatLoadScale;
|
heatLoadScaled = heatLoad * HeatLoadScale;
|
||||||
SubsystemCluster::Execute();
|
SubsystemCluster::Execute();
|
||||||
}
|
}
|
||||||
@@ -1831,6 +1840,8 @@ WeaponCluster::WeaponCluster(
|
|||||||
// weapon's name so the Execute probe can attribute its readings; tag the
|
// weapon's name so the Execute probe can attribute its readings; tag the
|
||||||
// recharge arc with the same name for its own [arc] probe.
|
// recharge arc with the same name for its own [arc] probe.
|
||||||
diagWeaponName = (subsystem_in != 0) ? subsystem_in->GetName() : "?";
|
diagWeaponName = (subsystem_in != 0) ? subsystem_in->GetName() : "?";
|
||||||
|
lastLoggedBayFire = -1; // #183 diagnostic edge tracker
|
||||||
|
lastLoggedJam = -1;
|
||||||
((SegmentArc270 *)rechargeArc)->diagName = diagWeaponName;
|
((SegmentArc270 *)rechargeArc)->diagName = diagWeaponName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1991,6 +2002,26 @@ BallisticWeaponCluster::BallisticWeaponCluster(
|
|||||||
// our compiled AmmoBin (base sizes differ), so the counter never moved.
|
// our compiled AmmoBin (base sizes differ), so the counter never moved.
|
||||||
extern int *BTAmmoBinCountPtr(void *bin);
|
extern int *BTAmmoBinCountPtr(void *bin);
|
||||||
int *ammoValue = BTAmmoBinCountPtr(ammoBin);
|
int *ammoValue = BTAmmoBinCountPtr(ammoBin);
|
||||||
|
// #183 probe: the digits capture this pointer ONCE at construction. If the
|
||||||
|
// weapon's ammo link is not bound yet at gauge-build time, ammoValue is NULL
|
||||||
|
// and the counter is frozen for the whole mission -- which is what the field
|
||||||
|
// sees when a bay fire empties a bin and the number does not move.
|
||||||
|
if (getenv("BT_BAYFIRE_LOG"))
|
||||||
|
{
|
||||||
|
// Second half of the probe: can the WAREHOUSE actually produce the two
|
||||||
|
// icon bitmaps? WarehouseBinOf::Get lazy-loads via BitMap::Make and a
|
||||||
|
// failure returns NULL -- which TwoState::Execute treats as "draw
|
||||||
|
// nothing", SILENTLY. (Helper lives in L4GAUGE.cpp where the types
|
||||||
|
// are complete.)
|
||||||
|
extern void *BTGaugeBitmapProbe(void *renderer, const char *name);
|
||||||
|
DEBUG_STREAM << "[gau-bind] cluster '"
|
||||||
|
<< (subsystem_in != 0 ? subsystem_in->GetName() : "?")
|
||||||
|
<< "' bin=" << ammoBin << " ammoValuePtr=" << (void *)ammoValue
|
||||||
|
<< " count=" << (ammoValue ? *ammoValue : -1)
|
||||||
|
<< " btejam=" << BTGaugeBitmapProbe(renderer_in, "btejam.pcc")
|
||||||
|
<< " btefire=" << BTGaugeBitmapProbe(renderer_in, "btefire.pcc")
|
||||||
|
<< "\n" << std::flush;
|
||||||
|
}
|
||||||
|
|
||||||
ammoCountA = new NumericDisplayInteger(ChildRate(), mfd_mode, renderer_in, // @00470cfc
|
ammoCountA = new NumericDisplayInteger(ChildRate(), mfd_mode, renderer_in, // @00470cfc
|
||||||
owner_ID, mfd_port, x + 0x27, y + 100, x + 0x5f, y + 0x76, font_a, 4,
|
owner_ID, mfd_port, x + 0x27, y + 100, x + 0x5f, y + 0x76, font_a, 4,
|
||||||
@@ -2080,12 +2111,46 @@ void BallisticWeaponCluster::BecameActive()
|
|||||||
//
|
//
|
||||||
void BallisticWeaponCluster::Execute()
|
void BallisticWeaponCluster::Execute()
|
||||||
{
|
{
|
||||||
jammed = (*(int *)((char *)subsystem + 0x364) == 5); // BEST-EFFORT raw (weaponAlarm==Jammed)
|
// #183 follow-up: the jam icon had the SAME trap as the bin link below --
|
||||||
void *ammoBin = ResolveLink((char *)subsystem + 0x43c); // FUN_00417ab4
|
// `subsystem + 0x364` is the BINARY's weaponAlarm level cell; on our
|
||||||
|
// compiled ProjectileWeapon that offset is garbage, so `jammed` could never
|
||||||
|
// be (5==5) and the jam icon has never lit in the port either. Oracle's
|
||||||
|
// "where the jammed icon is shown" is POD memory, not our pixels.
|
||||||
|
extern int BTWeaponJammed(void *weapon); // projweap.cpp (weaponAlarm==JammedState)
|
||||||
|
jammed = BTWeaponJammed(subsystem);
|
||||||
|
// #183 DATABINDING FIX (gotcha: never raw-read a compiled object's offsets).
|
||||||
|
// The hand-rolled walk `ResolveLink(subsystem + 0x43c)` is the BINARY's
|
||||||
|
// layout; on our compiled ProjectileWeapon it resolved to NULL, so
|
||||||
|
// cookOffArmed always read 0 and the bay-fire icon could never light --
|
||||||
|
// which is exactly the "no icon" the field reports (#183), even though #47
|
||||||
|
// had correctly fixed the flag this reads. The typed bridge already
|
||||||
|
// existed: BTWeaponAmmoBin (projweap.cpp) was written in July for this same
|
||||||
|
// trap, when the ammo DIGITS failed to bind for the same reason; these two
|
||||||
|
// call sites were missed.
|
||||||
|
extern void *BTWeaponAmmoBin(void *weapon); // projweap.cpp (typed link)
|
||||||
|
void *ammoBin = BTWeaponAmmoBin(subsystem);
|
||||||
extern int BTAmmoBinCookOffArmed(void *bin); // bin+0x18C (complete-type bridge)
|
extern int BTAmmoBinCookOffArmed(void *bin); // bin+0x18C (complete-type bridge)
|
||||||
extern int BTAmmoBinCookOffTime(void *bin); // bin+0x190
|
extern int BTAmmoBinCookOffTime(void *bin); // bin+0x190
|
||||||
int bayFire = BTAmmoBinCookOffArmed(ammoBin);
|
int bayFire = BTAmmoBinCookOffArmed(ammoBin);
|
||||||
firing = bayFire; // the btefire TwoState input
|
firing = bayFire; // the btefire TwoState input
|
||||||
|
// #183 probe: #47 fixed this data path but the pixels were never confirmed
|
||||||
|
// and the field reports no icon. Print what the CLUSTER sees, so a rig can
|
||||||
|
// separate "flag never arrives" from "flag arrives, lamp does not draw".
|
||||||
|
if (getenv("BT_BAYFIRE_LOG"))
|
||||||
|
{
|
||||||
|
// edge-only per cluster: a bay fire burns for 10s at gauge rate, and
|
||||||
|
// a per-frame line buries the transition we care about.
|
||||||
|
if (bayFire != lastLoggedBayFire || (int)jammed != lastLoggedJam)
|
||||||
|
{
|
||||||
|
lastLoggedBayFire = bayFire;
|
||||||
|
lastLoggedJam = (int)jammed;
|
||||||
|
DEBUG_STREAM << "[gau-fire] cluster '"
|
||||||
|
<< (diagWeaponName ? diagWeaponName : "?")
|
||||||
|
<< "' bin=" << ammoBin << " cookOffArmed=" << bayFire
|
||||||
|
<< " jammed=" << (int)jammed
|
||||||
|
<< " fireLamp=" << (void *)fireLamp << "\n" << std::flush;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (bayFire == 0)
|
if (bayFire == 0)
|
||||||
{
|
{
|
||||||
reloading = 0;
|
reloading = 0;
|
||||||
@@ -2127,8 +2192,10 @@ void BallisticWeaponCluster::DrawWarningLamp(int on)
|
|||||||
//
|
//
|
||||||
Logical BallisticWeaponCluster::TestInstance() const
|
Logical BallisticWeaponCluster::TestInstance() const
|
||||||
{
|
{
|
||||||
void *ammoBin = ResolveLink((char *)subsystem + 0x43c);
|
extern void *BTWeaponAmmoBin(void *weapon); // projweap.cpp (typed link)
|
||||||
if (ammoBin == NULL || *(int *)((char *)ammoBin + 0x180) == 0)
|
extern int BTAmmoBinRoundCount(void *bin); // ammobin.cpp (typed count)
|
||||||
|
void *ammoBin = BTWeaponAmmoBin(subsystem);
|
||||||
|
if (ammoBin == NULL || BTAmmoBinRoundCount(ammoBin) == 0)
|
||||||
return True;
|
return True;
|
||||||
if (jammed != 0)
|
if (jammed != 0)
|
||||||
return True;
|
return True;
|
||||||
@@ -2255,8 +2322,13 @@ Logical
|
|||||||
case 0xBC8: // Emitter
|
case 0xBC8: // Emitter
|
||||||
case 0xBD4: // PPC
|
case 0xBD4: // PPC
|
||||||
{
|
{
|
||||||
|
// #183 sweep: was a raw `sub+0x334` read (the BINARY's rearFiring
|
||||||
|
// flag) -- garbage on our layout, so rear-mounted energy weapons
|
||||||
|
// could pick the wrong quarter-circle tile art. The typed bridge
|
||||||
|
// already existed (task #68's rear-mount work).
|
||||||
|
extern int BTWeaponIsRearFiring(Subsystem *sub); // mechweap.cpp
|
||||||
const char *clusterImage =
|
const char *clusterImage =
|
||||||
(*(int *)((char *)sub + 0x334) == 0) ? "qcircle.pcc" : "qcircr.pcc";
|
(BTWeaponIsRearFiring(sub) == 1) ? "qcircr.pcc" : "qcircle.pcc";
|
||||||
new EnergyWeaponCluster(g.planeMask, renderer, 0, mfdPort, g.x, g.y, sub,
|
new EnergyWeaponCluster(g.planeMask, renderer, 0, mfdPort, g.x, g.y, sub,
|
||||||
g.subBit, g.engPortName, tile, label, clusterImage, imageNames,
|
g.subBit, g.engPortName, tile, label, clusterImage, imageNames,
|
||||||
"EnergyWeaponCluster");
|
"EnergyWeaponCluster");
|
||||||
|
|||||||
@@ -509,6 +509,8 @@
|
|||||||
int warningCenterY; // @0xE0 this[0x38]
|
int warningCenterY; // @0xE0 this[0x38]
|
||||||
int warningState; // @0xE4 this[0x39]
|
int warningState; // @0xE4 this[0x39]
|
||||||
const char *diagWeaponName; // PORT diagnostic (appended; clusters are plain-new)
|
const char *diagWeaponName; // PORT diagnostic (appended; clusters are plain-new)
|
||||||
|
int lastLoggedBayFire; // PORT diagnostic (#183): edge-only [gau-fire] logging
|
||||||
|
int lastLoggedJam; // PORT diagnostic (#183): jam edge in the same receipt
|
||||||
};
|
};
|
||||||
|
|
||||||
class EnergyWeaponCluster : // @004c93b0
|
class EnergyWeaponCluster : // @004c93b0
|
||||||
|
|||||||
@@ -1820,7 +1820,18 @@ public:
|
|||||||
: GaugeConnection(0), source(source), destination(destination) {} // FUN_004c3324
|
: GaugeConnection(0), source(source), destination(destination) {} // FUN_004c3324
|
||||||
void Update() // @004c3390
|
void Update() // @004c3390
|
||||||
{
|
{
|
||||||
*destination = *(int *)((char *)source + 0x14); // Subsystem state @0x14
|
// ⚠ This +0x14 is CORRECT -- do not "fix" it again. `source` is NOT a
|
||||||
|
// Subsystem/Entity: the call site passes a GaugeAlarm54's OWN ADDRESS
|
||||||
|
// (the connect-mode lamp hands modeAlarm @subsystem+0x2b8, cast to
|
||||||
|
// Entity* only to fit the ctor signature), and +0x14 is that object's
|
||||||
|
// LEVEL cell -- 0x2cc-0x2b8 == 0x14, static_asserted in powersub.hpp;
|
||||||
|
// reservoirAlarm shows the same internal offset (0x1e4-0x1d0). Since
|
||||||
|
// GaugeAlarm54 is layout-locked byte-exact to the binary, this read is
|
||||||
|
// both binary-faithful AND safe on our compile -- it is NOT the
|
||||||
|
// databinding trap. The 2026-08-15 sweep mis-read it as one, swapped
|
||||||
|
// in GetSimulationState() against the wrong object, and froze the
|
||||||
|
// AUTO/OFF mode lamp at frame 0 (field-caught same hour).
|
||||||
|
*destination = *(int *)((char *)source + 0x14); // GaugeAlarm54 LEVEL @+0x14
|
||||||
}
|
}
|
||||||
protected:
|
protected:
|
||||||
Entity *source; // @0x10
|
Entity *source; // @0x10
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -800,9 +800,86 @@ void
|
|||||||
}
|
}
|
||||||
injectAccumulator = 0; // this+0x228
|
injectAccumulator = 0; // this+0x228
|
||||||
}
|
}
|
||||||
|
// #191 -- tell the peers. The manual promises this ("other pilots will
|
||||||
|
// see steam rise from your 'Mech on the battlefield", p24) and on the
|
||||||
|
// machine it was free: the cloud renderable WATCHED the ReservoirState
|
||||||
|
// attribute, and a replicant's copy of that attribute moved with the
|
||||||
|
// update stream, so the peer's own renderable fired. The port
|
||||||
|
// consolidated those renderables into the psfx layer and hung the spawn
|
||||||
|
// off this handler -- which mech4 dispatches for the VIEWPOINT mech only,
|
||||||
|
// and Dispatch delivers locally, so a flush never left the flusher's
|
||||||
|
// machine (bench: scratchpad/night18/mp_flush7.sh -- A logged the cloud,
|
||||||
|
// B logged nothing).
|
||||||
|
//
|
||||||
|
// ⚠ Do NOT route this through Simulation::simulationState even though
|
||||||
|
// every subsystem record already carries it: on a MechSubsystem that cell
|
||||||
|
// is the TECH STATUS vocabulary (StateCount 7) and **state 1 is
|
||||||
|
// DestroyedState** -- btl4gaug.cpp:427 paints a full critical tint on it.
|
||||||
|
// Publishing the flush there would have drawn the reservoir as destroyed
|
||||||
|
// on every damage schematic for the duration of a flush. (Tried it; the
|
||||||
|
// [flush-tx] probe read state=0 back and sent me looking, which is how the
|
||||||
|
// collision surfaced before it shipped. Gotcha class: one cell, many
|
||||||
|
// meanings -- see reconstruction-gotchas #30.)
|
||||||
ForceUpdate(); // this+0x18 |= 1
|
ForceUpdate(); // this+0x18 |= 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// #191 -- FLUSH REPLICATION, both halves.
|
||||||
|
//
|
||||||
|
// The machine did not need these: BTL4VID.CPP built a mode-1 effect
|
||||||
|
// renderable per Reservoir on its "ReservoirState" ATTRIBUTE
|
||||||
|
// (part_014.c:10308, FUN_0041bfc0(sub,"ReservoirState") -> the alarm's own
|
||||||
|
// address) and the renderable's tick started the pfx when the watched state
|
||||||
|
// changed to 1 -- on whichever node it was built on, master or replicant.
|
||||||
|
// The port consolidated the effect renderables into the psfx layer and
|
||||||
|
// spawns from the InjectCoolant handler, which only runs on the flusher, so
|
||||||
|
// the flush state has to be shipped explicitly.
|
||||||
|
//
|
||||||
|
// Shape copied from the binary's own subsystem replication (MechWeapon
|
||||||
|
// @004b9690, Emitter @004ba65c): chain the base, set recordLength, append
|
||||||
|
// the field.
|
||||||
|
//
|
||||||
|
void
|
||||||
|
Reservoir::WriteUpdateRecord(Simulation::UpdateRecord *message, int update_model)
|
||||||
|
{
|
||||||
|
HeatSink::WriteUpdateRecord(message, update_model);
|
||||||
|
|
||||||
|
Reservoir__UpdateRecord *rec = (Reservoir__UpdateRecord *)message;
|
||||||
|
rec->recordLength = sizeof(Reservoir__UpdateRecord);
|
||||||
|
rec->flushActive = (reservoirAlarm.GetLevel() != 0) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// The receive half: turn the replicated flush state back into the cloud,
|
||||||
|
// where the attribute-watching renderable used to do it. EDGE only -- a
|
||||||
|
// long flush ships many records and the machine's watcher fired on the
|
||||||
|
// state CHANGE, not on every tick.
|
||||||
|
//
|
||||||
|
void
|
||||||
|
Reservoir::ReadUpdateRecord(Simulation::UpdateRecord *record)
|
||||||
|
{
|
||||||
|
HeatSink::ReadUpdateRecord(record);
|
||||||
|
|
||||||
|
const Reservoir__UpdateRecord *rec = (const Reservoir__UpdateRecord *)record;
|
||||||
|
if (record->recordLength < (int)sizeof(Reservoir__UpdateRecord))
|
||||||
|
return; // older/base record -- nothing to apply
|
||||||
|
|
||||||
|
const int now = rec->flushActive;
|
||||||
|
const int was = (reservoirAlarm.GetLevel() != 0) ? 1 : 0;
|
||||||
|
if (now == was)
|
||||||
|
return;
|
||||||
|
|
||||||
|
reservoirAlarm.SetLevel(now ? 1 : 0); // keep the peer's gauge/state in step
|
||||||
|
if (now)
|
||||||
|
{
|
||||||
|
extern void BTSpawnFlushCloud(void *owner_mech); // mech4.cpp (psfx 19)
|
||||||
|
BTSpawnFlushCloud(owner);
|
||||||
|
}
|
||||||
|
if (getenv("BT_FLUSH_LOG"))
|
||||||
|
DEBUG_STREAM << "[flush] PEER flush " << (now ? "STARTED -- cloud spawned" : "ended")
|
||||||
|
<< std::endl << std::flush;
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Gitea #7 -- the Reservoir handler registration (table @0x50e680: exactly
|
// Gitea #7 -- the Reservoir handler registration (table @0x50e680: exactly
|
||||||
// one entry {4, "InjectCoolant", @4aee70}). Chained onto the engine Receiver
|
// one entry {4, "InjectCoolant", @4aee70}). Chained onto the engine Receiver
|
||||||
|
|||||||
@@ -233,6 +233,33 @@
|
|||||||
static Receiver::MessageHandlerSet& GetMessageHandlers();
|
static Receiver::MessageHandlerSet& GetMessageHandlers();
|
||||||
static SharedData DefaultData;
|
static SharedData DefaultData;
|
||||||
|
|
||||||
|
// #191 -- FLUSH REPLICATION. On the machine the cloud came free: the
|
||||||
|
// mode-1 effect renderable was built per Reservoir on its
|
||||||
|
// "ReservoirState" ATTRIBUTE (BTL4VID.CPP build loop, part_014.c:10308
|
||||||
|
// -- FUN_0041bfc0(subsystem,"ReservoirState") resolves to the alarm's
|
||||||
|
// own address) and its tick started the pfx when the watched state
|
||||||
|
// changed to 1, on WHATEVER node it was built on. The port
|
||||||
|
// consolidated those renderables into the psfx layer and spawns from
|
||||||
|
// the InjectCoolant handler instead -- which only ever runs on the
|
||||||
|
// flusher -- so the state has to reach the peer explicitly.
|
||||||
|
//
|
||||||
|
// Done the way the binary does subsystem replication (MechWeapon
|
||||||
|
// @004b9690 / Emitter @004ba65c, mirrored in emitter.cpp): extend the
|
||||||
|
// record, set recordLength, append our field. Reservoir has no
|
||||||
|
// authored record of its own in the binary because it never needed
|
||||||
|
// one; this is the port's equivalent of the attribute the renderable
|
||||||
|
// used to watch.
|
||||||
|
struct Reservoir__UpdateRecord:
|
||||||
|
public Subsystem::UpdateRecord
|
||||||
|
{
|
||||||
|
int flushActive; // rec+0x18 -- reservoirAlarm level != 0
|
||||||
|
};
|
||||||
|
|
||||||
|
virtual void
|
||||||
|
WriteUpdateRecord(Simulation::UpdateRecord *message, int update_model);
|
||||||
|
virtual void
|
||||||
|
ReadUpdateRecord(Simulation::UpdateRecord *record);
|
||||||
|
|
||||||
// Attribute Support -- audio binds an AudioStateWatcher to ReservoirState;
|
// Attribute Support -- audio binds an AudioStateWatcher to ReservoirState;
|
||||||
// it resolves to reservoirAlarm (@0x1D0, a 0x54 StateIndicator-compatible
|
// it resolves to reservoirAlarm (@0x1D0, a 0x54 StateIndicator-compatible
|
||||||
// GaugeAlarm54; level@0x1e4 is the inject flag). CoolantSimulation SetLevel's
|
// GaugeAlarm54; level@0x1e4 is the inject flag). CoolantSimulation SetLevel's
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -8267,6 +8411,49 @@ void
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #183 jam-icon verify (BT_JAMTEST=<frame|key>): drive the AUTHENTIC
|
||||||
|
// jam latch -- message 2, the channel a heat-rolled jam uses
|
||||||
|
// (@004bcabc: weaponAlarm -> Jammed) -- on EVERY ballistic/missile
|
||||||
|
// weapon at once, so whichever weapon page the ENG panel shows lights
|
||||||
|
// its jam icon (the same arm-all reasoning as BT_BAYFIRE_TEST; the
|
||||||
|
// panel is one weapon-group page at a time). 'key' = F11 on demand
|
||||||
|
// (F10 is the Windows system-menu key; F9 belongs to the bay-fire rig).
|
||||||
|
{
|
||||||
|
const char *jt = getenv("BT_JAMTEST");
|
||||||
|
if (jt != 0 && *jt != '\0'
|
||||||
|
&& (Entity *)this == application->GetViewpointEntity())
|
||||||
|
{
|
||||||
|
static int s_jtFrame = 0, s_jtPrevKey = 0, s_jtDone = 0;
|
||||||
|
++s_jtFrame;
|
||||||
|
int latch = 0;
|
||||||
|
if (jt[0] == 'k')
|
||||||
|
{
|
||||||
|
const int down = (GetAsyncKeyState(VK_F11) & 0x8000) != 0;
|
||||||
|
latch = (down && !s_jtPrevKey);
|
||||||
|
s_jtPrevKey = down;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
latch = (!s_jtDone && s_jtFrame == atoi(jt));
|
||||||
|
if (latch)
|
||||||
|
{
|
||||||
|
s_jtDone = 1;
|
||||||
|
for (int s = 1; s < GetSubsystemCount(); ++s)
|
||||||
|
{
|
||||||
|
Subsystem *sub = GetSubsystem(s);
|
||||||
|
if (sub == 0) continue;
|
||||||
|
const int cid = (int)sub->GetClassID();
|
||||||
|
if (cid == 0xBCD || cid == 0xBD0) // ProjectileWeapon / MissileLauncher
|
||||||
|
{
|
||||||
|
DEBUG_STREAM << "[jamtest] message 2 (jam latch) -> "
|
||||||
|
<< sub->GetName() << "\n" << std::flush;
|
||||||
|
extern void BTWeaponForceJam(void *weapon); // projweap.cpp
|
||||||
|
BTWeaponForceJam(sub);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Gitea #7: the COOLANT FLUSH button (the coolant MFD's top-right
|
// Gitea #7: the COOLANT FLUSH button (the coolant MFD's top-right
|
||||||
// button, manual p24 -- "punch (or hold down) the coolant button to
|
// button, manual p24 -- "punch (or hold down) the coolant button to
|
||||||
// flush fresh coolant through your 'Mech's loops"). Press AND
|
// flush fresh coolant through your 'Mech's loops"). Press AND
|
||||||
|
|||||||
+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
|
||||||
|
|||||||
@@ -284,6 +284,30 @@ void *
|
|||||||
? (void *)((ProjectileWeapon *)weapon)->ammoBinLink.Resolve() : 0;
|
? (void *)((ProjectileWeapon *)weapon)->ammoBinLink.Resolve() : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #183 follow-up: the ENG-page JAM icon read `subsystem+0x364 == 5` -- the
|
||||||
|
// BINARY's weaponAlarm level cell, garbage on our compiled layout, so the jam
|
||||||
|
// icon has never lit in the port. Same complete-type-bridge cure as the two
|
||||||
|
// above (MissileLauncher derives ProjectileWeapon, so one bridge serves both
|
||||||
|
// cluster flavours -- same reasoning as BTWeaponAmmoBin).
|
||||||
|
int
|
||||||
|
BTWeaponJammed(void *weapon)
|
||||||
|
{
|
||||||
|
return (weapon != 0)
|
||||||
|
&& ((ProjectileWeapon *)weapon)->WeaponAlarmLevel()
|
||||||
|
== ProjectileWeapon::JammedState;
|
||||||
|
}
|
||||||
|
|
||||||
|
// #183 rig support (BT_JAMTEST): drive the AUTHENTIC jam latch -- message 2,
|
||||||
|
// @004bcabc, the same channel a heat-rolled jam uses -- without dragging the
|
||||||
|
// weapon headers into mech4.cpp. MissileLauncher has no HandleMessage
|
||||||
|
// override, so ProjectileWeapon's jam case serves both cluster flavours.
|
||||||
|
void
|
||||||
|
BTWeaponForceJam(void *weapon)
|
||||||
|
{
|
||||||
|
if (weapon != 0)
|
||||||
|
((ProjectileWeapon *)weapon)->HandleMessage(2);
|
||||||
|
}
|
||||||
|
|
||||||
// Eject-wipe gauge bridge (#118): the BallisticWeaponCluster's
|
// Eject-wipe gauge bridge (#118): the BallisticWeaponCluster's
|
||||||
// BitMapInverseWipeScalar watches the weapon's PercentOfEject (@0x3F8, the
|
// BitMapInverseWipeScalar watches the weapon's PercentOfEject (@0x3F8, the
|
||||||
// binary passes param_8+0x3f8 straight into the @004c61c8 ctor). Complete-
|
// binary passes param_8+0x3f8 straight into the @004c61c8 ctor). Complete-
|
||||||
|
|||||||
+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,31 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# =========================================================================
|
||||||
|
# #183 -- does the ENG-page BAY FIRE icon light?
|
||||||
|
# #47 fixed the data path and asked the field to "confirm the pixels";
|
||||||
|
# Oracle reports no icon. Force a real bay fire with BT_BAYFIRE_TEST and
|
||||||
|
# watch the whole chain:
|
||||||
|
# [bayfire-test] forced heat FAILURE ... the rig armed it
|
||||||
|
# [ammo] ... BAY FIRE: cook-off armed ... the AUTHENTIC arm fired
|
||||||
|
# [gau-fire] ... what the ENG cluster sees
|
||||||
|
# A solo expert madcat (ballistic bins) on grass.
|
||||||
|
# usage: bayfire183.sh [seconds-before-arming]
|
||||||
|
# =========================================================================
|
||||||
|
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
|
||||||
|
|
||||||
|
AT=${1:-20}
|
||||||
|
bt_expert_egg MP.EGG BF183.EGG
|
||||||
|
sed -i "s/^map=.*/map=grass/; s/^time=.*/time=day/; 0,/^vehicle=.*/s//vehicle=madcat/" BF183.EGG
|
||||||
|
LOG=bayfire183.log
|
||||||
|
rm -f "$LOG"
|
||||||
|
( export BT_BAYFIRE_TEST=$AT BT_DEATH_LOG=1 BT_BAYFIRE_LOG=1 BT_DEV_GAUGES=1
|
||||||
|
bt_launch "$LOG" BF183.EGG 0x03 )
|
||||||
|
sleep 55
|
||||||
|
bt_kill_ours
|
||||||
|
sleep 2
|
||||||
|
taskkill //F //IM btl4.exe > /dev/null 2>&1
|
||||||
|
echo "=== the chain ==="
|
||||||
|
grep -a "bayfire-test\|BAY FIRE\|EXTINGUISH\|gau-fire" "$LOG" | head -20
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
"""Snapshot the playtest tracker sheet into the repo so tester edits are a git diff.
|
||||||
|
|
||||||
|
python tools/tracker_snapshot.py # write docs/tracker/{OPEN,CLOSED}.csv
|
||||||
|
python tools/tracker_snapshot.py --diff # show what changed vs the committed snapshot
|
||||||
|
|
||||||
|
Rows are written sorted by TICKET NUMBER, not sheet order, so re-prioritising the sheet
|
||||||
|
produces no diff noise -- only real content changes show up.
|
||||||
|
|
||||||
|
CREDENTIALS ARE NEVER STORED HERE. The bridge URL + token together grant write access to a
|
||||||
|
live shared document, so they come from the environment:
|
||||||
|
|
||||||
|
set BT_TRACKER_URL=https://script.google.com/macros/s/..../exec
|
||||||
|
set BT_TRACKER_TOKEN=....
|
||||||
|
|
||||||
|
or from an untracked file `scratchpad/tracker_bridge.local` holding those two lines as
|
||||||
|
KEY=VALUE. See context/playtest-tracker.md.
|
||||||
|
"""
|
||||||
|
import csv
|
||||||
|
import io
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
OUTDIR = os.path.join(REPO, "docs", "tracker")
|
||||||
|
LOCAL = os.path.join(REPO, "scratchpad", "tracker_bridge.local")
|
||||||
|
|
||||||
|
TABS = {
|
||||||
|
"OPEN": ["#", "Priority", "Test Status", "Dev Status", "Dev Comments", "Tester Comments"],
|
||||||
|
"CLOSED": ["#", "Test Status", "Dev Status", "Dev Comments", "Tester Comments"],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def creds():
|
||||||
|
url = os.environ.get("BT_TRACKER_URL")
|
||||||
|
tok = os.environ.get("BT_TRACKER_TOKEN")
|
||||||
|
if (not url or not tok) and os.path.exists(LOCAL):
|
||||||
|
for line in io.open(LOCAL, encoding="utf-8"):
|
||||||
|
if "=" in line:
|
||||||
|
k, v = line.split("=", 1)
|
||||||
|
k, v = k.strip(), v.strip()
|
||||||
|
if k == "BT_TRACKER_URL" and not url:
|
||||||
|
url = v
|
||||||
|
if k == "BT_TRACKER_TOKEN" and not tok:
|
||||||
|
tok = v
|
||||||
|
if not url or not tok:
|
||||||
|
sys.exit("no bridge credentials -- set BT_TRACKER_URL / BT_TRACKER_TOKEN, or create "
|
||||||
|
"scratchpad/tracker_bridge.local (untracked). See context/playtest-tracker.md.")
|
||||||
|
return url, tok
|
||||||
|
|
||||||
|
|
||||||
|
def fetch(url, tok, tab, ncols):
|
||||||
|
body = json.dumps({"token": tok, "action": "get", "sheet": tab,
|
||||||
|
"range": "A1:%s300" % chr(ord('A') + ncols - 1)}).encode()
|
||||||
|
req = urllib.request.Request(url, data=body,
|
||||||
|
headers={"Content-Type": "application/json"})
|
||||||
|
with urllib.request.urlopen(req) as r:
|
||||||
|
out = json.load(r)
|
||||||
|
if not out.get("ok"):
|
||||||
|
sys.exit("bridge error: %s" % out)
|
||||||
|
rows = []
|
||||||
|
for raw in out["values"][1:]:
|
||||||
|
row = [str(x).strip() for x in raw] + [""] * ncols
|
||||||
|
if row[0]:
|
||||||
|
rows.append(row[:ncols])
|
||||||
|
rows.sort(key=lambda r: (0, int(r[0])) if r[0].isdigit() else (1, 0))
|
||||||
|
return rows
|
||||||
|
|
||||||
|
|
||||||
|
def write(tab, header, rows):
|
||||||
|
if not os.path.isdir(OUTDIR):
|
||||||
|
os.makedirs(OUTDIR)
|
||||||
|
path = os.path.join(OUTDIR, tab + ".csv")
|
||||||
|
with io.open(path, "w", encoding="utf-8", newline="") as f:
|
||||||
|
w = csv.writer(f, lineterminator="\n")
|
||||||
|
w.writerow(header)
|
||||||
|
w.writerows(rows)
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
url, tok = creds()
|
||||||
|
if "--diff" in sys.argv:
|
||||||
|
for tab, header in TABS.items():
|
||||||
|
write(tab, header, fetch(url, tok, tab, len(header)))
|
||||||
|
print(subprocess.run(["git", "diff", "--stat", "--", "docs/tracker"],
|
||||||
|
cwd=REPO, capture_output=True, text=True).stdout or "(no changes)")
|
||||||
|
print(subprocess.run(["git", "diff", "--", "docs/tracker"],
|
||||||
|
cwd=REPO, capture_output=True, text=True).stdout[:8000])
|
||||||
|
return
|
||||||
|
for tab, header in TABS.items():
|
||||||
|
rows = fetch(url, tok, tab, len(header))
|
||||||
|
print("%-7s %3d rows -> %s" % (tab, len(rows), write(tab, header, rows)))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user