# Optional Spatializer Architecture (Option A) ## Intent Keep all 2D levels and logic unchanged while enabling 3D/4D presentation via an **optional spatializer**. This matches the current Sphere behavior (projected 2D gameplay) and avoids forcing extra layers on 2D content. ## Core idea - **Rules** remain in existing `BaseLevel` subclasses (Standard/Clock/DarkMode/etc). - **Spatializer** exists **only when Dimension > 2**. - The spatializer owns all spatial presentation logic: - projection/unprojection - visibility/backface logic - line projection - overlays (equator) - rotation/axis input - menu preview reset behavior - optional feature modules (see "Core + Modifiers inside the Spatializer") ## Data model (minimal) ``` LevelDataSO LevelType = Standard | Clock | DarkMode | ... Dimension = 2 | 3 | 4 BoundaryType = Rect | Circle | Custom | ... ``` The spatializer can validate boundary compatibility (e.g., Sphere expects Circle). ## Responsibilities ### LevelMgr - Spawns dots/lines/obstacles as it does today. - Creates rule instance from `LevelType`. - Creates spatializer **only** if `Dimension > 2`. - Ticks and disposes the spatializer alongside rule logic. ### Rules (BaseLevel subclasses) - Own progression, win conditions, UI/audio effects. - No projection or dimension logic. ### Spatializer (new) - Caches dot/line local positions. - Updates projected `Line.Pts` each tick. - Adjusts dot visibility/colliders based on depth. - Manages overlays (equator) and axis hints. - Handles spatial rotation input. ## Core + Modifiers inside the Spatializer (optional) You can treat each spatializer as a **preset** that includes: - **Core behaviors** that are intrinsic to the space (always on): - projection/unprojection - rotation state and input - line projection + local caches - **Modifiers** that are optional and can be toggled per spatializer: - equator overlay - backface visibility tweaks - axis hint / reveal messaging - menu preview reset tween This keeps the "optional spatializer" model while letting each spatializer compose small feature modules internally without exposing modifiers to 2D levels. ## Mapping SphereLevel -> Spatializer Move these from `SphereLevel.cs` into `SphereSpatializer`: - Projection: `ProjectWorldToLocal`, `ProjectLocalToWorld` - Dot visibility/backface: `UpdateDots`, `GetBackfaceHidePos` - Line projection: `UpdateLines`, `ApplyProjectedLinePts` - Rotation input: `UpdateRotation`, `HandleAxisInput`, `ShowAxisMessage` - Equator overlay: `EnsureEquatorLine`, `RefreshEquatorStyle`, `UpdateEquatorLine`, `ShowEquator/HideEquator` - Menu preview reset: `ResetMenuPreview`, tween handling - Init/cache: `PrimeSphere`, `CacheDots`, `SyncLns` ## Effects and collisions - **Collisions remain 2D** using projected points. - **Arrow effects** continue to use `Line.Pts` after connection (no change needed). - **Obstacles** remain 2D overlays in projected space. ## Flow diagram ```text LoadLevelVisuals -> spawn boundary/dots/obstacles (unchanged) -> rules = LevelFactory.Create(LevelType) -> if Dimension > 2: spatializer = SpatializerFactory.Create(Dimension) -> spatializer.Init(LevelMgr, LevelDataSO) -> rules.Init(LevelMgr, LevelDataSO) -> GameEvents.TriggerLevelLoaded Update -> rules.Tick(dt) -> spatializer?.Tick(dt) Connection -> LevelMgr.CreateLine(...) adds Line with 2D pts -> spatializer keeps Line.Pts updated (projected 2D) -> EffectsFactory shows arrow using Line.Pts (unchanged) Exit -> spatializer?.Dispose() -> rules.Dispose() -> LevelMgr clears visuals (unchanged) ``` ## Why this is minimal and clean - 2D levels never see the spatializer. - All existing gameplay and editor flows remain valid. - Spatial complexity is isolated to Dimension > 2 only.