From f3b53be39d439d2ecb77e245680dd7f82fb665c6 Mon Sep 17 00:00:00 2001 From: Savya Bikram Shah Date: Wed, 27 May 2026 10:55:39 +0545 Subject: [PATCH] Gallery Done --- .../Code/Core/Contracts/Features.meta | 8 + .../Core/Contracts/{ => Features}/Paper.meta | 0 .../{ => Features}/Paper/IArtInputBridge.cs | 2 +- .../Paper/IArtInputBridge.cs.meta | 0 .../{ => Features}/Paper/IPaperRig.cs | 2 +- .../{ => Features}/Paper/IPaperRig.cs.meta | 0 .../Code/Core/Contracts/Services/Gallery.meta | 8 + .../Services/Gallery/IGalleryService.cs | 10 + .../Services/Gallery/IGalleryService.cs.meta | 2 + Assets/Darkmatter/Code/Services/Gallery.meta | 8 + .../Code/Services/Gallery/Core.meta | 8 + .../Services/Gallery/Core/GalleryService.cs | 13 + .../Gallery/Core/GalleryService.cs.meta | 2 + .../Code/Services/Gallery/Installers.meta | 8 + .../Installers/GalleryServiceModule.cs | 19 + .../Installers/GalleryServiceModule.cs.meta | 2 + .../Services/Gallery/Services.Gallery.asmdef | 19 + .../Gallery/Services.Gallery.asmdef.meta | 7 + Readme.md | 498 +++++++++++------- 19 files changed, 415 insertions(+), 201 deletions(-) create mode 100644 Assets/Darkmatter/Code/Core/Contracts/Features.meta rename Assets/Darkmatter/Code/Core/Contracts/{ => Features}/Paper.meta (100%) rename Assets/Darkmatter/Code/Core/Contracts/{ => Features}/Paper/IArtInputBridge.cs (74%) rename Assets/Darkmatter/Code/Core/Contracts/{ => Features}/Paper/IArtInputBridge.cs.meta (100%) rename Assets/Darkmatter/Code/Core/Contracts/{ => Features}/Paper/IPaperRig.cs (83%) rename Assets/Darkmatter/Code/Core/Contracts/{ => Features}/Paper/IPaperRig.cs.meta (100%) create mode 100644 Assets/Darkmatter/Code/Core/Contracts/Services/Gallery.meta create mode 100644 Assets/Darkmatter/Code/Core/Contracts/Services/Gallery/IGalleryService.cs create mode 100644 Assets/Darkmatter/Code/Core/Contracts/Services/Gallery/IGalleryService.cs.meta create mode 100644 Assets/Darkmatter/Code/Services/Gallery.meta create mode 100644 Assets/Darkmatter/Code/Services/Gallery/Core.meta create mode 100644 Assets/Darkmatter/Code/Services/Gallery/Core/GalleryService.cs create mode 100644 Assets/Darkmatter/Code/Services/Gallery/Core/GalleryService.cs.meta create mode 100644 Assets/Darkmatter/Code/Services/Gallery/Installers.meta create mode 100644 Assets/Darkmatter/Code/Services/Gallery/Installers/GalleryServiceModule.cs create mode 100644 Assets/Darkmatter/Code/Services/Gallery/Installers/GalleryServiceModule.cs.meta create mode 100644 Assets/Darkmatter/Code/Services/Gallery/Services.Gallery.asmdef create mode 100644 Assets/Darkmatter/Code/Services/Gallery/Services.Gallery.asmdef.meta 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.