Player assignment and input contract¶
Status: This is a dated input design and batch record. The Input System foundation exists, but production input and keybinding integration remain incomplete. The owner now directs preservation of binding data before removing legacy input, with no fallback; follow the roadmap.
Current update, 2026-11-02: The production gameplay input map now also has Reroll, with R, period for the arrow-keyboard preset, and gamepad RB as defaults. The original IDs are preserved; older saved profiles migrate in memory and keep their existing bindings. Simulated-device and reward-dispatch checks passed. Remapping UI and real-device checks remain open. The detailed batches below are historical. See control tests for current status.
R0 design deliverable, 2026-10-25; owner scope clarification added 2026-10-26. This specifies the first shared gameplay boundary; it does not implement the Input System, change scene assets or establish runtime equivalence. Follow the rebuild plan, source audit and existing issue evidence. Proposed type labels below describe responsibilities, not mandatory class names.
Current implementation boundary¶
Batch 37 pins Input System 1.17.0 and verifies slot/device ownership (9/9 simulated-device groups). Batch 38 adds per-slot action copies, a raw command source and Blocked/Gameplay/Reward routing (8/8, exit0). The existing InputUser remains the only pairing owner. Stable player/user identity survives retry/reload; held button edges cannot cross contexts or run boundaries. Loss/reassignment requires neutral input before a new press. Run-bound sessions stay blocked after release until a fresh run activates them.
Production actors, menus, prompts and reward selection still use legacy input. No persistent profile writing, capture/cancel/reset/conflict UI, setup/recovery UI or gameplay migration is complete. Legacy preset conversion is verified in memory by batch 39; it is not connected to the normal loader. Native reconnect without a serial requires explicit reassignment; setup mutators stay locked during a run. Physical behavior is unverified. Normal game data is preserved. Batch 43 intentionally changes GameSettings preset application and ConfigDocument; other pre-batch project bytes remain preserved as recorded in its evidence.
Batch 39 now verifies in-memory versioned preference profiles and legacy conversion (9/9 Edit Mode groups). See the profile wrapper contract for fields, revision/invalid-data rules and preservation. Batch 39 produces disabled disposable copies; batch 40 now applies validated profiles to isolated blocked sessions through the API below, without changing normal config or production consumers. The typed writer before batch 45 would drop inputBindings; the production config path now uses the document owner. Batch 42 verifies an independent full-config document/file adapter on disposable Linux paths; batch 43 now supplies explicit legacy-preset document edits and read-only legacy preset loading. Batch 44 separates audio/locale loaded application while preserving explicit first-run screen persistence. Batch 45 wires the full-document owner at the production config boundary and blocks generic typed config writes (10/10 new + 34/34 regression groups). Built-player/Windows/recovery acceptance and setup/remap UI remain open.
Concrete R2b action and command contract¶
The authoritative GameplayPlayer.inputactions contains one Player map, four actions (Move, Dash, Stats, Pause), 24 bindings and 29 stable map/action/binding IDs. Preserve those IDs; do not regenerate them to edit bindings. No global default input asset is assigned. Every session clones the definition, associates it with its slot's existing InputUser and filters by device plus KeyboardWASD, KeyboardArrows or Gamepad group. The template remains disabled and unchanged.
| Group | Move | Dash / Stats / Pause |
|---|---|---|
| KeyboardWASD | W/A/S/D; unnormalized digital composite | Space / Left Shift / Escape |
| KeyboardArrows | arrow keys; unnormalized digital composite | Slash / Comma / P |
| Gamepad | left-stick directional parts | South / left shoulder / Start |
PlayerCommandFrame exposes raw movement, separate negative/positive unprocessed directional pressures, filtered Movement and consumable button edges. Gameplay emits these plus Dash/Stats/Pause; Reward emits raw horizontal/Confirm using the same Dash binding and leaves gameplay Movement/pressure fields zero; Blocked emits nothing. Raw values still bypass SDK stick processors. Movement is the comparison-adapted input vector, not normalized velocity. Shared menus, Back/Tabs/dialogue/repeat and Reroll are not migrated by this frame.
Measured movement input · batches 51/52¶
Batch 51 pins actual default-WASD legacy axes and Player.Movement: 8,220 original samples, 2,967 retained boundaries, with original source/axis hashes in LegacyMovementReference.json. KeyboardMovementFilter uses sensitivity/gravity 5/3 with scaled delta; opposed holds retain prior values, release decays toward zero and reversal preserves its measured operation order. Nonzero smoothed values below .001 remain observable. Delta 0 freezes state, including changes in held keys; a held direction resumes without a fresh press.
Batch 52 places this history inside PlayerInputSession, not the actor. Its dynamic SDK update advances it once per Unity frame even with blocked commands/no active actor. ReadFrame fills only an unsampled keyboard frame; repeated reads cannot integrate time again. Retry, context switches and actual scene reload retain history; replacement/loss of its device resets it. Default WASD pre-activation/held retry are verified within a continuing session. Held input predating creation of a new input owner, focus and rebind-history rules still need their actual integration decisions; do not claim those resolved from a fresh neutral fixture.
LegacyGamepadReference.json pins 34 distinct pairs from 6,506 original Linux uinput/legacy/SDK observations. GamepadMovementFilter applies per-axis .19 dead-zone rescaling, rather than a radial processor. Actual Linux SDL readings need signed-16 endpoint conversion 32768/32767 before that rescale; the session selects it only for SDL on Linux. Standard normalized-domain processing on other interfaces/platforms remains subject to their physical/Windows comparison. Y sign agrees in the captured backend; analog changes are observable while scaled time is 0. The corrected actual-session comparison matches all 6,379 observed keyboard/joystick axes exactly across 48 controlled phases. This is OS-backed virtual input, not physical hardware acceptance.
PlayerMovementDriver reads one owned command frame each Update, passes Movement to Player.ApplyMovementInput and retains LastCommands for later consumers. Future ability/menu consumers must use that frame rather than taking button edges again. The driver does not execute Dash/Stats/Pause. Actor preparation explicitly opts into this module; the development launcher now does so. Production actors still use their legacy InputManager adapter, but share the extracted motor. Exclusive one-device-per-slot ownership remains the target policy; the legacy shipping adapter still has its original keyboard/joystick fallback until production rollout. Full UI/remap/four-player and F1 acceptance remain open.
Pinned 1.17.0 SaveBindingOverridesAsJson serializes an outer bindings array. Each entry carries action (map/action path), binding id, override path, interactions and processors; absent override fields use the literal string "null" in this SDK. An empty path disables a binding and must remain distinct from an absent override. Installed SDK InputActionMap.BindingOverrideJson and InputActionRebindingExtensions are the primary implementation references. A06 round-trips one path override onto the same IDs and verifies player/template isolation in memory. Batch 39 now verifies a versioned application wrapper, preference identity, invalid/unknown data handling and legacy conversion; actual persistence, production capture/settings UI and physical/legacy-equivalence checks remain PS-03 work. Do not write SDK JSON directly into normal config.
R2d applied profiles and reversible drafts¶
Batch 40 verifies ApplyProfile/BeginBindingEdit on PlayerInputSession: Context must be Blocked, the device/collection must still belong to the slot, and profile slot/family/authored definition must match. Apply changes overrides and the keyboard group on the existing action collection; it never creates/re-pairs a user. AppliedProfileId records the active in-memory choice. Standalone layout/manual-refresh APIs clear that marker; they cannot bypass an open draft.
Only one InputBindingDraft may own the session. Current is an immutable candidate; TrySetBinding validates before previewing, ResetToDefaults affects only its selected profile/scheme, Cancel/Dispose restores the actual pre-edit session overrides/layout/profile marker, and Accept closes the draft and returns the candidate without writing or resuming. Reset retains unknown override-record fields by restoring explicit authored paths rather than deleting their records. Other profiles/settings stay unchanged. Editing blocks Gameplay/Reward admission and new run preparation; session APIs, drafts and acceptance require their owning thread.
Held buttons cannot be replayed by apply, cancel or reset; release then press re-arms them, while held movement resumes. Device loss/reassignment, cancellation/run release and session disposal cancel a draft. Applying a profile checks ownership again after SDK resolution and rolls back on a controlled mid-application device loss; SDK callbacks cannot re-enter context/edit mutation while applying. Rollback failure is explicit (aggregate error with the map disabled), not reported as success. A returned accepted document is not a durable save.
New simulated-device groups pass 9/9 and prior session groups pass 8/8, both exit0. Batch 41 now verifies interactive capture logic; no UI view, normal writer or production input consumer is migrated; the current binding-profile API is exercised in the development fixture. Physical controls/legacy feel remain unverified.
R2e device-owned interactive capture¶
Batch 41 adds InputBindingDraft.BeginCapture(bindingId, cancelPath), one capture per draft. The selected ID must be a leaf in the edited profile/group; the device is the draft's existing assigned reference, never activity/connection index. Canonical paths come from the version-1 supported-control whitelist, stripping runtime device names and retaining direction/composite rules. Keyboard/gamepad/pointer events from other devices are ignored. This adds no global shortcut, UI view or device owner.
State begins WaitingForRelease. An input-update fence discards opening events and resamples committed held state, including when capture was created inside an event callback too late to receive that event. Supported held controls must release before Listening. State/partial-delta samples retain omitted controls and use SDK button press/release thresholds; stick/trigger noise below threshold cannot win capture. Input events only collect candidate paths. After the update commits device state, one candidate validates/previews through the draft; multiple candidates in the same event or update yield AmbiguousInput, and conflicts yield Conflict without mutation. Unsupported eligible-family controls are ignored/rejected for that selected target. Fresh release/press permits retry.
An explicit optional cancelPath must resolve to a supported button on the same device. Its fresh press outranks candidates in that update, cancels only capture and leaves the draft unchanged/open. Null reserves no key; the owner may use external Cancel(), allowing keys such as Escape to be remapped deliberately. Capture adds no automatic Enter/Escape/Back global policy. While active, direct draft edits/reset/accept/another capture are rejected; cancel capture first to reset/retry. Draft cancel/run/device/disposal closes capture and unsubscribes its hooks. An unexpected preview/after-update exception exposes Failed/Failure; expected owner teardown yields Cancelled.
Completed exposes CapturedPath and leaves input blocked/draft open; Accept remains a separate in-memory operation. Preview after state commit lets the session see a held completion button and require its release before commands. Public mutation/disposal requires the owning thread. Capture status, rejection and path are presentation data, not rendered glyphs/prompts or a durable save. Native UI dispatch suppression and physical-device behavior remain integration gates. New 10/10 plus prior draft 9/9 simulated groups pass, both exit0.
Behavior preservation rule¶
Owner instruction: work in dependency order and preserve gameplay. Keep movement feel, damage/stat formulas, ability triggers/costs/cooldowns, weapon timing, targeting priorities, enemies/spawn rules, reward eligibility/probabilities/counts, progression, difficulty and balance unchanged during architectural extraction. Correct crash/binding/lifecycle defects against recorded intended behavior; do not silently rebalance or change controls/mechanics while moving code. Any intentional rule/feel change needs an explicit owner decision and its own evidence.
Owner-approved exceptions, clarified 2026-10-26: explicit one-device-per-slot assignment; Player 1 uses keyboard or gamepad, Players 2–4 use gamepads; expand the planned capacity to four; add button remapping. These are approved extensions; batches 37/38 implement only the isolated ownership/command foundation, not complete gameplay support. Preserve existing one/two-player mechanics as the reference, and do not silently rebalance damage, spawn rates, rewards or difficulty for the added players. Moving to Unity's Input System is an implementation change; its defaults must not silently change analog speed, dead zones, smoothing, button edges or UI repeat behavior. Hot-joining mid-run, new ability buttons, manual aiming, new reward shortcuts and Tutorial redesign are not included. Three/four-player reward participation, HUD/camera and actor lifecycle require their own migration/acceptance design; assigning four devices alone does not finish four-player support.
Reference identity and evidence¶
Original gameplay comparison reference is the inspected dirty working tree at committed HEAD 5347e460, including existing batches through incomplete batch 25. It is not a certified copy of the Steam build. Source baseline records SHA-256 for 608 selected source/config/definition/prefab/scene files, including definitions found by script GUID outside Resources and recovery variants. A hash detects an edit; it does not prove gameplay, shipped reachability or approval of an old bug. Never regenerate the baseline silently to make a changed result pass. Batch 37 intentionally changes its two package pins for the SDK install, recording new hashes/reason in that batch; the other 606 pins remain unchanged. Checkpoint preparation on 2026-10-26 trimmed trailing spaces from four empty name/class fields in the two added Tutorial menu components (697363605/697363606). Exactly four whitespace-only differences were verified; gameplay values, references and component identity did not change. Tutorial scene bytes are outside the 608-file pin set, whose hashes remain unchanged.
Batch 24 remains the last fully player-verified checkpoint. Keep its gameplay evidence and meaningful existing checks as comparison cases. Batch 25's Tutorial migration is parked and unverified as a final player. Before modifying each gameplay system, characterize that system's actual behavior using a disposable development setup; mark code-only facts versus measured results. No runtime measurements were made for this contract.
| Behavior found in current source/data | Preserve/check during migration |
|---|---|
| Player.Movement normalizes the movement direction before applying finalMovementSpeed, including nonzero stick input. | No automatic proportional analog walking. Compare cardinal/diagonal motion, partial stick and direction transitions against the old sampler and motor. |
| Keyboard Horizontal1/2 gravity/sensitivity 5; Vertical1/2 gravity/sensitivity 3; dead 0.001. Joystick movement axes dead 0.19; vertical inverted; per-device legacy axes. | Record these parameters before translating processors. Input System default smoothing/dead-zone shape is not proof of equivalence. Characterize release/turning near thresholds; keep a compatibility sampler if necessary, with named consumers/removal conditions. |
| AbilityHolder uses the Dash rising edge, blocked by isDashing/combined pause and zero dashForce, through AbilityFunction.UseAbility. | Same dash direction, stamina/cooldown admission, duration/invulnerability and automatic ability behavior. No added generic ActivateAbility button. |
| UpgradeSelection uses horizontal threshold ±0.1, raw keyboard versus joystick axes, 0.25-second unscaled movement lock, Dash to confirm and per-player lock-in. | Preserve repeat/selection/confirmation rules and simultaneous-player outcomes while changing the source of commands. No dash can leak into gameplay from a reward confirmation. |
| StandaloneInputModule is configured for 10 navigation actions/sec and 0.5-second repeat delay in the seven enabled scenes. Submit has Return/gamepad button 0 and Enter/Space definitions; Cancel has Escape/gamepad button 1. | Preserve existing UI intents, timing and focus. Handle duplicated physical bindings once rather than processing both old and new UI modules. |
| Presets define a Reroll binding/glyph, but no runtime GetKeyDown Reroll consumer was found in the script scan. The reroll action exists as a UI callback. | Preserve existing callback/admission behavior. Do not add a keyboard/gamepad shortcut just because a binding field exists. Recheck serialized/custom input consumers before final disposition. |
| MainMenu StartGame transfers selected characters/player count and selected stage/difficulty; gameplay activates pre-wired players. | Preserve selection, order, default characters, unlock validation, stage/difficulty and shop modifiers while replacing the runtime request/assembly boundary. |
Movement/timing comparison tolerances must be justified by fixed-step/render sampling, not widened to hide a changed feel. Pin authored numbers/formulas exactly where no intentional rule change is approved. Stochastic reward/combat comparisons need controlled random state and identical inputs before comparing outcomes.
Ownership and lifetime¶
| Owner | Authoritative data / responsibility | Must not own |
|---|---|---|
| Local roster | Up to four stable local player IDs, display slots, selected character IDs, assigned input-session handles and connection status. Survives scene handoff/retry during a session. | Player HP, cooldowns, stage completion or persistent unlocks. |
| Input session | Per-player InputUser with later action copy and explicit device pairing; binding profile, input context and press/connection events. Lifecycle follows roster membership, not pooled/dead actor activation. | Character stats, movement execution, reward outcome or scene loading. |
| Run request | Validated snapshot of active player IDs/characters, selected stage/difficulty and existing shop/run modifiers. Same request shape for production selection and development bootstrap. | OS device indices, active scene objects or mutable authored assets. |
| Player assembler | Create/reuse the production player prefab, inject player ID/definition/input handle and dependencies, initialize runtime capabilities, then activate/register with camera/HUD/targets once. | Poll global input, select random characters, choose difficulty or silently pair devices. |
| Actor capabilities | Motor/vitals/stats/weapon/abilities consume commands and runtime context. Reset/dispose with that actor/run. | Input package APIs, legacy preset IDs, global device detection or choosing another player from an array. |
| Input-context router | Determine current recipients: startup, front end, gameplay, shared menu, reward selection, dialogue or transition. Deliver a physical press to one intended policy/recipient. | Set health/currency, roll rewards, own authoritative pause/time scale or replace the menu page stack. |
| Binding/profile service | Translate existing saved presets, expose supported layouts/overrides, persist only deliberate compatible settings edits. | Save progression or continuously rewrite profiles while loading views. |
| Prompt/UI presentation | Display the relevant action/profile/device glyph and react to changes; expose UI commands to the owner. | Reassign devices or enable gameplay actions because a panel was enabled. |
Concrete R2a implementation uses InputUser as the single per-slot pairing owner. Per-slot action copies will associate with that user; a pooled/stage actor must not create an independently auto-pairing PlayerInput or a second input user. This refines the earlier PlayerInput baseline recommendation while keeping actor/session lifetime separation. Package 1.17.0 is now installed and tested on the actual Editor, with Both input handling preserved; action APIs/profile wiring and physical hardware still require their scoped checks.
Roster and assignment rules¶
- Support one to four explicitly selected active slots in the target design, retaining existing character/stage/difficulty rules. A device button does not silently add a player; three/four-slot setup and presentation must be migrated and verified.
- Player 1 accepts keyboard or one gamepad. Players 2, 3 and 4 accept gamepads only. Keyboard cannot own a secondary slot. Each active slot has one gameplay device, and each device belongs to at most one slot. Mouse remains a shared UI pointer and does not introduce manual aiming.
- Keep player IDs distinct from slot display numbers, Character IDs, device enumeration numbers and Unity object instance IDs. A player ID remains stable through stage changes, death/revive and retry; it is not a new progression/save identity.
- Validate a complete run request before scene transition: supported player count, available/unlocked character/stage/difficulty, distinct valid input owners and required actor dependencies. Any requested player count without enough distinct permitted assignments must fail setup validation; do not silently fall back to split keyboard, share a pad or drop a player.
- Do not steal a paired device when another one connects or input activity changes. Selecting a different character retains that player's input ownership. Idle devices do not determine global prompts for all players.
- Input binding changes remain settings operations. Leave/join/assignment changes are permitted in the existing setup/settings flow before a run; hot-join or removing a player from the active run is outside this rebuild's behavior scope.
Explicit device-to-slot setup¶
| Slot | Permitted gameplay assignment |
|---|---|
| Player 1 | Keyboard or one unassigned gamepad. If a pad is chosen, the keyboard controls no gameplay actor. |
| Players 2, 3 and 4 | One distinct gamepad per active slot. |
A particular gamepad is not permanently Player 1/2 by connection order. Setup must show each active slot's selected character, assigned device and assignment validity, and allow an available pad to be assigned to any permitted slot before starting. Reassignment releases the old ownership before applying the new assignment, checks for conflicts, and preserves the slot's selected character. No simultaneous keyboard-plus-pad fallback for one actor. Global menu keyboard access does not count as keyboard ownership of an actor.
Required combinations include keyboard alone, one pad alone, keyboard plus one/two/three pads, and two/three/four pads. Reject keyboard on P2–P4, a shared pad, a duplicate keyboard actor, and starting without enough assignments. Existing one/two-player setup remains a regression case; added slots are an intentional capacity extension. Exact serialized setup-view wiring remains design work, not a finished lobby implementation.
Disconnect, reconnect, death and retry¶
| Event | Contract |
|---|---|
| Assigned device disappears | Keep the player/character/slot. Input commands from that device become neutral and button edges are cleared; no handoff to another device. Do not invent auto-pause, AI control, invulnerability or removal from rewards. Existing damage/spawn/clock policies remain authoritative. |
| Same paired device returns and identity is established | Restore that owner's pairing, display status and current binding profile. Require a fresh button edge; do not replay the press held during reconnection. |
| OS identity changes / pairing is ambiguous | Do not guess from joystick array index or device name alone. Keep disconnected status and use an explicit reassignment path through the supported setup/settings owner. This UI path must be specified before implementing it. |
| Actor dies/deactivates | Keep its roster/input identity. Actor gameplay capability gates determine permitted actions. Reward participation follows the existing alive/active-player rule, not physical device connection. |
| Revive/retry/stage change | Reinitialize actor state/register dependencies once and reuse the intended player identity/selection/input owner. No extra persistent PlayerInput user or duplicate listeners. |
| Return to front end | Release actor registrations and run-owned state; keep only session choices/profiles that the existing flow retains. Dispose unused input sessions when their roster entries leave. |
These rules avoid introducing new gameplay responses to disconnection. Physical-device and Windows verification remain mandatory; simulated device tests alone are not a reconnect acceptance pass.
Input contexts and recipients¶
Do not introduce new gameplay commands because the input package makes them convenient. Gameplay consumers continue to express existing movement, dash and stats intents; automatic attack/ability triggers remain automatic. Existing shared UI uses navigation/submit/cancel/pointer/tab intents. Startup press-to-continue and dialogue advance have their own bounded context.
| Context | Permitted recipients | Excluded overlap |
|---|---|---|
| Startup / transition | Existing skip/continue or transition policy only when ready. | No carried Submit/Dash press reaches the first menu or actor. No change to logo/task/scene timing policy in this input task. |
| Front end | Shared UI accepts keyboard/mouse/gamepad navigation; existing page/selection owner handles intent. Roster assignment is explicit, not a side effect of moving focus. | No actor commands or hidden-page callbacks. |
| Live gameplay | Each actor receives only its paired owner's movement/Dash/Stats. Existing pause policy receives Pause requests once. | No reward choice, hidden tabs or dialogue advancement unless that context is actually active. |
| Shared pause/settings/result menu | Existing shared page owner receives UI commands and Back/Pause once. Retain the current shared-menu behavior rather than creating one menu per player. | No movement/dash/attack from UI input; cancel/result actions do not resume an ended run. Unassigned UI devices never acquire actor ownership. |
| Reward selection | Each eligible player receives their own horizontal selection and Dash-bound confirmation; existing queue/lock-in/reroll admission policies decide effects. | No shared global-selected-card replacement for two-player selection; no gameplay dash from confirm; no added reroll shortcut. |
| Active dialogue / Tutorial lesson | Route only the existing active advance/lesson action to that owner; capability gates control whether the actor may move/dash. Tutorial migrates in R8. | No parallel TutorialPlayer input polling or globally active Space listener advancing hidden dialogue. |
Shared-menu access does not imply actor ownership. Preserve available global UI controls while ensuring a device's gameplay commands have one paired recipient. Specify which UI module/action source supplies the global menu in the scene wiring design; do not run two UI input modules or two competing Back handlers.
When a press changes context, consume its button edge and require release/new press before an unrelated button action. Preserve held movement sampling when normal play resumes rather than requiring a new movement gesture or leaving stale velocity. The run-state owner decides pause/time scale; the input router only gates commands. Menu pause, reward selection and game over remain distinct policies.
Existing binding profiles and migration¶
Bootloader references exactly two keyboard and two joystick preset assets; their Keys references resolve. Both joystick presets have the same named controls with controllerID 1 versus 2. Those numbers currently select legacy axes/KeyCodes and are not durable physical-device identities.
| Profile | Movement / controls in the current preset |
|---|---|
| KEYBOARD WASD, preset 0 | A/D, W/S; Q/E tabs; Space Dash/reward confirmation; Escape Pause; Backspace Back; LeftShift Stats; R configured Reroll. |
| KEYBOARD ARROW, preset 1 | Arrow movement; brackets tabs; Slash Dash/reward confirmation; P Pause; B Back; Comma Stats; Period configured Reroll. |
| Joystick 1/2 | Horizontal/Vertical axes; Button0 Dash; Button7 Pause; Button1 Back; Button4 Stats/left tab; Button5 configured Reroll/right tab. Verify physical button/control translation for each supported pad rather than treating numeric legacy buttons as universal labels. |
GameConfig stores P1_Keyboard/P1_Joystick/P2_Keyboard/P2_Joystick as preset indices. Design the new representation with a versioned, idempotent import: preserve the old data during compatibility, map a keyboard player's selected legacy layout explicitly, and retain both legacy keyboard choices as selectable layouts without enabling two keyboard players. Only P1 can own the keyboard in the new model. Keep both old layouts available for deliberate P1 selection; never assign an imported P2 keyboard profile to P2–P4. Do not merge P1 and P2 key maps into one accidental composite or discard the other saved preference. Preserve gamepad layout/preferences while separating them from device enumeration.
Missing/invalid profiles must be handled in a disposable migration fixture without resetting unrelated config/progression. If two old profiles conflict about the single keyboard layout, initialize P1 from its valid legacy keyboard preference and retain the alternate old keyboard preference as a selectable P1 layout; expose the active choice in settings, and retain the other legacy value for a future deliberate layout selection. Do not write new config merely by opening/refreshing Settings. Do not persist current OS device indices or make a reconnect re-run save migration.
The persistence/UI/service contract now specifies logical input schema/versioning, unknown-data preservation, legacy semantic conversion and view ownership. Its PS-03 requires the concrete package wire format and stable generated action/binding IDs with R2; PS-04 requires exact affected imported view bindings per implementation slice. Legacy removal requires supported import/round-trip evidence. The original design batch implemented neither schema nor import; batch 39 now verifies them in memory without changing the normal config class/loader/writer.
Button remapping requirements · owner-approved 2026-10-26¶
Remapping is in scope alongside the Input System migration. It is separate from device assignment: selecting the pad that owns P2 does not change what that player's buttons mean.
- Provide keyboard-key and gamepad-button remapping for existing supported actions, including keyboard movement directions. Do not add a new ability or Reroll shortcut merely to populate a remapping list. Analog processing/dead zones and gameplay timing remain covered by the behavior-preservation rule; an axis-calibration feature is not implied.
- Keep binding overrides scoped to the relevant player/control profile and control scheme, not a shared action asset that changes every player. Choose explicit saved profile IDs during schema design; OS device enumeration indices are not profile identity.
- Rebinding capture listens only to the intended assigned device/control scheme and suppresses gameplay/menu actions while capturing. A button used to finish a remap cannot also confirm a reward, dash or close the menu.
- Support cancel without losing the prior binding and restore defaults for the selected profile. An incomplete/cancelled capture must not persist partial state. Keep a usable recovery path if navigation/confirm/cancel bindings are being edited.
- Detect conflicting bindings within an active context and present a deliberate resolution. Sharing a button across mutually exclusive contexts (such as the existing Stats/left-tab gamepad mapping) is permitted; do not reject every cross-context reuse or silently overwrite another action/player.
- Apply confirmed edits through the profile owner, refresh the displayed binding/glyph, and persist/reload without touching progression, graphics or audio values. Reconnect and actor retry retain the intended overrides; remapping P1 must not modify P2–P4.
- Import legacy presets as defaults/compatible profiles before applying new overrides. Specify versioning, invalid/unknown action handling and old-profile retention in the persistence contract. Opening or refreshing settings must not write defaults over saved remaps.
Required verification: keyboard and pad rebinding, cancelling/resetting, conflicts and cross-context reuse, restart/reconnect/retry persistence, correct glyphs, capture isolation and per-player isolation for up to four slots. The logical override schema and UI ownership are now in the persistence/UI/service contract; actual package serialization, exact imported view bindings and physical-device verification remain required. No remap feature has been implemented by updating these documents.
Consumer and serialized migration map¶
Input consumers retains the original 21-file migration scan (20 runtime files and SettingsFlowChecks) and adds batch 37's deliberate Editor-only legacy API smoke, for 22 rows. No new runtime legacy-polling consumer was added. It records the replacement owner and scene/prefab/asset GUID consumers, including recovery material. No-reference counts are not deletion evidence.
The runtime source categories are input/bootstrap/configuration (AppInitiator, GameSettings, InputManager, SettingsInterface), actor/actions (Player, AbilityHolder, StatusPanel), reward selection (UpgradeSelection), menu/pointer/prompt (LevelManager, LevelSelectButton, MenuController, TabsManager, AutoScrollNavigation, Tooltip, JoystickRefreshPanel, ChangeKeyImage), startup (SplashUIController), and dialogue/Tutorial (DialogManager, TutorialManager, TutorialPlayer). Every category needs a disposition before the legacy path is retired.
Scene input module map identifies StandaloneInputModule records in all seven enabled scenes plus TileMap Tester. Preserve scene/component identity where feasible and migrate action/module references with their consumers. TileMap Tester is a development scene, not permission to add it to production. ProjectSettings/InputManager.asset has 44 axis records with several duplicate names; use their actual mappings rather than a default new package template.
Bootloader's serialized KeyBindings arrays are initialized with null Keys references and populated by GameSettings from the preset catalog; don't treat those raw zeros as missing content or copy that runtime binding container into the new asset design. Existing Keys/KeybindPreset assets, GameConfig preferences, ChangeKeyImage key strings and runtime UI callbacks are part of migration. Dead/comment-only controller views remain separate retirement candidates until their reference checks are complete.
Verification and acceptance¶
These are future required checks, not passes from this documentation batch.
- Validate each old profile/axis/control against the new command sampler with fixed input traces. Include cardinal/diagonal, partial stick/dead-zone edges, axis release/reversal, held buttons and two simultaneous devices. Keep current normalization and command timing until a separately approved behavior change.
- Verify one through four slots: keyboard-only P1, pad-only P1, keyboard plus one/two/three pads and two/three/four pads; explicitly assign the same test pad to P1 then P2 on separate setups. Reject keyboard on P2–P4, duplicate-device/two-keyboard and insufficient-device requests without touching saves; character/stage/difficulty/shop selection stays identical.
- Verify pause/settings/rewards/result/transition routing: one physical press produces the intended single action, held movement resumes correctly, and no confirmation causes dash or hidden UI activity.
- Verify each active player's stat toggle, reward marker/confirmation and prompts belong to that player; disconnect/death do not silently change eligibility or ownership. Check reconnect on physical devices, repeated retry and returns.
- Verify legacy config migration/reload and repeated initialization are idempotent with exact non-input fields preserved. Keep current/save/asset ID compatibility and no duplicate subscriptions/users.
- Verify remap capture/cancel/default restoration, conflicts, per-profile isolation, glyph updates and persistence across restart/reconnect/retry, including P3/P4 profiles.
- Compare the same existing character/weapon/ability in the dummy arena before/after migration: stats, movement/dash/health, attack/reload/cooldown and damage attribution. Use authored data hashes plus method checks and real player observations; a compile or unchanged asset hash alone is insufficient.
- Retire legacy code/assets only after every CSV row and serialized UI/preset reference has a verified migration/disposition. Re-run source and serialized scans then; retain meaningful fixtures rather than deleting failing tests.
- Complete Linux IL2CPP/player checks and later Windows IL2CPP/Steam-controller checks with platform/build identity. No claim of physical input equivalence from headless simulation.
Design status and next dependency¶
The contract establishes source-backed player/input ownership, legacy consumer/module maps and the owner-approved P1 keyboard-or-pad/P2–P4 pad-only assignment and remapping requirements. Its source baselines still describe the existing one/two-player implementation. Run/bootstrap, player capabilities, combat, stage/enemy/event, rewards and persistence/UI/services now supply the adjacent core contracts. Full reassignment/recovery UI, application profile conversion/persistence, imported UI bindings and legacy-equivalence traces retain their named follow-ups. Batch 38 records the pinned SDK override shape without closing PS-03.
Batches 35/36 verify the lifecycle/shared scene; batches 37/38 verify isolated roster/device/action/session/context ownership using existing InputUsers and the phase gates. Batch 39 adds verified in-memory binding profiles/legacy conversion. Batch 40 verifies blocked-session application and reversible drafts. Batch 41 verifies device-owned capture. Batch 42 verifies that document/file adapter on disposable Linux paths. Batch 43 verifies explicit control-preset compatibility and read-only saved-preset application (10/10 new + 10/10 persistence groups). Batch 44 separates audio/locale loaded application (8/8 new + 7/7 settings-screen groups); batch 45 now wires production config ownership. Batch 46 now verifies the basic Linux scalar startup/edit/restart path and scoped current/legacy/rejected-file startup. An explicit accepted-input persistence command, recovery/setup/remap UI, native remap edits and physical/legacy comparisons remain open. Legacy sampling equivalence, Windows/recovery/persistence rollout, remapping UI, actor assembly and physical-device gates remain open. Keep Tutorial and GAME-01 parked.
Presentation deferral · 2026-10-27¶
The owner moved new setup/rebind menus, four-player layout expansion and UI/UX redesign to R7. Preserve the existing roster/session/profile/draft/capture/config implementation and its IDs; do not discard it or claim its unfinished user-facing features complete. Core player assembly and movement/ability work may proceed through explicit development-fixture assignment and the current one/two-player compatibility flow without waiting for redesigned menus. Preserve current gameplay; P1 keyboard-or-pad/P2–P4 pad-only remains the later target contract. Accepted-remap save/cancel/retry semantics, new menu recovery/glyph/rebind layouts and full F1/four-player/physical acceptance remain deferred/open for their actual UI consumer. This supersedes the previous next-action requirement to finish remap persistence/UI before all core player work.
Batch 50 originally bound inert production-prefab actors to the paired roster/session/run context. Actor creation does not create another InputUser; retry/reload retains player/user identity and device loss neutralizes input without inventing removal/pause policy. The development launcher explicitly selects one P1 device by ID; it does not change production menus. Batch 52 subsequently connected movement.
Batch 58 now sends the same single command frame to movement and owned dash in the development actor. Assigned keyboard/gamepad dash, held-edge suppression, zero direction, insufficient stamina, scaled pause, reward-context isolation, player cleanup and later device loss pass headlessly. All seven characters retain the checked costs and impulses; their special attacks remain unexecuted. Production scenes retain legacy input. Full F1, physical/Windows controls, four-player/remap layouts and end-to-end gameplay acceptance remain open.