Author SHA1 Message Date
Joe DiPrimaandClaude Fable 5 0edac0e566 Field-guide ROUND TWO harvested: two annotated copies (Oracle 18 notes, NEW witness NANOOK 15) staged in scratchpad/night18/fieldlogs/ (uncommitted, field-log rule) and distilled into WEAPONS_DRIFT_AUDIT.md. The tiebreaker the guide was built to catch, caught: the machine HAD missile splash (Nanook answered the explicit self-splash question YES; Oracle's systematic 957 test shows the port has NONE -- walls/ground/hills/adjacent/point-blank, only the struck mech records damage -- which also fails 957's own hug-the-cover splash claim). Filed as #200 (P2, two witnesses, pilots 9-and-1); #175 stays cover-only, linked. Also flagged on #83: Nanook's era memory says the pod's incoming-alarm tempo did NOT ramp -- 957's ramp needs checking against Oracle's reference videos before that ticket closes. Ledger carries seven open contradictions incl. missile pursuit law (Oracle: PURE pursuit, guide said LEAD -- seeker decomp re-read owed), the guide's 'no dumb-fire path' machine claim (Oracle: trigger always fired; the PORT already always-launches so this is a guide correction not a defect), the energy-weapon X-out inter-witness dispute, and Oracle's generator-voltage-gauge-never-moved note (cross-ref #189 before anyone 'fixes' a static bar). Also in the staging pull: all five 8/14 log archives incl. Lynx's DESKTOP-QR9VPJQ zip (the #189 evidence, held until their group finishes testing) and SAURON's hand-packed .rar -- consistent with him being a SENDLOGS casualty (fix already committed 45750a6, ships Sunday).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UCMpoc1toNVepJniZvXxbY
2026-08-15 13:19:46 -05:00
Joe DiPrimaandClaude Fable 5 e4f4f4a926 KB corrections after the sweep retraction: the gotcha-8 addendum now records the CORRECTED census (one proven bug, two locked-layout false positives, two safe-either-way) and the refutation test a suspected raw read must fail before replacement -- pointed-at CLASS, static_assert grep, and a receipt that it is actually broken; field testimony outranks code-reading. gauges-hud: jam icon was never broken (locked offset), both icons pixel-verified side by side. decomp-reference: BT_JAMTEST rig + jammed edge in the receipts. Tracker snapshot: #183 row -> both icons verified, 4.11.988.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UCMpoc1toNVepJniZvXxbY
2026-08-15 13:06:32 -05:00
Joe DiPrimaandClaude Fable 5 1bfc24da59 #183 CLOSED-LOOP: jam + bay-fire icons BOTH pixel-verified live; the mode-lamp false positive reverted the same hour it shipped (dev box only, never a tester build). Joe saw btejam and btefire lit side by side on the ENG weapon page -- jam left (0xa0), fire right (0x121) -- via the new BT_JAMTEST rig (F11 -> the AUTHENTIC message-2 jam latch @004bcabc on every ballistic/missile weapon; new bridge BTWeaponForceJam keeps weapon headers out of mech4.cpp; [gau-fire] receipt now carries the jammed edge too).
SWEEP CENSUS CORRECTED -- of the five raw reads suspected as databinding traps, the evidence sorts them three ways:
- PROVEN BUG (receipt): the bin link -- ResolveLink(subsystem+0x43c) walked the 1995 CONNECTION-OBJECT internals and returned NULL (bin=00000000 before, live pointers after). The fix that matters, pixel-verified.
- FALSE POSITIVES (layout was LOCKED): the jam read (+0x364 == weaponAlarm level; static_assert @0x350 + alarm-internal 0x14) and the mode lamp (StateConnection source+0x14 -- source is a GaugeAlarm54's OWN ADDRESS, modeAlarm@+0x2b8, and +0x14 is its LEVEL cell, the same internal offset reservoirAlarm shows). The jam 'fix' was accidentally equivalent (same cell by name); the mode-lamp 'fix' cast the WRONG CLASS (Simulation vs GaugeAlarm54), froze AUTO/OFF at frame 0, was field-caught by Joe within the hour and is now REVERTED to the binary's exact read with a do-not-fix-again comment.
- SEMANTICALLY SAFE EITHER WAY: fail lamp (+0x40 -> GetSimulationState()==1, right class by name) and rear tile (+0x334 -> BTWeaponIsRearFiring, type-checked bridge) -- whether or not the raw forms happened to alias correctly, the typed forms read the same cell by name on the right class and cannot regress.

THE REFUTATION TEST the sweep should have run per site, now the rule: (1) identify the pointed-at object's CLASS -- member-internal offsets into a layout-locked object (GaugeAlarm54) are binary-faithful BY CONSTRUCTION; (2) grep for a static_assert locking the offset; (3) demand a RECEIPT that the site is actually broken before replacing it -- the one proven bug had one (bin=00000000), the one that broke a working widget had none and field testimony against.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UCMpoc1toNVepJniZvXxbY
2026-08-15 13:04:08 -05:00
Joe DiPrimaandClaude Fable 5 91f3a8fc12 KB: #183 wrap -- the ENG panel one-page-at-a-time architecture, the bay-fire/jam icon anatomy, the BT_BAYFIRE rig + receipts, and the gotcha-8 recurrence note (five raw-offset reads in one gauge sweep; when you find one, sweep the file -- they travel in packs). Tracker snapshot: #183 dev-box-verified note + jam-icon ask.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UCMpoc1toNVepJniZvXxbY
2026-08-15 12:44:39 -05:00
Joe DiPrimaandClaude Fable 5 9c11ad15c3 #183 PIXEL-VERIFIED + the trap swept across the gauge layer: 4 more raw-offset reads fixed, 1 marked. Joe watched the bay-fire icon light and count down live on the ENG page (F9 rig, arm-all-bins variant -- the ENG panel shows ONE weapon group's detail page at a time via subBit mode select on the shared eng1 port, so the earlier single-bin arm was invisible unless the panel happened to be on that weapon's page; that plus the load-time clock were the two missed sightings, not code bugs). The probes that localized it: warehouse produces both icon bitmaps (BitMap::Make lazy-load, NULL = silent no-draw), digit pointers bind with correct counts, and the TwoState edge census under BT_BAYFIRE_LOG shows which widgets actually execute.
Swept the file family for the same disease (grep '*(int *)((char *)'):
- JAM icon read subsystem+0x364==5 raw -- the BINARY's weaponAlarm level cell, garbage on our layout, so the jam icon has NEVER lit in the port either. New typed bridge BTWeaponJammed (projweap.cpp; MissileLauncher derives ProjectileWeapon so one bridge serves both cluster flavours). NOT yet pixel-verified -- needs a real jam.
- HeatSinkCluster::Execute read failSubsystem+0x40==1 raw -- the ENG fail lamp (btehfail/btepfail) tracked noise. Now GetSimulationState()==1, the same Destroyed test the MFD schematic tint uses.
- The cluster dispatcher read sub+0x334 raw to pick qcircle/qcircr tile art for rear-mounted energy weapons. Now BTWeaponIsRearFiring (bridge already existed from task #68).
- StateConnection::Update copied source+0x14 raw into EVERY OneOfSeveralStates frame selector -- including the AUTO/MANUAL connect-mode lamp (btemode.pcc), which has been choosing its image frame from garbage. Now GetSimulationState() (Entity and Subsystem both derive Simulation at offset 0). NOTE for #189: the mode lamp pilots read when judging AUTO behaviour may have been lying; re-observe on this build.
- DEFERRED, now loudly marked: the coolant-loop panel's linked heat-sink digit reads hs+0x1d4 (binary Condenser::condenserNumber, word 0x75) and the port class has no such member -- needs the streamed-parse addition, not a one-liner.

F9 rig now arms EVERY bin ticking within 2 ticks of the press (per-bin cookOffArmed gates dedup), so the icon test cannot be defeated by page choice. Remaining #183 gap is DISCOVERABILITY, the unbuilt half of #47: the machine flashed the ENG button to steer the pilot to the burning weapon's page.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UCMpoc1toNVepJniZvXxbY
2026-08-15 12:42:14 -05:00
Joe DiPrimaandClaude Opus 5 86c1f54644 #183 FIXED: the ENG-page BAY FIRE icon could never light -- the gauge cluster's ammo-bin link was a RAW-OFFSET walk that resolved to NULL. Oracle reported no bay-fire icon where the jam icon shows. Everything upstream was already correct and #47-verified: the AmmoBin arms cookOffArmed on the authentic gate (heat alarm FAILURE + rounds remaining), counts a fixed 10s fuse and detonates; the widget exists (btefire.pcc, TwoState) and its coordinates are BYTE-FAITHFUL to the binary (jam 0xa0,0xb2 / fire 0x121,0xb6, part_014.c:6510-6522 -- note the machine puts them in DIFFERENT places, so the report's 'same place as the jam icon' premise is wrong). The break was BallisticWeaponCluster::Execute resolving the bin with ResolveLink(subsystem + 0x43c) -- the BINARY's layout applied to our compiled ProjectileWeapon, i.e. the databinding trap the conventions forbid. It returned NULL, so BTAmmoBinCookOffArmed(NULL) read 0 forever and the icon had no input. The typed bridge already existed: BTWeaponAmmoBin (projweap.cpp) was written in July for this exact trap when the ammo DIGITS failed to bind the same way -- these two call sites were simply missed. Both now use it; TestInstance's raw bin+0x180 round-count read replaced with a new typed BTAmmoBinRoundCount. Rig added (BT_BAYFIRE_TEST=<seconds> drives the AUTHENTIC arming condition, same hook family as BT_VALVE/BT_FLUSH_TEST) plus an edge-only [gau-fire] receipt. Measured: before, bin=00000000 cookOffArmed=0 on every cluster; after, each cluster binds a real bin and the burning rack alone reports cookOffArmed 0->1->0 across arm and detonation while its neighbours stay 0. NOTE #47's fix was correct but only 'data path rig-verified' with the pixels left to the field -- this is why that gap mattered.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 11:04:02 -05:00
Joe DiPrimaandClaude Opus 5 4457a8503c #192 closed without a fix + KB note: the camera-seat stage-op omission is latent, not a defect. Checked the history rather than assuming: the camera seat is NOT back-burnered -- built and live-verified 2026-07-18 (2-seat relay, director locks the goal, camera ship tracks and cuts between authored cameras, ranking window and callsign banner reimplemented the same day, screenshots taken) -- but nothing has exercised it since, no tester ticket mentions it, and reaching it needs a deliberate egg entry (hostType=1 + vehicle=camera). So: the missing texture-stage ops in CameraHUDDrawQuad are real in source, the rendering consequence is unknown, and the screen was screenshot-verified working when written, which is decent evidence it inherits acceptable stage state in practice. Filing it as a bug was over-reach -- a code-audit observation promoted to a defect report without establishing a symptom, on a path nobody uses. Recorded instead as a latent-fragility note beside the camera-seat KB entry, with the six-line fix named for the day someone actually sees solid blocks there. Lesson for the sweep backlog: verified-in-code is not verified-as-broken.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 10:50:15 -05:00
Joe DiPrimaandClaude Opus 5 566daf358f #191 FIXED + 2-node benched: peers can see a coolant flush again. The manual promises it (p24 'other pilots will see steam rise from your Mech on the battlefield') and on the machine it was FREE: BTL4VID.CPP built a mode-1 effect renderable per Reservoir on its ReservoirState ATTRIBUTE (part_014.c:10308 -- FUN_0041bfc0(sub,'ReservoirState') resolves to 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. The port consolidated those renderables into the psfx layer and hung the spawn off Reservoir::InjectCoolantMessageHandler, which mech4 dispatches for the VIEWPOINT mech only and delivers with a local Dispatch -- so nothing about a flush ever left the flusher's machine.
Fixed the way the binary does subsystem replication (MechWeapon::WriteUpdateRecord @004b9690, Emitter @004ba65c, already mirrored in emitter.cpp): extend the record, set recordLength, append the field; the receive half applies it on the EDGE only, so a long flush spawns ONE cloud rather than one per packet.

TRAP AVOIDED, worth recording: the obvious route was Simulation::simulationState, which every subsystem record already carries -- but on a MechSubsystem that cell is the TECH STATUS vocabulary (StateCount 7) and state 1 is DestroyedState, which btl4gaug.cpp:427 paints as a full critical tint. Publishing the flush there would have drawn the reservoir as DESTROYED on every damage schematic for the duration of a flush. Surfaced only because a [flush-tx] probe read the state back as 0 and I went looking for why.

Bench (scratchpad/night18/mp_flush7.sh, madcat/grass, A flushes at frame 900 under BT_FLUSH_TEST): before = A logs the cloud, B logs nothing; after = B spawns the cloud at A's exact position (-241.894,7,-904.136) with a clean STARTED/ended pair.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 10:44:58 -05:00
Joe DiPrimaandClaude Opus 5 e2224fc587 tracker snapshot after housekeeping: #37 (MadCat torso backwards) and #143 (smoke from multiple locations) CLOSED on field verdicts -- both unreproduced for weeks, and both plausibly resolved by other work (#148 peer torso / #174 dead-zone cascade + #125 zone graphic edge). Two Pass marks deliberately NOT actioned: #74's Pass has nothing behind it (zero comments, ticket still blocked on VexUbiquity for the 8BitDo model/mode) and #172's 'OK' sits in the same cell as tester text that still describes the delayed first flash -- the OK refers to the half already ruled authentic. Answered the two 'Clarify please' rows in tester language (#41 = forensics armed, nothing to reproduce; #174 = shoot an already-blown section, damage and score should not move). Filed Draco's heat-sheds-too-fast report as #199 -- notable because it may be #173 overshooting: that fix removed ~two-thirds of a walker's drive heat as measured phantom acceleration, and jams/bay fires are heat-driven, so 'never jams any more' is the same claim. Also: a tester entered a row with a pre-assigned number, which is how sheet and tracker drift -- new reports go in a New row and get numbered here.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 10:19:40 -05:00
Joe DiPrimaandClaude Opus 5 c21e304ae9 CLAUDE.md: surface the tracker CSV snapshots in the Quick Lookup row, so the mechanism is discoverable from the router rather than only from inside the topic file.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 09:27:56 -05:00
Joe DiPrimaandClaude Opus 5 0de086de12 tracker snapshots: commit the sheet as CSV so tester edits are a git diff (tools/tracker_snapshot.py). Oracle edited fields overnight and working out WHICH fields required diffing against a stale snapshot -- three genuine tester edits were buried under ~60 lines of my own date-stamping and status normalisation, and one of them (#187, weapon list narrowed from ballistics to MISSILES) changed what the ticket was about. Now: snapshot writes docs/tracker/{OPEN,CLOSED}.csv sorted by TICKET NUMBER rather than sheet order, so re-prioritising produces no diff noise and only real content changes show; --diff refreshes and prints the delta against the committed copy. Credentials stay out of the repo (env vars or the gitignored scratchpad/tracker_bridge.local) since the bridge URL + token together are write access to a live shared document. Workflow documented in context/playtest-tracker.md: snapshot at the end of a session and before the next, and the gap between those commits is exactly what the testers did.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 09:24:40 -05:00
Joe DiPrimaandClaude Opus 5 37e1a0cd52 #189 re-described from Lynx's full account + sweep the stale AutoConnect entry. The ticket was filed from a one-line tracker row and had the symptom WRONG: 'an overheated generator refuses to cool down at all' is not what happens -- Lynx confirms flushing coolant brings generator temp down normally. The real bug is recovery: an overheated generator drops its weapon off the bus, the generator cools fine, and a weapon left on AUTO never re-attaches; only a forced reassignment revives it. Repro recorded (Nova, both PPCs seeked to 4, coolant loops boosted, fire repeatedly, ~8 min to force the overheat), plus his observation that it appeared in VETERAN while EXPERT behaved as expected (would make it experience-gated, cross-ref #177). Three hypotheses on the ticket: his two (generators not resetting / auto-assignment not recovering) and ours -- the generator sim has NO Ready-case recompute, so a cooled generator can hold stale OUT state until some transition forces a recalculation, which is exactly why a manual reassignment revives it; that is also the standing suspect on #154's never-root-caused half. Benchable via #62's BT_POWER_DETACH_TEST hook and its [power] AutoConnect RE-ATTACHED receipt: present after a plain detach but absent after an overheat separates the two causes in one run. Also sweeps a stale KB claim found en route -- open-questions still said AutoConnect was deliberately NOT fixed, when #62 landed HasVoltage and restored both call shapes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 08:31:28 -05:00
Joe DiPrimaandClaude Opus 5 e1ffc4a991 AV smoke test: record that the Bearfoos verdict was TRANSIENT and self-corrected (#190 closed). The false positive lived in exactly one definition set -- 1.457.172.0 (8/14 19:34) deleted the 4.11.957 exe from a normal Desktop install four times; 1.457.177.0 (8/15 03:41) passes the identical binary (sha256 6081B9BA..) with zero detections -- which also explains a second tester seeing no problem at all on midnight-updated definitions. No Microsoft submission needed, no tester instructions, no risk to the next session. The smoke test STAYS (75 seconds, and this will recur -- we ship an unsigned binary rebuilt every session), but the framing now leads with the response rather than the incident: an ML antivirus verdict is a time-varying OPINION about a file, not a fact about it, so update definitions and retest before warning testers, resigning or rebuilding. Independently, PE forensics ruled out tampering: identical section layout and entropy vs a known-good build, no added imports (419 functions vs 913's 420), no new DLLs, entry point in .text, byte-identical shipped DLLs.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 08:25:26 -05:00
Joe DiPrimaandClaude Opus 5 b93f1e903f build ritual: antivirus smoke test before shipping a zip (#190). Windows Defender's ML classifier flagged btl4.exe as Trojan:Win32/Bearfoos.A!ml and DELETED it -- a false positive, and crucially PER-BINARY rather than 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, raw sockets, process enumeration, writes beside itself), so each build is a coin flip; and it turns WITHOUT a code change -- the same binary ran clean across benches all evening, then the 8/14 19:34 signature update (1.457.172.0) condemned it hours later, which is why the field was quiet: nobody had launched since. Ritual added: extract the new exe OUTSIDE any Defender exclusion, wait ~60s (deletion is asynchronous -- an immediate Test-Path passes and the file vanishes a minute later), confirm it survives, and if not, submit to Microsoft as a false positive before shipping. Notes that a rename cannot help (content is what is scored; the exe name is constant, only the zip is versioned) and that an exclusion on the repo makes the test silently pass.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 01:26:39 -05:00
Joe DiPrimaandClaude Opus 5 45750a6fcf SENDLOGS.bat: fix the two field failures + stop it lying about success. Two testers hit DIFFERENT errors, both now reproduced on a rig and fixed.
(1) ConstrainedLanguage mode (locked-down Windows, WDAC/AppLocker) blocks .NET method calls, so [Environment]::GetFolderPath('Desktop') returned nothing -> null DestinationPath -> no zip, and [math]::Round blew up on the size. Desktop is now resolved from OneDrive/USERPROFILE env vars (still honouring a redirected Desktop) and the size via -f formatting. Deeper trap found while testing: Compress-Archive ITSELF cannot run in that mode -- it is a script module that calls .NET internally -- so fixing the path alone would only have moved the failure downstream. The zip is now built with Windows' native tar.exe (immune to language mode, present since Win10 1803, verified to emit a real Explorer-readable zip), with Compress-Archive kept only as a fallback.

(2) A log still held open by a running BattleTech made Compress-Archive abort the ENTIRE zip -- one locked file, no logs at all. Files are now staged with Copy-Item first: an unreadable one is skipped and NAMED, the zip still gets made from the rest, and the tester is told to close the game and re-run to capture the missing one.

(3) The script announced 'Wrote <path>' and 'Attach the BTLOGS zip' unconditionally -- tester 2's screenshot shows it claiming '11 files, 0 MB' for a zip that was never created. Success is now gated on Test-Path, failure exits 1, and the batch prints 'Nothing was created' instead of sending people to hunt for a file that does not exist.

Also adds ABOUT.txt inside the zip (machine, pack time, install folder, exe build date/version) so a report can be tied to a BUILD -- the ambiguity that has cost us repeatedly when a Pass/Fail arrives with no build number.

Verified on four scenarios: normal run, a log locked by another process, a session downgraded to ConstrainedLanguage (reproducing tester 1's exact MethodInvocationNotSupportedInConstrainedLanguage error first), and running from a folder with no content dir. Handout updated: close the game first, and what SKIPPED means.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 01:10:18 -05:00
Joe DiPrimaandClaude Opus 5 277337c3a5 tracker: date undated tester entries from gitea evidence, and record the self-reference trap. 12 entries pinned to real dates by finding where their text is quoted in a ticket body or an earlier comment -- if a report is quoted in a body, it existed by that ticket's creation date, which is how #170's three crash lines pin to 2026-08-12 (its body says FIELD 2026-08-12 and lists those exact times, settling that they are the ORIGINAL occurrences and not a post-fix recurrence). 27 entries that nothing in gitea pins stay marked (undated) rather than guessed. THE TRAP, hit once during this pass: dating an entry from a comment written while harvesting the sheet -- those quote the tester text verbatim, so a naive search dates the report to your own housekeeping instead of the event (a #170 line briefly carried the harvest date). Exclude same-day comments and take the earliest match.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 00:50:56 -05:00
Joe DiPrimaandClaude Opus 5 a4986faffc tracker: date every tester entry, and mark unknown-provenance ones (undated) rather than guessing. 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 the convention are marked (undated) = provenance unknown, not fresh evidence: 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. That is exactly the error that produced the false 'still crashing after the fix' claim on #170 -- its row carries the same three timestamps and the same players as its own 2026-08-12 body, and whether that is a copy-forward or a genuine recurrence is STILL UNRESOLVED. 41 legacy entries relabelled (undated); 17 tester-dated entries left intact; header hover notes on both Tester Comments columns teach the convention in the sheet itself.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-15 00:17:18 -05:00
Joe DiPrimaandClaude Opus 5 dba09e44b9 tracker: add the FAILED RETEST status + the explicit post-playtest sweep. A tester Fail on a shipped fix was collapsing into QUEUED, which reads as 'never started' and understates a regression -- it now gets its own red status. Sweep rules made explicit per Test Status on every PATCHED-RETEST row: Pass -> close + move to CLOSED; Fail with detail -> FAILED RETEST + reopen + raise to >=P2 + post the evidence; Fail without detail -> NEED INFO with ONE discriminating question; blank -> stays yellow and rolls forward. Also records the ambiguity underneath all of it: a Pass or Fail carrying no BUILD NUMBER is uninterpretable (a Pass on an older build cannot verify a later fix), which is exactly why #27 and #38 sit at NEED INFO asking for the boot-screen build instead of going straight to red -- so every PATCHED-RETEST comment should stamp the build the fix shipped in. Sheet hover legends on the Test Status and Dev Status headers rewritten to teach both the vocabulary and the sweep to testers directly.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 23:42:44 -05:00
Joe DiPrimaandClaude Opus 5 74534cec9e KB: the playtest tracker sheet is now a first-class topic (context/playtest-tracker.md) -- the testers moved issue tracking off the .txt handout onto a shared Google Sheet on 2026-08-14, making it the primary feedback channel with gitea still the system of record. Documents the two-tab shape (OPEN priority-ordered / CLOSED), the dev-status vocabulary with the colour EMBEDDED IN THE LABEL, the priority tiers, and the Dev-Comments contract (what is wrong / STATUS / NEED FROM YOU, written for a player in under ~55 words with a concrete ask). Captures the working rules that were learned the hard way this session: read the gitea COMMENT THREAD never the body (re-triaging 53 tickets from threads moved a third of the statuses and dropped P1 from 5 to 2); undated tester rows are HISTORICAL and reading them as fresh produced a false 'still crashing after the fix' claim on #170 that had to be retracted across seven tickets; never write DONE on the OPEN tab; merge duplicates to one row per ticket; never use cell fills for status (fills live on cells, labels on text -- every re-sort desyncs them, re-fixed three times before embedding); a leftover FILTER hides rows after a rewrite (skipping row numbers in the gutter = hidden rows, not lost data); pin row heights or autofit makes wrapped rows unreadably tall. Access documented both ways: read via the public CSV export (the Claude Drive connector CANNOT see it -- scoped to files it created, and it has no cell-write capability at all, so sharing does not help), write via an Apps Script bridge whose URL+token stay in local operator memory and NEVER in this repo. Quick Lookup row added; handout demoted to secondary; operator-console cross-linked. checkctx CLEAN.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 23:38:29 -05:00
Joe DiPrimaandClaude Opus 5 6dc86792ad #160 CLOSED -- the cab's RIO axis dropouts + frozen boots were the FTDI adapter's BUFFER SIZE set too high (Nick, on the cab). An oversized receive buffer batches incoming serial instead of delivering it promptly: analog samples arrive late and in clumps (the field 'encoder dropouts') and the boot handshake can miss its window entirely (the wedge). NO port code was ever at fault -- every software hypothesis in the pod-hardware RIO section (power-rail sag under mission load, board-side wedge, print deadlock) is superseded; what survives from those notes is the diagnostic procedure, not the theories. Recorded in context/pod-hardware.md 9a with the setting path (Device Manager -> COM port -> Port Settings -> Advanced -> lower Receive (Bytes); Latency Timer flagged [T4] as the adjacent standard knob, not confirmed part of the fix) and as an operator-facing troubleshooting row in docs/OPERATOR_GUIDE.md, because a fresh FTDI install on any future cab build comes up with defaults that reproduce it. Ledger lesson: a fault that reproduces on ONE machine and survives every code change is a DRIVER/CONFIG suspect before it is a code suspect.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 23:29:53 -05:00
Joe DiPrimaandClaude Fable 5 91351b94bc #125 FIXED + 2-node benched: peers now see a killed arm WRECK + its gun pod DROP OFF on the FIRST hit. Root pair: (1) the MechDeathHandler Tick transcription carried an INVENTED write -- ApplyDamageGraphicState(descriptor->graphicState) -- where the raw @0042aa2c (part_003.c:10963-93) only PICKS an effect resource (level flag & 4 -> DescriptorForLevel; gstate flag & 8 -> DescriptorForGraphicState(the zone's CURRENT gstate), overwriting the level pick); with the port's pick hardcoded to Destroyed(1) the invention STOMPED every destruction-cascade child's authored Gone(2) back to 1 in the same tick, master-side, BEFORE the record shipped -- the wire itself carried the corruption, so no peer could ever render the pod dropping (receipts: legacy A remade jointrgun to gstate 2 then re-remade it to 1; legacy B received 'dz_rgun gstate 0->1'). Gotcha 36: an added write is worse than a stub -- it actively fights the authentic writers on every node. (2) the destroyed-mesh swap trigger was descriptor-gated (DescriptorForLevel returns NULL past the last threshold; cascade children have gstate edges with NO level edge) -- the binary needed no trigger at all (its renderer re-picked every segment's mesh from zone gstate PER FRAME); the port's built-once render tree now gets the event equivalent: per-zone gstate caches in Tick fire ONE coalesced RemakeEntityRenderables on any edge, which is also the PEER's only trigger for replicated gstate-only beats. Restored authored two-stage story: a directly-killed gun zone hangs its wrecked variant mesh (thrdrgun-family, exterior skeleton; every zone's descriptor table carries lvl=-1 gs=1/gs=2 terminals -- unreachable by level, existing exactly for the gstate picks); a killed ARM swaps to its wrecked variant AND the pod segment vanishes. Effects suppressed on ->Exists edges (the binary's heal writes the cell raw, no flag -> no respawn puff; regression benched 0 heal puffs across a full death/respawn cycle). 2-node benched both ways (night18/mp_zone125.sh + zonedump.sh): fix = peer receives dz_rarm gstate 0->1 + dz_rgun 0->2 on ONE beat, swaps thrdrarm.bgf and HIDES the pod, first-beat remake receipt; legacy = the stomp conviction above. KB: combat-damage.md MechDeathHandler section rewritten to the corrected reading; gotcha 36.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 18:45:12 -05:00
Joe DiPrimaandClaude Fable 5 f597f76a30 #102 FIXED + benched: the Owens run->reverse 'hang' was the port missing the machine's whole HARD-BRAKE system -- master perf @0x4aa158-0x4aa365 (raw part_013.c:3353-95): a forward gait meeting a negative demand latches +0x3f4 (the recon's 'airborneSelect' -- another jump-jet-family misread: it is the BRAKE flag) and swaps the gait slew to the model's SuperStopAcceleration (+0x5bc, ex-'airborneCycleRate'), kicks the gyro with a random-sign Z lurch, judders the cab 0.2 every 0.4s while braking above walk speed (gyroRumbleTimer@0x5c4), ships the flag on the type-8 record, restores the ground rate on release. Content: heavies brake harder than they drive (10->20); the sprinters brake WEAKER -- owens 50->25, blkhawk 30->10 -- the Owens' long coast into reverse is AUTHORED, and the pod communicated it through the seat; the un-ported port coasted with zero feedback = the felt hang. Ported to the mech4 drive block ([brake] receipts under BT_GAIT_TRACE) + the replicant rate select restored in the peer motion path (the binary's IntegrateMotion:298 line -- dead TU in the port, Simulate bypassed). Plus gotcha 35: the binary's SM switch re-reads the state cell after the wind-down-gate/death-latch preamble (one cell: @0x3b0/@0x728 == the alarm level); the recon's split alarm/int pair dispatched the STALE pre-gate state for a frame -- re-synced, both channels. The 'leg in the air until you twist' statue is the MACHINE's own trn turn-stop MID-PIVOT park, authentically INVISIBLE on the pod (no external self-view existed; the chase cam is a port addition) -- bench-reproduced at trn legFrm=10 + twist rescue, documented not-a-bug (Owens reads worst: deepest mid-swing of the 8 chassis, thigh -43deg vs madcat -27). Reverse-input truth [T1]: reverse is the red button 0x3F/LALT held with the throttle OPEN (manual p7); demand = -runSpeed*throttle (NO fwdScale term in reverse); the throttle channel is [0,1] like the pod lever, so brake keys can NEVER produce reverse -> handout education line. Bench rig: BT_KEYSIM/_REV/_TURN drive the REAL lever+detent+SM chain headlessly (scratchpad/night18/gaithang_bench.sh). Benched (owens/grass): LALT flip at full run -> [brake] ENGAGE rate=25 (ground 50) -> 12->15->6->9->0->16->18<->19 back cycles at the authored -14.1 cadence -> clean recovery on release; held brake key -> authored stand park, ZERO brake engagements (authentic: no button, no brake). Side-find filed: hardware-RIO cab reverse is DEAD (AddOrErase stubs; open-questions row). 674 forensic: that build boots GLASS by default -> reverse was exclusively LALT in the field.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 17:55:08 -05:00
Joe DiPrimaandClaude Fable 5 b67cf72f38 radar audit ledger: docs/RADAR_MAP_AUDIT.md distills the 3-agent audit (architecture, authenticity verdicts, the #49 root+fix, deliberate port deviations, stale-note sweep, follow-ups incl. the arena-edge era question and the 45deg-wedge-vs-manual-60 flag); gauges-hud gains the pointer section; the mech.cpp 'SetTargetRange is stubbed' stale comment swept (live since the zoom work).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bw71WVsccjwW7uKRg4aWD
2026-08-14 16:23:50 -05:00
Joe DiPrimaandClaude Fable 5 c2193e2e80 #49 FIXED + OPERATOR-VERIFIED: the radar draws the static world again -- walls, buttes, hills, ridges, buildings, trees. Root cause (radar/map drift audit, 3-agent, receipts in scratchpad/night17/radar_audit_*.txt): the original filed every pip-bearing Terrain-GRAPH entity into the gauge staticEntities grid via the interest feed (@004cbfa0: pip gate, Mover->moving / Terrain->static; the derivation graph parents UnscalableTerrain 0x2A + CulturalIcon 0x5E + Landmark 0x56 onto Terrain [T0 TERRAIN.cpp:183]), and MapDisplay::DrawStatic (@004c25b4) drew each entity's AUTHORED vector-outline pip (110 type-0x12 GIMs ship: wall1-3, aw01-05 arena walls, buttea-e, hillg/dhillg, sr1e/n/s/w polar ridges, trees, buildings) world-rotated and metre-scaled. The port's RebuildEntityGrid (the shim replacing the dropped interest feed) iterated DYNAMIC entities only -- the 1008 statically-registered UnscalableTerrain instances never reached the grid, so the scope showed no world at all. Fix: a third pass files non-dynamic Terrain-graph entities from HostManager::AllEntityIterator; pipless entities (floors, sky, the wall10k outer fence -- authentically invisible on the shipped radar too) no-op at the pip lookup exactly as on the machine. Receipts: [map-grid] worldStatics census + a capped per-static [map] dump (cls/rid/pip/dsq) symmetric to DrawMoving's. Benched: grass files cls=42 hill rid=2190 pip=1 drawing at 466m beside 313 trees; arena1 files 126 world statics with the aw wall sections (rid 1918/1915) pip=1 drawing and the unpipped walkway/banner props correctly skipped (authored omissions preserved). Operator-verified live on arena1: 'muuuuch better'.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bw71WVsccjwW7uKRg4aWD
2026-08-14 16:21:44 -05:00
Joe DiPrimaandClaude Fable 5 7b641f9b40 KB: gotcha 34 -- change detection against a shared multi-writer scratch (the #155 previousModeMask class): a change-detector must own its own baseline; audit every manager-scratch read that an authentic high-frequency toggler can starve. Ticket #155 comment 1858 carries the fix + live verification.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bw71WVsccjwW7uKRg4aWD
2026-08-14 14:56:23 -05:00
Joe DiPrimaandClaude Fable 5 caa50ef0f6 #155 FIXED + LIVE-VERIFIED: the dead eng/weapon MFD banks under a lit PANIC lamp were the gauge renderer's change-detection XOR reading the mode MANAGER's previousModeMask -- a shared scratch overwritten by EVERY Add/RemoveModeMask call. The authentic panic-arm chatter (the no-hysteresis eject evaluator, kept for strict fidelity 2026-08-03) writes that scratch per frame, stomping the SetPresetMode page bits out of the XOR before ExecuteForeground samples it: page EXITS died (reactivation requires the change bit; first entries still worked via !alreadyActivatedFlag), always-active gauges (radar/score/coolant) stayed live -- Oracle's exact bank split. Fix: one line -- sample the renderer's OWN previousModeMask member (already ctor-init'd and maintained at the activate/deactivate guard; the manager read looks like lineage drift), making the change mask accumulate everything since the last RENDER frame, correct under any number of mode writers; single-transition frames identical. Diagnosis chain: authenticity cleared in three legs (121 streamed rows zero eject-gated, MechRIOMapper 0x200000 = PANIC+lamp only, engine dispatch = AND); headless injection exonerated the mapping layer under steady arm AND 2063-edge chatter; the OPERATOR's live mouse session produced the receipts that convicted presentation ([mode] preset fired for the stuck exits while the screen held); fix verified live same session -- 8 transitions incl. 4 exits across all 3 MFDs under 2833 flap edges, all rendered. The PANIC flicker itself stays: authentic (incandescent filament was the pod's low-pass; a glass persistence filter remains a design call). Rig hooks kept: BT_FORCE_EJECT_ARM=1/2.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bw71WVsccjwW7uKRg4aWD
2026-08-14 14:55:30 -05:00
Joe DiPrimaandClaude Fable 5 97bdb5cc0c #155 rig: BT_FORCE_EJECT_ARM hook (=1 steady / =2 chatter at the evaluator's rate) -- pins the panic-arm state with zero damage side-effects. Repro results so far, all NEGATIVE through the PadRIO screen-button injection path (BT_BTNTEST): bank presses deliver and pages flip with ModeEject steady ON (mask 0x650430 observed), and with the arm CHATTERING (2063 edges/run, two presses on two MFDs both delivered). Combined with the authenticity verdict (no eject gating anywhere in content/binary/engine), the dead-bank defect lives ABOVE the mapping layer: prime suspects are the glass window's mouse hit-test / panel rebuild under per-frame mode churn, and the HOTAS CONTROLS.MAP route. Next step needs a human at the desktop: BT_FORCE_EJECT_ARM=2 + mouse clicks on the eng/weapon MFD buttons (and the HOTAS bindings), watching [mode] preset receipts -- ten minutes, fully scripted otherwise.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bw71WVsccjwW7uKRg4aWD
2026-08-14 14:29:25 -05:00
Joe DiPrimaandClaude Fable 5 ccfa875b65 handout correction (#151): my 'expect ~zero on eject' line RETRACTED -- the ticket's own 2026-08-10 bench decode (unread in yesterday's body-only triage, the exact failure the new read-ticket-threads memory records) proves -499 is the machine's arithmetic: suicide (-CalcKillScore ~999) + death cost (-500) from 1000, three ejects benched, never doubled, #162 uninvolved. Entry rewritten as the correction + the restored design call (authentic ~-1500 swing vs house-rule); counts 16 verify / 3 design calls. Tracker #151 comment 1848 carries the retraction; #104 comment 1850 holds the NARC rename (the 360dps receipts say the label may be truthful about the round profile).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bw71WVsccjwW7uKRg4aWD
2026-08-14 13:39:23 -05:00
41 changed files with 2155 additions and 108 deletions
+3
View File
@@ -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
+2 -1
View File
@@ -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` |
+22
View File
@@ -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:
+27 -8
View File
@@ -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
+7
View File
@@ -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 |
+37
View File
@@ -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
View File
@@ -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
`0x4a9b5c0x4ab188` (functions_index.tsv jumps FUN_004a9770→FUN_004ab188; the SOLE reader of `0x4a9b5c0x4ab188` (functions_index.tsv jumps FUN_004a9770→FUN_004ab188; the SOLE reader of
+24 -2
View File
@@ -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
+2 -1
View File
@@ -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`
+163
View File
@@ -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)
+14
View File
@@ -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)
+95
View File
@@ -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.
+17 -11
View File
@@ -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
+1
View File
@@ -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
+110
View File
@@ -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.
+52
View File
@@ -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.
+141
View File
@@ -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.
1 # Test Status Dev Status Dev Comments Tester Comments
2 18 Pass ✅ DONE Engineering aux buttons crashed the game (glass windows) TEST: click every Engineering panel button, incl. the aux row. No crash.
3 20 Pass ✅ DONE Auto cooling BALANCE did nothing TEST: ADV control mode, press BALANCE -- coolant redistributes.
4 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
5 22 Pass ✅ DONE #22 Ammo + destroyed weapons did not reset on respawn TEST: die with empty bins / dead weapons -> respawn full and repaired.
6 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.
7 25 Pass ✅ DONE Gamepad turn profile (stick X could not turn the mech) TEST: RajelAran -- bind an axis to "Turn" in content\bindings.txt.
8 32 Pass ✅ DONE Audio cutting in/out late in the match TEST: long drop -- audio should stay clean to the end.
9 35 Pass ✅ DONE Owens crashed when firing lasers (Conn Man x2, long ago) TEST: Conn Man -- Owens, spam lasers. Believed fixed for a while.
10 36 Pass ✅ DONE Turning continued after releasing the pad/rudder axis TEST: RajelAran -- release/center the turn axis, turning stops.
11 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
12 39 Pass ✅ DONE Controls were live before the game screen appeared TEST: mash fire during load -- nothing happens until the scene shows.
13 40 Pass ✅ DONE Waiting/join screen burned 45-50% GPU TEST: GPU near idle while waiting in the join screen.
14 43 Pass ✅ DONE Comm display: pilot list with KILLS/DEATHS TEST: all pilots listed, kills/deaths update through the round.
15 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)
16 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)
17 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.
18 51 Pass ✅ DONE Coolant flush sound looped forever TEST: flush coolant -- the sound ends when the flush ends.
19 53 Pass ✅ DONE Generators could not be switched OFF TEST: ENG page -- toggle each generator off and back on.
20 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.
21 56 Pass ✅ DONE MFD buttons drift away from where they are drawn TEST: alt-tab / focus changes mid-round -- buttons stay aligned.
22 58 Pass ✅ DONE Reticle callsign plate drew as a solid rectangle TEST: target names under the reticle render as text on every map.
23 59 Pass ✅ DONE First death permanently froze parts of that player's simulation TEST: nothing specific -- covered by normal play after dying.
24 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.)
25 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.
26 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
27 70 Pass ✅ DONE (Draco) torso twist lost after a death/respawn TEST: die, respawn, twist. Fixed a while ago but never field-confirmed.
28 71 Pass ✅ DONE Closing the Plasma window lost it until restart TEST: X the plasma window -- it minimizes to the taskbar; click restores.
29 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.
30 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
31 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
32 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
33 107 Queued ✅ DONE Destroyed HeatSink annunciates nothing (no lamp for condition 0)
34 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
35 112 Queued ✅ DONE AFC rounds shotgun across zones (should land as one projectile)
36 114 Pass ✅ DONE #114 Missile impact smoke was a wall TEST: LRM exchanges -- smoke is present but no longer a smoke screen.
37 119 Pass ✅ DONE Coolant-warning voice "clipping" at empty reservoir = authentic (every leak-status edge restarts the phrase; the pod did this too).
38 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
39 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.
40 127 Pass ✅ DONE Weapon MFD EJECT graphic -- need a reference image/description. (undated) Working as expected
41 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.
42 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
43 131 Pass ✅ DONE #131 Reticle showed LOCK off-target / no-reg on target TEST: stationary targets -- lock ring matches where the reticle is.
44 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
45 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
46 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.
47 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.
48 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
49 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
50 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.
51 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).
52 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
53 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
54 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
55 153 Queued ✅ DONE Basic-mode elevation limit only applies after cycling modes (fix pending)
56 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.
57 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).
58 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
59 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.
60 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.
61 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
62 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
63 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)
64 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
65 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
66 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
67 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.
68 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
69 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
70 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.
71 AUDIT Pass ✅ DONE Armor audit � crouched and standing on all chassis with energy weapons. (undated) Completed by testers; no defects found.
+180
View File
@@ -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.",
1 # Priority Test Status Dev Status Dev Comments Tester Comments
2 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.
3 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.
4 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
5 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.
6 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.
7 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.
8 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.
9 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
10 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.
11 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.
12 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.
13 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.
14 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.
15 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.
16 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?
17 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
18 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.
19 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.
20 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
21 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.
22 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.
23 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.
24 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.
25 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.
26 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.
27 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.
28 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.
29 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.
30 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.
31 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.
32 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.
33 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.
34 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.
35 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.
36 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
37 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
38 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
39 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
40 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.
41 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
42 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.
43 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.
44 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.
45 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
46 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.
47 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)
48 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
49 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.
50 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
51 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.
52 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.
53 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.
54 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.
55 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.
56 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.
57 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.
58 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.
59 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.
60 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.
61 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
62 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.
+55 -1
View File
@@ -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
//------------------------------------------------------------------- //-------------------------------------------------------------------
+41
View File
@@ -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
//########################################################################### //###########################################################################
+64
View File
@@ -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);
+78 -6
View File
@@ -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");
+2
View File
@@ -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
+12 -1
View File
@@ -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
+14
View File
@@ -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;
+77
View File
@@ -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
+27
View File
@@ -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
+26 -3
View File
@@ -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
+19 -1
View File
@@ -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
+187
View File
@@ -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
View File
@@ -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);
} }
//############################################################################# //#############################################################################
+11
View File
@@ -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
+24
View File
@@ -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
View File
@@ -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
+82
View File
@@ -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'.
+31
View File
@@ -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
+33
View File
@@ -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
+80
View File
@@ -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
+42
View File
@@ -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
+27
View File
@@ -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
+99
View File
@@ -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()