Commit Graph
65 Commits
Author SHA1 Message Date
CydandClaude Fable 5 b2f0becbd4 Document Live Cam + Mission Review architecture (CAMERA-REVIEW-NOTES.md)
Researched from the readable RP MUNGA source (BT shares the engine):
host roles are assigned per-IP in the mission egg (hostType= on each
pilot page); a CameraShip/MissionReview host gets a CameraDirector
player that auto-cuts to the top-ranked pilot every 10s, with manual
flight via the same cockpit hardware as pods (CameraRIOMapper) and
fixed clamped trackside cameras authored in-game into MAPS/<map>.cam.
Mission Review records raw NetworkPackets (SpoolFile -> last.spl) and
replays them through a networkless playback app with timestamp bias --
the replay re-simulates the battle from replication traffic.

Same BTL4OPT.EXE serves every role; standing up Live Cam on the
emulated pod LAN is an egg edit, not a build.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 09:21:46 -05:00
CydandClaude Fable 5 6853b123ed Confs: default L4TIMER to FAST (setenv arg2 s->f, all 14 launch confs)
L4TIMER selects the game clock (L4TIME.CPP): FAST = HMI SOS interrupt
clock at 28 Hz, unset = polled 18.2 Hz BIOS tick. Not a speed switch --
finer time quantum, smoother sim dt and timing cadences.

Per the operator: the flag tracked the deployed fleet's hardware --
original Tesla pods ran Pentium Pro 90s (SLOW), later fleets Pentium
Pro 200s (FAST). The emulated pod has no ISR-headroom constraint, so
FAST is our default from here on.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 09:10:08 -05:00
CydandClaude Fable 5 e7e1d72a66 Move 410console to the TeslaSuite repo
The 4.10 operations-console archive and the BattleTech-port hand-off
spec now live in C:\VWE\TeslaSuite\410console, alongside the
modernized .NET TeslaConsole they feed into.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 00:09:18 -05:00
CydandClaude Fable 5 cbf7015620 Weapon visuals + aim: beams render from the guns; pick self-target fixes
Renderer (dpl3-revive/patha):
- Instance visibility honored at DRAW time (stored word 4 = live
  dpl_SetInstanceVisibility field): laser beams (4-frame pulses) and
  missile models now render; cache tags w3 0/1 instances 'gated'
- PSFX world bursts (dcs=0 muzzle/impact/explosion) rendered; psfx
  storage list-based so one-shot bursts stop collapsing onto one key
- fix_degenerate applied to INSTANCE chains (was camera-only): the
  beam chain rides four rank-2 rig DCSs; composed rank-2 drew beams
  away from the guns. Offline f1635 now shows both arm beams
  converging on the aim point
- First-person canopy cull: the always-armed cockpit model at the
  head drew as a vertical line down screen center (user-confirmed
  fixed live)

Device (vpx-device/vpxlog.cpp):
- CAM backchannel on the fifosock: bridge streams its validated
  camera (+ vehicle root DCS); raycast_pick aims along the player's
  real look instead of the static view-node pose, which had locked
  every missile/beam onto one fixed wrong world point
- raycast_pick skips hidden-until-armed instances and the player's
  own articulation subtree: picking own beams created a target
  feedback loop, picking own missiles retargeted salvos mid-flight,
  and a picked missile's deleted DCS froze the game (arena2 crash)

Sound (vpx-device/vweawe.cpp): stereo-linked peak limiter replaces
the hard output clamp (the "generator out" crackle); VWE_AWE_LOG
reports pre-limit peaks + duck counts, enabled in launch_pod.ps1.
Missile-fire crackle persists: per-voice EMU8000 clamp suspected.

Eggs/confs: TESTAR2.EGG + gauge_arena2_sound.conf (map=arena2 city
variant); cavern.egg (console-era reference); plasma display on COM2
in the gauge confs; gauge_arena_net_sound.conf (slirp net stack).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 00:09:03 -05:00
CydandClaude Opus 4.8 2bb2ff7302 Weapons fire; hat-glance + MFD fixes; windowless bridge
vpx-device/vpxlog.cpp -- WEAPONS FIRE. The game's fire gate is
targetReticle.targetEntity (mech+0x388), filled every frame by a Division-board
reticle pick (dpl_RapidSectPixel) our HLE never answered, so every shot
misfired. The device now casts the camera centre ray against the live scene
(Moller-Trumbore) and returns the full hit -- instance + DCS + geogroup +
geometry -- piggybacked on the draw_scene reply; terrain is a valid target so
you can fire and miss. Sending geogroup=0 was hanging the game (GetAppSpecific
on a null); returning the real geogroup fixed it. Pick is default-on with a
single VPX_NO_PICK escape hatch. Also swaps the upper-left/right MFD explode
windows (screen location only -- decode untouched, real cause TBD on a pod).

dpl3-revive/patha/vrview.py -- HAT-GLANCE fix at the source. One cockpit DCS
(0xa2c) flushes a rank-2 rotation (shifted body -> wrong read window) that
collapsed the camera chain to rank 2 and smeared the head glance onto the wrong
axis (left/right hat read as pitch). chain_matrix(fix_degenerate) treats a
degenerate chain DCS as identity: preserves the look exactly and keeps stick-Y
torso pitch working (an earlier bridge-level yaw/pitch swap broke stick-Y and
was reverted). User-verified live: hat all 4 dirs + stick-Y both correct.

render-bridge/launch_pod.ps1 -- launch the bridge with pyw (windowless) so no
console window parks over the cockpit displays (Start-Process ignores
-WindowStyle once stdout/stderr are redirected).

render-bridge/live_bridge.py -- surface bridge render errors, flush the status
line; reverted glance-swap note.

Also vendored HUD (dpl2d) renderer work in vrboard/vrview_gl and RENDERER-COLLAB
/ RIO notes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 09:53:14 -05:00
CydandClaude Fable 5 c18c253658 RIO: document the board init handshake (test-mode + version) for vRIO
Decoded from L4RIO.CPP/HPP and verified byte-for-byte against a real
v4.2 board tap: CheckRequest 80 -> board must send TestModeChange ENTER
(8C 01 0D), self-test status stream (85 <status> <unit> <ck>), EXIT
(8C 00 0C), then answer VersionRequest 81 with 86 <maj> <min> <ck>.
Full command map 80-8C both directions, ACK/retransmit rules, and the
~1ms/byte 9600-baud pacing requirement. This is the implementation spec
for the user's vRIO (virtual RIO on COM1): its missing 8C packets are
exactly why the game logs 'RIO never came back from check request!'.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 13:35:37 -05:00
CydandClaude Fable 5 1872799c7a VPX device: dpl3-revive shading port + wire lights; fix game-path yaw mirror
- rt_draw shading replaced with the dpl3-revive software-rasterizer model
  (vrview.py draw(), proven on our own arena captures): lit = sceneAmb +
  sum |N.L|*col double-sided; c = emis + diff*lit (tex*base*1.275 when
  textured); Division 10-bit-DAC gamma 1/1.25 on output. Default light rig
  matches theirs (ambient 0.35 + one 0.65 sun) until wire lights arrive.
- Real wire lights decoded per their SceneCache.rebuild(): lmodel type-6 /
  light type-0xe bodies (dcs @d+12, type @d+16: 2=ambient 3=directional,
  rgb @d+20), directional aim = light DCS +Z row, sanity-clamped; frame
  carries summed ambient + up to 2 suns.
- Projection: the hardcoded post-projection X flip (SMPTE-era) made the
  game view steer mirrored (turn right, view swings left). Game path
  (ydown) now projects without it -- matches dpl3-revive, which has no
  lateral mirror; demo/test path keeps the flip. User-verified: native
  window steering now correct.

Fork source (emulator/src/src/hardware/vpxlog.cpp) in sync; rebuilt.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 13:34:17 -05:00
CydandClaude Fable 5 0f5b2e28da Renderer: vendor live-bridge scripts; GPU backend unblocked (60fps)
- emulator/render-bridge/: the pod->Dave's-renderer live bridge (rescued
  from session scratchpad): live_bridge.py (first-person cam from the 0x1f
  pose, arrow-key eye-height trim), fp/chase offline renders, fifobridge,
  diagnostics, gauge_arena[_sound].conf, and launch_pod.ps1 (one-command
  pod+bridge restart: pentapus VPX_EXPLODE + AWE32 sound + GL bridge).
- _backend.py: renderer selection -- vrview_gl.GLRenderer (moderngl) by
  default, VRVIEW_SOFT=1 for the software reference; backend-agnostic
  frame save via last_frame.
- GPU backend runs on side-by-side CPython 3.13 (moderngl/glcontext cp313
  wheels; no MSVC needed): 63fps headless / 60.7 windowed vs 21fps software
  on the same scene, and the GL path also draws the vertex-alpha cloud dome.
- vrview.py: VRVIEW_GAMMA env selects DAC gamma (1.25 live-renderer legacy
  vs 1.7 per the original GAMMA.C) for A/B against the real pod.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 13:34:02 -05:00
CydandClaude Fable 5 afc3fd839e Vendor dpl3-revive: the Division/DPL3 renderer, now ours
Bring the graphics-dev collaborator's dpl3-revive into the repo as first-class
project code (they've handed it off; it's ours now). This is the proven
Division renderer that our in-process rt_draw has been trying to be.

What's here:
- parser/  B2Z/V2Z/SVT/SCN/SPL/BGF/BMF/BSL decoders (pure Python).
- spec/    reverse-engineered format + the definitive VelociRender wire
           protocol (from the original DIVISION source, matches our live
           VPX node/action tables exactly).
- source-ref/  read-only copies of the original DIVISION C (BIZREAD.C,
           DPLTYPES.H, DPL.H) that define the formats.
- patha/   the "virtual VelociRender board": vrboard.py (24-action protocol
           server), vrview.py (numpy software rasterizer, the reference),
           vrview_gl.py (moderngl GPU backend, 832x512@60Hz), plus the
           run/replay/regress tooling and evidence renders. Drives FLYK/BLADE/
           Star Trek demos AND our btl4opt/rpl4opt game binaries.
- viewer/  WebGL archive generators (.py); prebuilt HTML/data regeneratable.
- samples/ small test models/textures.
- bt*.raw.bin  real BTL4OPT arena wire captures (kept for offline renderer
           testing/regression against OUR game).

.gitignore keeps the multi-hundred-MB demo capture dumps + debug logs +
regeneratable viewer bundles out of history (they stay on disk).

Phase 0 of the integration is validated: their board decodes our bt8 capture
with zero errors (3748 nodes, 507 instances, 4 mechs) and renders our arena
(terrain/dome/sky, correct Division DAC gamma). Plan + status in memory;
integration continues in emulator/RENDERER-COLLAB.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 22:06:25 -05:00
CydandClaude Fable 5 9536d4cbc0 NET: full networked mission runs end-to-end; fix blank VDB heads (NIC/VDB port clash)
Console queues a mission -> egg over the wire -> pod loads + runs it -> all
cockpit heads render -> mission ends on the console timer. Four fixes:

1. ne2000.cpp: don't BX_PANIC on the TCR inhibit-CRC / auto-tx-disable bits.
   A full production boot runs the packet driver's internal loopback
   self-test, which sets those bits; Bochs' NE2000 aborted the whole
   emulator. Record them in TCR state instead (harmless for an emulated NIC;
   pcap/host does framing+CRC). Also fixes a latent coll_prio bit-4 vs bit-3
   round-trip bug. Committed copy in vpx-device/.

2. Confirmed the mission-load page fault (00FF:219D) was RIO-OFF; booting
   with the RIO live (net_full.conf) loads and runs the mission cleanly.

3. net_loop.conf + net-boot/loop.bat: GO.BAT-style loop so the pod stays
   connected to the console and picks up missions as they're queued (the
   real pod relaunches netnub after each mission/idle disconnect).

4. HEADLINE: blank MFD/radar heads were an I/O port collision. The VDB
   video splitter is hardwired at 0x300-0x31A (palettes 0x300/0x308/0x310);
   the NE2000 at nicbase=300 swallowed the game's VDB palette writes ->
   vdb_pal stayed zero -> pal_draw decoded every head to black. Same VDB
   spam corrupted NIC RX, dropping the console EndMission (mission overran
   its timer). Fix: move the NIC to 0x340 (DOSBox nicbase=340 + DOS NET.CFG
   PORT 340; must agree). VDB keeps 0x300. Config-only, no rebuild.

Adds: net_full.conf, net_loop.conf, net-boot/ (ODI drivers, NET.CFG@340,
loop.bat, README), vpx-device/{ne2000,ethernet_pcap}.cpp; updates NET-NOTES.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 21:18:42 -05:00
CydandClaude Opus 4.8 e12bfe1d3a NET milestone: real console->pod mission egg over the wire (+ mission-load crash)
Captured a real mission egg delivered from the SheepShaver console to the
DOSBox pod on TCP 1501, no -egg bypass: 8x ReceiveEggFileMessage chunks,
7514 bytes, reassembled byte-perfect (declared notationFileLength=7514).
The egg is the operator's own mission (BattleTech / cavern / night /
freeforall, pilot "cyd", vehicle madcat). This closes the networking
workstream end to end.

- net-tools/decode_egg.py: reassemble an egg from a captured console->pod
  TCP payload hex dump (skips interleaved StateQuery polls).
- net-tools/captured_cavern_mission.egg: the delivered egg.
- NET-NOTES.md: milestone writeup + the pod's mission-LOAD crash. The pod
  faults loading the mission (page fault, wild ptr 0xFF008B5B, exception
  000E at game 00FF:219D, NetNub Munga exit 14) -- a game-side bug in the
  mission-load path, separate from networking. Leading suspect: RIO was
  disabled for this run and the player vehicle/control setup derefs null.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 17:41:34 -05:00
CydandClaude Fable 5 bda2f7b8b3 Networking WORKS: real console <-> real pod over the emulated wire
The emulated SheepShaver console and DOSBox pod now exchange the live
console protocol on TCP 1501. Required chain: rebuild the DOSBox pcap
backend + launch with C:\Windows\System32\Npcap on PATH (npcap DLL
location); a two-TAP Windows bridge (single shared TAP fails); TAP2
MediaStatus=Always-Connected; and binding the pod's pcap to the BRIDGE
MINIPORT (not a member TAP -- member-injected frames aren't re-forwarded),
via realnic=DB5521D (letter-leading GUID fragment; leading-digit is parsed
as an interface index). Captured + decoded the real protocol values
(StateResponse clientID=5/flags=1/host=0/state=1/app=1=BattleTech), which
corrects the earlier synthetic guesses. Full writeup in NET-NOTES.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 17:14:48 -05:00
CydandClaude Fable 5 929b6bc267 Networking: add egg_capture.py — console->pod protocol capture tool
Listens on TCP 1501 as a stand-in pod, logs the console's egg-delivery
bytes, and auto-decodes each NetworkPacket in both byte orders to pin the
framing + PPC/x86 endianness (the two open caveats). Works whether the
console reaches it via a slirp gateway redirect or a bridged segment.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 23:17:15 -05:00
CydandClaude Fable 5 5a2c4b589f Networking: note console VM standup (SheepShaver/OS9.0.4) + TAP bridge plan
Records that Router mode is NAT (won't reach the pod) and the console<->pod
link needs bridged TAP on both ends: console TCP/IP manual 200.0.0.1, pod
DOSBox-X switched from slirp to pcap on the same segment.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 23:11:46 -05:00
CydandClaude Fable 5 b86fea71e0 Networking: determine Console 4.10 OS compatibility from the binary
PEF imports (InterfaceLib/MathLib/ObjectSupportLib only; no AppearanceLib,
no Open Transport libs) plus the resource-fork "MacTCP or OpenTransport"
Gestalt check show it is a classic PPC PowerPlant app: runs System 7.1.2
through Mac OS 9 (practical floor 7.5), classic MacTCP API but happy with
either stack. Recommended SheepShaver target: System 7.5.5/7.6.1 with a
7100/7500/8500 old-world ROM, TCP/IP set to 200.0.0.1.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 22:35:01 -05:00
CydandClaude Fable 5 e1d44251da Networking: decode egg-delivery protocol from source; archive console SW
Decoded the console<->pod message layout from the MUNGA framework headers
(the send/recv impl did not survive -- only headers + a test harness):
NetworkPacket = 16B NetworkPacketHeader + a Receiver::Message; the egg is
delivered as ReceiveEggFileMessage chunks (seq/totalLen/thisLen +
notationData[1000]), a full egg packet = 1040B (matches the VPX nb<=1040
cap). Console splits the mission notation file into <=1000B chunks; pod
reassembles + ACKs. Two bytes-on-the-wire details (stream framing +
PPC/x86 endianness) flagged for the first live capture. Full byte tables
in NET-NOTES.md.

Also moves the extracted console software (Console 4.10 PPC app + per-venue
Console.ini + fonts/logs, resource forks preserved) into
410console/4_10-console-extracted/.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 22:21:06 -05:00
CydandClaude Fable 5 5b44ede702 Networking: console SW extracted, TCP port 1501 found, pod = Puck
Console 4.10 (PowerPC PEF/CodeWarrior) + Console.ini extracted from the
.sit. Console.ini answers the open protocol question: the console<->pod
TCP port is 1501. Full cockpit endpoint roster recorded; our emulated pod
200.0.0.113 is the cockpit Puck. The real 6100 does not boot but is no
longer needed -- run Console 4.10 under SheepShaver. Only remaining
protocol unknown is the on-stream message framing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 22:12:49 -05:00
CydandClaude Fable 5 3e838bd4c7 Networking notes: console emulator = SheepShaver (real HW is a 6100/66)
The ops console is a Power Macintosh 6100/66 (PowerPC) -> SheepShaver, with
the caveat that the 6100 ROM is incompatible (use a 7100/7500/8500 old-world
ROM; PPC apps are ROM-agnostic). Plan to leverage the real 6100: image its
drive for a faithful SheepShaver console and/or bridge it live to the
pcap-pod to capture the real egg protocol.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 22:07:10 -05:00
Cyd 49bc526732 Networking M1: pod boots on the network path (no -egg) under emulation
NE2000 + Novell ODI chain (lsl/NE2000 MLID/odipkt) works against the
emulated card (odipkt at SINT 0x60, Ethernet_II). netnub -f btl4opt spawns
the game as btl4opt
2026-07-04 21:43:00 -05:00
CydandClaude Fable 5 5e0f9c8061 Networking recon: map WATTCP/NetNub pod stack + emulator bring-up plan
The pods do TCP/IP over Ethernet (WATTCP + NetNub real-mode server;
ops console is master, pushes mission eggs to pods over TCP). Pod is a
static host on an isolated 200.0.0.0/24 LAN (200.0.0.113, console
200.0.0.1); -egg is the dev bypass of this path. Fork already has NE2000
+ pcap/slirp backends; since the emulated card is NE2000 not PCI Lance we
skip the Novell ODI chain and load a Crynwr NE2000 packet driver directly.
NET-NOTES.md has the full architecture + milestone plan (headline goal:
host-side stand-in console pushes an egg over the wire, no -egg bypass).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 21:27:10 -05:00
CydandClaude Fable 5 3d5892c00f RIO firmware: build + verify patched image (reply-wedge fix)
make_patch.py applies the two-site fix to RIOv4_2.bin (asserting original
bytes first) -> RIOv4_2_patched.bin (23 bytes changed). Re-disassembling
and diffing confirms the change is confined to $D9DD, $DA21-$DA2E, and the
$DFF0 stub with no downstream desync. Clears the reply-in-progress latch
$2521 on every teardown so a stress collision no longer leaves the board
mute to analog. Flash directly to the DIP-28 W27C512; static verification
only -- dynamic RIO_TAP mash test pending the burned chip.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 21:14:07 -05:00
CydandClaude Fable 5 9fa5d101e7 RIO firmware: drop stray disasm.stderr, ignore *.stderr
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 20:42:49 -05:00
CydandClaude Fable 5 34383f3f2c RIO firmware: disassemble v4.2, find the reply-wedge root cause
68HC11 recursive-descent disassembler (disasm_6811.py, follows the
pointer-based RX/TX state dispatch) + full disassembly + analysis doc.

Root cause of the stress wedge: an orphaned reply-in-progress latch
($2521). It gates every analog request ($D758); it is set when a reply is
generated ($D84C) but the 4-retry give-up path ($D9DD -> $DA2F) tears down
reply state without clearing it, and the success teardown ($DA00) clears
it only conditionally on $2522. Once leaked, all analog requests are
dropped -> board mute, while RX/event stays alive; only the game-start
host-reset command ($C686) clears it -- matching the field button-resync
ritual exactly. Proposed minimal in-place fix (clear $2521 on every
teardown) documented with byte patches and a hardware validation plan;
untested pending a spare EPROM. RIOv4_2-ANALYSIS.md has the details.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 20:42:37 -05:00
CydandClaude Fable 5 d0b53e93dd RIO firmware notes: exact EPROM part (AM27C512-150) + replacement pick
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 20:22:14 -05:00
CydandClaude Fable 5 673cebf3b8 RIO: firmware v4.2 dump (Toshiba TMP68HC11) + first-look analysis
RIOv4_2.bin dumped from our own board EPROM (64KB, code $C000-$FFFF).
Vector table confirms 68HC11: RESET->$C000, SCI serial interrupt->$D630 =
the protocol state machine entry for the planned disassembly. Board patch
plan steps 1-2 done (RIO-NOTES.md updated); next: disassemble from the
SCI handler, find the reply-path wedge (button/event path survives it),
patch, burn to a fresh EPROM, preserve the original.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 20:21:42 -05:00
CydandClaude Fable 5 db7745fcd0 sda4: commit the Glaze developer hard-drive dump
Un-ignored: the dev drive is the ground truth the restoration and
emulator work constantly reference (DPL3/LIBDPL + VRENDER i860 renderer
source, BT/RP live+dev game trees, VGL_LABS pod boot, scene/audio
content). Kept in-repo for the pod-owner community.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 19:41:15 -05:00
CydandClaude Fable 5 504aef88d4 ALPHA_1: commit the live production-system working tree
Un-ignored: this is the working copy the emulator actually boots (patched
BTL4OPT.EXE v4 lineage + .orig/.nop14/.pre_idle/.pre_limit backups,
TESTARN.EGG arena1 test egg, REL410 BT+RP trees, VGL_LABS pod boot bats,
VWETEST factory test suites, SB16 Creative utilities). The pristine
untouched image remains ALPHA_1.zip.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 19:40:34 -05:00
CydandClaude Fable 5 a0a4260fd4 Sound: dual-AWE32 EMU8000 emulation -- the pod has audio
Vendored 86Box EMU8000 core (GPL-2, emu8k.cpp/.h + shim) plus vweawe.cpp:
two cards at the production addresses (0x620/0x640 register triplets),
rear-card DSP/mixer stub at 0x240, synthesis on an autonomous render
thread with direct winmm output so sustained voices ride through
emulation-thread stalls (RIO staging retries) like the real silicon did.
WC sample-counter interpolation between render chunks keeps HMI SOS
busy-waits fast. VWE_AWE32/VWE_AWE_ROM/VWE_AWE_RAM_KB/VWE_AWE_SHIFT/
VWE_AWE_LEAD_MS/VWE_AWE_DUMP/VWE_AWE_LOG env knobs.

emulator/roms: the AWE32 1MGM GM ROM dumped from our own pod card (SF2)
plus the reconstructed raw image the emulated EMU8000 loads. The HMI
driver refuses the AUDIO*.RES SoundFont upload without it (banks declare
irom=1MGM) -- that was the root cause of the first silent runs; full
debug chain in SOUND-NOTES.md. Repo serves the pod-owner community only.

.gitignore: drop the ROM excludes and un-ignore ALPHA_1/ and sda4/
(committed separately).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 19:40:15 -05:00
CydandClaude Fable 5 dabc204d0d VPX render: port the restoration gallery shading model; renderer collab doc
Device renderer (vpxlog.cpp, commit-copy of the fork source):
- View-flush fog decode (enable/near/far/rgb at d+72.., mode 5 linear,
  garbage-guarded; VPX_NOFOG=1 diagnostic) applied per fragment.
- Full dpl_MATERIAL parse: emissive/ambient/diffuse/opacity/specular/
  shininess (opacity+specular parsed, not yet applied); fixed-offset
  texture/ramp refs with lazy resolution, rebake on material/ramp reflush.
- Gallery shading model (restoration/gallery_template.html) as a GLSL 1.10
  program over the existing immediate-mode path, fixed-function fallback:
  c = (matAmb*sceneAmb + matDiff*mix(ramp.lo,ramp.hi,acc))*tex + matEmis,
  linear fog with immunity knob. Textured polys use an identity light-ramp
  (texels already baked through the material ramp). Newell flat normals for
  polys without wire normals. Interim sun/ambient until the light node is
  decoded: VPX_AMBIENT / VPX_SUN env overrides.

RENDERER-COLLAB.md: shared working doc with the external-renderer team --
verified FIFO action/node tables, struct offsets, scene assembly and
coordinate conventions, available data taps, open questions (incl. the
type-7 object-vs-light flush conflict).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 16:41:16 -05:00
CydandClaude Fable 5 e2005502ff RIO: gauge confs to priority=higher,higher
highest,highest (HIGH_PRIORITY_CLASS) starved the host desktop; with the
v3 idle-window and v4 retry patches a rare dropout self-recovers, so
ABOVE_NORMAL both ways is the better trade.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 16:41:02 -05:00
CydandClaude Fable 5 301f381305 VDB: exploded 7-display desktop mode (VPX_EXPLODE=1)
New layout mode showing every cockpit display as its own desktop
window -- the two MFD heads split into their individual color wires,
which is what the pentapus cable does on the real pod:

- wins 5-9 = the five mono MFDs (win5 LL = HEAD A red, win6 LR =
  HEAD A green, win7 UL = HEAD B red, win8 UC = HEAD B green,
  win9 UR = HEAD B blue), each decoding framebuffer bits 8-15 through
  its head's palette and rendering its single wire as green-phosphor
  brightness (G=v, R=B=v/8).
- Radar (win0) rotated 90 degrees clockwise and shown portrait
  480x640 -- the pod's radar CRT is mounted sideways -- centered
  between the two lower MFDs.
- All displays at native size: MFDs 640x480, uppers at
  (20,20)/(680,20)/(1340,20), lowers at (20,560)/(1340,560) aligned
  under the outer uppers; radar at (760,560); Division main 800x600
  at (2020,20).
- The DOSBox SDL main screen is auto-parked centered under the
  Division window (one-shot EnumWindows by title + SetWindowPos).
- Geometry overridable per window via VPX_WIN<g>/VPX_MAIN as usual;
  win<g>.bmp dumps cover g=5-9, radar dumps rotated as displayed.
  VPX_COCKPIT takes precedence if both modes are set; cockpit and
  debug layouts unchanged (regression-checked).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 00:46:54 -05:00
CydandClaude Fable 5 377047f37d RIO working: serial wire tap, conf tuning, four-patch writeup
The RIO cockpit board now runs sustained sessions with button mashing;
dropouts self-heal in ~1-3s. Documented in RIO-NOTES.md:

- directserial RIO_TAP=<path> (host env): logs every TX/RX byte with
  host-us + emu-ms timestamps, plus config/RTS/DTR/break lines. This
  instrument found every root cause below.
- Confs: rxburst:16 restored (no-burst reply pacing made the game ACK
  ~14ms late -> board dropped on the first long analog stream; the old
  'rxburst corrupts boot' belief was the then-unpatched PCSPAK crash).
  priority=highest,highest (unfocused DOSBox was demoted and blew the
  ACK deadline).
- BTL4OPT.EXE patch lineage (in ALPHA_1/, zip left pristine):
  v2 full DISABLE_AND_DIE NOPs (v1 left the IRQ/RTS-retract prologue
  live -> first protocol error deafened the driver), v3 TXMAXIDLE 4->32
  (kills the button-press ACK-window livelock), v4 analog retry limit
  15s->0.5s (dev value was 0.2s; recovery now near-instant).
- Board firmware patch plan recorded for the EPROM-dump route.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 00:22:14 -05:00
CydandClaude Fable 5 aea8b3eab4 Archive the Mac operations console (410console)
410consoleArchive.sit is the StuffIt archive of the 4.10 operations
console that ran the show floor for both BattleTech and Red Planet on a
classic Mac. Preserved as-is; extraction/porting comes later.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 00:19:35 -05:00
CydandClaude Fable 5 fa895afc0f Preserve ALPHA_1.zip (production-system image); ignore __pycache__
ALPHA_1.zip is the archived resource behind the git-ignored ALPHA_1/
working directory (the patched production hard-drive image the
emulator mounts). Also ignore Python __pycache__/ dirs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 22:35:09 -05:00
CydandClaude Fable 5 7670d40b37 VDB heads: 4-head cockpit layout (VPX_COCKPIT=1), drop exploratory windows
- VPX_COCKPIT=1 places the four VGA heads as borderless windows:
  Division main 800x600 @ 0,0; radar (win0) 640x480 @ 800,0; MFD heads
  win3/win4 640x480 @ 1440,0 / 2080,0 (heads 3+4 are driver-spanned
  into one 1280x480 canvas). VPX_MAIN / VPX_WIN<g> = x,y[,w,h]
  overrides any window without a rebuild; default debug layout kept.
- Remove exploratory win1/win2 (bits 0-7 via static palettes); keep the
  0/3/4 numbering so win<g>.bmp dump names stay stable.
- Sync commit copy with the build tree: per-head palette decode in
  pal_draw() and device-side BMP capture (VPX_DUMPDIR).
- RIO-NOTES: document the PCSPAK DISABLE_AND_DIE patch (error-3 crash
  on RIO resets); restore the terrain-shadow section heading it ate.
- Add gauge_rio{,_log}.conf launch configs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 22:30:33 -05:00
CydandClaude Opus 4.8 12d2952e9f VDB: palette-reload delta probe (test palette-animation hypothesis)
On each palette reload, diff against the previous captured palette and log how
many of the 256 entries changed plus the first few index:old->new. Confirms
the static/dynamic split: pal1/pal2 load once (static base structure -- red in
odd entries, etc.), pal0 is rewritten ~28x. In this capture pal0 is doing the
intro FadeToWhite -- ~216/256 entries ramping in lockstep from 0 to ~60/63 --
i.e. animating the whole display brightness via the palette without redrawing
the framebuffer. Selective per-element flashing (targeted small deltas) would
need an active mission to observe; this probe is the instrument for it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 20:31:51 -05:00
CydandClaude Opus 4.8 e75913b5d3 VDB windows: radar (bits 0-7) + per-bit fan-out of bits 8-15
Refine the display decode for pairing: window 0 renders the color radar from
bits 0-7 (3:3:2 RGB); windows 1-8 each render a single bit (8..15) as
white/black. Each bit turns out to be a recognizable display plane -- e.g.
bits 8 & 12 are both the weapons panel, bits 9/11/13 the systems panel -- so
the mono displays' 2-bit fields are scattered (out of order) across the word.
This fan-out lets the bits be re-paired into their six displays by eye.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 20:17:47 -05:00
CydandClaude Opus 4.8 7a615449c5 VDB decode SOLVED: six displays bit-packed in the 16bpp pixel
The gauge framebuffer is an encoded stream: each 16bpp pixel packs all six
cockpit displays. Decoded live into per-display windows:
  bits 0-5   = the COLOR radar/tactical display, as 6-bit RGB (2 bits/channel)
  bits 6-7   = mono display 1 (nav scope)
  bits 8-9   = mono display 2 (weapons/systems)
  bits 10-11 = mono display 3 (sensor cluster)
  bits 12-13 = mono display 4
  bits 14-15 = mono display 5
That's 6 + 5x2 = 16 bits exactly -> six displays (1 color + 5 mono), matching
the pod hardware. A 7th window (bits 16-17) confirms the budget: it's black.

Each display now renders in its own 640x480 window from the shared framebuffer
(vga.mem.linear). Mono screens show as brightness; the three VDB palettes are
the per-display color maps (next: apply them).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 20:09:36 -05:00
CydandClaude Opus 4.8 1b973917fa VDB windows: render the actual gauge framebuffer (similar output)
Replace the palette-swatch windows with ones that render DOSBox's live gauge
framebuffer -- the M_LIN16 16bpp video memory (vga.mem.linear, 640x480, 5:6:5,
start=vga.config.real_start) via glDrawPixels. Each window now shows the real
gauge image, matching the DOSBox screen ("similar output"), confirmed live.
Diagnostic: the VDB splitter-on log now prints the framebuffer mode
(0x111 / M_LIN16 / 640x480) -- confirming direct color, so the palettes are
not index LUTs.

All three windows currently show the shared framebuffer identically; the
per-display palette decode (how the VDB carves the encoded 16bpp stream into
the six monitor outputs) is the next step.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 19:55:53 -05:00
CydandClaude Opus 4.8 7fead4f493 VDB palettes: three dedicated 640x480 live windows (not strips)
Replace the in-render-window palette strips with three separate 640x480
windows, one per VDB display palette (secondary/aux1/aux2). Each has its own
GL context (rt_main wglMakeCurrent-s each per tick) and shows the palette as a
16x16 color grid, redrawn every 50ms so it animates live regardless of the VPX
render cadence. make_gl_window() factored out; strips + VPX_VDBSTRIP removed.

Confirmed live: aux1 = red/green/olive/black status checkerboard, aux2 = a
4-region color map (green/blue/red/purple, 64 entries each), secondary = the
animated one (black between flashes).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 19:30:25 -05:00
CydandClaude Opus 4.8 68e595c5a2 VDB: live palette strips in the render window (real-time view)
Draw the three VDB display palettes as 256-color bars along the bottom of the
GL render window, updated every frame straight from the VDB storage -- a live
view (like the Division output) instead of static snapshots. This matters
because pal0 (secondary) animates and the earlier file dumps caught pal0/pal2
at their SVGAZeroPalette init (all black); live, pal2 is actually a rich
4-region color map (green/blue/red/gray-purple, 64 entries each) and pal1 the
red/green/olive status palette. Toggle with VPX_VDBSTRIP=0.

Also: VDB_PALDUMP now keeps the most-lit snapshot per group (max non-black
bytes) so file dumps no longer capture the zero-init. VDBPalette storage moved
above the render thread so rt_draw can read it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 19:00:55 -05:00
CydandClaude Opus 4.8 981d98d784 VDB: dump captured palettes (VDB_PALDUMP) + swatch renderer
VDB_PALDUMP=<prefix> writes each completed 768-byte palette load to
<prefix>N.rgb (256 RGB entries per VDB display group). render_vdb_palettes.py
renders each as a 16x16 swatch grid + linear ramp.

First capture: pal1 (aux1) is a structured red/green/olive/black color map
(2-bit R x 2-bit G) -- likely the color display's status coloring; pal0
(secondary, dynamic ~29 reloads) and pal2 (aux2) are grayscale, sampled black
mid-animation. Palettes are the per-display color maps the VDB applies when
splitting the framebuffer to the six monitors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 18:27:10 -05:00
CydandClaude Opus 4.8 6b62bf9613 VDB: fix palette port offsets -- now captures the display color maps
The VDB device was only logging the splitter-clock strobe; the gauge code's
palette writes were hitting the handler but being dropped. Cause: I mapped the
palette register groups at Adam's base offsets (0x300/0x308/0x310, sub 0-3),
but per L4VB16.CPP's "+2" convention and L4SVGA16.ASM SVGAWriteFullPalette
(mov dx,base; add dx,2 -> write-addr; inc dx -> data) the real registers are
at base+2: secondary 0x302-0x305, aux1 0x30A-0x30D, aux2 0x312-0x315, VGA-DAC
layout within each (0 mask, 1 read-addr, 2 write-addr, 3 data).

With the offsets corrected the device now captures the full palettes -- e.g.
"VDB pal0 loaded 768 data bytes" (256 RGB) + pal1 -- the per-display color
maps the VDB uses to colorize each of the six monitors. Foundation for
reconstructing the individual displays.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 18:15:48 -05:00
CydandClaude Opus 4.8 e8876e521d Gauges: fix bank-switching (window-granularity mismatch)
The garbled top-strip render was a paging bug, not the video mode. The
pageFcnPtr in L4GAUGE.INI (C000:2616) is a red herring -- the driver's direct-
pointer path is commented out ("THIS DOES NOT WORK"); it pages via portable
VESA int 10h/4F05, which our S3 handles.

Real cause: window granularity. SVGASetPage passes the bank in granularity
units; the blit advances by pageDelta = pageSize/granularity per 64KB window
crossed. The INI's granularityInKB=4 is the Cirrus CL-GD5434's 4KB window
granularity (the pod's actual video card; the "STB Horizon+" board was
Cirrus-based). Our emulated S3 uses 64KB banks, so pageDelta=16 -> every bank
landed 16x too far -> the smear.

Fix (config only): L4GAUGE.INI [640x480x16] granularityInKB=64 (=sizeInKB, so
pageDelta=1, matching the S3's 64KB banks). Full framebuffer now pages
correctly -- 315+ frames, VDB splitter stays ON, display renders as expected
(blank between missions; user confirmed "that is the screen I expect").
ALPHA_1 is git-ignored so the INI edit isn't committed; reproduction + backup
(L4GAUGE.INI.orig) documented in GAUGE-NOTES.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 18:03:29 -05:00
CydandClaude Opus 4.8 b2e4c5eaa6 Gauges: fix out-of-memory crash (HEAPSIZE), draw instrument panels
Enabling gauges crashed (null-this, illegal 0x478). Root cause was NOT video:
the MUNGA main heap (fixed 6MB default) exhausted loading gauge images. The
heap size comes from getenv("HEAPSIZE") (BTL4OPT.EXE @0x401076; default
0x600000); the pod's PARAMETR.BAT sets it (:POD=15MB, :REVIEW/:LOOP=32MB) but
our minimal setenv launch never did. memsize is irrelevant (it's the game's
own heap, not DOS memory -- verified memsize=127 still gave 6MB). Pods have
32MB RAM so 15MB fits.

Fix (no binary patch): set HEAPSIZE=15000000 + L4GAUGE=640x480x16 in
gauge.conf. Game now runs sustained with gauges on (500+ frames); the VESA
640x480x16 mode switches fine on our emulated S3 and the cockpit instrument
panels draw (DISPLAY/PROGRAM/ENG DATA/TRIGGER CONFIG...).

patch_btl4opt.py also gained Verify_Failed (DEBUG-build assertion) neutral-
ization -- release-equivalent robustness, separate from the heap fix.

Known-open (GAUGE-NOTES.md): the framebuffer renders only a top strip,
garbled -- the game bank-switches via a hardcoded far pointer in L4GAUGE.INI
(C000:2616, the STB Horizon+ card's VESA WinFuncPtr) that isn't valid on our
emulated S3, so only the first bank(s) page in. Fixing bank-switching (or an
LFB mode) is next; then the full buffer can be split into the six displays.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 17:48:07 -05:00
CydandClaude Opus 4.8 08f71eb471 Phase 3f: lighting from wire normals; defuse shipped-in debug crash
Lighting: stride-8/9 vertices carry a normal (floats 3-5, uv at 6-7). The
backend transforms normals by the instance rotation and lights with a single
directional sun (GL_LIGHT0, smooth, two-sided, GL_COLOR_MATERIAL). Terrain
and hulls shade instead of reading flat.

LOD: the lod flush has no switch distances -- the host keeps the active LOD
at the child-list head and re-orders over the wire, so children[0] is right
and LOD changes come through as the sim runs.

The crash that halted the game once the RIO drove the sim was not ours: fault
at 0047E1D1 writing 0xFFFFFFFF is the RIO driver's DISABLE_AND_DIE debug
macro (PCSPAK.ASM, DIE_ON_ERROR=1), which deliberately faults on a serial-tx
anomaly -- error 3 = body char >0x7F during a RIO packet, i.e. a glitch on a
board reset. Release build (DIE_ON_ERROR=0) compiles it out and recovers
(and al,07Fh). patch_btl4opt.py NOPs all 12 sites by signature (with .orig
backup) = release behavior; game then survives RIO resets (1200+ frames).
(ALPHA_1 is git-ignored, so the tool ships, not the patched binary.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 17:13:46 -05:00
CydandClaude Opus 4.8 0bb493dd5f Phase 3e: live texturing + serial receive-latency fixes for the RIO
Texturing (validated live -- game-live-textured.png, the ravine's brown dirt
terrain): the wire model is Division's intensity+ramp scheme. action 26
uploads 8-bit intensity texels ([node][nbytes][w][h] + rows; type 13 =
texture); texmap (12) references the texture; material (11) references its
texmap and a ramp (14: lo/hi RGB); texel color = lerp(lo, hi, i/255). The
backend bakes RGBA per material, uploads to GL, maps with wire UVs
(stride-5: floats 3-4; stride-8/9: floats 6-7).

Serial (directserial fork options, tracked in vpx-device/serialport/):
- rxpollus:<us> -- receive poll tick (stock 1ms); 100us discovers inbound
  bytes ~10x sooner. Validated: sim advanced, camera moved with real RIO.
- rxburst:<n>  -- stock DOSBox re-serializes received bytes at emulated wire
  speed (~1ms/byte at 9600) though they already paid wire time on the real
  cable; a 15-byte analog reply gained ~14ms, blowing the RIO's few-ms ACK
  window and dropping the game into its 15-second retry fallback
  (L4CTRL.CPP limit=15.0 //0.2). rxburst:16 delivers buffered bytes 16x
  faster. game_rio.conf: realport:COM1 rxpollus:100 rxburst:16.

Crash-on-advance fixed: *_TSHD.BGF terrain shadows live only in arena
subdirs the search path misses; null shadow renderable was dereferenced once
the sim moved. All 11 copied into VIDEO/GEO in the working image; game now
runs sustained (560+ frames, textured, no crash). Details in RIO-NOTES.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 16:43:21 -05:00
CydandClaude Opus 4.8 99652d894b Phase 3d: game world decodes and renders -- offline and LIVE
The full DPL hierarchy the game uses (vs flyk's flat scene) is now decoded
and rendered:
- stride-aware set_geom_verts (header word 3 = floats/vertex: 3/4/5/8/9;
  mech meshes carry normals + UVs)
- instances are list_add children of DCS nodes; instance flush field 4 ->
  object; object->lod->geogroup->geometry; dcs_link builds the articulation
  tree of 4x4s (payload floats 4..19, row-major, row 3 = translation)
- game world is y-down (DCS matrices carry a reflection); projection flips
  x (Division mirror) and y

render_game.py reconstructs a captured game stream offline: the mission
arena (10km, 246 instances, 330 geometries), the player's Thor at the
camera, six enemy mechs 1.5km north -- game-mech-decoded.png shows one with
real hull/armor/glass materials; game-cockpit-decoded.png the cockpit view.

The live backend (vpxlog.cpp) gained the same traversal and now draws the
game's out-the-window view in real time (game-live-gl.png): sky, arena
floor to the horizon, own gun barrels at frame bottom.

Next: texturing (action-26 texel maps + UVs), lighting from wire normals,
per-frame articulation once the RIO drives the sim.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 16:22:35 -05:00
CydandClaude Opus 4.8 47fe174c6e Add VDB (VWE LBE4 video splitter) device + fault disassembly findings
The VDB is the ISA card that fans the PC's Cirrus Logic framebuffer to the six
secondary cockpit displays (5 mono + 1 color), dividing the pixel clock. From
the driver (CODE/RP/MUNGA_L4/L4SVGA16.ASM, L4VB16.CPP; "Adam's port decoder
design" -- Adam G., VWE hardware):
  0x300/0x308/0x310  three VGA-DAC-style palette groups
  0x31A / 0x319      splitter high-color clock divider ON / OFF

New emulated VDB device in vpxlog.cpp at I/O 0x300-0x31A (active when logging,
disable with VDB=0): decodes palette write-address/data/mask/read-address and
the clock strobes, recording palette contents + splitter state so the six-
display encoding can be decoded later. Validated: with gauges enabled
(gauge.conf, setenv arg4=g) the game issues "splitter clock ON (0x31A)" ->
captured. The board is write-only (game never reads it, per driver + owner),
so its absence is not the crash.

Crash diagnosis (BTL4OPT.EXE CODE+0x123B): the faulting routine is a heap
free() with boundary-tag coalescing; it was handed a garbage block pointer
(value 2). Symptom of upstream corruption, reached only when the RIO is in
sync and the sim advances. HISTORY.md gains a "Cockpit display hardware -- the
VDB" section with the attribution.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 15:55:00 -05:00
CydandClaude Opus 4.8 833fe85d63 RIO passthrough: solve analog-poll latency (core=dynamic + cycles=max)
The RIO drops comms if the analog-reply ACK is late by more than a few ms.
Empirically a slower CPU failed sooner, so the latency was dominated by how
fast the game processes the RIO packet and ACKs -- core=dynamic (recompiler)
+ cycles=max keeps the board in sync (user-confirmed). game_rio.conf passes
the real RIO through serial1=directserial realport:COM1.

With real control input the sim advances and then faults (null-ptr deref at
BTL4OPT.EXE CODE+0x123B) -- downstream of the minimal test.egg not setting the
arena objectpath, so VIDEO/GEO/ARENA/thr_tshd.bgf isn't found. That is content
config, separate from the now-working VPX protocol / renderer / RIO. Details
in RIO-NOTES.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 15:08:39 -05:00