Skip to content

Persistence, input preferences and UI/service integration

Status: This is a historical audit and design record. Some foundations described as proposed were implemented later. Follow the roadmap for current scope and verify implementation in source before using an old status claim.

R0 source-audit/design deliverable, 2026-10-26, batch 33. Proposed ownership and wire requirements below are unimplemented. Current one/two-player behavior remains the comparison baseline; the owner-approved device/remapping/four-player extension is specified in the input contract. This batch changes documentation only. No profile was opened, decrypted, reset or written; no network request or Unity run was made. GAME-01 remains deferred.

Evidence and reuse

Map Scope and limit
Saved schema All 27 public persisted fields across PlayerData, GameConfig, SavedKeybindPreset and GraphicSettingsData; source declarations, creation defaults and hashes. Defaults describe explicit initialization, not what deserializers guarantee for an old/malformed file.
Legacy control presets Bootloader's two keyboard/two joystick presets, nine roles each: 36 resolved Keys references, catalog order, IDs, names, types and hashes. These are legacy asset identities, not Input System binding IDs or assigned physical devices.
Persistence callers 27 file/category rows from non-Editor project source: selected Save fields/commands, Game.Save, SaveManager readiness/instance and GameSettings.Instance. Exact symbols/lines, no profile contents. This lexical scope is not every possible alias, reflective call or compiled reachability graph.
Readiness/lifetime edges Ten source edges for save readiness and its direct locale/achievement subscription neighbours. GameEvents' achievement publisher and other gameplay events remain in the prior event/caller maps.
UI/service adapters 23 inspected integration entry points with retain/split/replace dispositions, target owner and migration phase. Entry anchors are source evidence; the target owners are design.
Comparison cases 24 cases: 12 source-derived observations and 12 required future checks. None has been executed in Unity or against profiles.

Reuse input consumers/modules, run scene wiring, stage scene components/events, combat callers/bindings and reward/result bindings. Their existing scene/component IDs and source hashes are authoritative within their recorded scope. This contract does not copy a second scene or callback inventory, certify every nested imported override, or claim every UI file has been semantically reviewed.

Actual storage and compatibility baseline

FileConst, resolvers, SaveService and SaveManager establish these paths. D means MyDocuments, P means Unity persistentDataPath, I means the supplied Steam ID or offline, and E is Beta or Main. Comments mentioning Release do not match the actual directory name.

Operation Current branch/path Compatibility constraint
Current player/config With UNITY_STANDALONE_WIN: D/TinyChaos/E/I/{file}; otherwise P/TinyChaos/E/I/{file}. Constructor creates the directory. Preserve environment/account namespaces. This is a compile-target guard; do not substitute the legacy runtime-platform guard or claim Windows Editor behavior tested.
Player encoding player_v2.json.enc: Newtonsoft JSON, then the existing repeating-key XOR/Base64 codec. Retain decoder and existing key compatibility; no key material in docs/logs. This format is not a schema version or an integrity check. No cryptography replacement is in scope.
Config encoding config.json: Newtonsoft writer, Unity JsonUtility reader. Test both directions, old missing/nested fields and unknown fields with disposable fixtures before changing serializer. No explicit current config/input schema version.
Legacy existence WindowsPlayer/WindowsEditor: D/KatrGames/TinyChaos/{SteamId-or-empty}/player.dat; other platforms: P/player.dat. Null identity is already guarded (SAVE-01). An empty identity here does not introduce an offline legacy directory.
Legacy discovery Compiled only under UNITY_STANDALONE_WIN: identified legacy path when identity exists, plus no-ID legacy path. No paths on the other branch. SAVE-02: discovery runs only after the narrower existence gate. Linux can enter migration yet enumerate nothing. Do not delete legacy files or assume a default merge recovered them.
Legacy decoder/merge BinaryFormatter plus PlayerDataBinder redirects old unnamespaced model types. Currencies sum, booleans OR, shop/counters max; unknown keys copied; null objects skipped. Preserve supported import behavior, fixture it separately from the normal loader. Current-file existence takes authority; legacyMigrated does not determine authority. Duplicate/overlapping legacy files require a decision before broadening discovery.
Reset backup Current namespace backups/, timestamped reset name, File.Copy with overwrite enabled; then default data and restart helper. Not a rolling validated backup/transaction scheme. The standalone helper quits; it does not relaunch the executable. Reset remains an explicit owner action, never automatic corruption recovery.
Delete helper SaveService.Delete resolves the legacy path. Do not repurpose this method to remove current data by assumption. No deletion is required for the rebuild fixture.
Leaderboard cache Windows runtime guard: D/KatrGames/leaderboard.json; otherwise P/leaderboard.json. Separate from profile/account/environment. Preserve as disposable external-cache data, never import as progression or allow cache failure to reset a profile.

The schema map distinguishes PlayerData's version (Application.version, rewritten on each player save), future storage schema version, input profile version and authored content IDs. They serve different purposes. Current defaults give three initial characters and the first Easy/Medium stage unlocks; PlayerData exposes a beta-default parameter, but SaveManager's fresh/merge calls omit it. Do not silently change Beta unlock behavior while extracting services.

Character keys use Character.id; stages use StageMap.id; shop levels use the existing upgrade-type enum strings; counters and achievement IDs must retain their spelling. Special-upgrade defaults, ResourceInitializer loading and SaveResources saving all use upgradeName, while GameDataLoader indexes/looks up upgrades by idname. SAVE-04 is an identity/rename risk, not proof these three persistence paths currently disagree. Before converting to stable IDs, build an explicit old-name→ID table from shipped-compatible assets, detect duplicate/renamed/ambiguous names, preserve unknown keys and retain unresolved records. Do not use UI localization text as a replacement key.

Target profile boundary and write rules

One app-owned profile context pins environment, resolved account/offline identity and storage root for the session. Late Steam availability cannot switch or merge profiles automatically. Local gameplay and local results work without optional Steam/Discord/network readiness. An explicit later profile-switch feature is outside this batch.

The repository boundary separates discovery/read/decode/validate/migrate from in-memory state, and separates state application from explicit persistence. It returns a result that distinguishes missing, valid, migrated-in-memory, unsupported-version, corrupt and IO-failed data. Missing files can initialize defaults; present invalid files cannot be treated as fresh profiles. Preserve original bytes before a supported migration or recovery write. Recovery must identify a validated backup or request an explicit disposition; do not fall through to legacy currency merging or save defaults over the original.

Validation fills compatible missing fields without erasing known or unknown data, checks nested collections/types, supported preset/locale ranges and content-key ambiguity. Preserve unknown fields during round trips or refuse to rewrite an unsupported format. Range correction is a specified migration with evidence, not silently changing progression values. Old JSON/codec and BinaryFormatter compatibility stay behind narrow adapters until supported imports pass; fixture setup does not execute binary data from arbitrary external sources.

One writer owns each profile/config update. Prepare and validate the complete payload in memory, write to a separate temporary file, verify it, retain the last known-good original and then use a platform-tested replacement strategy. Do not promise atomic/durable replacement until Windows and Linux failure-injection checks pass. Permission, disk-full, interrupted write, backup and replacement failures retain recoverable originals, return failure and leave retryable in-memory dirty state. No success message or result acknowledgement precedes a successful commit. Avoid unrelated simultaneous whole-config writes dropping a remap change.

Profile result commit uses the progression contract: a stable run/result ID, frozen outcome and durable processed-result marker committed with the award/counters. Retrying a failed save or recreating a view cannot award twice. The present format has no result ledger; do not infer IDs for old historical runs or promise exactly-once behavior from an in-memory flag. RP-03's mastery/extra-player payout decision remains open, and formulas stay at their source baseline until an explicit correction is approved.

Implemented R2c boundary

Batch 39 implements in-memory InputBindingProfiles/LegacyInputProfileImport on pinned 1.17.0, verified by 9/9 Edit Mode groups. It accepts full config JSON and returns a status, exact original JSON, and a writable immutable candidate only for supported valid/default/imported data. It does not open a profile, write a file, change live controls or create a device/user. Present invalid data is not replaced with defaults. The existing typed GameConfig reader can ignore the new section while reading old fields, but its writer drops that section; production writer integration remains blocked.

Implemented R2f config document/file boundary

Batch 42 adds ConfigDocument and ConfigFileStore plus IConfigFileOperations/ConfigFileOperations. They were verified independently in batch 42; batch 45 now routes the production config boundary through them and blocks the previous typed config writer. Ten Edit Mode groups pass using real Linux disposable paths plus injected failures. Windows/player/IL2CPP and crash/power-loss evidence remain open.

ConfigDocument.Read uses the validated input wrapper and retains full root/nested unknown values. Settings returns a fresh GameConfig view, not the authority. Known field types/DTO representability are checked without clamping, assigning content ranges or materializing missing non-input fields; live catalog/range/application policy still belongs to the settings owner. WithSettings(baseline, edited) patches only changed fields, rejects same-field staleness and preserves other current changes, precise untouched values and nested extensions. It refuses removal of the graphics object or edits to the legacy control list. WithInput(baseline, accepted) replaces only the validated input section if its baseline still matches; stale accepted-root audio/unknown fields cannot overwrite current settings. Explicit legacy preset compatibility is required before production integration.

The store takes an absolute path/definition supplied by its profile owner; it does not discover Steam identities, select environment/root folders, create directories or invoke GameConfig.InitializeDefaults. Load distinguishes Missing, Loaded, Invalid, Unsupported input and IoFailed, preparing supported input defaults/import only in memory. Load/view/apply does not mark dirty or save. Stage explicitly accepts the current document's candidate; revision tokens reject siblings/foreign documents without retaining an unbounded old-document chain. Store mutation requires its creating thread and rejects reentry during IO. Dirty reload requires explicit DiscardAndReload.

Save distinguishes Saved, NoChanges, NotWritable, Conflict, Failed and Indeterminate. It acquires a create-new adjacent write-lock, compares original bytes, writes a unique candidate with a flush request, verifies bytes/parse, writes/verifies a unique byte-exact backup for an existing original, rechecks source bytes, then uses native Replace (existing) or non-overwriting Move (missing), and verifies published bytes. Unsupported replacement does not fall back to deleting the original. Only owned candidate/lock cleanup is attempted; backups and unknown existing locks are retained. A failed write keeps the candidate document dirty/retryable. If publication may have happened, the verified backup is retained and the result is Indeterminate; no automatic rollback can overwrite a possible later write. Explicit reconciliation is required.

Input is decoded strictly from UTF-8 with/without BOM and UTF-16/32 LE/BE with BOM; explicit output is UTF-8 without BOM. Backups retain original encoding/whitespace bytes exactly. Linux tests cover these six forms, two successful generations, fresh-owner reload and SDK application of the persisted remap. Permission/full-disk/replacement/cleanup errors are injected, not claims of actual disk exhaustion or OS permission testing. Locks coordinate new writers using the same path; old/noncooperating writers and alternate path aliases are not a filesystem compare-and-swap guarantee. Parent-directory sync, arbitrary process interruption, Windows replacement, stale-lock recovery and backup retention policy remain rollout work. No atomic/power-loss durability promise.

Target input preferences and legacy import

The current saved control value is {id, preset} with IDs P1_Keyboard/P1_Joystick/P2_Keyboard/P2_Joystick and preset indices, not custom buttons or persistent device ownership. GameSettings dereferences the matching entry/preset without validating missing entries or index range at that point. Import must use the recorded Bootloader catalog order and resolved Keys roles, not list position in a later redesigned asset or a display label.

The implemented application wrapper is the inputBindings object inside a cloned full config document. The table below now describes its R2c fields, distinct from the pinned SDK override JSON:

Field Meaning and rule
inputSchemaVersion Version 1; independent of application/progression versions. Unsupported versions return no writable candidate and retain exact original JSON.
actionMapRevision gameplay-player-v1: plus SHA-256 of the pinned SDK's serialized authored definition, including its IDs/defaults. Changed definitions require an explicit migration; mismatches return unsupported status.
profiles[]: profileId, slotPreference, controlScheme Six profiles: P1 KeyboardWASD and KeyboardArrows, plus one Gamepad profile for each slot 1–4. Nonempty unique preference IDs, unique slot/scheme pairs; keyboard only on P1. These are preferences, not active players, leases, hardware IDs or progression identities.
profiles[].overrides[]: actionId, bindingId, overridePath, interactions, processors, compositePart Stable authored action/leaf-binding IDs, exact group and composite-part identity, explicit supported control path. Empty/wildcard/cross-family/conflicting paths and composite-parent edits are rejected. Version 1 supports path edits; interactions/processors must be absent or JSON null, preserving current timing. Unknown fields are retained.
legacyImportRevision and retainedLegacyData Revision 0 for fresh defaults or 1 for a successful mapping of all four known records. Retain the whole old array, including inactive P2 keyboard and unknown records. Duplicate/missing/invalid known entries or unsupported indices reject import without a writable candidate. Existing inputBindings never reimports legacy values.
keyboardLayout Wasd or Arrows selects P1's preferred keyboard profile, imported only from P1_Keyboard; both layouts remain independently editable. It does not pair a keyboard.

At the batch-33 audit the input package was absent. Batch 37 now pins/resolves 1.17.0 and verifies initial slot/device ownership, with activeInputHandler still2 and both backend defines checked. Batches 38/39 now verify action/binding IDs, concrete SDK override shape and in-memory profile fixtures. Normal-write safety, live application, remap/recovery UI and legacy-feel/physical checks remain open. No production input/config writer is migrated.

P1 imports its chosen keyboard layout, including the arrow preset if selected, plus its gamepad preference. Retain old P2 keyboard preferences as inactive legacy data; never assign a second keyboard player. Map gamepad presets to semantic controls without retaining controllerID/joystick enumeration index as an assigned device. P1 can choose keyboard or a pad; P2–P4 lease distinct pads. Pairing is explicit and connection order is not identity. P3/P4 get validated scheme defaults; no invented prior preferences or automatic keyboard fallback.

InputBindingProfiles.WithBinding returns a new validated candidate and leaves the source unchanged on failure. CreateActions produces a caller-owned disabled SDK copy with only that profile's overrides/mask, without enabling input or pairing devices. Batch 40 now supplies the blocked-session application/draft adapter, including selected-profile reset with extension records preserved. Batch 41 now adds device-owned capture. Production UI/persistence remain pending. Accept returns a document to the owner; it does not invoke a writer. Known required gameplay controls cannot be disabled in version 1; accepted paths are an explicit keyboard/gamepad whitelist. Conflicts are checked within each active profile across movement/Dash/Stats/Pause, while reuse between isolated profiles is allowed. String dates and unknown root/section/profile/override fields survive supported edits. Numbers that cannot round-trip exactly through the decimal JSON model are rejected rather than silently rounded; exact raw formatting is retained in OriginalJson, not promised by exported JSON.

Rebind edits use a temporary draft with input capture in its own context. Prevent the triggering submit/back press from binding accidentally; show valid device-specific prompts; validate duplicate/conflicting actions according to the input contract before committing. Cancel changes no persistent data; reset affects only the selected profile/scheme after the intended reset command. After success apply/persist the same validated draft, refresh glyphs and preserve other profiles/schemes/settings. Disconnect/cancel/unload terminates capture safely and releases context ownership. Persist remaps, not live Unity device IDs; reconnect and mid-reward recovery remain tied to the roster/reward decision, not a new silent takeover policy.

Readiness and lifetime

Current AppInitiator initializes input, localization/achievement/shop/collection managers and catalogs before constructing app services. Service construction creates audio objects and the save directory. SaveManager loads player then config, sets IsReady and invokes synchronous OnSaveReady; then GameSettings applies settings/graphics, sets input readiness and startup loads MainMenu. Localization's handler starts a coroutine awaiting LocalizationSettings; returning from the event is not locale completion. A failing handler can prevent later handlers completing. Resource projection depends on enabled subscriptions and catalog/profile readiness, not merely object existence.

Current AchievementManager/ShopManager/LocalizationManager Initialize methods add static handlers with no matching teardown found in those managers; ResourceInitializer and LocalizedTextHandler pair enable/disable subscriptions. Repeated initialization is not yet a reproduced duplicate-callback failure. An edge-only OnSaveReady also has no automatic replay for late subscribers. SAVE-05 is now source-audited with runtime reproduction still pending.

Target composition has explicit profile-validated, content-projected, input-ready and settings-applied stages, with locale readiness awaited where needed. Optional-service status is separate. Give each consumer a current snapshot plus a scoped subscription tied to a generation/lifetime, so late binding and repeated scene entry cannot miss or duplicate application. App initialization is idempotent or rejects a second composition explicitly; it does not append global handlers or construct extra audio/native clients. Teardown removes its own handlers/owned resources without clearing unrelated owners. Async completion checks its generation/cancellation before applying to an unloaded scene/run. Use the scoped event contract, not a universal bus for save commands or readiness queries.

UI, audio and optional-service adapters

Views consume immutable snapshots and issue explicit commands to the responsible owner. A scene view cannot create a player/device lease, roll/apply a reward, write progression, set global pause state or post scores by being enabled. Move ownership before replacing presentation; preserve current layout/text/selection semantics until the separate approved capacity/remap adaptations require a bounded change. Existing menu/settings/quit regression evidence remains relevant.

Surface Target integration and dependency
Character/stage entry Selection edits an app roster and validated run request. R2 owns device assignment; stage IDs resolve through the catalog/shared runtime. Existing menu can enter the first R1 fixture through a development adapter without a second production state owner.
Menu/Page/Settings Scene navigation owns stack, modal/input context, transitions and return focus. Bind once, refresh without notifying setters, detach on disable/disposal and cancel delayed focus/tweens. Pause is a reason held by the run state owner; closing settings cannot release reward/result pause. Preserve pending-versus-applied graphics semantics and Back/quit behavior.
HUD/slot panels GameManager currently contains HUD responsibilities. Present by joined slot identity, with separately owned actor subscriptions that survive death/revive/replacement safely. Capacity four does not mean four active players or four keyboard readers. Exact imported layout wiring belongs to the implementation slice, with no general UI redesign.
Reward/result Reuse offer/recipient snapshots and approved shared distinct picks/N+1 count. Never derive participants from visible GameObjects. Result view reads frozen result/commit status; retry/navigation/upload cannot recompute or re-award it. Preserve source result panels until adapter migration, then verify all joined slots.
Shop/achievement/collection/localization Project profile state into read models instead of mutating shared authored definitions as the authority. Keep existing shop formulas/enum keys, character/stage IDs and achievement ordering/rewards. Steam synchronization is an optional adapter after local state; unavailable service cannot undo local progression or double-pay on retry. Locale initialization applies a validated value without treating loading as a user edit.
Audio App owns playback resources once; settings owner applies loaded volumes without saving again. Explicit user edits persist through the single writer. Run actors obtain bounded footstep emitters by slot/lease; reset/dispose stops/releases those emitters. Preserve current clips, stacking/time behavior and paused-sound behavior until a separately justified correction. Four allocated channels are not a completed four-player system.
Leaderboard Separate transport, version/filter/cache handling and scene view. Source GET timeout20s and popularity10s do not cover POST (no assigned timeout), parse/cache exceptions or UI-wait coroutines. Target bounds/cancels requests, disposes them, reports offline/malformed data, and rejects stale view generations. Local play/results cannot wait on a UI singleton or network availability. Client eligibility remains separate from server-side Steam admission (NET-01).
Three/four-player external result Current payload has statsp1/statsp2 only. Never silently drop extra participants or mislabel their score as a supported two-player submission. Local results remain available; submission stays explicitly unsupported until backend compatibility is specified and verified. Backend source/access is absent; this is an integration gate, not approval to build new backend features now.
Discord Keep presence optional. Guard construction as well as callbacks/status, publish a bounded unavailable state and dispose native resources at app shutdown/replacement. Source Start constructs outside its later catches; NET-02's observed failure remains separate from GAME-01. No unconditional package upgrade/removal.

Existing bounded GameDiagnostics is available for phase/operation/result timing and identifiers safe for local diagnosis. Add trace points with each implemented boundary, including load outcome, migration revision, request/result ID and lifecycle generation where appropriate. Do not log raw save payloads, Steam identity/path, encryption material, credentials or submission bodies. Do not introduce telemetry or attribute the deferred freeze to these source risks.

Implementation order and remaining gates

  1. R1: define the narrow profile/settings/service interfaces and app/run lifetime boundary required by the shared runtime skeleton. Development composition injects no-write/in-memory progression, input and unavailable optional-service adapters; production services remain app scope. Do not invoke SaveService merely to construct a test (its constructor creates directories). Keep the current production startup behind the compatibility seam while the first lifecycle slice is verified.
  2. R2: select/validate the input package, stable action identities, legacy semantic conversion and disposable config fixtures together with device/remap ownership. Do not defer this minimum persistence adapter to R7. New config writes require backup/validation evidence before use on normal profiles.
  3. R3–R6: migrate the relevant HUD/audio/stat/payout/achievement adapters with their owners. Introduce explicit result transaction fixtures before production reward/progression persistence. Preserve currencies/formulas and pending RP decisions; do not let views bridge independent old/new authorities.
  4. R7: complete production menus/settings/locale/profile recovery and optional services, close serialized callback/scene/prefab disposition and real controller/online/offline tests. R8 migrates Tutorial/released content after the shared owners, retaining useful prior evidence. Windows IL2CPP and backend admission require the other PC/access; Linux static checks cannot certify them.
Follow-up Required evidence / timing
PS-01 · Import/identity Enumerate disposable Windows and Linux legacy/current layouts, repeated migration and overlapping-source policy. No automatic Steam/offline merge or deletion. Close before enabling migration changes.
PS-02 · Codec/schema/recovery Golden old/new JSON+codec fixtures, unknown/missing fields, corrupt/truncated data, backup/reset/write-failure behavior and platform replacement evidence. Close before production format/write changes.
PS-03 · Input wire schema Chosen package compatibility, generated stable IDs, full axis/composite translation, per-profile/scheme round trips, cancel/reset/conflicts and physical reconnect tests. Close with R2 before replacing legacy consumers.
PS-04 · UI/payout integration Exact affected imported bindings/layouts and teardown plan per slice; durable result replay cases. RP-01–RP-05 and mastery/extra-player decisions remain with the reward contract. Close before affected production paths.
PS-05 · Optional integrations Versioned extra-player result payload agreement, bounded malformed/offline transport/cache/native-client tests and server-side/Windows evidence. Local development is independent; supported production submission is gated.

Batch 34 now records the cross-contract phase gates and first R1a lifecycle slice. Batch 35 now implements/verifies its isolated core/checks; no profile/service adapters or production write changes were introduced. Batch 36 now verifies that isolated scene/development entry; Batch 37 verifies R2a roster/device work; batch 38 verifies action/session/context ownership and one in-memory SDK override round trip. Its bindings/action/id/path/interactions/processors shape and string-null sentinel are recorded in the input contract. Batch 39 verifies the versioned application wrapper, legacy conversion and supported data preservation. Batch 40 now verifies session/draft application. Batch 41 verifies capture logic; production UI, writer safety and physical/legacy-equivalence checks remain PS-03 work. Batch 42 now verifies that document/file adapter with disposable Linux files and failures. Batch 43 supplies explicit control-preset compatibility and read-only loaded preset application; batch 44 separates audio/locale loaded application. Batch 45 wires the caller handoff. Built-player and normal-profile acceptance, Windows/recovery/retention and release rollout remain open. PS-01–PS-05 retain their affected-phase gates; the core architecture review is not a blanket implementation pass. The original batch-33 contract alone implemented no runtime code; subsequent scoped implementations and their limits are recorded above and below.

Implemented R2g control-preset compatibility slice

Batch 43 adds ConfigDocument.WithLegacyPreset(id, expectedPreset, selectedPreset). It accepts the four audited P1/P2 keyboard/joystick IDs and indices 0/1, validates unique complete compatibility records, rejects a stale selected record, and returns a new document. It updates only the target preset in current and retained legacy records, preserving unknown records/fields and remap profiles. If retained import data is absent at revision zero, an explicit command snapshots the current compatibility records and advances that marker; it does not replay another legacy choice. Only P1 keyboard sets the new Wasd/Arrows preference. P2 keyboard stays retained compatibility data; joystick indices are not hardware pairing assignments. Generic WithSettings still rejects legacy-list edits.

GameSettings.ApplySavedControlPresets validates all four plans before mutating runtime bindings. It copies the same nine role assets and preset metadata without changing its snapshot, saving, or marking input ready. Startup calls it instead of four public saving setters. Explicit setters/Next keep their UnityEvent signatures, mappings, wrap behavior and one old SaveService save per command. Invalid catalog/data commands reject before partial mutation. Batch 45 now routes production preset setters through the document/file owner, including explicit same-number intent; the old generic config writer rejects. Batch 44 now separates loaded audio/locale application from saving; the following section records the explicit first-run initialization exception and integration limits.

Ten new Edit Mode groups exercise the actual GameSettings methods with injected temporary SaveManager/SaveService dependencies and the new store with real Linux replacement/reload and injected failure/retry. The ten existing persistence groups also pass; both processes exit0. No normal SaveService constructor or normal config write, full startup/player/physical mapping check. Batch 45 implements caller routing and structured write outcomes; built-player acceptance, Windows/recovery and UI remain open.

Implemented R2h audio and locale application

Batch 44 adds AudioService.ApplySavedVolumes(GameConfig). It applies the saved direct 0–1 volume values to the listener, music, effects and four existing footstep sources without writing or editing config. Startup calls it; existing ChangeMaster/Music/Fx/Footstep methods retain their public signatures and one legacy save per deliberate edit. There is no mixer/dB conversion, new range policy or playback change.

LocalizationManager's save-ready callback now requests ApplySavedLocale(id), sharing the existing coroutine with ChangeLocale(id) through an explicit persist flag. Both still wait for SDK initialization, select the same indexed locale and notify game observers. Only a deliberate change updates/saves the choice before notification. Invalid indices reject; finally releases the busy flag after normal completion or an exception/iterator disposal. Requests made while busy are still ignored, without introducing a queue. Repeated Initialize removes only its own previous callback before adding one, retaining other save-ready subscribers. This does not add runtime shutdown ownership or change CoroutineRunner lifetime. A failed save/observer does not roll back the already-applied locale or typed setting; the wider write-outcome handoff remains open.

First-run resolution/dimension initialization previously relied on incidental audio saves to reach disk. It now has an explicit conditional save, skipped for existing profiles and unchanged initial values. SaveManager's initial-default file creation remains separate. Graphics application/readiness and preset behavior are unchanged.

Eight new Edit Mode groups plus seven existing settings-screen groups pass, both process exit0. Audio uses actual sources with an uninitialized service fixture; locale tests manually step the actual iterator with disposable SDK settings, three in-memory locales, completed initialization and no localization databases. They verify values, writes, notifications and busy cleanup, not actual scheduler timing, packaged catalog initialization, localized UI or audio playback. Batch 45 wires production config ownership; full startup/player, Windows/recovery and remapping rollout remain gated.

Implemented R2i production config handoff

Batch 45 adds ConfigSettingsOwner and routes SaveManager.LoadConfig/SaveConfig through it. The owner receives ConfigFileStore with the existing Game.Save.SavePath(CONFIG_FILE), pins that location for its lifetime, and does not choose identity/environment or create directories. Bootloader supplies the pinned InputActionAsset. SaveManager.Config remains the legacy mutable caller view; the immutable document owns complete JSON. A separate snapshot tracks staged edits. Explicit saves patch known scalar/nested fields and validated legacy selections, preserving unknown records/extensions, precise untouched values and input profiles. Membership/order/unknown-record edits reject; no full-view overwrite is used. Generic WithSettings remains strict.

GameSettings preset setters preserve their public signatures and now call SaveControlPreset(id, expected, selected), including same-number reselection that changes the new P1 keyboard preference. Other explicit legacy list changes are diffed/validated before compatibility commands. P2 keyboard stays compatibility data, not a new lease. Pending preset baseline advances when staged, so a failed write can retry without replaying an outdated expected value. Later edits to the caller view during a write stay pending. Owner operations require its thread and reject IO-time reentry.

Existing/legacy loads prepare input conversion in memory without writing. Missing files alone admit explicit default initialization/save. Required graphics and four unique supported legacy presets are validated before handing the view to callers; invalid/unsupported/incomplete/IO-failed data remains unwritten and stops startup readiness. SaveManager startup clears readiness before work and publishes its existing ready event only after successful load. Player/progression load/write method bodies remain unchanged; they still precede config loading in startup.

SaveManager.LastConfigWrite/HasPendingConfigChanges expose outcomes/pending state. Only Saved/NoChanges complete normally; NotWritable/Failed/Conflict/Indeterminate throw ConfigWriteException carrying the result. Invalid view commands reject before publication. The view/applied runtime values are not rolled back on failure. Explicit owner DiscardAndReload reconciles disk state; a SaveManager recovery command/screen, user feedback and retry policy remain unfinished. SaveService.WriteNew rejects the normalized config path, including relative/absolute aliases, while ordinary player/tracker files retain their path. This guard does not prevent arbitrary filesystem writers or symlink aliases.

Ten new integration groups plus 34 settings/preset/audio/locale/graphics regression groups pass across five Editor processes, all exit0. Bootloader's actual imported reference resolves; actual SaveManager methods use disposable injected paths. Tests cover supported full/legacy JSON, missing/invalid/future/read-failed documents, preserved remaps/extensions/precision/backups, same-number preset selection, failed retry, external conflicts, uncertain publication and later edit ownership. Existing fixtures now attach the new owner instead of relying on the retired typed config writer. Normal profiles are untouched. This is active production wiring with Linux Editor evidence only: full startup/player/IL2CPP, packaged locale/audio behavior, recovery/stale-lock/retention policy, Windows replacement and remapping UI remain acceptance work. Backups currently accumulate; do not claim retention/recovery rollout complete.

R2i native Linux evidence and remaining input commit boundary

Batch 46 builds the production owner into a Linux IL2CPP player (exit0). Two interactive processes verify a real master-volume edit 0.5 → 0.7309383, byte-exact pre-edit backup, natural exit and restored value on restart; startup and opening Settings do not rewrite the saved config. Four additional synthetic startup probes load current data with unknown fields/custom P3 override and legacy data without inputBindings byte-unchanged, and preserve/block corrupt/future data. Those probes are stopped after assertions; they do not verify menu interaction or a custom-remap/legacy edit in the player. A failed custom-profile UI attempt is excluded from edit evidence. Earlier 44 Editor groups remain the stronger evidence for those edit/failure combinations.

Normal profiles are untouched and implementation is unchanged. Current evidence qualifies the basic Linux scalar persistence handoff, not complete recovery/retention, Windows, all locale/audio behavior or full gameplay. ConfigSettingsOwner still needs an explicit accepted-input preference command exposed through SaveManager before remap UI: stale input baselines, failed commit/retry/cancel and unrelated pending scalar changes must be handled without whole-root replacement or false acknowledgement. Owner reconciliation exists, but a completed startup recovery command/screen does not.

Owner sequencing update · 2026-10-27

Accepted-remap persistence and its new view/recovery interaction move with the deferred setup/rebind UI work to R7. Existing config-owner safeguards and native scalar persistence evidence remain valid and required; no rollback to typed whole-file writing. Core player assembly/motion/abilities must not wait for a new settings layout. Define actual draft close/retry/cancel and pending-scalar semantics before that later UI writes input preferences. Keep full UI/remap/Windows/recovery gates open.