ThePirateRay/docs/research/rayforge-addon-spike.md

3.6 KiB

Rayforge external binary-driver addon spike

  • Date: 2026-10-18
  • Upstream: barebaric/rayforge
  • Commit: c28918c87f8273dd1d244f5a057addc192807fa5
  • Rayforge addon API: 23
  • Scope: Windows, external addon lifecycle, no device or transport

Result

An external addon loaded through Rayforge's real AddonManager and registered PirateRayJCZNoDeviceDriver. The driver declares uses_gcode = False, has no discovery specification or transport, and returns a recognizable binary test payload from an OpsEncoder.

The following lifecycle completes against the pinned Rayforge checkout:

  1. load the external addon;
  2. resolve the driver by class name;
  3. create its binary encoder and verify its payload;
  4. disable the addon and verify driver removal;
  5. re-enable it and verify re-registration;
  6. reload it and verify there is exactly one registry entry;
  7. package its source and manifest files into a ZIP; and
  8. disable it cleanly.

The current checked result is tests/fixtures/rayforge-addon-spike.json with SHA-256 c7a7ba7d561af58829c9c0fc271234e7600d50cca5f8125fc574fa3f0a1aa25f.

Registration seam finding

Rayforge's addon hook specification still exposes register_machines(machine_manager), but the inspected startup registration table does not invoke that hook, and MachineManager has no method for adding a driver class. The source-level driver registry instead exposes rayforge.machine.driver.register_driver(driver).

The working spike therefore registers during import of the addon's worker entry point and removes its class in on_unload. All access to the mutable module-level registry is now isolated in rayforge_compat.py. The adapter accepts only addon API 23, checks the expected registry shape, refuses class name collisions, is idempotent, and verifies symmetric cleanup. Contract drift therefore fails closed instead of silently replacing another driver. Before a production addon, Rayforge should still expose a dedicated addon-aware driver registry hook with supported unregister behavior.

Reproduce

Create an isolated environment containing Rayforge's non-GUI runtime driver dependencies, then run:

python scripts/spike_rayforge_addon.py C:\path\to\rayforge

The script refuses any Rayforge revision other than the pinned commit.

What this proves

  • An external addon can load a binary, non-G-code driver without patching the Rayforge source tree.
  • Its worker entry point is sufficient in headless mode.
  • The driver can remain completely free of discovery and transport.
  • Addon load, disable, enable and reload work when registration and cleanup are handled explicitly.
  • The addon itself can be packaged as a deterministic set of files on Windows.

What remains open

  • The ZIP check is addon packaging, not a proof that a complete PyInstaller or MSIX Rayforge build discovers an externally installed addon.
  • The compatibility adapter contains the unstable seam, but does not make it a supported addon API. Upstream feasibility should be assessed before relying on it in production.
  • No JCZ command encoding, machine profile, UI, project reload, discovery, transport, controller status or cancellation is implemented here.
  • Nothing in this spike authorizes USB access, galvo movement or emission.

Decision impact

Spike B is a qualified go: the external binary-driver boundary is viable without a Rayforge core fork, but registration lifecycle support should be made explicit upstream. The next technical proof is Spike C: translate a tiny Rayforge Ops stream into a deterministic no-I/O JCZ trace and compare its semantics with the pinned BalorMK mock trace.