"""Night-6 tracker batch: reopen #35 with the root cause, comment on #62/#70, file the six new reports. Idempotent-ish: refuses to re-post a comment whose first line already exists on the issue, and skips creating an issue whose exact title already exists.""" import base64 import json import subprocess import urllib.request REPO = r"C:\git\bt411" BASE = "https://gitea.mysticmachines.com/api/v1/repos/VWE/BT411" out = subprocess.run(["git", "credential", "fill"], input="protocol=https\nhost=gitea.mysticmachines.com\n\n", capture_output=True, text=True, cwd=REPO) cred = dict(l.split("=", 1) for l in out.stdout.strip().splitlines() if "=" in l) AUTH = "Basic " + base64.b64encode( (cred["username"] + ":" + cred["password"]).encode()).decode() def call(method, path, payload=None): data = json.dumps(payload).encode() if payload is not None else None r = urllib.request.Request(BASE + path, data=data, method=method) r.add_header("Authorization", AUTH) r.add_header("Content-Type", "application/json") return json.load(urllib.request.urlopen(r)) def existing_titles(): seen = {} for state in ("open", "closed"): page = 1 while True: b = call("GET", "/issues?state=%s&limit=50&page=%d" % (state, page)) if not b: break for i in b: seen[i["title"].strip()] = i["number"] page += 1 return seen def comment(n, body): marker = body.strip().splitlines()[0][:60] for c in call("GET", "/issues/%d/comments" % n): if marker in c.get("body", ""): print(" #%d already has this comment - skipped" % n) return call("POST", "/issues/%d/comments" % n, {"body": body}) print(" commented on #%d" % n) def newissue(titles, title, body): if title.strip() in titles: print(" SKIP (exists as #%d): %s" % (titles[title.strip()], title[:60])) return j = call("POST", "/issues", {"title": title, "body": body}) print(" created #%d %s" % (j["number"], title[:66])) C35 = """**ROOT-CAUSED 2026-07-29** - no longer blocked on logs. Conn Man's log arrived (night 6, `message (2).txt`, machine MS-FIREFLY/santo). He was the only pilot who crashed, always in an Owens; Elengil, SAURON and RajelAran all flew Owens the same night and never reproduced it. **8 crashes, all byte-identical stacks** - deterministic, not memory corruption. Symbolized against the archived `BT411_4.11.621.map`: ``` addr=0x1c3101 (btl4+0xd3101) code=0xc0000005 access=0 target=0x0 <- READ from NULL btl4+0xd3101 ParticleEngine::Destroy() +0x11 btl4+0x9c263 RendererManager::Execute(Time,Time,Time) +0xd3 btl4+0x94f30 Application::ExecuteForeground(Time,float) +0xe0 btl4+0x68710 ApplicationManager::RunMissions() +0xf0 btl4+0xa579 _WinMain@16 +0x2af9 ``` `engine/MUNGA_L4/L4PARTICLES.cpp:252` has no guards and never nulls after release: ```cpp void ParticleEngine::Destroy() { mVertBuffer->Release(); // +0x11 = the faulting instruction mParticleTexture->Release(); } ``` Both statics initialise to NULL (`L4PARTICLES.cpp:6,9`). `Destroy()` is called ONLY from the D3D9 **lost-device reset** path (`L4VIDEO.cpp:8927`): `Destroy() -> mDevice->Reset() -> Initialize()`. `Initialize()` ignores every return code, so a failed `CreateVertexBuffer` / `D3DXCreateTextureFromFile` leaves a NULL that the next reset dereferences. **This is not a weapons bug.** Nothing in the stack touches weapons, missiles or the Owens - it is the plain per-frame render path. The hardware is the differentiator: Conn Man is on a **Surface Pro 9, Intel Iris Xe, 128 MB shared**. The Owens + multi-trigger + missiles combination is how he provokes a GPU timeout or resource exhaustion -> device lost -> reset -> null deref. The current title ("firing lasers") is misleading: missiles are actually mandatory, and lasers alone never crash. **Conn Man's test matrix (2026-07-29):** CRASHED: - lasers T1 + missiles T4 - lasers T1 + missiles T3 - single ER Medium T2 + single SRM6 T3 - both stock and custom trigger assignments Did NOT crash: - lasers only - missiles only - all lasers on a single trigger - all missiles on a single trigger - full alpha strike on trigger 1 No generator, seek-level or coolant changes were involved in any crash. Still untested: two lasers on two triggers; two missiles on two triggers; keyboard vs joystick firing. **UNRESOLVED CONTRADICTION - do not skip.** A full alpha strike on ONE trigger (every weapon at once) did NOT crash, while two triggers with fewer weapons did. Pure particle volume does not explain that. Either the trigger is timing (two fire events in the same frame) rather than particle count, or the two-trigger path re-enters the reset. Worth settling before accepting the volume story. Likely fix shape (not yet applied): null-guard `Destroy()`, null the pointers after `Release()`, and check the `Initialize()` return codes.""" C62 = """Night 6 (2026-07-28), partial signal only - **not** closing on this. SAURON reports the engineering page now shows a real third generator state: "engineering display panels now have the AUTO option for gens, before was just seeing Gen # and off, now have third display rotating AUTO". That confirms the **mode cycle** presents AUTO. It does **not** confirm the claim in this issue, which is that AutoConnect actually re-*attaches* a detached subsystem. Nobody detached a subsystem and watched it recover during the session. Leaving open; needs someone to lose a generator mid-match (or use `BT_POWER_DETACH_TEST=`) and confirm the part comes back.""" C70 = """Night 6 (2026-07-28): **fix landed and bench-verified, but got NO live exercise** - staying open per the house rule. Fixed in `b70654d` (+ `819772f`). The root cause was not the torso at all: the Myomers Performance registered the inner drive-heat integrator (`@004b8d18`) instead of the wrapper (`@004b8b9c`) that chains `PoweredSubsystemSimulation`. With the electrical state machine never running, a Myomers that lost power during the death/reset window sat at NoVoltage forever - and the Torso is a PowerWatcher that MIRRORS the watched subsystem's electrical level, so `torso.cpp:570` held `effectiveTwistRate` at 0. Bench result, self-damage across two full death/respawn cycles: torso **96/96 samples at elec=4, zero dips** (before the fix: 2 dead windows per respawn). Nobody exercised torso twist after a respawn during night 6, so there is still no field confirmation. Please test it explicitly next session.""" N1T = "Damage is credited to the WRONG ZONE -- aimed at right arm, reported elsewhere" N1 = """Reported night 6 (2026-07-28) by SAURON, Conn Man and Elengil together, with screenshots. **Conditions were controlled:** all mechs **stationary**, short range, Elengil firing **only** at Conn Man's left arm. Damage showed up on other locations of Conn Man's paper doll. Conn Man: "Critical damage display did not show any crits." Paper-doll orientation is ruled out - Cyd confirmed in-session that the doll is mirrored (screen-left = the mech's own left arm) and the testers accounted for it. **VGL Lynx's theory: LOD.** Mech models swap level-of-detail with range, and the hit test may be running against a different LOD than the one being drawn and aimed at. His suggested retest: small lasers at point-blank to remove range as a variable. Touches the cylinder hit-location table (STEP 6, `context/combat-damage.md`). Check against **#16** (shots land LOW, boresight parallax) before working this. #16 is about where the shot *lands*; this is about which zone gets *credited*. Probably distinct, possibly the same root cause.""" N2T = "8BitDo gamepad works in solo but is dead in Steam multiplayer" N2 = """Reported night 6 (2026-07-28) by VexUbiquity: "my 8bitdo gamepad works (somewhat) in solo, but steamplay is a no go." Distinct from **#25** (stick-X turn-mode profile), which is a tuning request - this is the controller not functioning at all in the Steam path while working in solo. Needs from the reporter: exact 8BitDo model, connection mode (XInput / DInput / Switch), and whether Steam Input is applying a controller layout on top. The "somewhat" in solo also wants pinning down - which inputs work there and which do not.""" N3T = "Myomer damage does not affect mech speed -- half-wired (display only)" N3 = """Night 6 testers spent a large part of the session trying to observe damage-induced slowdown that cannot currently happen. The release note for 4.11.621 overstated this: the fix reaches the display, not locomotion. **What works now** (`819772f`): `Myomers::DamageStructureLevel()` was a hardcoded `return 0.0f` stand-in, so accumulated damage was invisible to the whole subsystem. It now reads the real zone damage, which flows into `AvailableOutput()` - and `SeekVoltageGraph::Execute()` samples that every frame and replots, so **the ENG-page myomer power curve now drops as the myomers take damage**. That part is live and verified. **What does not work:** `speedEffect@0x31C` is computed correctly (measured `dmg=0.6 -> speed=0.4`, a clean `1 - damage`), but `Myomers::MoverAttach()` is still a **no-op stub**, so nothing consumes it for velocity. Nothing outside the Myomers class reads `speedEffect` at runtime. **Blocked on the WAVE 6 mover cutover.** The real `MoverAttach` feeds the Mech's JointedMover roster (`**(Mech[0xD0]+0x128)`, mover vtable +0x38). Per `context/subsystems.md` WAVE 6 this must be reconciled with the gait cutover FIRST, since both would drive the same mover input. Field confirmation (Elengil, night 6): "Nova lost right arm, took damage to pretty much all other locations. Never saw any decrease in max speed of mech." Testing was done with all players on Experience Expert and ADV Damage on.""" N4T = "Glass panels: main cockpit and Plasma window positions are not restored by BT_GLASS_LAYOUT" N4 = """Reported night 6 (2026-07-28) by SAURON after enabling the new sticky layout. Both minor - he described them as "not a problem" - but they are inconsistent with the feature. With `BT_GLASS_PANELS=1` + `BT_GLASS_LAYOUT=save`, the 7 MFD windows save and restore correctly. Two windows do not: 1. The **cockpit main screen** reverts to the upper-left instead of the full-screen windowed position it was left in. 2. The **Plasma window** reverts to the main monitor. Both are one click to fix each launch, so low priority - but the layout feature otherwise works, which is what makes these stand out. For Cyd.""" N5T = "Secondary/Radar glass window is fixed-size -- make it resizable (enables touch-panel cockpits)" N5 = """Enhancement, from a genuinely useful experiment by SAURON on night 6 (2026-07-28). He ran an **iPad mini as a 4th monitor over USB** (via the free "Splashtop Wired XDisplay Agent"), 1600x1200, touch working, and fit 4 of the MFD windows on it - dropping to 800x600 for larger MFDs. His conclusion: "I'm calling this iPad mini a success." The blocker: "I tried with secondary/radar screen in both landscape and portrait mode - if it could be made to be a full screen window instead of locked size, it would work better." Worth taking seriously beyond one tester's convenience: cheap tablets and touch panels as MFD surfaces is a plausible path to a low-cost cockpit, and the Secondary/Radar window's fixed size is what currently prevents it. He is testing an old Surface Pro next.""" N6T = "RESEARCH: does Tesla 4.10 have a legged/limping mech mode?" N6 = """Open question raised night 6 (2026-07-28) - two knowledgeable people disagree, so settle it from the binary rather than from argument. **VGL Lynx asserts** a legged mech should limp: roughly 50% speed, reverse disabled, and an audio cue ("leg damaged"), with a distinct cockpit-bounce rhythm. Cited video evidence: - limping Owens at 3:41 - https://youtu.be/7W57JAzPM-M - a legged Loki at 4:30 with a different cockpit bounce - https://youtu.be/eWb4bZSRkWY **SAURON disputes it** for 4.x: destroying either leg (only one is needed) destroys the mech outright; there is no limp mode and no reverse-disable, and that behaviour belongs to Firestorm, not Tesla. **Both may be correct** - leg DESTROYED = death, leg DAMAGED = limp. That would reconcile the video with the tested behaviour. Relevant to the myomer speed work: if a limp mode exists it is another consumer of the same mover feed that is currently stubbed out, so resolving this shapes the WAVE 6 cutover.""" def main(): print("=== 1. reopen #35 ===") cur = call("GET", "/issues/35") if cur["state"] == "closed": call("PATCH", "/issues/35", {"state": "open"}) print(" #35 reopened") else: print(" #35 already open") comment(35, C35) print("=== 2. comments on existing ===") comment(62, C62) comment(70, C70) print("=== 3. new issues ===") titles = existing_titles() for t, b in ((N1T, N1), (N2T, N2), (N3T, N3), (N4T, N4), (N5T, N5), (N6T, N6)): newissue(titles, t, b) print("\nBATCH COMPLETE") main()