diff --git a/Assets/Darkmatter/Code/Core/Contracts/Features.meta b/Assets/Darkmatter/Code/Core/Contracts/Features.meta
new file mode 100644
index 0000000..8eeaad1
--- /dev/null
+++ b/Assets/Darkmatter/Code/Core/Contracts/Features.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 5f9926c57855c418c8298f8b3c44e034
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Darkmatter/Code/Core/Contracts/Paper.meta b/Assets/Darkmatter/Code/Core/Contracts/Features/Paper.meta
similarity index 100%
rename from Assets/Darkmatter/Code/Core/Contracts/Paper.meta
rename to Assets/Darkmatter/Code/Core/Contracts/Features/Paper.meta
diff --git a/Assets/Darkmatter/Code/Core/Contracts/Paper/IArtInputBridge.cs b/Assets/Darkmatter/Code/Core/Contracts/Features/Paper/IArtInputBridge.cs
similarity index 74%
rename from Assets/Darkmatter/Code/Core/Contracts/Paper/IArtInputBridge.cs
rename to Assets/Darkmatter/Code/Core/Contracts/Features/Paper/IArtInputBridge.cs
index 7d4f425..dcc8dae 100644
--- a/Assets/Darkmatter/Code/Core/Contracts/Paper/IArtInputBridge.cs
+++ b/Assets/Darkmatter/Code/Core/Contracts/Features/Paper/IArtInputBridge.cs
@@ -1,6 +1,6 @@
using UnityEngine;
-namespace Darkmatter.Core.Contracts.Paper
+namespace Darkmatter.Core.Contracts.Features.Paper
{
public interface IArtInputBridge
{
diff --git a/Assets/Darkmatter/Code/Core/Contracts/Paper/IArtInputBridge.cs.meta b/Assets/Darkmatter/Code/Core/Contracts/Features/Paper/IArtInputBridge.cs.meta
similarity index 100%
rename from Assets/Darkmatter/Code/Core/Contracts/Paper/IArtInputBridge.cs.meta
rename to Assets/Darkmatter/Code/Core/Contracts/Features/Paper/IArtInputBridge.cs.meta
diff --git a/Assets/Darkmatter/Code/Core/Contracts/Paper/IPaperRig.cs b/Assets/Darkmatter/Code/Core/Contracts/Features/Paper/IPaperRig.cs
similarity index 83%
rename from Assets/Darkmatter/Code/Core/Contracts/Paper/IPaperRig.cs
rename to Assets/Darkmatter/Code/Core/Contracts/Features/Paper/IPaperRig.cs
index 498bfc5..82823df 100644
--- a/Assets/Darkmatter/Code/Core/Contracts/Paper/IPaperRig.cs
+++ b/Assets/Darkmatter/Code/Core/Contracts/Features/Paper/IPaperRig.cs
@@ -1,6 +1,6 @@
using UnityEngine;
-namespace Darkmatter.Core.Contracts.Paper
+namespace Darkmatter.Core.Contracts.Features.Paper
{
public interface IPaperRig
{
diff --git a/Assets/Darkmatter/Code/Core/Contracts/Paper/IPaperRig.cs.meta b/Assets/Darkmatter/Code/Core/Contracts/Features/Paper/IPaperRig.cs.meta
similarity index 100%
rename from Assets/Darkmatter/Code/Core/Contracts/Paper/IPaperRig.cs.meta
rename to Assets/Darkmatter/Code/Core/Contracts/Features/Paper/IPaperRig.cs.meta
diff --git a/Assets/Darkmatter/Code/Core/Contracts/Services/Gallery.meta b/Assets/Darkmatter/Code/Core/Contracts/Services/Gallery.meta
new file mode 100644
index 0000000..681d6d9
--- /dev/null
+++ b/Assets/Darkmatter/Code/Core/Contracts/Services/Gallery.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 3681f5b008ffa422cb0d53ea3a04d839
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Darkmatter/Code/Core/Contracts/Services/Gallery/IGalleryService.cs b/Assets/Darkmatter/Code/Core/Contracts/Services/Gallery/IGalleryService.cs
new file mode 100644
index 0000000..743fe61
--- /dev/null
+++ b/Assets/Darkmatter/Code/Core/Contracts/Services/Gallery/IGalleryService.cs
@@ -0,0 +1,10 @@
+using Cysharp.Threading.Tasks;
+using UnityEngine;
+
+namespace Darkmatter.Core.Contracts.Services.Gallery
+{
+ public interface IGalleryService
+ {
+ void SaveImageAsync(Texture2D sprite, string fileName);
+ }
+}
\ No newline at end of file
diff --git a/Assets/Darkmatter/Code/Core/Contracts/Services/Gallery/IGalleryService.cs.meta b/Assets/Darkmatter/Code/Core/Contracts/Services/Gallery/IGalleryService.cs.meta
new file mode 100644
index 0000000..a9fa138
--- /dev/null
+++ b/Assets/Darkmatter/Code/Core/Contracts/Services/Gallery/IGalleryService.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 02a64c2248ad94919af786bbe61fb43e
\ No newline at end of file
diff --git a/Assets/Darkmatter/Code/Services/Gallery.meta b/Assets/Darkmatter/Code/Services/Gallery.meta
new file mode 100644
index 0000000..1fffd38
--- /dev/null
+++ b/Assets/Darkmatter/Code/Services/Gallery.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: b570aac99834740da891dc197d1183a1
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Darkmatter/Code/Services/Gallery/Core.meta b/Assets/Darkmatter/Code/Services/Gallery/Core.meta
new file mode 100644
index 0000000..9ac322a
--- /dev/null
+++ b/Assets/Darkmatter/Code/Services/Gallery/Core.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 6f6d23b09611c45aca2d6f5a3cd6196c
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Darkmatter/Code/Services/Gallery/Core/GalleryService.cs b/Assets/Darkmatter/Code/Services/Gallery/Core/GalleryService.cs
new file mode 100644
index 0000000..99a2b31
--- /dev/null
+++ b/Assets/Darkmatter/Code/Services/Gallery/Core/GalleryService.cs
@@ -0,0 +1,13 @@
+using Darkmatter.Core.Contracts.Services.Gallery;
+using UnityEngine;
+
+namespace Darkmatter.Services.Gallery
+{
+ public class GalleryService : IGalleryService
+ {
+ public void SaveImageAsync(Texture2D image, string fileName)
+ {
+ NativeGallery.SaveImageToGallery(image, "ColorBook", fileName);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/Darkmatter/Code/Services/Gallery/Core/GalleryService.cs.meta b/Assets/Darkmatter/Code/Services/Gallery/Core/GalleryService.cs.meta
new file mode 100644
index 0000000..525a710
--- /dev/null
+++ b/Assets/Darkmatter/Code/Services/Gallery/Core/GalleryService.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: ef8e2d865f2c14be98f8f21f8081ac15
\ No newline at end of file
diff --git a/Assets/Darkmatter/Code/Services/Gallery/Installers.meta b/Assets/Darkmatter/Code/Services/Gallery/Installers.meta
new file mode 100644
index 0000000..d527532
--- /dev/null
+++ b/Assets/Darkmatter/Code/Services/Gallery/Installers.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 49bd8a78f60094fc390a5b99b2c78b66
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Darkmatter/Code/Services/Gallery/Installers/GalleryServiceModule.cs b/Assets/Darkmatter/Code/Services/Gallery/Installers/GalleryServiceModule.cs
new file mode 100644
index 0000000..749a376
--- /dev/null
+++ b/Assets/Darkmatter/Code/Services/Gallery/Installers/GalleryServiceModule.cs
@@ -0,0 +1,19 @@
+using UnityEngine;
+
+namespace Darkmatter.Services.Gallery
+{
+ public class GalleryServiceModule : MonoBehaviour
+ {
+ // Start is called once before the first execution of Update after the MonoBehaviour is created
+ void Start()
+ {
+
+ }
+
+ // Update is called once per frame
+ void Update()
+ {
+
+ }
+ }
+}
diff --git a/Assets/Darkmatter/Code/Services/Gallery/Installers/GalleryServiceModule.cs.meta b/Assets/Darkmatter/Code/Services/Gallery/Installers/GalleryServiceModule.cs.meta
new file mode 100644
index 0000000..3cf9500
--- /dev/null
+++ b/Assets/Darkmatter/Code/Services/Gallery/Installers/GalleryServiceModule.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: f03c84255756e497f96c3baa7f6abe16
\ No newline at end of file
diff --git a/Assets/Darkmatter/Code/Services/Gallery/Services.Gallery.asmdef b/Assets/Darkmatter/Code/Services/Gallery/Services.Gallery.asmdef
new file mode 100644
index 0000000..d8d9b33
--- /dev/null
+++ b/Assets/Darkmatter/Code/Services/Gallery/Services.Gallery.asmdef
@@ -0,0 +1,19 @@
+{
+ "name": "Services.Gallery",
+ "rootNamespace": "Darkmatter.Services.Gallery",
+ "references": [
+ "GUID:6a0a834eb41764f12ba55c3fb04a40cb",
+ "GUID:c1c03c0e5b2f4412b9f2be1c20d6a9b1",
+ "GUID:b0214a6008ed146ff8f122a6a9c2f6cc",
+ "GUID:6e5063adab271564ba0098a06a8cebda"
+ ],
+ "includePlatforms": [],
+ "excludePlatforms": [],
+ "allowUnsafeCode": false,
+ "overrideReferences": false,
+ "precompiledReferences": [],
+ "autoReferenced": true,
+ "defineConstraints": [],
+ "versionDefines": [],
+ "noEngineReferences": false
+}
\ No newline at end of file
diff --git a/Assets/Darkmatter/Code/Services/Gallery/Services.Gallery.asmdef.meta b/Assets/Darkmatter/Code/Services/Gallery/Services.Gallery.asmdef.meta
new file mode 100644
index 0000000..fd9731b
--- /dev/null
+++ b/Assets/Darkmatter/Code/Services/Gallery/Services.Gallery.asmdef.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: b817d098d27854edb9b82cfee89717a8
+AssemblyDefinitionImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Readme.md b/Readme.md
index 2a58491..9415c78 100644
--- a/Readme.md
+++ b/Readme.md
@@ -68,139 +68,209 @@ Features ──► Core ◄── Services
## 4. Folder Structure
+This section reflects the **actual project on disk today**. Empty folders that have been reserved for upcoming work are marked `(planned)`; everything else has at least one file in it. Aspirational additions for the rest of the game are listed in §4c at the bottom.
+
+### 4a. Actual layout on disk
+
```
-Assets/Darkmatter/Code/
-├── App/
-│ ├── Boot/
-│ │ └── AppBoot.cs
-│ └── LifetimeScopes/
-│ ├── RootLifetimeScope.cs
-│ ├── MainMenuLifetimeScope.cs
-│ ├── ColorBookLifetimeScope.cs
-│ └── ArtBookLifetimeScope.cs
+Assets/Darkmatter/
+├── Scenes/
+│ └── Boot.unity ← only scene wired so far
│
-├── Core/
-│ ├── Drawing/
-│ │ ├── IDrawingTemplate.cs
-│ │ ├── IDrawingTemplateCatalog.cs
-│ │ ├── ShapePieceDTO.cs
-│ │ └── ColorRegionDTO.cs
-│ ├── Coloring/
-│ │ ├── IColorPalette.cs
-│ │ └── PaintCommandDTO.cs
-│ ├── Paper/ (shared art rig — RT-as-paper)
-│ │ ├── IPaperRig.cs
-│ │ └── IArtInputBridge.cs
-│ ├── History/
-│ │ ├── ICommand.cs
-│ │ └── IUndoStack.cs
-│ ├── Gallery/
-│ │ ├── IGalleryService.cs
-│ │ └── SavedArtworkDTO.cs
-│ ├── Capture/
-│ │ └── ICaptureService.cs
-│ ├── Progression/
-│ │ └── IProgressionService.cs
-│ └── Signals/
-│ ├── DrawingSelectedSignal.cs
-│ ├── ShapeAssembledSignal.cs
-│ ├── ColorAppliedSignal.cs
-│ ├── ArtworkCapturedSignal.cs
-│ └── ArtworkSavedSignal.cs
+├── Content/ ← singular ("Content", not "Contents")
+│ └── Gameplay/
+│ └── PaperRig/ ← (planned — paper rig prefabs)
│
-├── Libs/
-│ ├── CommandStack/ (generic bounded undo/redo)
-│ ├── EventBus/ (shared with bus game if monorepo)
-│ └── FSM/ (optional, for ColorBookFlow)
+├── Data/
+│ ├── Inputs/ (Input System .inputactions)
+│ └── Settings/
+│ ├── Persistance/Resources/ (ProtectedPlayerPrefs settings)
+│ ├── Rendering/ (URP renderer + asset)
+│ └── Scenes/URP2DSceneTemplate.unity
│
-├── Services/
-│ ├── Audio/
-│ ├── Inputs/
-│ ├── Assets/ (Addressables wrapper — IAssetProviderService)
-│ ├── Scenes/
-│ ├── Persistence/ (JSON / PlayerPrefs for non-image state)
-│ ├── Gallery/ (file IO — PNG + sidecar JSON)
-│ └── Capture/ (RenderTexture → PNG, paper bg compositing)
-│
-└── Features/
- ├── MainMenu/
- ├── DrawingCatalog/
- ├── Paper/ (RT paper rig — ArtCamera + RenderTexture + input bridge)
- ├── ShapeBuilder/
- ├── Coloring/
- ├── History/
- ├── Capture/
- ├── Progression/
- ├── ColorBookFlow/ (orchestrates panel swap, next, capture chain)
- └── ArtBook/
+└── Code/
+ ├── App/
+ │ └── LifetimeScopes/
+ │ └── RootLifetimeScope.cs ← scope loads serialized IServiceModule list
+ │ Darkmatter.App.asmdef
+ │
+ ├── Core/ (asmdef name: `Core`, namespace root `Darkmatter.Core.*`)
+ │ ├── Compatibility/
+ │ │ └── IsExternalInit.cs (C#9 init shim for older runtimes)
+ │ ├── Contracts/
+ │ │ ├── Paper/ ← misplaced empty folder — should be Contracts/Features/Paper/ (delete or move)
+ │ │ └── Services/
+ │ │ ├── Assets/IAssetProviderService.cs
+ │ │ ├── Audio/IAudioService.cs, ISfxPlayer.cs
+ │ │ ├── Camera/ICameraService.cs
+ │ │ ├── Capture/ ← (planned — ICaptureService)
+ │ │ ├── Inputs/IInputReader.cs
+ │ │ └── Scenes/ISceneService.cs
+ │ ├── Data/
+ │ │ ├── Dynamic/Services/Audio/ (AudioHandle, AudioRequest)
+ │ │ └── Static/Services/Audio/ (SfxCatalogSO)
+ │ └── Enums/
+ │ └── Services/
+ │ ├── Audio/ (AudioChannel, AudioPlayMode, SfxId)
+ │ ├── Camera/CameraType.cs (MainCamera, UICamera — ArtCamera not added yet)
+ │ └── Scenes/GameScene.cs
+ │
+ ├── Features/ ← (planned — empty folder today)
+ │
+ ├── Libs/
+ │ ├── FSM/ (IState, State, StateMachine + Docs)
+ │ │ Libs.FSM.asmdef
+ │ ├── Installers/ (IServiceModule — Register(IContainerBuilder))
+ │ │ Libs.Installers.asmdef
+ │ ├── Observer/ (IEventBus, EventBus — note: not named "EventBus")
+ │ │ Libs.Observer.asmdef
+ │ ├── PlayerPrefs/ (ProtectedPlayerPrefs — used in place of a Persistence service)
+ │ │ ├── Editor/ Libs.PlayerPrefs.Editor.asmdef
+ │ │ └── Runtime/ Libs.PlayerPrefs.asmdef
+ │ └── UI/ (ToggleButton, ToggleButtonGroup)
+ │ Libs.UI.asmdef
+ │
+ └── Services/
+ ├── Analytics/
+ │ ├── Installers/AnalyticsServiceModule.cs
+ │ └── Systems/FirebaseAnalyticsSystem.cs
+ │ Services.Analytics.asmdef
+ ├── Assets/
+ │ ├── AddressableAssetProviderService.cs
+ │ └── AddressableLoadHandleTracker.cs
+ │ Services.Assets.asmdef
+ ├── Audio/
+ │ ├── AudioService.cs
+ │ └── SfxPlayer.cs
+ │ Services.Audio.asmdef
+ ├── Camera/
+ │ ├── Installers/CameraServiceModule.cs
+ │ └── Service/CameraService.cs
+ │ Services.Camera.asmdef
+ ├── Inputs/
+ │ ├── Generated/GameInputs.cs (Input System codegen)
+ │ ├── Installers/InputServiceModule.cs
+ │ └── Readers/InputReaderSO.cs
+ │ Services.Inputs.asmdef
+ └── Scenes/
+ └── SceneService.cs
+ Services.Scenes.asmdef
```
-### Per-feature folder layout
+### 4b. Conventions visible in current code
-Every feature follows the same internal shape:
+- **Asmdef per Service / Lib / App / Core.** No Feature asmdefs yet (folder is empty).
+- **Core sub-tree shape:** `Core/{Compatibility, Contracts, Data, Enums}` — deeply nested by Service rather than by topic. Game-specific Core types (Drawing, Coloring, Paper, Gallery, History, Progression, Signals) will be added under either `Core/Contracts//` or new top-level `Core//` — pick one convention before adding the first one.
+- **Service sub-shape varies** — some are flat (`Services/Audio/*.cs`), some split (`Services/Camera/Installers/`, `Services/Camera/Service/`). The "official" per-feature shape (§4d) hasn't been validated against real code yet.
+- **Installer pattern:** `MonoBehaviour, IServiceModule` with `Register(IContainerBuilder builder)`. **Not** `IInstaller.Install(...)` as older sections of this doc imply. New installers must follow `IServiceModule`.
+- **No `AppBoot`** entry point exists yet. `RootLifetimeScope` only iterates a serialized `MonoBehaviour[] serviceModules` and calls `Register` on each `IServiceModule`. Boot sequence in §29 is aspirational.
+- **No Persistence service.** `Libs/PlayerPrefs` (the `ProtectedPlayerPrefs` library) is in place for small-key state. JSON persistence for gallery sidecars will need a separate service when Gallery lands.
+- **Camera service is a registry**, not a fitter. `ICameraService.Register/Get` only — no aspect-fit logic.
+
+### 4c. Planned additions (not on disk yet)
+
+All new game code follows the same nesting pattern as existing Services — `Contracts/Features//`, `Data/{Dynamic,Static}/Features//`, `Features//` — and asmdefs drop the `Darkmatter.` prefix to match `Core`, `Services.Audio`, `Libs.Observer`.
+
+Rough landing order for ColorBook scene to be playable:
+
+| Path | Role |
+|---|---|
+| `Core/Contracts/Features/Paper/IPaperRig.cs`, `IArtInputBridge.cs` | Paper rig contracts |
+| `Core/Contracts/Services/Capture/ICaptureService.cs` | Capture service contract |
+| `Core/Contracts/Services/Gallery/IGalleryService.cs` | Gallery service contract |
+| `Core/Contracts/Features/Drawing/IDrawingTemplate.cs`, `IDrawingTemplateCatalog.cs` | Drawing template contracts |
+| `Core/Contracts/Features/Coloring/IColorPalette.cs` | Palette contract |
+| `Core/Contracts/Features/History/ICommand.cs`, `IUndoStack.cs` | Undo/redo contracts |
+| `Core/Contracts/Features/Progression/IProgressionService.cs` | Progression contract (despite the name, it's a feature contract since it's game-specific) |
+| `Core/Data/Static/Features/Drawing/` (DrawingTemplateSO) | Authored drawing data |
+| `Core/Data/Static/Features/Coloring/` (ColorPaletteSO) | Authored palette data |
+| `Core/Data/Dynamic/Features/Drawing/ShapePieceDTO.cs`, `ColorRegionDTO.cs` | Runtime drawing structs |
+| `Core/Data/Dynamic/Features/Coloring/PaintCommandDTO.cs` | Runtime coloring struct |
+| `Core/Data/Dynamic/Features/Gallery/SavedArtworkDTO.cs` | Runtime gallery struct |
+| `Core/Data/Dynamic/Features/Signals/` (DrawingSelectedSignal, ShapeAssembledSignal, ColorAppliedSignal, ArtworkCapturedSignal, ArtworkSavedSignal) | Cross-feature signal structs |
+| `Core/Enums/Services/Camera/CameraType.cs` | Add `ArtCamera` enum value to existing file |
+| `Libs/CommandStack/` (+ `Libs.CommandStack.asmdef`) | Bounded undo/redo |
+| `Services/Capture/` (+ `Services.Capture.asmdef`) | `RenderTextureCaptureService` reads `IPaperRig.Surface` |
+| `Services/Gallery/` (+ `Services.Gallery.asmdef`) | `FileGalleryService` — PNG + sidecar JSON IO |
+| `Features/Paper/` (+ `Features.Paper.asmdef`) | Scene-bound RT rig |
+| `Features/{MainMenu,DrawingCatalog,ShapeBuilder,Coloring,History,Capture,Progression,ColorBookFlow,ArtBook}/` (+ asmdefs each) | Game features |
+| `App/LifetimeScopes/{MainMenu,ColorBook,ArtBook}LifetimeScope.cs` | Per-scene scopes |
+| `App/Boot/AppBoot.cs` | Bootstrap entry point |
+| `Assets/Darkmatter/Scenes/{MainMenu,ColorBook,ArtBook}.unity` | Scenes |
+| `Content/Gameplay/Drawings///{Template.asset, Pieces/, Regions/, PaperBackground.png}` | Authored drawings (under existing `Content/Gameplay/` root) |
+| `Content/Gameplay/Palettes/*.asset` | Color palettes |
+| `Content/Audio/{UI,Coloring}/` | SFX banks |
+
+### 4d. Per-feature folder layout (matches existing Services pattern)
+
+Look at `Services/Camera/` (`Installers/` + `Service/`) and `Services/Analytics/` (`Installers/` + `Systems/`) — that's the convention. Features adopt the same shape, adding `UI/` or `Views/` only when there's something to put in them.
```
Features/[Name]/
-├── Installers/ IInstaller — VContainer registrations
-├── Systems/ Controllers, services (pure C#)
-├── Repository/ In-memory state holders
-├── Commands/ ICommand implementations (if feature mutates undoable state)
-├── UI/
-│ ├── *Presenter.cs Pure C#, listens to model, drives view
-│ └── *View.cs MonoBehaviour, setters only
-├── Views/ World-space MonoBehaviours (sprites, colliders)
-└── Docs/ Feature-specific markdown
+├── Installers/ IServiceModule — VContainer registration
+├── Systems/ (or Service/) Controllers, repositories, factories (pure C#)
+├── UI/ (only if the feature has Canvas UI)
+│ ├── *Presenter.cs Pure C#, listens to model, drives view
+│ └── *View.cs MonoBehaviour, setters only
+├── Views/ (only if the feature has world-space MonoBehaviours)
+└── Features..asmdef
```
-### Asset folder parallel
-
-```
-Assets/Darkmatter/Contents/
-├── Drawings/
-│ ├── Animals//
-│ │ ├── Template.asset (DrawingTemplateSO)
-│ │ ├── Pieces/*.png
-│ │ ├── Regions/*.png
-│ │ └── PaperBackground.png
-│ └── Vehicles/...
-├── Palettes/*.asset (ColorPaletteSO)
-├── Audio/
-│ ├── UI/ (tap, swipe, button)
-│ └── Coloring/ (fill, complete, sparkle)
-└── UI/ (HUD prefabs, fonts, icons)
-```
+Rules of thumb pulled from current Services:
+- **Use `Service/` (singular)** when the feature has exactly one main implementation class (like `Services/Camera/Service/CameraService.cs`).
+- **Use `Systems/` (plural)** when there are multiple pure-C# coordinators (like `Services/Analytics/Systems/`).
+- **Skip nesting entirely** when the feature has only 1–2 files at root (like `Services/Audio/AudioService.cs` + `SfxPlayer.cs` flat).
+- **`Docs/` is per-folder** in current code — drop a `Docs/` inside any sub-folder that needs notes, don't make a global feature-level Docs.
---
-## 5. Namespaces
+## 5. Namespaces & Asmdef naming
-`Darkmatter.[Layer].[Module]`
+C# namespace pattern is `Darkmatter..[.]` — the `Darkmatter.` prefix stays on namespaces. Examples already in code:
-- `Darkmatter.Features.Coloring`
-- `Darkmatter.Features.ShapeBuilder`
-- `Darkmatter.Services.Gallery`
-- `Darkmatter.Services.Capture`
-- `Darkmatter.Core.Drawing`
-- `Darkmatter.Lib.CommandStack`
+- `Darkmatter.Core.Contracts.Services.Camera` ([ICameraService.cs](Assets/Darkmatter/Code/Core/Contracts/Services/Camera/ICameraService.cs))
+- `Darkmatter.Services.Camera` ([CameraService.cs](Assets/Darkmatter/Code/Services/Camera/Service/CameraService.cs))
+- `Darkmatter.Services.Camera.Installers` ([CameraServiceModule.cs](Assets/Darkmatter/Code/Services/Camera/Installers/CameraServiceModule.cs))
+- `Darkmatter.Libs.Installers` ([IServiceModule.cs](Assets/Darkmatter/Code/Libs/Installers/IServiceModule.cs))
-Each maps 1:1 to a `.asmdef`.
+**Asmdef names drop the `Darkmatter.` prefix.** Existing pattern:
+
+| Namespace | Asmdef |
+|---|---|
+| `Darkmatter.Core.*` | `Core` |
+| `Darkmatter.App` | `Darkmatter.App` (one exception — keep as-is, don't churn) |
+| `Darkmatter.Libs.Observer` | `Libs.Observer` |
+| `Darkmatter.Libs.FSM` | `Libs.FSM` |
+| `Darkmatter.Libs.Installers` | `Libs.Installers` |
+| `Darkmatter.Libs.PlayerPrefs` | `Libs.PlayerPrefs` (+ `Libs.PlayerPrefs.Editor`) |
+| `Darkmatter.Libs.UI` | `Libs.UI` |
+| `Darkmatter.Services.Audio` | `Services.Audio` |
+| `Darkmatter.Services.Assets` | `Services.Assets` |
+| `Darkmatter.Services.Camera` | `Services.Camera` |
+| `Darkmatter.Services.Inputs` | `Services.Inputs` |
+| `Darkmatter.Services.Scenes` | `Services.Scenes` |
+| `Darkmatter.Services.Analytics` | `Services.Analytics` |
+
+New asmdefs follow the same convention: `Services.Capture`, `Services.Gallery`, `Libs.CommandStack`, `Features.Paper`, `Features.Coloring`, etc.
---
## 6. Scenes & Lifetime Scopes
-| Scene | Scope | Contents |
-|---|---|---|
-| `Boot.unity` | `RootLifetimeScope` | All Services + `IEventBus`. Persists forever. |
-| `MainMenu.unity` | `MainMenuLifetimeScope` | Menu presenter, art book entry. |
-| `ColorBook.unity` | `ColorBookLifetimeScope` | DrawingCatalog, ShapeBuilder, Coloring, History, Capture, ColorBookFlow. |
-| `ArtBook.unity` | `ArtBookLifetimeScope` | Gallery presenter, viewer, share. |
+| Scene | Scope | Status | Contents |
+|---|---|---|---|
+| `Boot.unity` | `RootLifetimeScope` | ✅ exists | All Services + Libs. Persists forever. |
+| `MainMenu.unity` | `MainMenuLifetimeScope` | ⚠️ planned | Menu presenter, art book entry. |
+| `ColorBook.unity` | `ColorBookLifetimeScope` | ⚠️ planned | `PaperRig`, DrawingCatalog, ShapeBuilder, Coloring, History, Capture, ColorBookFlow. |
+| `ArtBook.unity` | `ArtBookLifetimeScope` | ⚠️ planned | Gallery presenter, viewer, share. |
+
+Only `Boot.unity` exists today; the three scene scope classes haven't been written yet either (only `RootLifetimeScope` exists in [App/LifetimeScopes/](Assets/Darkmatter/Code/App/LifetimeScopes/)).
Scopes nest: `Root → (MainMenu | ColorBook | ArtBook)`. Services resolved from the root parent. Scene scopes only register their own features.
-### Boot chain
+### Boot chain (planned)
-`AppBoot` runs once, in order:
+No `AppBoot` class exists yet — today `RootLifetimeScope` only registers services and stops there. When `AppBoot` is added (as an `IAsyncStartable` registered via `builder.RegisterEntryPoint()`), it should run once, in order:
1. Initialize `IAssetProviderService` (Addressables init).
2. Preload essential bundles (palettes, UI sounds).
@@ -275,8 +345,10 @@ All Core types are pure data or interfaces.
### Drawing
+> Contracts live in `Darkmatter.Core.Contracts.Features.Drawing`; DTOs in `Darkmatter.Core.Data.Dynamic.Features.Drawing`.
+
```csharp
-namespace Darkmatter.Core.Drawing;
+namespace Darkmatter.Core.Contracts.Features.Drawing;
public interface IDrawingTemplate {
string Id { get; }
@@ -305,8 +377,10 @@ public readonly struct ColorRegionDTO {
### Coloring
+> Contracts in `Darkmatter.Core.Contracts.Features.Coloring`; DTOs in `Darkmatter.Core.Data.Dynamic.Features.Coloring`.
+
```csharp
-namespace Darkmatter.Core.Coloring;
+namespace Darkmatter.Core.Contracts.Features.Coloring;
public interface IColorPalette {
string Id { get; }
@@ -322,8 +396,10 @@ public readonly struct PaintCommandDTO {
### Paper (RT rig + input bridge)
+> Contracts live in `Darkmatter.Core.Contracts.Features.Paper`. Files at `Core/Contracts/Features/Paper/`.
+
```csharp
-namespace Darkmatter.Core.Paper;
+namespace Darkmatter.Core.Contracts.Features.Paper;
public interface IPaperRig {
Camera ArtCamera { get; } // offscreen, targetTexture = Surface
@@ -346,8 +422,10 @@ public interface IArtInputBridge {
### History
+> Contracts in `Darkmatter.Core.Contracts.Features.History`.
+
```csharp
-namespace Darkmatter.Core.History;
+namespace Darkmatter.Core.Contracts.Features.History;
public interface ICommand {
void Execute();
@@ -366,8 +444,10 @@ public interface IUndoStack {
### Gallery & Capture
+> `IGalleryService` is a Service contract → `Darkmatter.Core.Contracts.Services.Gallery`. `SavedArtworkDTO` is a runtime data struct → `Darkmatter.Core.Data.Dynamic.Features.Gallery`. `ICaptureService` → `Darkmatter.Core.Contracts.Services.Capture`.
+
```csharp
-namespace Darkmatter.Core.Gallery;
+namespace Darkmatter.Core.Data.Dynamic.Features.Gallery;
public readonly struct SavedArtworkDTO {
public string Id { get; }
@@ -377,6 +457,8 @@ public readonly struct SavedArtworkDTO {
public string ThumbnailPath { get; }
}
+namespace Darkmatter.Core.Contracts.Services.Gallery;
+
public interface IGalleryService {
UniTask SaveAsync(byte[] png, string templateId);
UniTask> ListAsync();
@@ -384,7 +466,7 @@ public interface IGalleryService {
UniTask DeleteAsync(string artworkId);
}
-namespace Darkmatter.Core.Capture;
+namespace Darkmatter.Core.Contracts.Services.Capture;
public interface ICaptureService {
// No camera or paperBg args — capture reads directly from IPaperRig.Surface.
@@ -397,8 +479,10 @@ public interface ICaptureService {
### Signals
+> Signal structs live in `Darkmatter.Core.Data.Dynamic.Features.Signals` (runtime data, cross-feature).
+
```csharp
-namespace Darkmatter.Core.Signals;
+namespace Darkmatter.Core.Data.Dynamic.Features.Signals;
public readonly struct DrawingSelectedSignal {
public string TemplateId { get; }
@@ -713,109 +797,123 @@ Maintained alongside the [Darkmatter Architecture Guide](../Assets/Darkmatter_Ar
## 20. Assembly Definition Map
-Every folder under `Code/` is its own `.asmdef`. References follow the layer rules exactly.
+Every Lib / Service / Feature is its own `.asmdef`. The `Darkmatter.` prefix is **only** on the App asmdef; everything else uses bare `.` names. References follow the layer rules.
+
+### On disk today
| Asmdef | Path | References |
|---|---|---|
-| `Darkmatter.App` | `App/` | All Features, all Services, Core, Libs |
-| `Darkmatter.Core` | `Core/` | (none — `UniTask` allowed in async signatures) |
-| `Darkmatter.Lib.CommandStack` | `Libs/CommandStack/` | `Darkmatter.Core` |
-| `Darkmatter.Lib.EventBus` | `Libs/EventBus/` | `Darkmatter.Core` |
-| `Darkmatter.Lib.FSM` | `Libs/FSM/` | `Darkmatter.Core` |
-| `Darkmatter.Services.Audio` | `Services/Audio/` | `Darkmatter.Core` |
-| `Darkmatter.Services.Inputs` | `Services/Inputs/` | `Darkmatter.Core` |
-| `Darkmatter.Services.Assets` | `Services/Assets/` | `Darkmatter.Core` |
-| `Darkmatter.Services.Scenes` | `Services/Scenes/` | `Darkmatter.Core` |
-| `Darkmatter.Services.Persistence` | `Services/Persistence/` | `Darkmatter.Core` |
-| `Darkmatter.Services.Gallery` | `Services/Gallery/` | `Darkmatter.Core` |
-| `Darkmatter.Services.Capture` | `Services/Capture/` | `Darkmatter.Core` |
-| `Darkmatter.Features.MainMenu` | `Features/MainMenu/` | `Darkmatter.Core`, Libs |
-| `Darkmatter.Features.DrawingCatalog` | `Features/DrawingCatalog/` | `Darkmatter.Core`, Libs |
-| `Darkmatter.Features.Paper` | `Features/Paper/` | `Darkmatter.Core`, `Lib.Installers` |
-| `Darkmatter.Features.ShapeBuilder` | `Features/ShapeBuilder/` | `Darkmatter.Core`, Libs |
-| `Darkmatter.Features.Coloring` | `Features/Coloring/` | `Darkmatter.Core`, `Lib.CommandStack` |
-| `Darkmatter.Features.History` | `Features/History/` | `Darkmatter.Core`, `Lib.CommandStack` |
-| `Darkmatter.Features.Capture` | `Features/Capture/` | `Darkmatter.Core` |
-| `Darkmatter.Features.Progression` | `Features/Progression/` | `Darkmatter.Core` |
-| `Darkmatter.Features.ColorBookFlow` | `Features/ColorBookFlow/` | `Darkmatter.Core`, `Lib.FSM` |
-| `Darkmatter.Features.ArtBook` | `Features/ArtBook/` | `Darkmatter.Core` |
+| `Darkmatter.App` | `App/` | All Services, Libs, Core (Features when they land) |
+| `Core` | `Core/` | (none — `UniTask` allowed in async signatures) |
+| `Libs.FSM` | `Libs/FSM/` | `Core` |
+| `Libs.Installers` | `Libs/Installers/` | (VContainer only) |
+| `Libs.Observer` | `Libs/Observer/` | `Core` |
+| `Libs.PlayerPrefs` | `Libs/PlayerPrefs/Runtime/` | (standalone) |
+| `Libs.PlayerPrefs.Editor` | `Libs/PlayerPrefs/Editor/` | `Libs.PlayerPrefs` |
+| `Libs.UI` | `Libs/UI/` | `Core` |
+| `Services.Analytics` | `Services/Analytics/` | `Core`, `Libs.Installers` |
+| `Services.Assets` | `Services/Assets/` | `Core`, `Libs.Installers` |
+| `Services.Audio` | `Services/Audio/` | `Core`, `Libs.Installers` |
+| `Services.Camera` | `Services/Camera/` | `Core`, `Libs.Installers` |
+| `Services.Inputs` | `Services/Inputs/` | `Core`, `Libs.Installers` |
+| `Services.Scenes` | `Services/Scenes/` | `Core`, `Libs.Installers` |
-**Hard rule:** No Service asmdef references any Feature asmdef. No Feature asmdef references another Feature asmdef. Compiler enforces the architecture.
+### Planned (not on disk yet)
+
+| Asmdef | Path | References |
+|---|---|---|
+| `Libs.CommandStack` | `Libs/CommandStack/` | `Core` |
+| `Services.Capture` | `Services/Capture/` | `Core`, `Libs.Installers` |
+| `Services.Gallery` | `Services/Gallery/` | `Core`, `Libs.Installers` |
+| `Features.Paper` | `Features/Paper/` | `Core`, `Libs.Installers` |
+| `Features.MainMenu` | `Features/MainMenu/` | `Core`, `Libs.Installers` |
+| `Features.DrawingCatalog` | `Features/DrawingCatalog/` | `Core`, `Libs.Installers` |
+| `Features.ShapeBuilder` | `Features/ShapeBuilder/` | `Core`, `Libs.Installers` |
+| `Features.Coloring` | `Features/Coloring/` | `Core`, `Libs.Installers`, `Libs.CommandStack` |
+| `Features.History` | `Features/History/` | `Core`, `Libs.Installers`, `Libs.CommandStack` |
+| `Features.Capture` | `Features/Capture/` | `Core`, `Libs.Installers` |
+| `Features.Progression` | `Features/Progression/` | `Core`, `Libs.Installers`, `Libs.PlayerPrefs` |
+| `Features.ColorBookFlow` | `Features/ColorBookFlow/` | `Core`, `Libs.Installers`, `Libs.FSM` |
+| `Features.ArtBook` | `Features/ArtBook/` | `Core`, `Libs.Installers` |
+
+**Hard rules:**
+- No Service asmdef references any Feature asmdef.
+- No Feature asmdef references another Feature asmdef.
+- All Services and Features depend on `Libs.Installers` so they can implement `IServiceModule`.
+- The compiler enforces this — if a `using` won't resolve, the dependency is wrong.
---
## 21. LifetimeScope Concrete Sample
-### `RootLifetimeScope` (Boot scene, persists forever)
+All scopes use the same pattern: a serialized `MonoBehaviour[]` list of `IServiceModule` installers. Each installer is a MonoBehaviour on a child GameObject of the scope. Scope iterates and calls `Register`. **No hardcoded registrations in the scope itself.** This is exactly what [RootLifetimeScope.cs](Assets/Darkmatter/Code/App/LifetimeScopes/RootLifetimeScope.cs) already does today.
+
+### `RootLifetimeScope` (Boot scene, persists forever) — actual code
```csharp
-namespace Darkmatter.App.LifetimeScopes;
+using Darkmatter.Libs.Installers;
+using UnityEngine;
+using VContainer;
+using VContainer.Unity;
-public sealed class RootLifetimeScope : LifetimeScope {
- [SerializeField] private AudioServiceConfig _audioConfig;
- [SerializeField] private InputReaderSO _inputReader;
+public class RootLifetimeScope : LifetimeScope {
+ [SerializeField] private MonoBehaviour[] serviceModules;
protected override void Configure(IContainerBuilder builder) {
- // EventBus
- builder.Register(Lifetime.Singleton);
-
- // Services
- builder.RegisterInstance(_inputReader).As();
- builder.Register(Lifetime.Singleton)
- .WithParameter(_audioConfig);
- builder.Register(Lifetime.Singleton);
- builder.Register(Lifetime.Singleton);
- builder.Register(Lifetime.Singleton);
- builder.Register(Lifetime.Singleton);
- builder.Register(Lifetime.Singleton);
-
- // App entry
- builder.RegisterEntryPoint();
+ foreach (var module in serviceModules) {
+ if (module is IServiceModule serviceModule)
+ serviceModule.Register(builder);
+ }
}
}
```
-### `ColorBookLifetimeScope` (per-scene, child of Root)
+The inspector lists the installer MonoBehaviours in `serviceModules[]`. Drag the children of the Boot scope GameObject (e.g. `AudioServiceModule`, `CameraServiceModule`, `InputServiceModule`, `AssetProviderServiceModule`, `AnalyticsServiceModule`, `SceneServiceModule`) into that slot. Each is a `MonoBehaviour, IServiceModule`.
+
+### `ColorBookLifetimeScope` (per-scene, child of Root) — same pattern
```csharp
namespace Darkmatter.App.LifetimeScopes;
public sealed class ColorBookLifetimeScope : LifetimeScope {
- [SerializeField] private ColorBookSceneRefs _sceneRefs; // ArtCamera, panel roots, prefabs
- [SerializeField] private IInstaller[] _installers; // assigned in inspector
+ [SerializeField] private MonoBehaviour[] sceneModules;
protected override void Configure(IContainerBuilder builder) {
- builder.RegisterInstance(_sceneRefs);
-
- // Each feature ships an IInstaller
- foreach (var installer in _installers) installer.Install(builder);
-
- // Scene-scoped orchestrator
- builder.RegisterEntryPoint();
+ foreach (var module in sceneModules) {
+ if (module is IServiceModule serviceModule)
+ serviceModule.Register(builder);
+ }
}
}
```
-Drag these installers in the inspector:
-- `DrawingCatalogServiceModule`
-- `ShapeBuilderServiceModule`
-- `ColoringServiceModule`
-- `HistoryServiceModule`
-- `CaptureFeatureModule`
-- `ProgressionServiceModule`
+Drag the scene's installer MonoBehaviours into `sceneModules[]`:
+- `PaperRigModule`
+- `DrawingCatalogModule`
+- `ShapeBuilderModule`
+- `ColoringModule`
+- `HistoryModule`
+- `CaptureModule`
+- `ProgressionModule`
+- `ColorBookFlowModule`
+
+Each registers its own classes via `IServiceModule.Register(IContainerBuilder)`.
+
+> If a scope needs a non-installer reference (e.g. a `ColorBookSceneRefs` MB holding camera + roots), expose it as a separate `[SerializeField]` and `builder.RegisterInstance(...)` it inside the scope's `Configure`. Don't put scene refs inside an installer — keep installers stateless across scenes.
---
## 22. Installer Pattern — Concrete Coloring Sample
+Mirrors the existing [CameraServiceModule.cs](Assets/Darkmatter/Code/Services/Camera/Installers/CameraServiceModule.cs) and [InputServiceModule.cs](Assets/Darkmatter/Code/Services/Inputs/Installers/InputServiceModule.cs) — a `MonoBehaviour` implementing `IServiceModule.Register`.
+
```csharp
namespace Darkmatter.Features.Coloring.Installers;
-[CreateAssetMenu(menuName = "Darkmatter/Installers/Coloring")]
-public sealed class ColoringServiceModule : ScriptableObject, IInstaller {
+public sealed class ColoringModule : MonoBehaviour, IServiceModule {
[SerializeField] private ColoringConfig _config;
- public void Install(IContainerBuilder builder) {
+ public void Register(IContainerBuilder builder) {
builder.RegisterInstance(_config);
builder.Register(Lifetime.Scoped).AsSelf();
builder.Register(Lifetime.Scoped)
@@ -828,9 +926,11 @@ public sealed class ColoringServiceModule : ScriptableObject, IInstaller {
```
Convention:
-- One `IInstaller` per feature.
-- `ScriptableObject` so it can be referenced by scene scope inspector.
+- One `IServiceModule` per feature, named `Module` (matches `CameraServiceModule`, `InputServiceModule`, `AnalyticsServiceModule` already in the project).
+- `MonoBehaviour` lives on a GameObject under the scope's hierarchy; dragged into the scope's `serviceModules[]` / `sceneModules[]` inspector list.
+- Method name is `Register`, not `Install`. There is **no `IInstaller`** in this project — uses `IServiceModule` from [Libs.Installers](Assets/Darkmatter/Code/Libs/Installers/IServiceModule.cs).
- Registers only its own types. Never touches another feature's types.
+- If the installer needs to wire scene-bound MonoBehaviours into DI, expose them as `[SerializeField]` fields on the installer itself and `builder.RegisterInstance(_foo)` them. See the planned `PaperRigModule` in §32.5b for an example.
---
@@ -1092,13 +1192,13 @@ Toddler-mode error UI:
## 30. Setup Checklist (new dev, day one)
-1. Open `Bus Game.sln` (color book lives in same repo / Unity project per plan).
-2. Verify Addressables groups exist: `Drawings_*`, `Palettes`, `Audio_*`.
-3. Open `Boot.unity` → confirm `RootLifetimeScope` references the right configs.
-4. Open `ColorBook.unity` → confirm `ColorBookLifetimeScope._installers[]` is fully populated.
-5. Hit Play from `Boot.unity` (entry scene). Never start mid-flow — DI parent scope must exist.
-6. To author a new drawing: duplicate `Animals/elephant/`, edit `Template.asset` (pieces + regions), add to the appropriate Addressables group.
-7. Run `Tests > EditMode` and `Tests > PlayMode` before pushing.
+1. Open `Colorbook.sln` at the repo root.
+2. Open `Assets/Darkmatter/Scenes/Boot.unity` (currently the only scene wired).
+3. Inspect the `RootLifetimeScope` GameObject — confirm its `serviceModules[]` list references the child installer MonoBehaviours (`AudioServiceModule`, `CameraServiceModule`, `InputServiceModule`, etc.).
+4. Hit Play from `Boot.unity`. Other scenes (`MainMenu`, `ColorBook`, `ArtBook`) don't exist yet — they're listed in §6 / §4c as planned work.
+5. When new scene scopes land, the same rule applies: never start a scene mid-flow, always enter from `Boot.unity` so the root scope exists.
+6. When drawings are authored: duplicate the template folder under `Content/Gameplay/Drawings///`, edit `Template.asset` (pieces + regions), add to the appropriate Addressables group.
+7. Run `Tests > EditMode` and `Tests > PlayMode` before pushing (test infra not set up yet — see §16).
---
@@ -1106,19 +1206,19 @@ Toddler-mode error UI:
| Class | Layer | Asmdef |
|---|---|---|
-| `IDrawingTemplate`, `ShapePieceDTO`, `ColorRegionDTO` | Core | `Darkmatter.Core` |
-| `IPaperRig`, `IArtInputBridge` | Core | `Darkmatter.Core` |
-| `ICommand`, `IUndoStack` | Core | `Darkmatter.Core` |
-| `BoundedUndoStack` | Libs | `Darkmatter.Lib.CommandStack` |
-| `AddressableAssetProviderService` | Services | `Darkmatter.Services.Assets` |
-| `FileGalleryService` | Services | `Darkmatter.Services.Gallery` |
-| `RenderTextureCaptureService` | Services | `Darkmatter.Services.Capture` |
-| `PaperRig`, `ArtInputBridge`, `PaperRigModule` | Features | `Darkmatter.Features.Paper` |
-| `ColoringController`, `PaintRegionCommand` | Features | `Darkmatter.Features.Coloring` |
-| `ShapeBuilderController`, `ShapePieceView` | Features | `Darkmatter.Features.ShapeBuilder` |
-| `HistoryController` | Features | `Darkmatter.Features.History` |
-| `ColorBookFlowController` | Features | `Darkmatter.Features.ColorBookFlow` |
-| `GalleryPresenter`, `GalleryGridView` | Features | `Darkmatter.Features.ArtBook` |
+| `IDrawingTemplate`, `ShapePieceDTO`, `ColorRegionDTO` | Core | `Core` |
+| `IPaperRig`, `IArtInputBridge` | Core | `Core` |
+| `ICommand`, `IUndoStack` | Core | `Core` |
+| `BoundedUndoStack` | Libs | `Libs.CommandStack` |
+| `AddressableAssetProviderService` | Services | `Services.Assets` |
+| `FileGalleryService` | Services | `Services.Gallery` |
+| `RenderTextureCaptureService` | Services | `Services.Capture` |
+| `PaperRig`, `ArtInputBridge`, `PaperRigModule` | Features | `Features.Paper` |
+| `ColoringController`, `PaintRegionCommand` | Features | `Features.Coloring` |
+| `ShapeBuilderController`, `ShapePieceView` | Features | `Features.ShapeBuilder` |
+| `HistoryController` | Features | `Features.History` |
+| `ColorBookFlowController` | Features | `Features.ColorBookFlow` |
+| `GalleryPresenter`, `GalleryGridView` | Features | `Features.ArtBook` |
| `ColorBookLifetimeScope`, `AppBoot` | App | `Darkmatter.App` |
If a class's natural home doesn't match its asmdef, the architecture is bent — fix the placement, don't add a reference.