# Technical structure How this project is laid out, what each layer is allowed to do, and how to work on it. Companion to [ARCHITECTURE.md](ARCHITECTURE.md), which states the *intent*; this file states the *implementation*. - Engine: **Cocos Creator 3.8.8**, TypeScript. - Targets: Web (mobile browser), Android, iOS. Portrait only. - Editor automation: the `cocos-code-mode` extension — see [CODE_MODE_SETUP.md](CODE_MODE_SETUP.md). ## Layering Four layers, with dependencies pointing strictly downward. A layer may import from the layers below it and never from the ones above. ``` ui/ Cocos components: scenes, prefabs, views, input, animation ↓ app/ bootstrap, service locator, scene transitions ↓ modules/ engine-aware services: savegame, (later) audio, analytics ↓ core/ pure rules and bots — no `cc` import anywhere ``` The rule, enforced by `npm run check:core-purity`: **nothing under `assets/scripts/core/` may import from `cc`**, and under `assets/scripts/modules/` only the adapters explicitly listed in that script may. Everything else is plain TypeScript, runnable under Node, which is what makes the rules and the save format testable without the engine. ## Folder map ``` assets/ scenes/ loader.scene module init, then → lobby lobby.scene main menu; hosts the match UI prefabs/ card.prefab one playing card, face or back ... art/ cards/ 36 faces + back, atlased ui/ panels, buttons, table surface scripts/ core/ PURE TS — no engine imports cards/ Suit, Rank, Card, Deck rules/ GameState, actions, events, the reducer bot/ PlayerView projection, Bot contract, BasicBot match/ Match (stateful shell), runMatch (headless driver) util/ seeded RNG, shuffle modules/ save/ SaveManager, ISaveSection, StatsSection, IStorage + MemoryStorage, CocosStorage (engine) app/ Bootstrap, ServiceLocator, SceneRouter ui/ lobby/ lobby screen controller game/ match screen: hand, table, deck, animations common/ reusable widgets, safe-area root tests/ core/ vitest specs against assets/scripts/core support/ position fixtures and the state invariant checker tools/ measuring tools, not part of the suite docs/ specs, this file, plan, decisions local/ scratch art storage; contents get consumed extensions/cocos-code-mode/ editor tool server ``` ## The rules core A deterministic state machine. The only entry point is a pure reducer: ```ts apply(state: GameState, action: Action): { state: GameState; events: GameEvent[] } ``` - `GameState` is plain data: hands, deck, trump card, table slots, discard, phase, whose turn it is, and the RNG state. It is serialisable, which makes every test a fixture and every bug reproducible from a seed. - `Action` is what a participant *wants* to do — `Attack`, `Defend`, `Take`, `Pass`. Legality is decided by the core, never by the caller; `legalActions()` exists so the UI can grey out impossible taps, not as the source of truth. - `GameEvent` is what *happened* — `CardDealt`, `CardPlayed`, `CardBeaten`, `TableTaken`, `TableDiscarded`, `TurnPassed`, `PlayerOut`, `GameOver`. The view layer animates the event stream; it never inspects `GameState` directly for anything it is not allowed to see. - Randomness comes from a seeded PRNG carried inside the state. Same seed plus same action sequence always reproduces the same match. ## Bots A bot is `decide(view: PlayerView) => Action`. `PlayerView` is a projection of `GameState` containing only legitimately visible information — own hand, table, trump, opponents' hand *sizes*, deck and discard counts — plus the list of legal actions. `BasicBot` implements the policy in [GAMEPLAY.md](GAMEPLAY.md). `Match` wraps the reducer with the current state and an event subscription; the game screen and the headless `runMatch` driver both hold one. As a reference point, 5000 `BasicBot` self-play matches split roughly 43/43/14 between the two seats and draws, with the opening attacker losing about 37% of the time — `npm run stats:bot` reprints these. ## Savegame Per [ARCHITECTURE.md](ARCHITECTURE.md): `SaveManager` keeps a registry of `ISaveSection` implementations. `save()` enumerates them and builds `{ [section.key]: section.save() }`; `load()` parses and dispatches each payload back to the matching section. A missing or unparsable section is skipped with a warning rather than failing the whole load, so a bad release cannot brick a save. Keys are explicit string constants, not class names — release builds minify. The document written is `{ version, sections: { : } }`. Storage sits behind `IStorage`, so all of the save logic is testable headlessly; `CocosStorage` is the only file in the module that imports the engine, and it falls back to an in-memory store when the device refuses to persist (private browsing, a full quota) rather than taking the game down. ## Conventions - TypeScript, 4-space indent, `PascalCase` for types, `camelCase` for members, `_private` for backing fields (matching Cocos's own style). - One exported class per file, file named after it. - Cocos components live only in `ui/`; they are thin and hold no rules logic. - No `any` in `core/`. `strict` is off in the Cocos tsconfig for engine-side convenience, but the test harness type-checks `core/` in strict mode. ## Workflows ### Tests ```bash npm test ``` Runs vitest over `tests/core` against `assets/scripts/core`. No editor required. The soak specs play several thousand complete matches and re-check every state invariant after every single action, so a rule regression surfaces at the move that caused it. ```bash npm run typecheck # strict tsc over core/ and the tests npm run check:core-purity # fails if core/ ever imports the engine npm run stats:bot # BasicBot self-play distribution, not a test ``` ### Driving the editor Cocos Creator must be running with this project open. See [CODE_MODE_SETUP.md](CODE_MODE_SETUP.md); the port changes on every editor restart, so always read `~/.utcp_config.json`. Scripts and other text assets are written to disk directly and picked up by the editor's asset watcher. Scenes and prefabs are authored through the tools — never by hand-editing the `.scene`/`.prefab` JSON. ### Builds Web: `web-mobile` template. Mobile: ASTC textures per [ART.md](ART.md), PNG for web.