7.1 KiB
Skybound Code Structure Audit and Stabilization Plan
Date: 2026-04-24
Project: Skybound Protocol
Author: Codex
Status: Raw analysis logged before implementation
1. Overview
This document records the first code-level audit after reviewing Skybound-related wiki documents. The goal was to compare the documented architecture with the actual code path in /Volumes/Data/project/Antigravity/Skybound, identify design and feature risks, and define the first stabilization pass.
2. Documents Reviewed
Topics/Skybound/Skybound-Knowledge-Hub.mdTopics/Skybound/01_Core_Engine/Skybound-Modular-Game-Architecture.mdTopics/Skybound/01_Core_Engine/Game-Engine-Loop-and-System-Orchestration.mdTopics/Skybound/05_Project_Issues/2026-04-22_Engine_Stability_Audit.mdTopics/Skybound/02_Combat_AI/Combat-System-and-Bullet-Interaction-Pipeline.mdTopics/Skybound/04_Mechanics_Progression/Campaign_and_Dual_Loop_System.mdTopics/Skybound/04_Mechanics_Progression/InGame_Progression_System.mdTechnical_Reports/2026-04-22_Boss_Battle_System_Implementation.md
3. Code Structure Observations
Skybound's actual runtime center is src/features/game/hooks/useGameEngine.ts. It directly instantiates and updates EntityManager, StageDirectorSystem, SpawnerSystem, CombatSystem, ProgressionSystem, ModularWeaponSystem, TacticalSystem, HazardSystem, BossSystem, EffectSystem, and GameRenderer.
SystemManager.ts exists and documents a centralized orchestration pattern, but the active engine path does not use it. This is not inherently broken, but it creates a mismatch between the documented orchestrator and the actual execution path.
React is primarily responsible for scene composition and modal/UI handling through GameSceneRenderer.tsx, while Zustand state is centralized in useGameStore.ts.
4. Confirmed Problems
4.1 Build Gate Is Broken
npm run build fails. Representative causes:
App.tsximportsuseGameEnginebut does not use it.- Legacy
src/features/game/combatSystem.tsis still included in TypeScript compilation and has type drift against current domain models. useSceneAudio.tsdoes not includeHANGARinSCENE_AUDIO, despiteSceneincludingHANGAR.SystemBoss.phaseis typed as1 | 2 | 3, whilebossRegistry.tscontains a 4-phase boss.GameRenderer.tscallsCanvasRenderingContext2D.close(), which should beclosePath().EntityManagerinitializes enemies withmovePattern: 'NONE', which is not part of the currentEnemy.movePatternunion.
4.2 Timeline Spawn Intents Are Not Wired
StageDirectorSystem dispatches SCRIPTED_SPAWN and SPAWN_MODE intents, but useGameEngine.ts does not route those intents to SpawnerSystem.
SpawnerSystem already provides notifyScriptedSpawn() and activateSwarmBurst(), so the design exists but the active dispatch wiring is missing.
Expected impact: scripted waves and burst events may not appear as designed, leaving procedural spawning to carry too much of the combat pacing.
4.3 Campaign Stage State Is Not a Single Source of Truth
StageDirectorSystem receives campaignStageIndex when created, but GameState.currentStage still defaults to 1 and is not initialized from the selected campaign stage.
Expected impact: Standard campaign can show stage progression in UI while the engine continues to evaluate boss selection, damage curves, backgrounds, rewards, and tech-part rolls as Stage 1.
4.4 StageManager Mode Can Drift from Store Mode
StageManager has its own internal mode, defaulting to BLITZ. The UI updates Zustand stageMode, but stageManager.setMode() is not called from the store action.
Expected impact: Standard campaign clear can call stageManager.onStageClear() while the singleton still believes it is in Blitz mode, preventing next-stage campaign progression.
4.5 COMMS Events Are Not Rendered
COMMS events are emitted by engine systems, and CommsOverlay.tsx exists, but useGameEngine.ts does not map COMMS events to useGameStore.setComms(). CommsOverlay is also not rendered by App.tsx.
Expected impact: mission dialogue, warning text, and tactical pacing messages are silently dropped.
4.6 Starter Weapon Timer Is Not Cleaned Up
useGameEngine.ts starts a setTimeout() for starter weapon selection, but cleanup does not clear the timer.
Expected impact: if the engine unmounts quickly, a stale timer can emit a level-up modal or pause signal from an old engine instance.
4.7 Skill Sync Boundary Is Fragile
GameSceneRenderer.tsx calls store addSkill() and then engine applySkill(). ProgressionSystem.applySkillSelection() says Zustand owns skill increments, but also mutates state.skills as a fallback.
Expected impact: this usually works because of the store subscription bridge, but the ownership boundary is not clean and may cause missed or inconsistent skill levels under timing stress.
5. Stabilization Plan
Priority order:
- Restore TypeScript build by removing obvious compile blockers and isolating legacy drift.
- Wire
SCRIPTED_SPAWNandSPAWN_MODEintents fromStageDirectorSystemintoSpawnerSystem. - Initialize engine
currentStagefromstageModeandcampaignStageIndex. - Keep
StageManagermode synchronized with ZustandstageMode. - Route
COMMSevents into the store and renderCommsOverlay. - Clear the starter weapon timer during engine cleanup.
6. Verification Targets
npm run buildnpm run lintas a visibility check, with the expectation that broad historical lint debt may remain after the first stabilization pass.
7. Implementation Result
The first stabilization pass was applied immediately after this raw audit.
7.1 Build Recovery
- Removed an unused
useGameEngineimport fromApp.tsx. - Added
HANGARtoSCENE_AUDIOinuseSceneAudio.ts. - Excluded legacy
src/features/game/combatSystem.tsfromtsconfig.app.jsonbecause the active runtime path usessrc/features/game/systems/CombatSystem.ts. - Expanded boss phase typing from
1 | 2 | 3tonumberso 4-phase registry bosses are valid. - Fixed
EntityManagerdefault enemymovePatternfrom invalidNONEtoSIDE_TO_SIDE. - Fixed
CanvasRenderingContext2D.close()toclosePath(). - Removed compile-blocking unused locals from active systems.
7.2 Runtime Wiring Recovery
SCRIPTED_SPAWNintents now callSpawnerSystem.notifyScriptedSpawn().SPAWN_MODEintents now activateSWARM_BURSTor enqueueMINI_BOSSspawns.- Engine
currentStagenow initializes fromcampaignStageIndex + 1in Standard mode. - Zustand
setStageMode()now synchronizes theStageManagersingleton mode. COMMSengine events now populateuseGameStore.activeComms.CommsOverlayis rendered while playing.- Starter weapon selection timer is cleared during engine cleanup.
7.3 Verification Result
npm run build: passed.npm run lint: still fails due broad pre-existing lint debt, mostly@typescript-eslint/no-explicit-any, older helper files, and React hook lint findings. The first pass reduced the functional blockers but did not attempt a large-scale type cleanup.