Skip to content

Releases

v0.3.13

Choose a tag to compare

@github-actions github-actions released this 28 Apr 05:09
v0.3.13: write only nextPhysRotation; rewrite README to lead with dea…

v0.3.12

Choose a tag to compare

@github-actions github-actions released this 28 Apr 04:56
v0.3.12: tag float configs with AcceptableValueRange so r2modman rend…

v0.3.11

Choose a tag to compare

@github-actions github-actions released this 28 Apr 04:51
v0.3.11: bump deadzone defaults from 5% to 20% to suppress puck drift

v0.3.10 SHIPPED A WORKING MOD — siappdll motion events flow into
HeldObjectController and physRotation writes apply visibly to the
held object. The remaining issue is jitter from the puck's idle drift
(every SpaceMouse has ±2-15% sensor noise at rest depending on age),
which our 5% default deadzone wasn't catching, so accumulated
micro-rotations made the held object oscillate.

Bumps both Translation and Rotation deadzone defaults to 20% (0.20)
which reliably suppresses idle drift on consumer SpaceMouse Wireless
units. Users with a fresher unit can dial it back to 0.10 or even
0.05 in BepInEx/config/com.zanewebb.spacemouse_repo.cfg without
restarting the game (config changes apply live).

Comments updated to point users at this knob if they still see
jitter.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

v0.3.10

Choose a tag to compare

@github-actions github-actions released this 28 Apr 04:44
v0.3.10: call SiGetEventWinInit before SiGetEvent (canonical pattern)

v0.3.9 confirmed siappdll IS dispatching messages: all 20 logged were
msg=0xC20B (a registered Si message) — but SiGetEvent returned
SI_SKIP_EVENT for every one. SI_SKIP_EVENT means 'siappdll handled it
internally, you should ignore.' We never got SI_IS_EVENT for motion.

The SDK's canonical call pattern is:

  SiGetEventData ed;
  SiGetEventWinInit(&ed, msg, wParam, lParam);  // initializes hidden state
  SiGetEvent(handle, 0, &ed, &spwEvent);

We were skipping SiGetEventWinInit and constructing SiGetEventData via
C# field-initializer. That fills the public msg/wParam/lParam fields
but may leave native-only hidden state uninitialized that SiGetEvent
checks before classifying the message as a real Si event.

Adds the SiGetEventWinInit P/Invoke and calls it before SiGetEvent.
If that still gives SI_SKIP_EVENT for everything, the next move is
abandoning siappdll for 3DxNavLib (the modern API that doesn't go
through window messages at all).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

v0.3.9

Choose a tag to compare

@github-actions github-actions released this 28 Apr 04:36
v0.3.9: log first 20 raw Windows messages with SiGetEvent return code

v0.3.8's heartbeat showed:
- wndProcCalls jumped 16 → 184 → 322 (~30/sec) during puck wiggle
- siEvents/siMotion stayed 0 the whole time
- axes always (0,0,0)

So *something* is being dispatched to our hidden window during input,
but SiGetEvent doesn't recognize the messages as Si events. Either
they're regular Windows messages firing on input (raw input, focus,
etc.) or siappdll is sending them with a format that doesn't match
SiGetEvent's expectations.

Logs the raw msg/wParam/lParam plus SiGetEvent return for the first
20 calls, in InfoLog so it ends up in BepInEx's LogOutput.log. From
that we'll know:
- if msg values are in WM_USER (0x400) + 0x6000-ish range → these
  ARE 3DxWare events; the issue is in our SiGetEvent call/struct
- if they're ordinary Windows messages (WM_INPUT 0xFF, WM_TIMER 0x113,
  WM_KEYDOWN, etc.) → siappdll isn't dispatching events; the driver
  is using the legacy mouse-emulation path for R.E.P.O. and we'd need
  to switch to 3DxNavLib (option C) instead

No behavior change, pure diagnostic.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

v0.3.8

Choose a tag to compare

@github-actions github-actions released this 27 Apr 23:35
v0.3.8: heartbeat reports SDK telemetry to diagnose silent SpaceMouse

v0.3.7's side channel proved patches fire and grab is detected, but
'First rotation write' never logged — meaning the SpaceMouse delta is
identity every frame. Either siappdll isn't dispatching motion events
to our hidden window, or events arrive but parsing yields zero.

Adds counters/values to SpaceMouseSdk that the per-5s heartbeat reads
and logs:
- WindowProcCalls — total times our background-thread WndProc was hit.
  Stays 0 if message routing is broken (driver sends events to a
  different window, or our hidden window isn't picking up the queue).
- SiAnyEvents / SiMotionEvents — out of those WndProc calls, how many
  were Si events (vs other Windows messages) and how many were
  specifically motion events.
- RawAxes — current normalized axis values [-1..1]. If non-zero while
  the user wiggles the puck, parsing works and the issue is in our
  identity-check or HeldObjectController math.

Heartbeat line goes from a bare 'tick #N' to:
  [heartbeat] tick #N t=Xs | active=True wndProcCalls=NNN siEvents=NN
  siMotion=NN axes=T(0.00,0.00,0.00) R(0.00,0.00,0.00)

This narrows the failure to one of three places without another ship.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

v0.3.7

Choose a tag to compare

@github-actions github-actions released this 27 Apr 23:23
v0.3.7: read/write PhysGrabber fields via Traverse to bypass access c…

v0.3.6

Choose a tag to compare

@github-actions github-actions released this 27 Apr 23:01
v0.3.6: ship System.Numerics.Vectors.dll alongside the plugin

v0.3.5's BepInEx LogOutput.log was 2.2 MB / 31k lines, almost entirely
this exception repeating every frame:

  FileNotFoundException: Could not load file or assembly
  'System.Numerics.Vectors, Version=4.1.3.0, ...'
  (wrapper dynamic-method) PhysGrabber.DMD<PhysGrabber::Update>

Our Core project uses System.Numerics.Quaternion and Vector3, which
live in System.Numerics.Vectors.dll. The Release build correctly
emits it next to SpaceMouseRepo.dll, but the package script and CI
workflow only copied the two SpaceMouseRepo DLLs into the staging
directory — System.Numerics.Vectors.dll never made it into the zip.
Unity's Mono runtime doesn't have this assembly in the GAC, so
Harmony's JIT-compiled wrapper for PhysGrabber.Update threw on every
frame, taking the game hostage.

Fix: copy System.Numerics.Vectors.dll in both packaging paths.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

v0.3.5

Choose a tag to compare

@github-actions github-actions released this 27 Apr 20:58
v0.3.5: move rotation injection from GetRotationInput to Update postfix

v0.3.4 confirmed two things from the BepInEx log:
- Lifecycle workaround (orphan + DontDestroyOnLoad + HideAndDontSave)
  unblocked patches: PhysGrabber.Update POSTFIX fired at line 28,
  Plugin.Update heartbeats fired every 5s for 2+ minutes
- GetRotationInput POSTFIX never appeared, even during gameplay

So PhysGrabber.GetRotationInput() simply isn't called by the vanilla
code path that drives normal grab rotation — it's likely only invoked
when a specific keyboard rotation modifier is held. The actual per-
frame rotation must happen elsewhere (probably in Update or
ObjectTurning).

This release moves the rotation injection into the Update postfix
that we know fires. On each call we:
- gate on isLocal && grabbed
- accumulate the SpaceMouse delta in HeldObjectController
- compose ourDelta * vanilla.physRotation and write it back
- mirror to nextPhysRotation in case downstream reads from that field

Three new diag log lines (each fires once per session):
- 'PhysGrabber.Update POSTFIX firing.' — confirms the new postfix
- 'First time observing grabbed=true on local PhysGrabber.' — confirms
  we hit the holding branch
- 'First rotation write: before=(...) delta=(...)' — confirms we
  computed and wrote a non-identity rotation

Stub PhysGrabber.cs adds nextPhysRotation since we now write to it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

v0.3.4

Choose a tag to compare

@github-actions github-actions released this 27 Apr 01:03
v0.3.4: orphan GameObject + DontDestroyOnLoad to survive scene changes

The v0.3.3 side-channel diagnostic was conclusive: Plugin.Awake fires
once and writes its 'Awake complete' line, then Update never runs
again — across 5+ minutes of gameplay we got zero heartbeats. The
plugin's MonoBehaviour is being torn down between Awake and the first
in-game frame.

The working DualGrab mod's decompiled Awake explicitly orphans its
GameObject (parent = null) and sets HideFlags.HideAndDontSave. We were
relying on BepInEx's default lifecycle, which is apparently not
sufficient in R.E.P.O. — its scene transitions seem to clean up the
BepInEx_Manager GameObject our component lived on. Replicating
DualGrab's three-step trick (reparent to root, hide+don't-save flag,
explicit DontDestroyOnLoad) should keep our component alive across
the main-menu → level transition.

Also bundles the new 256x256 logo (six-axis SpaceMouse puck design)
into the package — first non-placeholder icon on Thunderstore.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>