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>
"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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>