# /client/docs Client-specific documentation. Scoped to the Godot project. Put here: - Scene tree / autoload structure - Input model — menu vs free-text boundary, which must be visible (§15) - Combat wiring — deterministic damage, per-encounter seeding (§10) - Canon log data structure and fact-harvesting (§11) - HTTP client + fallback-degradation handling (§13) Cross-cutting design (anything touching both client and api) goes in the root [`/docs`](../../docs), not here. ## UI scene conventions (shell + all screens) Screens are **authored editor-first** — the layout lives in the `.tscn`, not in code. See ADR [0001](../../docs/adr/0001-editor-first-ui-scenes.md) for the why; this is the how. Reference: `scenes/shell/{MainWindowShell,SystemDock,NarrationBook}.tscn` + `scripts/ui/shell/*.gd`, and `scenes/creation/CharacterCreation.tscn` + `scripts/ui/creation/{character_creation,creation_draft,creation_copy}.gd` (M4-b). - **Clickable cards are Buttons with `mouse_filter = 2` on every child.** A card that holds a name, a blurb and a trait line is a `Button` (Button-base variation, so `normal`/`hover`/`pressed` actually skin it) with a `VBoxContainer` of Labels inside. Every child needs `mouse_filter = 2` (IGNORE) or the Label swallows the click and the card never toggles. `pressed` on a `toggle_mode` Button *is* the "chosen" state — no script-side stylebox swapping. - **The `.tscn` owns the tree.** Author every node in the editor: containers, panels, labels, buttons, art-slot placeholders. Assign the shared theme on the scene root (`theme = game_theme.tres`) and set each node's `theme_type_variation` (from `ThemeKeys`) in the editor, so the scene previews themed. Instance sub-screens (`PackedScene` ext_resource) rather than rebuilding them. - **The script does on-load work only.** `@onready var x := $Path/To/Node` refs, signal/service wiring, animations, and binding state into the authored nodes (label text, colours from `Palette`). Do **not** create nodes in `_ready()`. - **Data-driven repeats = fixed authored slots, bound at runtime.** Author enough turn-order tokens / consumable slots / etc. in the editor; the script fills the first N from state and hides the rest. Previewable now; systems write them later. - **Scene-root sizing.** Give the root a fixed size via offsets (e.g. 1920×1080) for editor preview, but **no full-rect anchors on the root** — a root Control run via F6 collapses to (0,0) under full-rect anchors. Keep `size = get_viewport_rect().size` on `_ready` + a `size_changed` connection (the M3-a collapse lesson). - **Button styling → Button-base variations only.** Never put a PanelContainer variation (`DarkPanel`, `ParchmentCard`) on a `Button`; it has no `normal/hover/ pressed` styleboxes so the button renders unskinned. Use the Button-base variations (`PrimaryCTA`, `Tab`, `TabActive`, `Chip`, `DockButton`, `ParchmentButton`). - **`RichTextLabel` prose** reads its dark serif ink from the theme's base `RichTextLabel` style (it does *not* inherit `default_font`/`default_color`). Just add the `RichTextLabel` — don't override its colour per-node. - **Theme is generated.** All colours live in `palette.gd`; `game_theme.tres` is built by `scripts/theme/build_game_theme.gd` (source of truth). To add/adjust a variation, edit the builder + `theme_keys.gd`, then regenerate: `godot --headless -s res://scripts/theme/build_game_theme.gd`. Never hand-edit the `.tres` or put a hex literal in a script or `.tscn`. - **Wiring the DM loop / injectable services.** Follow the shell: a `service` var settable *between* `instantiate()` and `add_child()` lets tests inject a `FakeTransport`-backed `DmService` so on-load calls stay hermetic; `_ready` builds the real `HttpTransport`-backed one when none was injected. - **Visual gate.** Headless GUT proves structure (nodes mount, sizes non-zero, bindings run); it cannot prove rendering. Every screen needs a **human F6 eyeball** before it's done. Note shader surfaces (`DarkBay`, vignette) render their texture only on an F6 run, not in the editor. ## Narrate live smoke (M2 client HTTP loop) The `narrate_harness` scene is a throwaway proof of the client → `/dm/narrate` loop. It is not the game UI. **Prereqs:** the proxy running and Ollama reachable. 1. Start the proxy: from `api/`, `uvicorn app.main:app --port 8000` (with the `.env` from `.env.example`; Ollama up at `OLLAMA_BASE_URL`). 2. In the Godot editor, open `res://scenes/narrate_harness.tscn` and run it (F6). 3. Click **Narrate this scene**. Expect grounded prose in the Narrator's voice (referencing Greywater / Brannoc / the washed-out bridge), tags stripped, and `facts harvested: N` with N ≥ 0. 4. **Degraded path:** stop the proxy and click again. Expect the authored fallback line and `(degraded)` in the status — never an error. Override the proxy URL with the `coc_rpg/proxy_base_url` project setting.