Switch 2 runtime registration boundary¶
Status: dormant and offline-verified. One mixed-transport service now owns one
transaction core for Switch 2 Pro USB, standalone Switch 2 Bluetooth, and an
explicitly joined Joy-Con 2 pair. This boundary still has no ControlService,
DS4Devices, discovery, output, or hardware call site.
Purpose¶
Physical Switch 2 transports do not own DS4Windows controller slots. A
transport produces validated, generation-bound canonical input; one runtime
device projects that input into the existing DS4State/Report mapping seam;
and the service owns slot admission, mapping callbacks, profile actions, and
removal. InputControllerRegistrationTable is the transaction boundary
between those owners.
The table deliberately does not identify devices by serial number, path, or
display name. Its authority is an exact tuple of private table issuer, service
generation, slot generation, DS4Device reference, logical runtime generation,
and registration-owner reference. This permits identical or deliberately
anonymous controllers without making a mutable identity string an ownership
credential.
Attach transaction¶
Switch2RuntimeRegistrationTransactionCore executes these steps in order for
the participant selected by Switch2RuntimeRegistrationService. The older
Switch2ProUsbRuntimeRegistrationCoordinator remains a USB compatibility
facade over the same core rather than a second implementation:
- Validate the exact owner-created, owner-authenticated
InputControllerRegistration. - Atomically reserve and bind an available slot, retaining both the exact
token and
SetupRollbackClaim. A retained setup-publication epoch prevents service close from passing this operation while the table call itself runs outside the core lifecycle gate. - Ask the physical owner to adopt that exact bound token. The first distinct table/token wins and returns a private-fenced adoption credential. A competing table that loses adoption can roll back only its own Bound slot; it receives no authority to abort or prepare the winning physical owner.
- Publish a binding which retains the exact setup rollback and owner-adoption
credentials, then subscribe one exact
ReportHandler<EventArgs>delegate. Complete all other external setup outside the table lock. - Prepare the physical owner: arm the runtime and start its worker parked behind an exact, single-use commit credential. Every fallible worker-start operation occurs here, and no native read/report is permitted yet.
- Acquire a retained activation epoch under the coordinator gate, begin table activation, and acquire the table-issued activation-commit credential. Release all coordinator/table locks before invoking the fallible owner commit. Complete table activation only with that exact commit credential. Close waits for the retained activation epoch, so it cannot manufacture a retirement claim in the middle of this external commit transaction.
If table admission fails, the coordinator has received no ownership credential
and therefore does not mutate the supplied runtime owner. If table admission
succeeds but owner adoption loses, it rolls back the local table claim without
calling any owner cleanup method. This is essential when another coordinator
already owns that same physical lifetime. If any later step before table
activation fails, the coordinator boundedly aborts the provably unpublished
physical/runtime lifetime with the exact adoption or prepare credential,
unsubscribes that exact delegate, then rolls back the table and clears the
exact binding in one lifecycle-gated transaction. The abort path proves
zero native reads and zero reports and therefore neither publishes nor
acknowledges terminal neutral. A commit failure after table activation is an
invariant failure and quarantines the exact slot; it must not be disguised as
a Bound rollback. TryCancel is valid only before binding. A service close
revokes raw reservations, preserves bound setups for exact rollback, and moves
attached slots to Retiring.
Report admission¶
Switch2RuntimeInputDevice reuses two preallocated, immutable
Switch2RuntimeReportEventArgs instances. Every event identifies either
Regular or TerminalNeutral and carries the device's exact nonzero runtime
generation. The coordinator rejects any sender, event type, report kind,
or runtime generation that does not match its token before calling the mapping
callback.
For an accepted regular event, the coordinator acquires a normal report lease,
calls the existing service mapping callback outside table locks, and disposes
the lease in finally. Retirement closes ordinary report admission before the
transport is stopped.
Before that regular Report, the runtime publishes its already decoded,
oriented, family-calibrated DS4State.Motion through the established
DS4SixAxis.SixAccelMoved event. Switch 2 runtimes are primary gyro sources,
so normal profile setup attaches the existing Gyro Controls, Gyro Mouse, Gyro
Mouse Joystick, and steering consumer. The runtime does not invoke the legacy
raw-report calibration a second time. One per-device borrowed event envelope
is refreshed in place, keeping the subscribed steady-state path allocation-
free. A terminal-neutral report never manufactures a motion event. A throwing
motion observer makes the regular publication fail visibly while the runtime
still invokes ordinary Report observers, releases its publication gate, and
retains terminal-neutral progress.
An exclusive action is not permission to discard a physical report. Before
requesting an action lease, the future orchestrator must suspend the exact
producer generation at a boundary which preserves its bounded transition
journal; after the lease is released it must resume or explicitly retire that
generation. The USB runtime owner now distinguishes temporary
PublicationBusy internally and waits boundedly before retrying the same exact
frame, so its outer Boolean sink returns false only for a terminal refusal.
Profile/action wiring still requires a synchronous adapter operation that owns
the runtime publication gate before acquiring the table action lease, releases
the table lease before reopening publication, and never falls back to an
asynchronously queued action.
For the one terminal event, the coordinator acquires the retirement claim's
terminal report lease, calls the same mapping callback with the neutral
DS4State, and acknowledges terminal neutral only after that callback returns
successfully. Event publication is not itself service acknowledgement. The
lease is then disposed, allowing the retirement drain to complete.
Stop and removal transaction¶
The coordinator uses one outer monotonic deadline:
TryBeginRetirebefore asking the owner to stop. This closes ordinary report and new action admission.- Wait for every already-admitted ordinary report/action lease to drain, then wait for the runtime publication tail to commit after the mapping handler returns. Terminal admission is deliberately exclusive and rejects while any lease remains. The second boundary is required because the runtime retains its publication gate while it drains post-handler actions and commits state.
- Cancel and drain the exact physical input generation.
- Publish exactly one typed terminal-neutral report.
- Wait again for the acknowledged terminal report lease to drain.
TryMarkQuiescedonly after terminal acknowledgement and both drains.- Ask the exact registration owner to remove its runtime objects without blocking.
- Only after external removal succeeds, perform
TryCompleteRemovaland clear the exact token/reference-matched binding in one lifecycle-gated transaction. A new attach therefore cannot observe a reusable table slot while the prior binding is still installed.
A removal request made reentrantly from the exact report/terminal callback must fail boundedly rather than waiting on its own lease. A service-close snapshot may supply the existing exact retirement claim; it must not begin a second retirement lifetime.
Callback admission and removal/close ownership are atomic per binding. A cross-thread removal request made while a mapping callback is active is also rejected immediately; this prevents a callback from waiting on a task whose removal is itself waiting for that callback's lease.
A timeout, owner exception/authentication loss, missing terminal acknowledgement, drain failure, or uncertain removal quarantines that exact slot lifetime. Quarantine persists across later service generations; healthy slots remain reusable. The service must never infer successful teardown from a missing controller path or a disconnected transport.
After TryClose has successfully closed the table, the coordinator retains an
exact service-generation close epoch containing immutable slot snapshots and
binding identities. One teardown owner advances that epoch. A short observer
timeout releases only execution ownership, not close intent or teardown state;
the same-generation retry joins or resumes the epoch and receives its cached
terminal result. TryOpen remains fenced while an epoch is incomplete, so a
new service generation cannot overtake old teardown.
Automatic lifecycle attention¶
The owner exposes one preallocated, immutable, exact-generation lifecycle attention envelope. The first malformed or cross-generation input rejection, mapping-subscriber rejection, or native read/retirement failure wins; later sources are coalesced. Pump and owner callbacks run outside their locks and the binding only queues service-owned retirement, so the pump worker never joins itself. Automatic retirement waits until the attach transaction has completed: a successful setup proceeds through normal retirement, while failed setup or post-commit quarantine retains ownership of its own result.
This wake-up is not teardown evidence. The coordinator still performs
TryBeginRetire, both drains, owner stop and typed terminal publication,
terminal acknowledgement, exact delegate removal, owner removal, and table
completion. Failure at any stage quarantines the exact token.
Proven offline properties¶
- Cross-table, copied, default, stale, and ABA credentials fail closed.
- Service, slot, report-lease, and action-lease counters never wrap.
- Report leases use fixed per-slot cells and allocate no managed memory after construction.
- Exclusive actions close report admission and drain existing reports with a bounded wait.
- Close versus activate, close versus action wait, concurrent anonymous registration, hostile owner callbacks, and per-slot quarantine are covered by deterministic tests.
- The typed regular and terminal report paths use preallocated envelopes and allocate zero bytes after warm-up.
- Projected Pro, standalone Joy-Con 2, and joined Joy-Con 2 motion reaches the existing gyro mapping event before the same regular Report; the subscribed steady-state path also allocates zero bytes after warm-up.
- Bind plus binding insertion and Attached plus parked-worker commit are each serialized against close by retained setup/activation epochs and the narrow coordinator lifecycle gate; fallible owner operations execute outside it.
- Two independent tables racing to attach the same physical owner have exactly one adoption winner. The loser removes its own Bound slot without aborting the winner.
- USB, standalone Bluetooth, and joined Joy-Con participants can attach in
parallel to one table and coexist in
Attached. One service close retires every exact owner through the same core and observes one terminal neutral and one removal for each lifetime. - The mixed service owns no second gate, table, mapping callback, or transport lifecycle. Participant factories, participant methods, and mapping callbacks are characterized outside the core lifecycle gate.
- A service-close observer timeout after table closure preserves an exact resumable close epoch; a new open cannot overtake it.
- Deterministic barriers cover close during parked prepare, attention arriving before commit returns, active-callback close/removal, and the runtime post-handler publication tail.
These properties do not establish live controller compatibility, application visibility, latency, reconnect behavior, or safe haptic output. Production wiring and hardware validation remain separate gates.