"""#48 solo test: does moving a coolant VALVE after the coolant BARS have repainted leave a stuck XOR block on the Heat panel? Hypothesis (from operator_1.log): VertNormalSlider paints its indicator with an XOR fill and "erases" by XORing the same spot. Firing drops CoolantMass, which repaints the coolant vertBars over the valve track; a later valve move then XORs pixels that are no longer the indicator -> a permanently stuck block. Sequence: autofire (coolant falls) + periodic 'C' valve presses, capturing the composite each cycle, then diff the Heat panel region for pixels that turn ON and STAY on. Kills only the PID it spawns. """ import ctypes import os import shutil import subprocess import sys import time KEYUP = 0x0002 VK_C = ord('C') user32 = ctypes.windll.user32 REPO = r"C:\git\bt411" CONTENT = os.path.join(REPO, "content") EXE = os.path.join(REPO, "build", "Release", "btl4.exe") SHOT = os.path.join(REPO, "scratchpad_valve.png") LOG = os.path.join(REPO, "scratchpad_valve.log") OUT = os.path.join(REPO, "scratchpad", "valve") os.makedirs(OUT, exist_ok=True) for f in (SHOT, LOG): if os.path.exists(f): os.remove(f) env = dict(os.environ) env.update({ "BT_START_INSIDE": "1", "BT_KEY_NOFOCUS": "1", "BT_DEV_GAUGES": "1", # cockpit surround -> BT_SHOT captures the panels "BT_AUTOFIRE": "1", # hold the trigger: coolant mass falls, bars repaint "BT_SHOT": SHOT, "BT_LOG": LOG, }) proc = subprocess.Popen([EXE, "-egg", "LAST.EGG"], cwd=CONTENT, env=env) print("pid", proc.pid) def tap(vk, hold=0.12): user32.keybd_event(vk, 0, 0, 0) time.sleep(hold) user32.keybd_event(vk, 0, KEYUP, 0) frames = [] try: # wait for the mission (the shot file only appears once rendering starts) deadline = time.time() + 220 while time.time() < deadline: if os.path.exists(SHOT) and os.path.getsize(SHOT) > 20000: break time.sleep(2) else: print("FAIL: never rendered"); sys.exit(2) time.sleep(6) for cycle in range(6): # let autofire burn coolant so the bars repaint time.sleep(7) dst = os.path.join(OUT, "f%d_before_valve.png" % cycle) try: shutil.copy(SHOT, dst); frames.append(dst) except Exception as e: print(" copy failed:", e) # move a coolant valve -> XOR erase at the old row tap(VK_C) print(" cycle %d: valve tap sent" % cycle) time.sleep(4) dst = os.path.join(OUT, "f%d_after_valve.png" % cycle) try: shutil.copy(SHOT, dst); frames.append(dst) except Exception as e: print(" copy failed:", e) finally: proc.terminate() time.sleep(1) print("\ncaptured %d frames in %s" % (len(frames), OUT)) txt = open(LOG, errors="replace").read() if os.path.exists(LOG) else "" import re print("[valve] events :", len(re.findall(r"^\[valve\]", txt, re.M))) for l in re.findall(r"^\[valve\].*$", txt, re.M)[:8]: print(" ", l) print("[emitter] FIRED:", len(re.findall(r"\[emitter\] FIRED", txt)))