ClaudeDemoController.Start coroutine rewrite — capture wasn’t loading the Combat scene or initializing an encounter. Sequence asset specifies sceneToLoad: “Combat” but the controller never honored it; ScriptedInputs fired into a null CombatManager. Now:

  1. Resolve weapon class by name from GameFlowManager.weaponClasses
  2. GameState.StartNewRun(weaponClass, difficulty) sets up PlayerState
  3. ApplySequenceRunState overrides the deck / gear / health
  4. Resolve enemies from EnemyDatabase by name; stage on GameFlowManager.currentEncounterEnemies
  5. SceneManager.LoadScene(“Combat”) — bypassing SceneTransitioner so we don’t get fade overlays in captured footage
  6. Wait until CombatManager.Instance != null AND phase == PlayerTurn before activating ScriptedInputProvider (max 8s with clean exit on timeout)
  7. Camera + UI tweaks AFTER scene loads (so main camera is combat cam)
  8. Recorder + scripted inputs start last

ControllerNavigator — new MonoBehaviour at Assets/Scripts/UI/ that layers controller input over the existing pointer-driven combat UI. Unity Input System (already installed via com.unity.inputsystem) reads Gamepad.current; controller events synthesize calls to existing PlayCardFromHand / EndPlayerTurn / ChangeStance entry points so gameplay logic is untouched.

Bindings: L1 / R1 — cycle cards in hand A — play selected card (or confirm target) B — cancel targeting Y — end turn D-pad up/right/left — Front/Mid/Back stance D-pad in targeting — cycle alive enemies Start — open Escape menu

Input mode (Mouse vs. Controller) auto-detected: any gamepad input switches to Controller mode + shows focus rings; mouse movement

3px switches back to Mouse mode + hides rings. Mouse players see no overlap with the existing pointer code.

Focus rings built from 4 thin Image-based borders (no shader, no sprite atlas dependency). Cyan ring on cards; red ring on enemies during targeting. Parented to the target’s RectTransform so they follow card animations.

CombatUIManager exposes IReadOnlyList HandCardUIs and IReadOnlyList GetAllEnemyUIs() so the navigator can read selection state without reaching into private fields.

CombatSceneBuilder.Start spawns ControllerNavigator after combat inits in both code paths (mid-run + standalone test).

Telemetry stats handler — bulletproof error path. Root cause of the “Unexpected token ’<’” dashboard error: aeRes.json() on an HTML body threw, the catch block tried aeRes.text() which ALSO threw because the body had already been consumed, and the function crashed. Fixed:

  • Wrap entire handler in top-level try/catch returning JSON
  • Read body as text FIRST (single read), then JSON.parse the string
  • 404 / “table does not exist” responses are caught explicitly and returned as { data: [], notice: ‘dataset_empty’ } status 200, so the dashboard renders “No data yet” cleanly until events flow
  • Every failure path returns a structured JSON object with data: [] so the dashboard’s r.json() never throws

Co-Authored-By: Claude Opus 4.7 (1M context) [email protected]


Commit 775ed43 by astafford8488.