73 lines
4.7 KiB
Python
73 lines
4.7 KiB
Python
"""File the crit-panel issue (#80) and cross-link it."""
|
|
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))
|
|
|
|
|
|
TITLE = "Critical panel never lights from weapon fire -- crit roll has no caller + MechSubsystem::TakeDamage is a stub"
|
|
BODY = """Field observation (night 6, Conn Man 22:22, screenshot): "Armor panel damage. Critical damage display did not show any crits." Investigated 2026-07-29 -- it is a REAL port gap, three layers deep.
|
|
|
|
**First, what is NOT a bug:** the paper doll (Damage view) shows per-ZONE armor tint; the Critical view (`cmCrit`) is a per-SUBSYSTEM list. They display different data by design, so "doll shows damage, crit panel shows nothing" is the expected state right up until a subsystem actually takes critical damage. The bug is that subsystem critical damage essentially cannot happen:
|
|
|
|
**Layer 1 -- the crit roll is never called.** `Mech__DamageZone::CriticalHit @0049ccc4` is reconstructed and faithful (half the hit to armour, half to ONE critical subsystem chosen by `criticalWeight`, capped by `damagePercentage`) -- and has ZERO callers in the port. Raw byte-scan of `BTL4OPT.EXE`: exactly one call site, `@0x4a0461`, which sits in the **un-exported decomp gap** (nothing covers `0x4a03xx-0x4a05xx` -- the same dark region as the targeting `0x37c/0x388/0x38c` writer). So the authentic trigger conditions (per-hit chance? zone-threshold crossing?) are unknown.
|
|
|
|
**Layer 2 -- even if called, the damage sink is dead.** `MechSubsystem::TakeDamage` is an empty bring-up stub (`btstubs.cpp:179`), so `ApplyDamageAndMeasure` would measure a delta of 0 for generic subsystems. The weapon-family overrides chain to the engine base, whose private-zone write is the zero-`damageScale` no-op (the 2026-07-28 subsystem-zone finding).
|
|
|
|
**Layer 3 -- what still works, and why the panel is not ALWAYS empty:** zone destruction (`SendSubsystemDamage`) and ammo cook-off (`DistributeCriticalHit`) both pin subsystem damage DIRECTLY, bypassing the dead paths. So the Critical view can light after a zone is fully destroyed -- never from accumulating fire.
|
|
|
|
**Likely same root:** #28 (confirm the vital-subsystem-crit death path can fire) -- vital-subsystem kills route through exactly this machinery.
|
|
Related: #73 (zone selection is a weighted lottery -- once crits are wired they will be probabilistic by design, so verify against the model, not against per-limb expectations).
|
|
|
|
**Recovery plan:**
|
|
1. Raw-disasm the container at `~0x4a03xx-0x4a05xx` (the #60-class gap workflow) to recover the crit trigger conditions and, likely in the same function, the rest of the un-exported damage-application path.
|
|
2. Dump MechSubsystem vtable `0050e210` slot `+0x24` and reconstruct the real `TakeDamage` body (replacing the btstubs stand-in).
|
|
3. Wire the caller; the authored `criticalWeight` / `damagePercentage` tables then come alive, and the Critical view starts reporting real crits.
|
|
"""
|
|
|
|
|
|
def main():
|
|
titles = {}
|
|
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:
|
|
titles[i["title"].strip()] = i["number"]
|
|
page += 1
|
|
if TITLE.strip() in titles:
|
|
print(" SKIP (exists as #%d)" % titles[TITLE.strip()])
|
|
return
|
|
j = call("POST", "/issues", {"title": TITLE, "body": BODY})
|
|
n = j["number"]
|
|
print(" created #%d" % n)
|
|
call("POST", "/issues/28/comments", {"body":
|
|
"Very likely blocked by the same root as #%d: the crit machinery's single binary call site "
|
|
"(`@0x4a0461`) sits in the un-exported decomp gap and the port never calls "
|
|
"`Mech__DamageZone::CriticalHit`; `MechSubsystem::TakeDamage` is additionally an empty stub. "
|
|
"The vital-subsystem death path routes through exactly this machinery -- resolve #%d first." % (n, n)})
|
|
print(" cross-linked on #28")
|
|
|
|
|
|
main()
|