@@ -68,139 +68,209 @@ Features ──► Core ◄── Services
## 4. Folder Structure
## 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/
Assets/Darkmatter/
├── App/
├── Scenes/
│ ├── Boot/
│ └── Boot.unity ← only scene wired so far
│ │ └── AppBoot.cs
│ └── LifetimeScopes/
│ ├── RootLifetimeScope.cs
│ ├── MainMenuLifetimeScope.cs
│ ├── ColorBookLifetimeScope.cs
│ └── ArtBookLifetimeScope.cs
│
│
├── Core/
├── Content/ ← singular ("Content", not "Contents")
│ ├── Generated/GameInputs.cs (Input System codegen)
└── ArtBook/
│ ├── 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/<Area>/` or new top-level `Core/<Area>/` — 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/<Name>/`, `Data/{Dynamic,Static}/Features/<Name>/`, `Features/<Name>/` — 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 |
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.
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.
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<AppBoot>()`), it should run once, in order:
@@ -366,8 +444,10 @@ public interface IUndoStack {
### Gallery & Capture
### 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`.
@@ -713,109 +797,123 @@ Maintained alongside the [Darkmatter Architecture Guide](../Assets/Darkmatter_Ar
## 20. Assembly Definition Map
## 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 `<Layer>.<Module>` names. References follow the layer rules.
### On disk today
| Asmdef | Path | References |
| Asmdef | Path | References |
|---|---|---|
|---|---|---|
| `Darkmatter.App` | `App/` | All Features, all Services, Core, Libs |
| `Darkmatter.App` | `App/` | All Services, Libs, Core (Features when they land) |
**Hard rule:** No Service asmdef references any Feature asmdef. No Feature asmdef references another Feature asmdef. Compiler enforces the architecture.
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
### `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
Drag the scene's installer MonoBehaviours into `sceneModules[]`:
-`DrawingCatalogServiceModule`
-`PaperRigModule`
-`ShapeBuilderServiceModule`
-`DrawingCatalogModule`
-`ColoringServiceModule`
-`ShapeBuilderModule`
-`HistoryServiceModule`
-`ColoringModule`
-`CaptureFeatureModule`
-`HistoryModule`
-`ProgressionServiceModule`
-`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.
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`.
@@ -828,9 +926,11 @@ public sealed class ColoringServiceModule : ScriptableObject, IInstaller {
```
```
Convention:
Convention:
- One `IInstaller` per feature.
- One `IServiceModule` per feature, named `<Feature>Module` (matches `CameraServiceModule`, `InputServiceModule`, `AnalyticsServiceModule` already in the project).
-`ScriptableObject` so it can be referenced by scene scope inspector.
-`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.
- 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<IFoo>(_foo)` them. See the planned `PaperRigModule` in §32.5b for an example.
---
---
@@ -1070,8 +1170,10 @@ Paths are **relative** to `persistentDataPath`. Never store absolute paths — t
## 29. Boot & Error Handling
## 29. Boot & Error Handling
> **Status: not implemented.** No `AppBoot` class exists. Today, [RootLifetimeScope.cs](Assets/Darkmatter/Code/App/LifetimeScopes/RootLifetimeScope.cs) only iterates installer MonoBehaviours and registers them — nothing runs after that. The block below is the *target* sequence when `AppBoot` is added as an `IAsyncStartable` entry point under `App/Boot/`.
```
```
AppBoot.StartAsync()
AppBoot.StartAsync() (planned — Features/Boot/AppBoot.cs, registered via builder.RegisterEntryPoint<AppBoot>())
1. Open `Bus Game.sln` (colorbook lives in same repo / Unity project per plan).
1. Open `Colorbook.sln` at the repo root.
2.Verify Addressables groups exist: `Drawings_*`, `Palettes`, `Audio_*`.
2.Open `Assets/Darkmatter/Scenes/Boot.unity` (currently the only scene wired).
3.Open`Boot.unity` → confirm `RootLifetimeScope` references the right configs.
3.Inspect the`RootLifetimeScope` GameObject — confirm its `serviceModules[]` list references the child installer MonoBehaviours (`AudioServiceModule`, `CameraServiceModule`, `InputServiceModule`, etc.).
4.Open `ColorBook.unity` → confirm `ColorBookLifetimeScope._installers[]` is fully populated.
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.Hit Play from `Boot.unity` (entry scene). Never start mid-flow — DI parent scope must exist.
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.To author a new drawing: duplicate `Animals/elephant/`, edit `Template.asset` (pieces + regions), add to the appropriate Addressables group.
6.When drawings are authored: duplicate the template folder under `Content/Gameplay/Drawings/<theme>/<id>/`, edit `Template.asset` (pieces + regions), add to the appropriate Addressables group.
7. Run `Tests > EditMode` and `Tests > PlayMode` before pushing.
7. Run `Tests > EditMode` and `Tests > PlayMode` before pushing (test infra not set up yet — see §16).
If a class's natural home doesn't match its asmdef, the architecture is bent — fix the placement, don't add a reference.
If a class's natural home doesn't match its asmdef, the architecture is bent — fix the placement, don't add a reference.
@@ -1127,6 +1229,8 @@ If a class's natural home doesn't match its asmdef, the architecture is bent —
## 32. Class Reference (Detailed)
## 32. Class Reference (Detailed)
> **Status: target spec, mostly unimplemented.** Of everything below, only the following Service classes exist on disk today: `AddressableAssetProviderService`, `AudioService` / `SfxPlayer`, `CameraService`, `SceneService`, `InputReaderSO`, `FirebaseAnalyticsSystem`. Everything else (Paper, Drawing, Coloring, History, Capture, Gallery, Progression, ColorBookFlow, ArtBook, AppBoot) is the target shape for when those classes are written. Treat this section as a contract for new code, not documentation of current state.
Canonical breakdown of every concrete class and interface. For each: **purpose**, **public surface** (signatures), **injected dependencies**, and **collaborators** (signals or interfaces it talks to).
Canonical breakdown of every concrete class and interface. For each: **purpose**, **public surface** (signatures), **injected dependencies**, and **collaborators** (signals or interfaces it talks to).
> Convention used below
> Convention used below
@@ -1134,6 +1238,7 @@ Canonical breakdown of every concrete class and interface. For each: **purpose**
> - `// pub:` = events / signals fired
> - `// pub:` = events / signals fired
> - `// sub:` = events / signals consumed
> - `// sub:` = events / signals consumed
> - All async returns are `UniTask` unless noted.
> - All async returns are `UniTask` unless noted.
> - Folder labels follow the actual nesting pattern: `Core/Contracts/Features/<Name>/`, `Core/Contracts/Services/<Name>/`, `Core/Data/Dynamic/Features/<Name>/`, `Features/<Name>/<Sub>/`, `Services/<Name>/<Sub>/`.
---
---
@@ -1141,7 +1246,7 @@ Canonical breakdown of every concrete class and interface. For each: **purpose**
Converts screen-space pointer coords to art-world coords inside the RT.
Converts screen-space pointer coords to art-world coords inside the RT.
```csharp
```csharp
publicinterfaceIArtInputBridge{
publicinterfaceIArtInputBridge{
@@ -1221,7 +1326,7 @@ public interface IArtInputBridge {
```
```
Returns `false` when the pointer is outside the displayed RawImage rect (toddler tapped the HUD or backdrop). Every art-world raycast goes through this.
Returns `false` when the pointer is outside the displayed RawImage rect (toddler tapped the HUD or backdrop). Every art-world raycast goes through this.
Addressables wrapper. Hides handle bookkeeping from features.
Addressables wrapper. Hides handle bookkeeping from features.
```csharp
```csharp
publicinterfaceIAssetProviderService{
publicinterfaceIAssetProviderService{
@@ -1246,7 +1351,7 @@ public interface IAssetProviderService {
}
}
```
```
#### `IEventBus` *(Libs/EventBus, also referenced from Core)*
#### `IEventBus` *(Libs/Observer — ✅ exists; note the folder is `Observer`, not `EventBus`)*
```csharp
```csharp
publicinterfaceIEventBus{
publicinterfaceIEventBus{
voidPublish<T>(Tsignal)whereT:struct;
voidPublish<T>(Tsignal)whereT:struct;
@@ -1259,15 +1364,15 @@ Signals are structs to avoid GC. Disposable subscription so presenters can unsub
### 32.2 Services Layer
### 32.2 Services Layer
Concrete infrastructure. One implementation each. All singletons in `RootLifetimeScope`.
Concrete infrastructure. One implementation each. All singletons in `RootLifetimeScope`, registered via per-service `MonoBehaviour, IServiceModule` installers.
Generic state machine used by `ColorBookFlowController`.
Generic state machine. Current shape on disk uses `IState` / `State` / `StateMachine` (see [Libs/FSM/](Assets/Darkmatter/Code/Libs/FSM/)). `ColorBookFlowController` (planned) will use this. The generic sketch below is the target shape if you decide to make it strongly-typed via an enum — verify against actual API before consuming.
Headless logic. Owns the list of template IDs visible in the grid.
Headless logic. Owns the list of template IDs visible in the grid.
@@ -1398,7 +1503,7 @@ public interface IDrawingCatalogView {
---
---
### 32.5 Feature — `ShapeBuilder`
### 32.5 Feature — `ShapeBuilder` *(planned)*
#### `ShapeBuilderController` *(Systems)*
#### `ShapeBuilderController` *(Systems)*
Spawns shape pieces for the selected template, tracks snap progress, fires `ShapeAssembledSignal` when complete.
Spawns shape pieces for the selected template, tracks snap progress, fires `ShapeAssembledSignal` when complete.
@@ -1437,7 +1542,7 @@ public sealed class ShapePieceFactory {
---
---
### 32.5b Feature — `Paper`
### 32.5b Feature — `Paper` *(planned)*
The shared art rig — RT, offscreen camera, screen↔world bridge. Every other feature in the ColorBook scene resolves `IPaperRig` and `IArtInputBridge` from DI and never touches `Screen.*` or `Camera.*` directly.
The shared art rig — RT, offscreen camera, screen↔world bridge. Every other feature in the ColorBook scene resolves `IPaperRig` and `IArtInputBridge` from DI and never touches `Screen.*` or `Camera.*` directly.
@@ -1508,7 +1613,7 @@ public void Register(IContainerBuilder builder) {
---
---
### 32.6 Feature — `Coloring`
### 32.6 Feature — `Coloring` *(planned)*
#### `ColoringStateRepository` *(Repository)*
#### `ColoringStateRepository` *(Repository)*
In-memory model. Owns "currently selected color" and the palette in use.
In-memory model. Owns "currently selected color" and the palette in use.
@@ -1570,7 +1675,7 @@ Mirror of `ShapePieceFactory` for regions. Pool-friendly.
@@ -1710,25 +1815,29 @@ public sealed class AppBoot : IAsyncStartable {
```
```
#### LifetimeScopes
#### LifetimeScopes
-`RootLifetimeScope` — section 21. Registers all services + `IEventBus`. Persists for app lifetime.
-`RootLifetimeScope` — ✅ exists ([source](Assets/Darkmatter/Code/App/LifetimeScopes/RootLifetimeScope.cs)). Iterates a serialized `MonoBehaviour[] serviceModules` and calls `Register` on each `IServiceModule`. Persists for app lifetime.
-`MainMenuLifetimeScope` — registers `MainMenuPresenter` and view.
-`MainMenuLifetimeScope` — planned. Same pattern as Root (serialized installer list, no hardcoded registrations).
All scope classes are thin: serialized fields for scene refs, `Configure(IContainerBuilder)` only.
All scope classes are thin: a serialized installer-MonoBehaviour list (+ optional scene refs as separate fields) and a `Configure(IContainerBuilder)` that iterates and calls `Register`.
Aggregates all scene-bound Unity references that features need: `Camera artCamera`, `Transform catalogRoot`, `Transform builderRoot`, `Transform coloringRoot`, `RectTransform hudRoot`, `ColorPaletteView paletteView`, `HistoryButtonsView historyView`. Registered as a singleton in `ColorBookLifetimeScope` so features don't `Find` things.
Aggregates scene-bound Unity references that features need: `Camera artCamera`, `Transform catalogRoot`, `Transform builderRoot`, `Transform coloringRoot`, `RectTransform hudRoot`, `ColorPaletteView paletteView`, `HistoryButtonsView historyView`. Registered in `ColorBookLifetimeScope` via `builder.RegisterInstance(_sceneRefs)` so features don't `Find` things.
#### `IInstaller` *(App)*
> Most of these refs are subsumed by `IPaperRig` now (which owns `ArtCamera` and `PaperRoot`). `ColorBookSceneRefs` reduces to the HUD-side refs (palette view, history buttons, panel roots).
Implemented as `ScriptableObject` per feature so scopes can drag them in the inspector (section 22).
Implemented as `MonoBehaviour` per feature/service so scopes can drag them in the inspector ([CameraServiceModule.cs](Assets/Darkmatter/Code/Services/Camera/Installers/CameraServiceModule.cs) shows the pattern). The method is `Register`, not `Install` — there is no `IInstaller` in this project.
---
---
@@ -1766,3 +1875,5 @@ Implemented as `ScriptableObject` per feature so scopes can drag them in the ins
| `ProgressionService` | Service | Completion tracking | persistence |
| `ProgressionService` | Service | Completion tracking | persistence |
If you add a class not in this table, add it here in the same PR. This table is the cheap mental-model index — keep it honest.
If you add a class not in this table, add it here in the same PR. This table is the cheap mental-model index — keep it honest.
> Today only these rows are real on disk: `RootLifetimeScope` (App), `AddressableAssetProviderService` (Service), `AudioService` (Service), `CameraService` (Service), `SceneService` (Service), `InputReaderSO` (Service), plus the Firebase analytics class, plus the `Libs.*` entries (`EventBus`, `StateMachine`, `IServiceModule`, PlayerPrefs lib, UI toggles). Everything else is the target.
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.