docs added

This commit is contained in:
Savya Bikram Shah
2026-05-26 17:35:06 +05:45
parent 8a018d14c5
commit 8d599a6396
2 changed files with 551 additions and 0 deletions

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 43ea0ae4e7f8a4f108088032adaaccd6
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

543
Readme.md
View File

@@ -1047,3 +1047,546 @@ Toddler-mode error UI:
| `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.
---
## 32. Class Reference (Detailed)
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
> - `// fields:` = constructor-injected dependencies
> - `// pub:` = events / signals fired
> - `// sub:` = events / signals consumed
> - All async returns are `UniTask` unless noted.
---
### 32.1 Core Contracts
Pure interfaces and DTOs. Zero logic.
#### `IDrawingTemplate` *(Core/Drawing)*
Immutable view of a single drawing's authored data.
```csharp
public interface IDrawingTemplate {
string Id { get; } // e.g. "animals/elephant"
string DisplayName { get; } // user-facing
Sprite Thumbnail { get; } // 256×256 for catalog grid
Sprite PaperBackground { get; } // composited under artwork
IReadOnlyList<ShapePieceDTO> Pieces { get; } // for ShapeBuilder
IReadOnlyList<ColorRegionDTO> Regions { get; } // for Coloring
}
```
Implemented by `DrawingTemplateSO` (ScriptableObject) loaded via Addressables.
#### `IDrawingTemplateCatalog` *(Core/Drawing)*
Authority on which drawings exist, completion state, and "next" selection.
```csharp
public interface IDrawingTemplateCatalog {
UniTask InitializeAsync(); // pulls manifest from Addressables
IReadOnlyList<string> AllTemplateIds { get; }
UniTask<Sprite> GetThumbnailAsync(string id); // cheap; for grid
UniTask<IDrawingTemplate> LoadAsync(string id); // expensive; full template
void Release(string id); // Addressables ref count down
string NextUnseen(string currentId); // progression hint
}
```
#### `IColorPalette` *(Core/Coloring)*
Set of colors offered to the child. Authored as `ColorPaletteSO`.
```csharp
public interface IColorPalette {
string Id { get; }
IReadOnlyList<Color> Colors { get; } // 610 entries typical
}
```
#### `ICommand` & `IUndoStack` *(Core/History)*
Already shown in section 8. Each undoable user action is one `ICommand`; the stack is bounded.
#### `IGalleryService` *(Core/Gallery)*
Persistent store of saved artwork PNGs.
```csharp
public interface IGalleryService {
UniTask<SavedArtworkDTO> SaveAsync(byte[] png, string templateId);
UniTask<IReadOnlyList<SavedArtworkDTO>> ListAsync(); // sorted newest first
UniTask<Texture2D> LoadFullAsync(string artworkId); // for fullscreen view
UniTask<Texture2D> LoadThumbnailAsync(string artworkId);
UniTask DeleteAsync(string artworkId);
}
```
#### `ICaptureService` *(Core/Capture)*
Snapshots the artwork camera to a PNG blob.
```csharp
public interface ICaptureService {
UniTask<byte[]> CaptureAsync(
Camera artCamera,
Sprite paperBackground,
int width = 2048,
int height = 2048);
}
```
#### `IProgressionService` *(Core/Progression)*
Tracks which templates the child has completed and what they last opened.
```csharp
public interface IProgressionService {
UniTask LoadAsync();
UniTask SaveAsync();
IReadOnlyCollection<string> CompletedTemplateIds { get; }
string LastOpenedTemplateId { get; }
void MarkCompleted(string templateId);
void SetLastOpened(string templateId);
}
```
#### `IAssetProviderService` *(Core/Assets)*
Addressables wrapper. Hides handle bookkeeping from features.
```csharp
public interface IAssetProviderService {
UniTask InitializeAsync();
UniTask<T> LoadAsync<T>(string address) where T : UnityEngine.Object;
UniTask<IReadOnlyList<T>> LoadByLabelAsync<T>(string label) where T : UnityEngine.Object;
void Release(string address);
void ReleaseAll();
}
```
#### `IEventBus` *(Libs/EventBus, also referenced from Core)*
```csharp
public interface IEventBus {
void Publish<T>(T signal) where T : struct;
IDisposable Subscribe<T>(Action<T> handler) where T : struct;
}
```
Signals are structs to avoid GC. Disposable subscription so presenters can unsubscribe in `Dispose()`.
---
### 32.2 Services Layer
Concrete infrastructure. One implementation each. All singletons in `RootLifetimeScope`.
#### `AddressableAssetProviderService` *(Services/Assets)*
Implements `IAssetProviderService`.
- **Responsibility:** Wrap `Addressables.LoadAssetAsync<T>` and ref-count handles by address.
- **State:** `Dictionary<string, AsyncOperationHandle>` keyed by address.
- **Notes:** `Release(address)` decrements; `ReleaseAll()` for scene teardown. Initialization must complete before any other service may load.
#### `FileGalleryService` *(Services/Gallery)*
Implements `IGalleryService`.
```csharp
// fields:
// IPathProvider _paths (wraps Application.persistentDataPath for tests)
// IThumbnailGenerator _thumb (downscale + encode)
// IEventBus _bus
// pub: ArtworkSavedSignal, ArtworkDeletedSignal
```
- **Save flow:** write `{guid}.png.tmp` → fsync → rename; generate thumbnail on a worker; write sidecar JSON last (so partial saves are detectable by absence of JSON).
- **List flow:** enumerate `*.json` in `Gallery/`, deserialize, sort by `CreatedUtc desc`.
- **Delete flow:** delete png + thumb + json; missing files ignored (idempotent).
#### `RenderTextureCaptureService` *(Services/Capture)*
Implements `ICaptureService`.
- **Steps:** allocate `RenderTexture(width, height, 0, ARGB32)` → bind to `artCamera.targetTexture``artCamera.Render()``ReadPixels` into `Texture2D` → composite `paperBackground` underneath (single shader blit) → `EncodeToPNG` → release RT + textures.
- **Threading:** PNG encode happens on a `UniTask.RunOnThreadPool` to avoid hitching the main thread on tablets.
- **Sizing:** default 2048², overridable. Capped at device max texture size.
#### `JsonPersistenceService` *(Services/Persistence)*
Implements `IPersistenceService` (small JSON blob; not the gallery).
```csharp
public interface IPersistenceService {
UniTask<T> LoadAsync<T>(string key) where T : class, new();
UniTask SaveAsync<T>(string key, T value);
}
```
- **Path:** `Application.persistentDataPath/save.json`.
- **Format:** single JSON object keyed by `key` so multiple services can share one file.
- **Atomicity:** write to `save.json.tmp` → rename.
#### `SceneService` *(Services/Scenes)*
Implements `ISceneService`. Wraps `SceneManager.LoadSceneAsync` with `UniTask` plus a fade-curtain.
```csharp
public interface ISceneService {
UniTask LoadAsync(string sceneName, LoadSceneMode mode = LoadSceneMode.Single);
UniTask UnloadAsync(string sceneName);
}
```
#### `AudioService` *(Services/Audio)*
Implements `IAudioService`. Plays SFX clips loaded by address, mixes via Unity AudioMixer groups.
```csharp
public interface IAudioService {
UniTask PreloadAsync(string label); // e.g. "sfx,ui"
void PlayOneShot(string clipId, float volume = 1f);
void SetCategoryVolume(AudioCategory cat, float v01);
}
```
Holds an internal `Dictionary<string, AudioClip>` populated at preload.
#### `InputReaderSO` *(Services/Inputs)*
ScriptableObject wrapping the new Input System; exposes events.
```csharp
public interface IInputReader {
event Action<Vector2> PointerDown; // world pos
event Action<Vector2> PointerDrag;
event Action<Vector2> PointerUp;
}
```
- **Why an SO:** assignable in inspector and survives scene loads, but still resolvable via DI (`builder.RegisterInstance(_inputReader).As<IInputReader>()`).
---
### 32.3 Libs
Generic, project-agnostic utilities.
#### `BoundedUndoStack` *(Libs/CommandStack)*
Implements `IUndoStack`. Source already in section 24.
- **Capacity:** default 20.
- **Invariant:** `_redo` cleared on any new `Push`.
- **Edge cases:** `Undo`/`Redo` on empty stack is a no-op (never throws).
#### `EventBus` *(Libs/EventBus)*
Implements `IEventBus` with a `Dictionary<Type, Delegate>` of `Action<T>` per signal type.
- **Subscribe** returns an `IDisposable` that removes the handler on `Dispose`.
- **Publish** snapshots the invocation list before iterating (so handlers may safely unsubscribe during dispatch).
#### `Fsm<TState>` *(Libs/FSM)*
Generic state machine used by `ColorBookFlowController`.
```csharp
public sealed class Fsm<TState> where TState : struct, Enum {
public TState Current { get; }
public event Action<TState, TState> Transitioned;
public void Bind(TState state, IFsmState handler);
public void Go(TState next); // calls Exit on old, Enter on new
}
public interface IFsmState { void Enter(); void Exit(); }
```
---
### 32.4 Feature — `DrawingCatalog`
#### `DrawingCatalogController` *(Systems)*
Headless logic. Owns the list of template IDs visible in the grid.
```csharp
// fields: IDrawingTemplateCatalog _catalog, IEventBus _bus
public sealed class DrawingCatalogController : IAsyncStartable {
public IReadOnlyList<string> VisibleIds { get; }
public event Action ListChanged;
public UniTask StartAsync(CancellationToken ct); // pulls catalog, refreshes list
public void OnTemplateSelected(string id); // bus.Publish(new DrawingSelectedSignal(id))
}
// pub: DrawingSelectedSignal
```
#### `DrawingCatalogPresenter` *(UI)*
Bridges controller ↔ view.
```csharp
// fields: IDrawingCatalogView _view, DrawingCatalogController _ctrl, IDrawingTemplateCatalog _catalog
public sealed class DrawingCatalogPresenter : IStartable, IDisposable {
public void Start(); // wires view.OnItemClicked → _ctrl.OnTemplateSelected
public void Dispose();
}
```
- **Thumbnail load:** `_catalog.GetThumbnailAsync(id)` per visible cell, with placeholder while loading.
#### `DrawingCatalogView : MonoBehaviour` *(UI)*
Implements `IDrawingCatalogView`. Pure setters + click event.
```csharp
public interface IDrawingCatalogView {
event Action<string> OnItemClicked;
void SetItems(IReadOnlyList<CatalogItemVM> items); // vm = id + Sprite thumbnail
}
```
---
### 32.5 Feature — `ShapeBuilder`
#### `ShapeBuilderController` *(Systems)*
Spawns shape pieces for the selected template, tracks snap progress, fires `ShapeAssembledSignal` when complete.
```csharp
// fields: IDrawingTemplateCatalog _catalog, ShapePieceFactory _factory, IEventBus _bus, ShapeBuilderConfig _cfg
public sealed class ShapeBuilderController : IDisposable {
public IReadOnlyList<ShapePieceView> Active { get; }
public UniTask BuildAsync(string templateId); // load template, spawn pieces in tray
public void Reset(); // clear, unsubscribe
}
// sub: DrawingSelectedSignal
// pub: ShapeAssembledSignal
```
- **Internal:** counts `PieceSnappedSignal` against expected piece count.
#### `ShapePieceView : MonoBehaviour` *(Views)*
World-space draggable sprite with collider. Source for snap-or-return logic shown in section 26.
```csharp
public sealed class ShapePieceView : MonoBehaviour {
public string PieceId { get; }
public bool IsLocked { get; }
public event Action<string> Snapped; // raised when piece locks into slot
public void Initialize(ShapePieceDTO dto, IInputReader input, IAudioService audio);
}
```
- **No public mutators** for position once locked — controller treats `IsLocked` as the source of truth.
#### `ShapePieceFactory` *(Systems)*
Instantiates `ShapePieceView` prefabs from a pool. Avoids re-instantiating across "Next" cycles on the same template family.
```csharp
public sealed class ShapePieceFactory {
public ShapePieceView Spawn(ShapePieceDTO dto, Transform parent);
public void Despawn(ShapePieceView view);
}
```
---
### 32.6 Feature — `Coloring`
#### `ColoringStateRepository` *(Repository)*
In-memory model. Owns "currently selected color" and the palette in use.
```csharp
public sealed class ColoringStateRepository {
public IColorPalette Palette { get; private set; }
public int SelectedIndex { get; private set; }
public Color CurrentColor => Palette.Colors[SelectedIndex];
public event Action<int> SelectedIndexChanged;
public void SetPalette(IColorPalette palette); // resets SelectedIndex to 0
public void SelectColor(int index);
}
```
- **Why a repository:** presenter and controller both need to read/write current color; an event-emitting POCO is simpler than wiring two signals.
#### `ColoringController` *(Systems)* — implements `IColoringController`
Builds and pushes `PaintRegionCommand` instances; spawns `ColorRegionView` per region.
```csharp
// fields: IUndoStack _undo, ColoringStateRepository _state, ColorRegionFactory _factory, IEventBus _bus
public interface IColoringController {
UniTask SpawnRegionsAsync(IDrawingTemplate template);
void PaintRegion(ColorRegionView view); // builds command, pushes to undo stack
void Clear();
}
// sub: ShapeAssembledSignal (via flow controller, not direct)
// pub: ColorAppliedSignal (via PaintRegionCommand)
```
#### `ColorRegionView : MonoBehaviour` *(Views)*
Sprite + `PolygonCollider2D`, on `Artwork` layer. Tapped via `Physics2D.OverlapPoint` from `ColoringInputBinder`.
```csharp
public sealed class ColorRegionView : MonoBehaviour {
public string RegionId { get; }
public Color Color { get; } // current paint
public void Initialize(ColorRegionDTO dto);
public void SetColor(Color c); // setter only; no logic
}
```
#### `ColoringInputBinder` *(Systems)* — `IStartable, IDisposable`
Subscribes to `IInputReader.PointerDown`, raycasts on the `Artwork` layer mask, calls `ColoringController.PaintRegion(view)` on hit.
#### `PaintRegionCommand` *(Commands)*
Source in section 23. Holds `view`, `fromColor`, `toColor`, `bus`. Symmetrical execute/undo.
#### `ColorPaletteView`, `ColorPalettePresenter` *(UI)*
Sources in section 25. Presenter binds `ColoringStateRepository.SelectedIndexChanged``IColorPaletteView`.
#### `ColorRegionFactory` *(Systems)*
Mirror of `ShapePieceFactory` for regions. Pool-friendly.
---
### 32.7 Feature — `History`
#### `HistoryController` *(Systems)* — `IStartable, IDisposable`
Owns the per-session `IUndoStack` (registered scoped, so a new ColorBook scene = new stack).
```csharp
// fields: IUndoStack _stack, IEventBus _bus
public sealed class HistoryController : IStartable, IDisposable {
public bool CanUndo => _stack.CanUndo;
public bool CanRedo => _stack.CanRedo;
public event Action StateChanged;
public void Undo(); // _stack.Undo() + StateChanged
public void Redo();
// sub: DrawingSelectedSignal → _stack.Clear()
}
```
#### `HistoryButtonsView : MonoBehaviour` *(UI)*
Two big arrow buttons. Setters only.
```csharp
public interface IHistoryButtonsView {
event Action UndoClicked;
event Action RedoClicked;
void SetUndoEnabled(bool enabled);
void SetRedoEnabled(bool enabled);
}
```
#### `HistoryPresenter` *(UI)*
Wires controller `StateChanged` ↔ view enable/disable; view click events → controller.
---
### 32.8 Feature — `Capture`
#### `CaptureController` *(Systems)*
The orchestrator behind the "Capture" button. Stateless other than guarding against concurrent captures.
```csharp
// fields: ICaptureService _capture, IGalleryService _gallery, IEventBus _bus, ColorBookSceneRefs _refs
public sealed class CaptureController {
public bool IsCapturing { get; }
public UniTask<SavedArtworkDTO> CaptureCurrentAsync(string templateId, Sprite paperBg);
}
// pub: ArtworkCapturedSignal (mid-flow), ArtworkSavedSignal (post-save)
```
- **Concurrency:** sets `IsCapturing = true` on entry; UI binds button enabled to `!IsCapturing` to prevent double-tap.
#### `CaptureButtonPresenter` *(UI)*
Wires button click → `CaptureController.CaptureCurrentAsync`. Disables button while in progress. Shows toast on `ArtworkSavedSignal`.
---
### 32.9 Feature — `Progression`
#### `ProgressionService` *(Systems)* — implements `IProgressionService`
The only place that knows what "completed" means.
- **Persistence:** delegates to `IPersistenceService` under key `"progression"`.
- **Load order:** `AppBoot` calls `LoadAsync()` early.
- **Save trigger:** after `MarkCompleted`, debounced 500 ms to coalesce a burst of "Next" presses.
#### `ProgressionRepository` *(Repository)*
Pure in-memory holder used by the service. Separated so tests can inspect state without going through file IO.
---
### 32.10 Feature — `ColorBookFlow`
#### `ColorBookFlowController` *(Systems)* — `IStartable, IDisposable`
**The only orchestrator inside the ColorBook scene.** Drives the panel FSM: `Catalog → Building → Coloring → Done`.
```csharp
// fields:
// IEventBus _bus
// IDrawingTemplateCatalog _catalog
// ShapeBuilderController _builder
// IColoringController _coloring
// CaptureController _capture
// IProgressionService _progression
// ColorBookSceneRefs _refs (panel roots to enable/disable)
// Fsm<ColorBookState> _fsm
```
- **State table:**
| State | On enter | Triggers exit |
|------------|-----------------------------------------------|--------------------------------|
| `Catalog` | Show catalog panel | `DrawingSelectedSignal` |
| `Building` | `_builder.BuildAsync(id)` | `ShapeAssembledSignal` |
| `Coloring` | `_coloring.SpawnRegionsAsync(template)` | "Next" or "Capture" pressed |
| `Done` | Run autosave capture, mark completed, `Go(Catalog)` for next | always advances |
- **"Next" sequence:** `_capture.CaptureCurrentAsync``_progression.MarkCompleted``_catalog.Release(current)``_catalog.LoadAsync(_catalog.NextUnseen(current))` → re-enter `Building`.
---
### 32.11 Feature — `ArtBook`
#### `GalleryPresenter` *(UI)* — `IAsyncStartable, IDisposable`
Lists artworks, opens fullscreen view, deletes, shares.
```csharp
// fields: IGalleryService _gallery, IGalleryView _view, IExternalShareService _share, IEventBus _bus
```
- **Start:** `_gallery.ListAsync()``_view.SetItems(...)`.
- **Subscribes** to `ArtworkSavedSignal` to live-refresh if the user pops back in.
#### `IGalleryView` *(UI)*
```csharp
public interface IGalleryView {
event Action<string> OnArtworkTapped;
event Action<string> OnDeleteRequested;
event Action<string> OnShareRequested;
void SetItems(IReadOnlyList<GalleryItemVM> items);
void ShowFullscreen(Texture2D full);
void HideFullscreen();
}
```
#### `IExternalShareService` *(Core)*
Platform plugin shim (iOS Photos / Android MediaStore).
```csharp
public interface IExternalShareService {
UniTask SaveToCameraRollAsync(byte[] png);
UniTask ShareAsync(byte[] png, string subject);
}
```
---
### 32.12 App Layer
#### `AppBoot` *(App/Boot)* — `IAsyncStartable`
Single entry point. Steps in section 29.
```csharp
// fields: IAssetProviderService _assets, IPersistenceService _persist, IProgressionService _progress,
// IAudioService _audio, ISceneService _scenes, BootConfig _cfg
public sealed class AppBoot : IAsyncStartable {
public UniTask StartAsync(CancellationToken ct);
}
```
#### LifetimeScopes
- `RootLifetimeScope` — section 21. Registers all services + `IEventBus`. Persists for app lifetime.
- `MainMenuLifetimeScope` — registers `MainMenuPresenter` and view.
- `ColorBookLifetimeScope` — section 21. Registers feature installers + `ColorBookFlowController` as entry point.
- `ArtBookLifetimeScope` — registers `GalleryPresenter` + view + `IExternalShareService`.
All scope classes are thin: serialized fields for scene refs, `Configure(IContainerBuilder)` only.
---
### 32.13 Cross-cutting types
#### `ColorBookSceneRefs : MonoBehaviour` *(App)*
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.
#### `IInstaller` *(App)*
```csharp
public interface IInstaller { void Install(IContainerBuilder builder); }
```
Implemented as `ScriptableObject` per feature so scopes can drag them in the inspector (section 22).
---
### 32.14 Class summary table
| Class | Layer | Role | Key dependencies |
|---|---|---|---|
| `AppBoot` | App | Startup sequencer | assets, persist, progression, scenes |
| `RootLifetimeScope` | App | Root DI | configs |
| `ColorBookLifetimeScope` | App | Scene DI | scene refs, installers |
| `DrawingCatalogController` | Feature | Grid logic | catalog, bus |
| `DrawingCatalogPresenter` | Feature | UI bridge | view, controller, catalog |
| `ShapeBuilderController` | Feature | Piece spawn + snap tracking | catalog, factory, bus, cfg |
| `ShapePieceView` | Feature | Draggable piece MB | input, audio |
| `ColoringStateRepository` | Feature | Current color model | — |
| `ColoringController` | Feature | Region spawn + paint cmd | undo, state, factory, bus |
| `ColorRegionView` | Feature | Region sprite MB | — |
| `PaintRegionCommand` | Feature | Undoable paint | view, bus |
| `HistoryController` | Feature | Undo/redo facade | undo stack, bus |
| `CaptureController` | Feature | Capture+save orchestration | capture svc, gallery, bus, refs |
| `ColorBookFlowController` | Feature | Scene FSM | bus, catalog, builder, coloring, capture, progression |
| `GalleryPresenter` | Feature | Art book listing | gallery, share, view, bus |
| `BoundedUndoStack` | Lib | Capped undo store | — |
| `EventBus` | Lib | Pub/sub | — |
| `Fsm<TState>` | Lib | Generic FSM | — |
| `AddressableAssetProviderService` | Service | Addressables wrapper | — |
| `FileGalleryService` | Service | Gallery file IO | paths, thumb gen, bus |
| `RenderTextureCaptureService` | Service | PNG render | — |
| `JsonPersistenceService` | Service | Settings/progression IO | — |
| `SceneService` | Service | Async scene loads | — |
| `AudioService` | Service | SFX playback | assets |
| `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.