diff --git a/context/pod-hardware.md b/context/pod-hardware.md index f22f418..fff6e25 100644 --- a/context/pod-hardware.md +++ b/context/pod-hardware.md @@ -184,6 +184,46 @@ All cockpit surfaces are bit-plane MASKS over ONE shared `SVGA16` pixelBuffer: ` byte; `Heat`=0x4000, `Mfd2`=0x0400, `Comm`=0x8000, `Mfd1`=0x0100, `Mfd3`=0x1000; `Eng1-3` = engineering-mode alt planes; `overlay`=0x00C0 (shares the sec surface). See [[gauges-hud]]. [T2] +## ⭐ THE RGB SPLIT — how ONE VGA port drives THREE mono MFDs (decoded 2026-08-06) [T0 engine source + T1 authentic pod config] +The five monochrome MFDs are NOT five video outputs. Each VGA port's **R, G and B analog lines +are split to three separate monochrome monitors**, and the software puts a different MFD in each +colour channel of one shared palettized framebuffer. Mechanism, end to end: + +1. **Every surface is a bit-plane + a CHANNEL.** `content/GAUGE/L4GAUGE.CFG` (the authentic 1996 + pod config) configures each port as `configure(idx, port, rotation, bitMask, clut, COLOUR, palette)`: + | port | panel | mask | clut | channel | + |---|---|---|---|---| + | `Comm` | upper right | 0x8000 | clut2 | **red** | + | `Mfd2` (Engineering) | upper centre | 0x0400 | clut2 | **green** | + | `Heat` | upper left | 0x4000 | clut2 | **blue** | + | `Mfd1` | lower left | 0x0100 | clut1 | **red** | + | `Mfd3` | lower right | 0x1000 | clut1 | **green** | + | `sec` (+`overlay` 0x00C0) | secondary/radar | 0x003F | clut0 | **rgb** (full colour, rotation 270 — the physically ROTATED portrait CRT) | + `Eng1/2/3` are the engineering-page twins of Mfd1/2/3: same monitor, second bit-plane, switched + by `reconfigure(...)` giving one plane the channel and the other `blank`. +2. **The channel assignment is literally a palette write.** `L4GraphicsPort::BuildSecondaryColor` + (L4VB16.cpp) walks the palette entries owned by the port's bit group (`BitWrangler(byteMask,8)`) + and writes ONE component: `RedChannel -> triplet->Red`, `GreenChannel -> ->Green`, + `BlueChannel -> ->Blue`, `AllChannels -> the whole triplet`. `BlankColor` blanks the group + (`BlankPalette()`), which is how a page swap silences the plane it replaces. The + `*TransparentZero` variants skip colour 0 so zero reads as transparent for that group. +3. **So the DAC output carries three independent pictures**, one per analog line, and the splitter + hands each line to its own mono monitor. Three MFDs per VGA port; the pod's two MFD ports are + the **1280x480 "horizontally spanned" surface** (2 x 640x480 halves) the Displays section + describes — clut2 = the upper row (Comm/Mfd2/Heat), clut1 = the lower row (Mfd1/Mfd3, blue + spare). The radar rides its own port in real colour. + +**Why this matters for the port [T2]:** our modern path renders each surface as its own +mono-tinted window on its own Windows display (see §MFD PANELS ON REAL HARDWARE), which is right +when every panel has its own output. **On splitter-wired glass it is wrong** — three monitors +would share one Windows display and each would show only its channel's share of a single tinted +image. Driving original splitter hardware needs a CHANNEL-COMPOSITE mode: extract three planes +into ONE 640x480 RGB image with pure (255,0,0)/(0,255,0)/(0,0,255) tints, one window per VGA +output. `SVGA16::ExpandPlaneToBGRA` already does the per-plane extraction with a tint, so the +composite is a small addition. OPEN: which way Nick's crash cart is wired (Windows shows three +separate 640x480 displays there, which suggests per-panel outputs via the Trigger 6 USB adapter, +not a splitter) — settle it by eye before building. + ## MFD PANELS ON REAL HARDWARE — the bring-up path (2026-08-06) [T2 local / T4 on-pod] Nick's crash cart (pod hardware + Chrome Remote Desktop on a burner account) is the first chance to drive the real panels. **The 1995 display path is NOT the way in.** That rig spanned the five diff --git a/scratchpad/pod/fs.py b/scratchpad/pod/fs.py new file mode 100644 index 0000000..ecb16cb --- /dev/null +++ b/scratchpad/pod/fs.py @@ -0,0 +1,47 @@ +"""Browse the VWE/firestorm gitea repo (read-only) without cloning 10GB.""" +import base64, json, subprocess, sys, urllib.request +sys.stdout.reconfigure(encoding='utf-8', errors='replace') +_out = subprocess.run(["git","credential","fill"], + input="protocol=https\nhost=mysticmachines.com\n\n", + capture_output=True, text=True, cwd=r"C:\git\bt411") +_c = dict(l.split("=",1) for l in _out.stdout.strip().splitlines() if "=" in l) +AUTH = "Basic " + base64.b64encode((_c["username"]+":"+_c["password"]).encode()).decode() +B = "https://mysticmachines.com/api/v1/repos/VWE/firestorm" + +def api(p, t=120): + r = urllib.request.Request(B+p, headers={"Authorization": AUTH}) + return json.load(urllib.request.urlopen(r, timeout=t)) + +def ls(path=""): + try: + return api("/contents/" + urllib.request.quote(path)) + except Exception as e: + return [] + +def raw(path, t=120): + r = urllib.request.Request(B+"/raw/"+urllib.request.quote(path), headers={"Authorization": AUTH}) + return urllib.request.urlopen(r, timeout=t).read().decode("utf-8","replace") + +if __name__ == "__main__": + cmd = sys.argv[1] + if cmd == "ls": + for e in sorted(ls(sys.argv[2] if len(sys.argv)>2 else ""), key=lambda x: x["type"]+x["name"]): + print(f' {e["type"]:4} {e.get("size",0):>9} {e["path"]}') + elif cmd == "cat": + print(raw(sys.argv[2])[:int(sys.argv[3]) if len(sys.argv)>3 else 4000]) + +def subtree(path, recursive=True): + """Full file list under a directory, via its tree sha (avoids the 1000-entry + truncation of a whole-repo recursive tree).""" + e = api("/contents/" + urllib.request.quote(path)) + # the dir's own sha comes from its parent listing + parent = "/".join(path.split("/")[:-1]) + name = path.split("/")[-1] + for it in ls(parent): + if it["name"] == name and it["type"] == "dir": + sha = it["sha"] + break + else: + return [] + t = api(f"/git/trees/{sha}?recursive={1 if recursive else 0}&per_page=99999", t=180) + return [(x["path"], x.get("size", 0)) for x in t.get("tree", []) if x["type"] == "blob"], t.get("truncated") diff --git a/scratchpad/pod/podssh.sh b/scratchpad/pod/podssh.sh new file mode 100644 index 0000000..a09b69f --- /dev/null +++ b/scratchpad/pod/podssh.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +# Run a command on the pod (bt411-pod, user 'user') over the tailnet. +exec ssh -i ~/.ssh/bt411_pod -o BatchMode=yes -o StrictHostKeyChecking=no \ + -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -o ConnectTimeout=10 \ + user@100.107.167.96 "$@" diff --git a/scratchpad/pod/runpod.bat b/scratchpad/pod/runpod.bat new file mode 100644 index 0000000..a5476ad --- /dev/null +++ b/scratchpad/pod/runpod.bat @@ -0,0 +1,18 @@ +@echo off +REM BT411 pod launch -- runs from the game's content dir in the CONSOLE session. +REM Started via schtasks /IT so the windows land on the REAL panels (an SSH +REM session is session 0 with a dummy display and would render nowhere). +cd /d C:\bt411\BT411_4.11.801\content + +REM glass cockpit, per-display windows, POD surface mode (bare 640x480 pictures, +REM no on-screen button banks -- the cab's buttons are physical), sticky layout +REM loaded from glass_layout.cfg beside this file. +set BT_GLASS=1 +set BT_GLASS_PANELS=1 +set BT_POD_SURFACES=1 +set BT_GLASS_LAYOUT=load +set BT_GLASS_LOG=1 +set BT_START_INSIDE=1 +set BT_LOG=podrun.log + +start "" ..\build\Release\btl4.exe -egg MP.EGG