Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
6a2bee3
Merge pull request #123 from giantorth/dev
giantorth Aug 11, 2026
157eb5d
Update README.md
giantorth Aug 13, 2026
5f43a57
ignoring vscode folder
tacodevhaydz Aug 13, 2026
91b2e02
Merge branch 'feature/mbooster' of https://github.com/tacodevhaydz/AZ…
tacodevhaydz Aug 13, 2026
428628b
Merge pull request #1 from giantorth/main
tacodevhaydz Aug 13, 2026
9162714
Merge pull request #128 from giantorth/dev
giantorth Aug 15, 2026
2afb175
Merge branch 'giantorth:main' into feature/mbooster
tacodevhaydz Aug 17, 2026
8419dc2
Merge remote-tracking branch 'upstream/dev' into feature/mbooster
tacodevhaydz Aug 17, 2026
c8341ca
Merge branch 'feature/mbooster' of https://github.com/tacodevhaydz/AZ…
tacodevhaydz Aug 17, 2026
0e0ff91
Merge remote-tracking branch 'upstream/dev' into feature/mbooster
tacodevhaydz Aug 18, 2026
9762b9d
bug fix for max force and max threshold miscommunication
tacodevhaydz Aug 18, 2026
d34f1b0
Merge pull request #3 from tacodevhaydz/bugfix/mbooster-max-force-thr…
tacodevhaydz Aug 18, 2026
e825d6b
redesign input curve editors to match pithouse config
tacodevhaydz Aug 19, 2026
f30f3c9
shifting pedal feel UI to mirror pithouse design
tacodevhaydz Aug 19, 2026
3b36fa3
UI change to segmented damping
tacodevhaydz Aug 19, 2026
1f0a6eb
fixing segmented editor
tacodevhaydz Aug 19, 2026
988f229
adding toggles to friction and damping controls
tacodevhaydz Aug 19, 2026
88b7bb6
adding localisation, fixing ui bugs
tacodevhaydz Aug 19, 2026
0930ed7
input curve controls update
tacodevhaydz Aug 19, 2026
91bd9b3
fixed role flipping bug
tacodevhaydz Aug 19, 2026
69ce5e7
profile loads correct vlaues on start up
tacodevhaydz Aug 19, 2026
71031f5
updated damping curve rendering
tacodevhaydz Aug 19, 2026
c66833a
seeding ui with loaded profile values
tacodevhaydz Aug 19, 2026
6eaf02d
adding diagnostics for profile seeding
tacodevhaydz Aug 19, 2026
01927cc
fixing profile re-seeding issue, still
tacodevhaydz Aug 20, 2026
c4cac8a
fixing profile re-seeding
tacodevhaydz Aug 20, 2026
4b78020
correcting input curve editors with additional nodes
tacodevhaydz Aug 25, 2026
fd89ab8
int overflow on max force
tacodevhaydz Aug 25, 2026
d641551
max force fixes
tacodevhaydz Aug 25, 2026
6faf868
re-implementation of max-threshold
tacodevhaydz Aug 26, 2026
7376996
input force measured on screen
tacodevhaydz Aug 26, 2026
319c0a6
Merge remote-tracking branch 'origin/dev' into feature/mbooster
giantorth Aug 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
_todo.md
.vscode/*

## Claude stuff
.claude*
Expand Down
90 changes: 62 additions & 28 deletions Devices/MBoosterDeviceController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,10 @@ public sealed class MBoosterDeviceController : IDisposable
// (mbooster-brake-threshold); -1 until it answers. Live only, never
// persisted and never copied into MBoosterDeviceSettings.MaxThresholdKg:
// that field's -1 means "user set no override", and seeding it would make
// the plugin start writing the value back on every connect. Used purely as
// ApplyDeadzoneAndMaxForce's fullScaleKg reference in place of the old
// hardcoded 200kg guess. Volatile: written on the serial read thread, read
// by the HID thread and the UI.
// the plugin start writing the value back on every connect. Surfaced in
// Diagnostics (see DiagnosticsTextBuilder) as a read-back sanity check.
// Volatile: written on the serial read thread, read by the HID thread
// and the UI.
private volatile float _deviceReportedMaxThresholdKg = -1;
public float DeviceReportedMaxThresholdKg
{
Expand Down Expand Up @@ -246,9 +246,23 @@ public int SoleActiveAxis()
// Per-axis pre-input-curve percent (0..100) — the same signal as
// LastRawPercentPreCurve (after deadzone/max-force, before the input
// curve) but for EVERY pedal, so the settings tab's live curve markers
// track whichever pedal is selected, not just the master.
// track whichever pedal is selected, not just the master. NOTE: since
// MozaMBoosterRegistry.OnHidAxisUpdate added the host-side Max
// Threshold rescale, this is "% of Threshold's span" (the Sim Input
// Mapping curve's own input domain) — see LastAxisRawPercentPreThreshold
// below for the true raw reading (% of Max Force's span) instead.
public readonly double[] LastAxisRawPercentPreCurve = new double[MaxAxes];

// Per-axis TRUE raw HID percent (0..100), captured BEFORE the host-side
// Max Threshold rescale (see MozaMBoosterRegistry.OnHidAxisUpdate) —
// i.e. genuinely "% of Max Force's own hardware ceiling", the physical
// force the user is actually applying to the pedal. This is the
// Pedal Feel curve's real input domain (Deadzone-Max Force span), and
// what the "Input Force" live label/marker should show — unlike
// LastAxisRawPercentPreCurve, which is now post-Threshold-rescale and
// represents the Sim Input Mapping curve's own (different) domain.
public readonly double[] LastAxisRawPercentPreThreshold = new double[MaxAxes];

// Highest axis index + 1 the HID has reported for this lane: 1 for a
// lone pedal, up to 3 for a full chain. 0 until the first axis update.
public int AxisCount { get; internal set; }
Expand Down Expand Up @@ -1181,34 +1195,54 @@ public bool SendFloatWrite(string commandName, float value, byte? device = null)
}

/// <summary>
/// EXPERIMENTAL / unverified — resend the output curve at 7
/// breakpoints (<c>mbooster-brake-curve7-*</c>, cmdId 0xAB) after a
/// real hardware calibration write. pedal_travel.pcapng showed Pit
/// House doing exactly this alongside a Travel Start write, and
/// omitting it is what made Travel Start/End silently no-op on
/// hardware despite the raw register write reading back fine — see
/// MozaCommandDatabase.cs's mbooster-brake-curve7-* comment. Callers
/// use this after any of Travel/Endstop/Ratio/Threshold's own writes
/// on the theory that the same firmware requirement applies to all of
/// them, not just Travel — unconfirmed for the others.
/// Write Deadzone, Max Force, and the Pedal Feel curve's 6 nodes on
/// BOTH axes between them (cmdId 0xAB selectors 0x01-0x0E) as one
/// atomic burst — CONFIRMED real hardware calibration. The Y half
/// (selectors 0x07-0x0E: Deadzone, 6 nodes, Max Force) is reverse-
/// engineered from max-force-24-75-128-166-200.pcapng and
/// deadzone-0-5-11-14.pcapng (bug bundle 5VR5AQ8Y): every Deadzone or
/// Max Force change in both captures resent the whole 8-value family
/// together, not just the field that moved — same "no partial
/// update" shape as Segmented Damping. The X half (selectors
/// 0x01-0x06, one per node) is reverse-engineered from
/// pedal-feel-node{2,5}-{x,y}-adjust.pcapng: every isolated single-
/// node drag (on EITHER axis) wrote that node's X selector and its Y
/// selector together, X first — sent here in the same order for
/// consistency, though a full resync's exact intra-burst ordering is
/// unconfirmed to matter. All fields use the identical kg encoding
/// as Max Threshold (<see cref="MozaMBoosterProtocol.EncodeThresholdKg"/>).
/// <paramref name="inputCurveY"/>/<paramref name="inputCurveX"/> are
/// the Pedal Feel curve's own 6 user-adjustable nodes per axis
/// (0-100%, null/wrong-length = use the default Linear shape) — see
/// <see cref="MozaMBoosterRegistry.ComputeFeelCurve"/>.
/// </summary>
public void PushCurve7Resync(float[]? curveX, float[]? curveY, byte device)
public void PushFeelCurveResync(double deadzoneKg, double maxForceKg, float[]? inputCurveY, float[]? inputCurveX, byte device)
{
var curve7 = MozaMBoosterRegistry.ResampleCurveAtSevenths(curveX, curveY);
for (int i = 0; i < curve7.Length; i++)
SendIntWrite($"mbooster-brake-curve7-{i + 1}", MozaMBoosterProtocol.EncodeCurve7Point(curve7[i]), device);
SendIntWrite("mbooster-brake-deadzone", MozaMBoosterProtocol.EncodeThresholdKg(deadzoneKg), device);
var midX = MozaMBoosterRegistry.ComputeFeelCurve(deadzoneKg, maxForceKg, inputCurveX);
var midY = MozaMBoosterRegistry.ComputeFeelCurve(deadzoneKg, maxForceKg, inputCurveY);
for (int i = 0; i < midY.Length; i++)
{
SendIntWrite($"mbooster-brake-feelcurve-x-{i + 1}", MozaMBoosterProtocol.EncodeThresholdKg(midX[i]), device);
SendIntWrite($"mbooster-brake-feelcurve-{i + 1}", MozaMBoosterProtocol.EncodeThresholdKg(midY[i]), device);
}
SendIntWrite("mbooster-brake-maxforce", MozaMBoosterProtocol.EncodeThresholdKg(maxForceKg), device);
}

// ── Coalescing gate for UI-driven calibration writes ──
// A slider raises ValueChanged per tick, and every one of these commands
// is a flash-backed calibration register that additionally drags a
// 6-frame PushCurve7Resync burst behind it. Bundle KY3HK4QP shows what
// that costs unthrottled: a ~2 s Max Threshold drag emitted 77 threshold
// + 462 curve7 frames, ~40 writes/second into flash. So UI writes are
// parked in a latest-wins slot per (device, command) and flushed once the
// user stops moving, collapsing a whole drag into one write set. Same
// pending+coalesce+throttle shape HardwareApplier.QueueWheelCfgWrite uses
// for the wheel's own flash-backed writes, minus its change cache.
// A slider raises ValueChanged per tick, and every one of these
// commands is a flash-backed calibration register — writing it on
// every tick of a drag would hammer flash unnecessarily. (An
// earlier design also dragged a 6-frame curve7 resync behind every
// write here, motivated by bundle KY3HK4QP's "~2s Max Threshold
// drag emitted 77 threshold + 462 curve7 frames" cost — that resync
// was later removed as unconfirmed/unneeded, but the coalescing
// below is still worth it purely for the primary writes.) UI writes
// are parked in a latest-wins slot per (device, command) and
// flushed once the user stops moving, collapsing a whole drag into
// one write set. Same pending+coalesce+throttle shape
// HardwareApplier.QueueWheelCfgWrite uses for the wheel's own
// flash-backed writes, minus its change cache.
//
// The connect-time apply (MozaPlugin.ApplyMBoosterToHardware) deliberately
// does NOT go through this — it fires once and must not be deferred.
Expand Down
Loading