40 Commits
Author SHA1 Message Date
CydandClaude Fable 5 2ecb617c09 Phase 8D: universal deployment package (XP + Win10/11, two entry points)
One RIOJoy-<ver>.zip now deploys on both OS generations (~75 MB with the
offline XP prerequisites):
- Layout: RIOJoy\app (net48 x64) + RIOJoy\app-xp (net40 x86) +
  RIOJoy\vendor (ViGEmBus; vendor\xp: .NET 4.0 offline installer +
  KB2468871, both signature-verified; RioGamepadXP driver slots in when
  8B lands - build warns/skips until then).
- RIOJoy\install-core.bat: shared OS-detecting install logic (ver ->
  5.1 = XP); pure cmd.exe on the XP path, delegates to install-rio.ps1
  on 10/11. Exports RIOJOY_APPDIR for shortcut creation.
- postinstall.bat (TeslaConsole, unattended): elevates, runs the core,
  then deletes install.bat + README.txt so C:\games stays clean.
- install.bat (standalone computers): same core + Start Menu/desktop
  shortcuts via make-shortcut.vbs (XP-safe); keeps the README.
- README.txt at the zip root explains which entry point to run.
- deploy *.ps1 re-encoded UTF-8-with-BOM: Windows PowerShell read the
  BOM-less files as ANSI, where an em-dash byte parses as a curly quote
  and breaks string parsing (bit install-rio.ps1 in dry-run testing).

Verified: zip layout correct; extracted package dispatch-tested on
Win10 non-elevated (OS detect -> modern route -> admin guard, system
untouched).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 20:53:43 -05:00
CydandClaude Fable 5 63af7bde01 Phase 8C: RioJoy.Tray multi-targets net48 (x64) + net40 (x86, Windows XP)
The XP flavor keeps the full runtime AND the mapping editor (decided:
editor everywhere). Gated per flavor:
- Wallpaper maker + WallpaperCanvas + generation (SkiaSharp) are
  net48-only; the XP coordinator applies the profile's pre-rendered
  wallpaper instead, and WallpaperApplier converts PNG->BMP on net40
  (XP SPI_SETDESKWALLPAPER accepts only BMP).
- Joystick chain: ViGEm branch compiled out on net40 (HID feeder stays,
  ready for RioGamepadXP.sys); the editor button picker offers the full
  96 HID buttons on net40 vs the 11 named Xbox buttons on net48.

Verified: net40/x86 exe boots on Win10; the editor renders offline in a
32-bit host with a Button-50 joystick binding resolving correctly and
live gauges working (screenshot harness).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 20:45:34 -05:00
CydandClaude Fable 5 1ff0b16015 Phase 8A (2/2): RioJoy.Core multi-targets net48 + net40 (Windows XP flavor)
- TargetFrameworks net48;net40. net48 keeps x64 + ViGEm + System.IO.Ports
  package; net40 adds Microsoft.Bcl.Async + System.ValueTuple and uses the
  in-box SerialPort.
- Compat/TaskCompat bridges Task.Run/Delay/WhenAny/WhenAll (TaskEx on
  net40) and SemaphoreSlim.WaitAsync (net40 blocks briefly - trivial at
  9600 baud).
- IReadOnlyList/IReadOnlyDictionary -> IList/IDictionary throughout
  (net40 predates the IReadOnly* interfaces and the Bcl backport cannot
  make arrays implement them).
- HashCode.Combine replaced with a manual combine (Bcl.HashCode has no
  net40 build); Marshal.SizeOf<T> -> typeof form; ViGEmJoystickSink
  gated #if !NET40. HidFeederJoystickSink stays on both flavors - it
  will drive RioGamepadXP.sys on XP via the same contract.

Both TFMs build; 275 tests green on net48.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 20:41:58 -05:00
CydandClaude Fable 5 3b2af7b79a Phase 8A (1/2): de-Span the serial layer, swap JSON to Newtonsoft
Prepares RioJoy.Core for the net40 (Windows XP) target, which has no
System.Memory, ValueTask, or System.Text.Json:
- IRioTransport and the whole protocol/framing layer now use byte[] +
  Task (RioPacket.Payload, PacketParser/Builder, RioChecksum, replies,
  AnalogReport, RioHidReport). At 9600 baud Span bought nothing; the
  SerialPortTransport bridge copies disappear entirely.
- ConfigStore/OverlayTemplateStore switch to Newtonsoft 13 with the
  same conventions (indented, PascalCase, string enums, null-skipping);
  verified against the real STJ-written config.json and regions.json
  (load + round-trip). System.Memory and System.Text.Json packages
  dropped.

275 tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 20:36:19 -05:00
CydandClaude Fable 5 02abfa8a14 Phase 8D: root README.txt + postinstall.bat cleans the C:\games root
The universal archive gains a root README.txt aimed at standalone
installers (which entry point to run, supported OSes, offline prereqs).
postinstall.bat (the TeslaConsole hook) deletes install.bat and that
README as its final step so cabinet deploys leave C:\games clean;
install.bat keeps the README as the standalone machine's docs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 20:27:41 -05:00
CydandClaude Fable 5 a9de26eff6 Phase 8: final decisions — editor everywhere, universal archive, dual installers
- Mapping editor ships in all instances, XP included (wallpaper maker
  stays modern-only with SkiaSharp).
- One dist zip deploys on both XP and Win10/11: app (net48 x64) +
  app-xp (net40 x86) + vendor prereqs incl. offline .NET 4.0/KB2468871
  redistributables and RioGamepadXP.sys.
- Two entry points: postinstall.bat (TeslaConsole/launcher, unattended)
  and install.bat (freestanding, adds shortcuts). OS-detected via ver;
  pure cmd on the XP path.

All Phase 8 open decisions are now resolved; ready to implement 8A.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 20:22:20 -05:00
CydandClaude Fable 5 6ad1abc919 Phase 8 decision: no vJoy — XP gets RioGamepadXP.sys, our own HID minidriver
vJoy/PPJoy are unmaintained, so third-party virtual joysticks are out.
The XP joystick becomes a thin WDM HID minidriver (vhidmini shape,
WDK 7.1.0, x86, no signing required on XP) exposing the same Public.h
IOCTL + 25-byte report contract as the modern RioGamepad.sys, so
HidFeederJoystickSink drives both. Precedent: FASA's tasgame.sys was an
XP HID minidriver. Staged: XP app ships keyboard/lamps/plasma first,
driver as milestone 2. Win10/11 chain unchanged (ViGEm preferred).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 20:14:23 -05:00
CydandClaude Fable 5 ae7a5d21ee Phase 8 decision: Xbox 360 pad (ViGEm) stays preferred on Win10/11
vJoy is fallback-only on modern Windows and the primary backend on XP.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 20:12:06 -05:00
CydandClaude Fable 5 39d79e1fb1 Plan Phase 8: Windows XP compatibility (net40/x86 + vJoy dual-target)
One codebase, two flavors: Win10/11 stays net48/x64 with ViGEm/RioGamepad
and remains the authoring platform; XP SP3 gets a net40/x86 runtime with
vJoy (full 96-button fidelity), pre-rendered wallpapers (PNG converted to
BMP at apply), Newtonsoft JSON shared by both flavors, Bcl.Async + small
compat shims, and a .bat-only installer. Grounded in a source audit:
about 20 Span/Memory uses in the 9600-baud serial path, 6 modern Task
calls, 1 HashCode.Combine, System.Text.Json in two stores.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 19:55:38 -05:00
CydandClaude Fable 5 2ac3188528 Fix pedal gauges frozen under ZR mix: feed L/R from pre-mix pedal readouts
The editor's L/R gauges read Rx/Ry, but EnableZR (on in real profiles)
folds the pedals into Rz and pins Rx/Ry to center, so the bars never
moved with the physical pedals. AxisCalibrator now exposes
LeftPedalOutput/RightPedalOutput (the calibrated pre-mix positions) and
RioRuntime.AxesUpdated carries an AxisReadout (the six virtual axes +
both pedals); the strip draws L/R from the pedal readouts, Z/Rz/X/Y
from the axes. With ZR off the readouts equal Rx/Ry, so nothing
changes there. Verified by screenshot: Rx/Ry centered while L=75% and
R=25% render correctly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
v20260706-2ac3188
2026-07-06 10:22:05 -05:00
CydandClaude Fable 5 b2d1927c51 Fix UI hang on editor close: bound the serial port teardown
Closing the profile editor froze the tray app: the FormClosed handler
tears down the coordinator on the UI thread, and net48's
SerialStream.Dispose calls FlushFileBuffers, which blocks until the
driver drains buffered TX — indefinitely when the peer has stopped
reading (a wedged board, or a virtual-port pair with no reader). The
55 ms analog polls guarantee a TX backlog against such a peer.
Diagnosed from a live hang: the UI thread was parked in
NtFlushBuffersFile under SerialPortTransport.Dispose.

SerialPortTransport.Dispose now aborts/clears both queues first (so the
flush has nothing to wait on) and runs the close on the pool with a 2 s
grace period, so no driver can hang the calling thread. Verified against
the same wedged COM1: a 400-byte TX backlog that never drains, yet
Dispose returns in ~0 ms and a full BeginEditorSession/EndEditorSession
cycle closes in 1 ms.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 09:54:43 -05:00
CydandClaude Fable 5 e60d551f39 Phase 7: live encoder gauges in the profile editor
RioRuntime raises AxesUpdated with the calibrated AxisOutputs after every
analog reply (independent of the joystick sink, so the gauges work while
editor input routing is suppressed). The editor strip now renders the
values live: Z and the L/R pedals (Rx/Ry) fill bottom-up, Rz deflects
from a center tick, and the X/Y box tracks the stick as a dot over a
crosshair. Fraction/deflection math is pure in Core.Editing.AxisGauges
(clamped, unit-tested); the canvas repaints only the strip and only on
change. Verified by offline form screenshots against injected values.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 09:37:29 -05:00
CydandClaude Fable 5 30d5223b9b RIO firmware: v4.2 EPROM dump, 68HC11 disassembly, reply-latch wedge patch
- RIOv4_2.bin: 64K image dumped from the board's AM27C512 (code at
  $C000-$FFFF, TMP68HC11).
- disasm_6811.py + RIOv4_2.disasm.asm: vector-rooted 68HC11 disassembly;
  SCI ISR at $D630 traced to the $2521 reply-in-progress latch leak that
  wedges the analog reply path under button-mash stress.
- make_patch.py + RIOv4_2_patched.bin: two in-place edits (abort-path stub
  at $DFF0, unconditional latch clear at $DA21) statically verified by
  re-disassembly diff. Dynamic proof awaits a burned W27C512.
- Analysis + burn/validation plan in RIOv4_2-ANALYSIS.md and README.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 09:29:33 -05:00
CydandClaude Fable 5 d1a938dd43 Phase 7: region box editor in the wallpaper maker
"Edit cell boxes" mode: the selected cell grows resize handles on the
preview -- drag to move/resize with live outline tracking and a re-render
on drop, or type exact X/Y/W/H in the new geometry fields. Plain clicks
still select and cycle stacked cells (click-vs-drag resolved by a movement
threshold on mouse-up). The move/resize math is pure and unit-tested
(OverlayRegionEdit: corner/edge/move handle hit-testing with tolerance,
min-size clamped resizing that pins the opposite edge). "Save template"
persists the shared cell geometry back to the regions.json; "Reload
template" re-reads it to discard unsaved box edits. Cursor feedback per
handle; template save/reload disabled when no template path was supplied.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 01:14:36 -05:00
CydandClaude Fable 5 0623891f74 Phase 7: wallpaper maker (live preview, click-to-edit labels, .data import)
The interactive replacement for the legacy Sheet -> GIMP/Script-Fu pipeline:
WallpaperMakerForm (tray -> "Wallpaper maker") renders the profile wallpaper
live via SkiaOverlayRenderer, outlines all 119 template cells, and lets any
cell -- including the heading/banner cells the button editor cannot reach --
be clicked and retitled with debounced re-render. Clicks on stacked cells
cycle through the regions at that pixel (OverlayHitTester, unit-tested);
stacking is legitimate chroma-split display encoding. Imports a legacy .data
sheet row (with a game picker for multi-row sheets), exports the PNG, and can
apply the desktop wallpaper immediately to the same per-profile path the
runtime uses. Prompts for and remembers AppConfig.OverlayTemplatePath on
first use. PLAN.md: record this + the completed Phase 6 deploy package.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 00:54:54 -05:00
CydandClaude Opus 4.8 b275892451 Remove auto-start: launcher owns the app lifecycle
The TeslaConsole launcher starts and stops RIOJoy, so the app no longer
registers any auto-start entry:
- install-rio.ps1: drop the HKLM ...\Run registration and the post-install
  launch (and the -NoLaunch param).
- Tray: remove the "Start with Windows" menu item and delete AutoStartManager
  (HKCU ...\Run writer).
- AppConfig: drop the now-inert AutoStart field (+ test).
- Docs (PLAN.md, README-DEPLOY.txt) updated to reflect launcher-managed start/stop.

uninstall-rio.ps1 still clears any leftover Run entry from older auto-starting
builds. Solution builds; 241 tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 14:36:32 -05:00
CydandClaude Opus 4.8 0a4b3e3c25 Deploy: trim unused native SkiaSharp variants from the package
SkiaSharp drags in win-x64/x86/arm64 native libs plus a macOS .dylib
regardless of RID. The net48 loader finds the native in either the app
root or an arch subfolder (both verified), so keep only the win-x64
libSkiaSharp.dll at the app root and drop the x64/x86/arm64 subdirs and
the .dylib. Package drops from ~25 MB to ~8 MB.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 14:27:10 -05:00
CydandClaude Opus 4.8 270abfc5ad Docs: reflect net48; convert tools projects to net48
Documentation now describes the .NET Framework 4.8 stack (was .NET 8):
- README build prerequisites + framework-dependent/no-runtime-install note,
  test count 136 -> 241.
- PLAN.md architecture diagram, stack decision (with PolySharp/shims note),
  suite total 136 -> 241.
- deploy/README-DEPLOY.txt: app is net48 framework-dependent (4.8 is in-box on
  Win10/11), and build prerequisites.

Also migrate the three tools (RioJoySmokeTest, RioSerialMonitor, XcfRegionExtract)
to net48 so they keep building against the now-net48 RioJoy.Core (a net8 project
cannot reference a net48 one). Added PolySharp + System.Memory/System.Text.Json
shims and replaced net-core-only APIs (string.Contains/IndexOf with comparison,
Array.Fill, generic Enum.IsDefined, int.TryParse(span)). All three build clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 13:11:25 -05:00
CydandClaude Opus 4.8 fe87c79f55 net48 port (test branch): retarget all projects to .NET Framework 4.8
Retargets RioJoy.Core/Overlay/Tray + tests from net8.0-windows to net48 so
the app can be tested as a framework-dependent build (relies on the in-box
.NET Framework 4.8 on Windows 10/11). Builds clean; all 241 tests pass.

Polyfills (no behavior change):
- PolySharp source generator for init/records/Index/Range/required members.
- System.Memory, System.Text.Json, Microsoft.Bcl.HashCode,
  System.Threading.Channels (tests) NuGet packages.
- Compat/Net48Polyfills.cs: GetValueOrDefault, KeyValuePair.Deconstruct,
  Math.Clamp; tests/TestPolyfills.cs: Task.WaitAsync.

Source adjustments for APIs absent on net48:
- ArgumentNullException/ArgumentException.ThrowIf* inlined to manual guards.
- Convert.ToHexString, Encoding.Latin1, Environment.ProcessPath,
  ApplicationConfiguration.Initialize, Enum.GetNames<T>/GetValues<T>,
  string.StartsWith(char), string.Split(char, opts), TextBox.PlaceholderText,
  PeriodicTimer, Memory-based Stream Read/WriteAsync, array range-slicing.
- Dropped [SupportedOSPlatform] hints (net48 is single-platform).

deploy/build-package.ps1: publish framework-dependent (no self-contained).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 12:34:47 -05:00
CydandClaude Opus 4.8 67b56559f7 Deploy: RIOJoy payload folder + pre-uninstall cleanup
Rename the package payload folder to RIOJoy (was riojoy) so it extracts
to C:\games\RIOJoy, and add a pre-uninstall path the console can run
before deleting the folder.

- pre-uninstall.bat (inside RIOJoy\): self-elevating entry point that
  runs uninstall-rio.ps1.
- uninstall-rio.ps1: stops the tray app, removes the HKLM Run entry,
  uninstalls ViGEmBus (-KeepDriver to skip), and clears per-user config
  for every profile (-KeepConfig to skip). Idempotent and non-fatal.
- build-package.ps1 bundles both; README documents the uninstall flow.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 10:31:08 -05:00
CydandClaude Opus 4.8 d17fd018eb Add cockpit deployment package + single-instance guard
deploy/ builds a single zip for TeslaConsole: the self-contained tray app and
the signed ViGEmBus installer under a riojoy\ folder, with postinstall.bat at the
root. postinstall elevates and runs riojoy\install-rio.ps1, which installs
ViGEmBus silently (if absent) and registers RIOJoy to start at logon — no cert
trust, test signing, Secure Boot change, or reboot.

Also guard against two instances fighting over the COM port (machine-wide +
per-user autostart both firing): a per-session named mutex makes a second launch
exit immediately. Built zips (dist/) and the bundled installer (deploy/vendor/)
are git-ignored.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 09:29:01 -05:00
CydandClaude Opus 4.8 2f434517b7 Add signed ViGEmBus virtual-joystick support (Xbox 360)
Replace the test-signed RioGamepad driver as the default joystick output with a
ViGEmBus virtual Xbox 360 controller (Nefarius.ViGEm.Client) — properly signed,
so it installs with no test-signing / Secure Boot change / reboot.

The coordinator now selects ViGEm first, then the RioGamepad HID feeder, then a
no-op. The XInput layout caps joystick buttons at 11, so the editor's joystick
picker lists the named Xbox buttons (A, B, X, Y, LB, RB, Back, Start, L3, R3,
Guide); the hat maps to the D-pad and the six axes to the sticks + triggers.
Bind anything beyond 11 to the keyboard.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 09:28:48 -05:00
CydandClaude Opus 4.8 6ad180df89 Editor: add per-profile trigger-executables field
Add a "Triggers:" field under the profile name that edits the profile's
MatchExecutables (comma-separated, .exe optional). On save, the listed
foreground executables auto-activate the profile in Auto mode — no more
hand-editing config.json.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 23:07:27 -05:00
CydandClaude Opus 4.8 195038080e Editor: profile rename/new, PC-output toggle, save & lamp feedback
- Profile name field in the editor (rename, with empty/duplicate checks);
  "Edit profile" becomes a submenu listing each profile plus "New profile…".
- "Send button output to the PC" toggle: editor sessions route keyboard/mouse
  and joystick through gates (GatedInputSink/GatedJoystickSink) that stay closed
  until the user opts in, so editing never injects input by default.
- Save profile now confirms ("Saved ✓"/"Save failed") — the write was silent.
- A lit button renders dim at idle and bright only when physically pressed, so
  the press stays visible (three states: off / dim-lit / bright-held).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 22:39:50 -05:00
CydandClaude Opus 4.8 905b9facc9 Fix SendInput crash; dim lit lamps after reset; harden the link
SendInput declared its parameter as Span<INPUT>, which cannot be marshalled
(MarshalDirectiveException) — the first keyboard/mouse output threw on the
serial read thread and killed the link. Pass a blittable INPUT[] instead;
add a regression test.

Also: defend the runtime so a faulty output sink can never tear down the
serial link (output is best-effort), and dim lit lamps on the first reply
from the board rather than in Start() — lamp commands sent in the first ms
after the DTR reset (on port open) are dropped before the board has booted.

Verified end-to-end against the partial RIO on COM1 (keypad types to the PC,
joystick feeds, comms stay up; lit MFD buttons come up dim).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 22:39:40 -05:00
CydandClaude Opus 4.8 6fed476713 Editor: live lamp echo, bright-cell feedback, version/status readout
While a profile is being edited, light every pressed button on the RIO
(RioRuntime.EchoAllLamps, enabled for editor sessions) regardless of mapping,
so a physical press always responds. On the panel, a held button now
brightens the cell in its own colour (red MFD / blue keypad / yellow board)
instead of a cyan outline.

Surface the RIO version/check replies (RioRuntime.VersionReceived/
CheckReceived) and show them grouped in a new "RIO reply" box below the
command buttons when "Request version & status" is clicked. Drop the two
diagnostic readout-toggle buttons (comms bring-up leftovers).

Verified against the partial RIO on COM1 (firmware 4.2; MFD + keypad light
and report; missing-board status shown).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 21:50:00 -05:00
CydandClaude Opus 4.8 36bf1c0190 Add RioSerialMonitor hardware-bringup tool
A console diagnostic that opens the RIO COM port, runs the real serial link,
and logs every event (buttons, keypad, axis, version/check, control bytes,
framing). Flashes all lamps once to prove the PC->RIO path and echoes a lamp
on each button press. Verified end-to-end against a partial RIO on COM1
(firmware 4.2; MFD + keypad inputs and the throttle axis all decode).

    dotnet run --project tools/RioSerialMonitor -- COM1 30

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 21:12:22 -05:00
CydandClaude Opus 4.8 4994ab699f Editor cockpit rework + dormant-by-default serial port use
Profile editor:
- Cockpit-style encoder gauges centered over the Upper Middle MFD: Z left,
  X/Y right, and L/R/Rz arranged as a big "U".
- [JoyStick] section surfaced as a vertical "Axis" toggle column (6 inverts
  + ZR mix), written back to the profile's calibration.
- Tighten the panel layout (close gaps, trim empty border columns); colour the
  Secondary/Screen columns yellow; rename the "Joystick / Hat" board to
  "Joystick"; add an "Import .ini..." tray entry.
- "RIO commands (live)" button group (all RioCommandCodes) fired at the live RIO.

Serial-port handling:
- Start dormant; only take the COM port for a profiled game (auto-switch) or
  while editing, so the native games can open the port without clashing.
- Editor sessions hold the port but route keyboard/mouse/joystick to no-op
  sinks (NullInputSink), so button function can be checked without injecting
  input; RioRuntime.ButtonActivity drives a live cyan press indicator and
  lamp feedback still applies.
- AutoSwitchWatcher.Reset() re-syncs the watcher after an editor session.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 22:55:53 -05:00
CydandClaude Opus 4.8 a4a3b58f7e INI import: strip greeting quotes; add gsheet reference
Strip a surrounding pair of double quotes from the [Plasma] Greeting on
import (the gsheet "INI_V2" output writes Greeting="..."), and add the
master button-layout spreadsheet that documents the RIO.ini format and the
16-bit map-word encoding. Test covers the real sheet output (modifier-stacked
words, keypad VK words, RIO command words) round-tripping verbatim.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 22:55:36 -05:00
CydandClaude Opus 4.8 094637b0a4 Editor: rework to the original Win32 RIO control-panel layout
Reworked the profile/mapping editor to mirror the original unfinished Win32
RIO driver's control-panel design (docs/Win32RIO, by FASA / Michel Lowrance)
instead of the flat config-sheet grid. The wallpaper region positions are a
VGA chroma-split display artifact, not the cockpit's logical shape.

- RioJoy.Core.Editing.CockpitPanel: the functional layout — five MFD clusters,
  four board columns (Throttle/Secondary/Screen/Joystick-Hat), an encoder-gauge
  strip, and the two later-added 4x4 keypads (Internal/External). Places every
  address 0x00-0x47 / 0x50-0x6F exactly once (unit-tested).
- Tray PanelView/PanelCanvas render the panel; ProfileEditorForm drives it.
  Buttons show label + assigned function (ButtonBinding.Describe -> CTL-N, JOY1,
  POV-U, Mse LB, RIO command names; unit-tested). Lamp shade is driven by the
  IsLit flag (not by whether a function is assigned); keypads are neutral blue
  with no Lit checkbox. Added an Unassign button next to Apply.
- Removed the superseded sheet-grid UI (SheetView/SheetCanvas); SheetLayout +
  config-sheet.csv stay as reference data.

docs/Win32RIO: the original driver (tasgame.sys, a 32-bit kernel HID minidriver
that opened the serial port, set baud/8N1/DTR, and spoke the *identical* RIO
protocol -- AnalogRequest/Reply, Button Pressed/Released, Check/Lamp/Reset/
Version -- validating our protocol port), oemsetup.inf, RemoteDriver.doc, and the
extracted control-panel / game-controllers mockups.

~236 xUnit tests green. PLAN.md updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 19:48:14 -05:00
CydandClaude Opus 4.8 8d2d0b71aa Phase 7: cockpit overlay generator, region extraction, and profile editor
Overlay generator (pure, tested) — RioJoy.Core/Overlay:
- FontFitter ports the legacy calc-fontsize auto-fit search (validated against a
  brute-force oracle); OverlayLayoutEngine ports create-data-layer's fit + h/v
  justification and adds per-region 90 deg CCW rotation; OverlayTemplate/Region +
  OverlayTemplateStore hold cell geometry/colour/rotation (regions.json).
- GoobieDataImporter parses the legacy .data label sheet into label rows.
- src/RioJoy.Overlay: SkiaSharp rasterizer (SkiaTextMeasurer shared with the engine
  so measured layout == drawn output; SkiaOverlayRenderer -> PNG;
  ProfileWallpaperGenerator). Verified end-to-end on the real cockpit art
  (regions.json + riojoy.png + TEST.data) by OverlayRenderIntegrationTests.
- RioJoy.Tray/WallpaperApplier applies via SystemParametersInfo; RioCoordinator
  generates+applies on profile activation (opt-in via AppConfig.OverlayTemplatePath).

Region geometry — tools/XcfRegionExtract parses riojoy.xcf (a GIMP-format binary
reader, no GIMP needed) into the 119-cell regions.json: per-layer offsets/size/
font/colour, with 90 deg CCW rotation on a-00 + b-10..b-1F. Base image riojoy.png.
NOTE: the wallpaper positions are a VGA chroma-split display artifact (6 displays),
not the cockpit's logical layout.

Mapping editor (first cut) — RioJoy.Tray/Editor/ProfileEditorForm renders the
config sheet's logical grid (SheetLayout parses the sheet CSV export). The iRIO
word is edited via ButtonBinding <-> RioMapEntry.Create (no hex bit-twiddling), with
a context-sensitive picker: keyboard keys by name (KeyCatalog VK names), joystick
Button N, hat direction, mouse/RIO-command enum names; modifiers only for keyboard.
Opened from the tray ("Edit profile..."). The sheet-grid layout is a starting point
that needs rework from a better-formatted source.

~220 xUnit tests green. Docs (PLAN.md, README) updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 17:13:48 -05:00
CydandClaude Opus 4.8 2deeb374ae Verify HID feeder end-to-end; add smoke-test tool; update docs
The RioGamepad driver now installs and enumerates, so the user-mode
feeder path is verifiable. Add tools/RioJoySmokeTest, a standalone
on-cabinet utility that drives the real HidFeederJoystickSink (open the
device, submit reports via IOCTL_RIO_SUBMIT_REPORT) and reads the gamepad
back through winmm joyGetPosEx, asserting axes (min/mid/max), buttons,
and the POV hat all surface to the OS. Verified: all checks pass against
the installed driver.

Update docs to match reality (PLAN.md predated the HidFeederJoystickSink
commit): Phase 1 is now test-signed/installed/verified with the VHF
LowerFilters requirement noted; the stale "NullJoystickSink placeholder"
remainders in Phases 3 and 5 are corrected to reflect the wired,
verified feeder. driver/README.md notes the end-to-end verification.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 23:56:16 -05:00
CydandClaude Opus 4.8 38ffb27458 Fix RioGamepad Code 31: add VHF lower filter + joy.cpl friendly name
The driver installed but the device failed with Code 31
(CM_PROB_FAILED_ADD): VhfCreate returned STATUS_INVALID_DEVICE_REQUEST
(0xC0000010) because vhf.sys was never attached beneath the FDO. Add the
required LowerFilters=vhf AddReg to the INF so VHF loads as a lower
filter; the device now starts clean and enumerates in joy.cpl.

A VHF virtual HID device cannot supply a HID product string (VHF_CONFIG
has no string field and VHF owns IOCTL_HID_GET_STRING), so register the
DirectInput OEMName (VID_1209&PID_5249 -> RIOJoy Virtual Gamepad) in
install.ps1 -CreateDevice and remove it in uninstall.ps1, giving the
controller a proper name instead of the generic VHF default.

Also fix a non-ASCII em-dash in sign.ps1 that broke parsing under
Windows PowerShell 5.1 (UTF-8 without BOM), and document the Code 52 /
Code 31 / friendly-name troubleshooting in driver/README.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 23:39:55 -05:00
CydandClaude Opus 4.8 5dddbd2694 Phase 1 (3b): driver signing/install scripts + C# HID feeder sink
Make the virtual gamepad deployable on owned cabinets and wire the real
user-mode feeder:

- driver/sign.ps1 (non-admin): create a self-signed code-signing cert, build the
  catalog with inf2cat, and SHA-256-sign RioGamepad.sys + .cat (embed-sign the sys
  before cataloguing so the .cat matches). Exports RIOJoyTest.cer. Verified
  end-to-end against the EWDK (signability + catalog clean; both files signed).
- driver/install.ps1 (admin, two-phase): trust the cert (LocalMachine Root +
  TrustedPublisher), stage the package (pnputil /add-driver), enable test signing;
  after reboot, -CreateDevice runs devgen to create root\RioGamepad so PnP installs
  it. uninstall.ps1 reverses it.
- RioJoy.Core.Output.HidFeederJoystickSink: opens the driver by
  GUID_DEVINTERFACE_RIOGAMEPAD (SetupAPI), maintains a RioHidReport, and submits it
  via DeviceIoControl(IOCTL_RIO_SUBMIT_REPORT) on each axis/button/hat change.
  RioCoordinator now uses it when the driver is present and falls back to the no-op
  sink otherwise (status shows "[no joystick driver]").
- gitignore the signing outputs (driver/package/, *.cat); driver/README.md gets the
  full build → sign → install → joy.cpl workflow.

Remaining = the actual elevated install + reboot + joy.cpl verification on the
cabinet (admin steps), and on-hardware confirmation of the feeder.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 21:17:04 -05:00
CydandClaude Opus 4.8 24cdf495e3 Phase 1: RioGamepad virtual HID driver (KMDF + VHF) + C# report packer
Author the custom virtual HID gamepad that replaces vJoy, and pin its wire
format on both sides. Builds clean to RioGamepad.sys against the EWDK
(KMDF 1.15 + VHF, x64, warnings-as-errors).

driver/RioGamepad/:
- ReportDescriptor.h: 6x16-bit axes (X,Y,Z,Rx,Ry,Rz), one 4-direction hat with
  null state, and 96 buttons — the legacy vJoy layout. 25-byte input report.
- Device.c/Driver.c: KMDF root-enumerated device that creates the VHF virtual HID
  device (VhfCreate in DeviceAdd, VhfStart in D0Entry, VhfDelete on cleanup) and
  exposes a device interface + IOCTL_RIO_SUBMIT_REPORT that forwards the caller's
  report bytes to VhfReadReportSubmit. Thin relay: no report logic in the kernel.
- Public.h: device-interface GUID, IOCTL, and the report byte layout shared with
  the C# client. RioGamepad.inf + build.cmd (EWDK build, catalog/sign disabled).

src/RioJoy.Core/Hid/RioHidReport.cs: packs AxisOutputs + hat + 96 buttons into
the exact 25-byte report (LE axes, hat nibble with 0x0F=centered, button bitmap).
13 new xUnit tests (136 total).

Remaining (deploy-side): test-sign + pnputil install + verify in joy.cpl, and
wire the real DeviceIoControl feeder sink (replacing NullJoystickSink). The
EWDK's in-build catalog task (DrvCat) can't load Microsoft.Kits.Logger on this
image, so the .cat is produced with inf2cat/signtool at install time instead.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 21:06:16 -05:00
CydandClaude Opus 4.8 1348040e1c Phase 5: tray app + profiles + runtime wiring
Wire the Core pieces into a runnable tray app with per-game profiles and the
three-state serial-yield auto-switch:

- Profiles/: RioProfile + AppConfig model; ConfigStore (System.Text.Json,
  round-tripped); RioIniImporter ports the legacy RIO.ini (button table, invert
  flags, plasma greeting); AutoSwitchResolver + AutoSwitchWatcher resolve the
  foreground executable into Yield (native game) / Activate (profile) / Idle, with
  native always winning and change-only notifications. IForegroundProcessProvider
  abstracts the OS.
- RioRuntime assembles a profile's live pipeline: serial ButtonPressed/Released +
  KeyPressed/Released → InputRouter (via RioAddress); AnalogReply → AxisCalibrator
  → the six joystick axes; RIO commands → calibration resets + version/check
  requests + lamp re-init. SerialLampSink sends lamp feedback over the link;
  NullJoystickSink is a placeholder until the Phase 1 HID feeder exists.
- RioJoy.Tray: NotifyIcon menu mirroring the legacy console menu (axis resets,
  version/status, raw-axes & poll-rate toggles, quit) + profile selection
  (auto vs. manual) + "start with Windows"; RioCoordinator owns the serial
  acquire/release tied to the watcher (native-game COM-port yield). OS adapters:
  ForegroundProcessProvider (Win32 foreground PID→exe) and AutoStartManager (HKCU
  Run key).
- tests: 18 new xUnit tests (123 total) for config round-trip, ini import,
  the three-state resolver + watcher, and RioRuntime end-to-end over the fake
  transport (button→joystick, keypad-offset→keyboard, analog→six axes).

The joystick output stays a no-op until the Phase 1 driver; on-cabinet
verification of the acquire/release lifecycle remains.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 15:36:58 -05:00
CydandClaude Opus 4.8 24a1b64519 Phase 4: axis calibration + plasma display (RioJoy.Core)
Port the analog calibration math and the plasma/VFD command set:

- Calibration/: AxisCalibrator ports UpdateThrottle/UpdatePadal/UpdateJoystick
  (riovjoy2.cpp#L1504+) — throttle deadzone + ratchet field, pedal deadzones,
  joystick X/Y auto-ranging from observed min/max, rudder mixing (enableZR), and
  the per-axis invert flags. Stateful (start positions, last outputs, observed
  extremes) like the legacy globals, with the RIOcmd axis resets. Final outputs
  clamp to the documented 0..32766 range (also guards a legacy compounding quirk).
  AxisOutputs carries the six values; IJoystickSink gains SetAxis(JoyAxis,value)
  so calibrated axes reach the HID feeder.
- Plasma/: PlasmaCommands builds the CPlasma ESC sequences (clear/cursor/font/
  attr/box draw+fill/text) + GetFontSize + the PlasmaPosText auto-fit/centering;
  PlasmaDisplay writes them over the secondary COM transport.
- tests: 21 new xUnit tests (105 total) for throttle proportional/saturation/
  invert, pedal ZR mix vs. direct, joystick centering/direction/invert/reset, the
  output clamp, and plasma byte sequences/font sizes/auto-fit positioning.

Hardware verification of axis feel + plasma output remains; the game-specific
PlasmaScoreDraw layout is deferred to profile content (Phase 5/7).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 15:24:45 -05:00
CydandClaude Opus 4.8 cc64d241c9 Phase 3: input mapping + output routing (RioJoy.Core.Mapping)
Port the iRIO decode and Press_V2/Release_V2 routing into testable C#:

- Mapping/: RioMapEntry decodes the 16-bit map word (lamp/mouse/hat/joy/extended/
  alt/ctrl/shift flags + value) and resolves the routing Kind by the legacy
  precedence (joy+hat+mouse => RIO command; none => keyboard; else joy>hat>mouse).
  RioAddress translates button/keypad events to table addresses (+0x50/+0x60
  keypad offsets); RioInputMap is the 112-entry per-profile table replacing the
  hard-coded iRIO[].
- InputRouter ports Press_V2/Release_V2: modifier press/release ordering,
  scancode keys, joystick buttons, POV hat, mouse move/click (deltas corrected
  per PROTOCOL.md), RIO-command dispatch, and lamp feedback (bright on press, dim
  on release; RIO commands carry none). InitializeLamps() dims all lamp entries.
- Output is split behind sink interfaces (IInputSink/IJoystickSink/ILampSink/
  IRioCommandSink) so routing is pure + unit-tested; Output/SendInputSink is the
  real SendInput keyboard/mouse adapter (scancode injection).
- tests: 30 new xUnit tests (84 total) for entry decode, address translation,
  and router routing/precedence/lamp/modifier ordering via a recording sink.

The joystick sink's real adapter (HID feeder -> RioGamepad via DeviceIoControl)
is blocked on the Phase 1 driver; routing already targets IJoystickSink.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 15:10:06 -05:00
CydandClaude Opus 4.8 b3cb764f4d Phase 2: serial + RIO protocol core (RioJoy.Core) with unit tests
Port the RIO wire protocol from legacy/riovjoy2.cpp into testable C#:

- Protocol/: command + length table, 7-bit checksum, packet builder, and a
  streaming receive-side framing state machine (PacketParser) that mirrors the
  legacy ReadCommBlock framing/resync (high-bit-mid-packet abort). Typed RIO->PC
  decodes: AnalogReport (14-bit sign-extend), VersionInfo, CheckStatus; lamp-state
  composition.
- Serial/: RioSerialLink drives an async receive loop with ACK/NAK reply policy
  (legacy force-accept vs. opt-in VerifyInboundChecksum), the analog poll timer,
  and the >5s reset-recovery watchdog. IRioTransport abstracts the COM port; the
  SerialPort-backed transport does 9600 8N1 + DTR reset pulse, and acquire/release
  is just create/dispose (foundation for native-game serial yield).
- tests/RioJoy.Core.Tests: 54 xUnit tests covering checksum, framing/resync,
  builder round-trips, analog sign-extension + sentinel rejection, lamp combos,
  and the read loop driven against an in-memory fake transport.

Hardware verification (version/check/analog against a cabinet) remains; it can't
be done off-device.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 13:04:03 -05:00
CydandClaude Opus 4.8 39a3dab1fc Phase 0: scaffold modern RIOJoy solution + plan
Modernization of the legacy vJoy-based RIO cockpit interface for Win10/11,
removing the vJoy dependency in favor of a custom VHF/UMDF HID driver,
rewritten in C#/.NET 8 as a background tray app with per-game profiles.

- Reorganize: legacy C++ -> legacy/, cockpit art -> docs/reference/
- RioJoy.sln: src/RioJoy.Core (lib) + src/RioJoy.Tray (tray app), net8.0-windows x64
- driver/ placeholder for the RioGamepad WDK driver
- docs/PLAN.md (7-phase plan; profiles + serial-yield model)
- docs/PROTOCOL.md (RIO wire format + iRIO input-map reference)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 12:43:01 -05:00