First-run onboarding: contextual coach-marks + How-to-Play card + dev replay toggle
Teaches the deep, interlocking loop — especially the inverted win condition (you win by CLEARING EXPEDITIONS, not by surviving base sieges; DR-042/DR-043). - OnboardingSystem: client-only observe-only PresentationSystemGroup overlay (own UIDocument @ sortingOrder 60), soft-gated 10-beat coach-mark sequence with a world-space ▶ pointer; never mutates sim / never destroys a ghost. - OnboardingStepMath: pure, unit-tested step machine (snapshot + IsSatisfied + scheme-aware prompts + pointer kinds + persisted-mask helpers). - HowToPlayPanel: tabbed reference card (Controls / The Loop / Build / Threats / Win-Lose), reachable from the main menu and the pause overlay. - Per-client client-local state in GameSettings (TutorialHints + OnboardingMask bitmask, additive) — a Join client keeps its own; a host save-wipe never re-teaches. Settings toggle + menu "Replay Tutorial". - Dev "Force Each Launch" toggle (GameSettings.ForceOnboardingEachLaunch): SettingsService.Boot wipes the mask + forces hints on in-memory every launch so the tutorial always replays fresh. - HudSystem suppresses its own location hint while onboarding is active (single prompt voice), via OnboardingState + [UpdateAfter(OnboardingSystem)]. Validated green: 20/20 EditMode; Play smoke confirmed overlay render, clean U+25B6 pointer glyph, no system sort-cycle, and the force-wipe end-to-end. Docs: DR-043 + session log; reusable lesson archived in the build-gotchas note. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
---
|
||||
id: DR-043
|
||||
title: First-Run Onboarding — contextual coach-marks + replayable How-to-Play card
|
||||
status: accepted
|
||||
date: 2026-06-28
|
||||
tags:
|
||||
- decision
|
||||
- design
|
||||
- onboarding
|
||||
- ux
|
||||
- hud
|
||||
- client-only
|
||||
- presentation
|
||||
permalink: gamevault/07-sessions/decisions/dr-043-first-run-onboarding
|
||||
---
|
||||
|
||||
# DR-043 — First-Run Onboarding
|
||||
|
||||
> Operator (2026-06-28): *"This game needs an onboarding style type of thing, plan something that makes sense."* The game teaches NOTHING today (only a "Tab/Y — BUILD" discovery chip + a one-line expedition objective readout) yet has a deep, interlocking stack to learn: twin-stick combat → mine/economy → build palette → defend-the-Core sieges → walk-the-gate expeditions → an **inverted win condition** (you win by CLEARING EXPEDITIONS — [[DR-042_Loop_Reshape_Expedition_Driven]] — not by surviving base sieges). Designed via a 7-fork decision-tree (operator-locked) backed by a genre-precedent research pass, then built through the `/dots-dev` ladder.
|
||||
|
||||
## The problem
|
||||
A new player is dropped cold into the full loop with no scaffolding, and the win condition is **counter-intuitive** — base defense *feels* like the goal, so without explicit framing players read it as tower-defense and never discover that expeditions are the win (the "Don't Starve / Hades II — no framing" failure the research flagged). Nothing in the build addresses this.
|
||||
|
||||
## Locked design — 7 forks (decision-tree, operator-chosen)
|
||||
| Fork | Decision |
|
||||
|---|---|
|
||||
| **Style** | Contextual just-in-time coach-marks **+** a replayable How-to-Play reference card (not a guided rail, not a static-only screen) |
|
||||
| **Scope** | The **full first lap** — through one expedition clear AND the retaliation siege (stopping earlier hides the win) |
|
||||
| **Pacing** | **Soft-gated** objectives (a step shows until its action is done; never physically blocks) + auto-suppress |
|
||||
| **Guidance** | Text prompt **+ one world-space pointer** per step (off-screen edge-arrow for navigation; text for conceptual beats) |
|
||||
| **Welcome** | A **tiny non-modal welcome strip** on first spawn (names the inverted goal) → then silent coach-marks |
|
||||
| **Reference card** | **Tabbed** (Controls · The Loop · Build & Economy · Threats · Win/Lose), reachable from **menu + pause** |
|
||||
| **Opt-out** | Settings **toggle** + **auto-suppress** (a taught action already done never fires) + **replayable** from the menu + a dev **Force Each Launch** toggle (wipes the mask every boot — see below) |
|
||||
| **Co-op** | **Per-client**, keyed to each player's own first-encounter; flags in **client-local `GameSettings`**, NOT the host-only `SaveData` |
|
||||
|
||||
Research backing (3-agent genre-precedent pass, run `wf_f41c8423-68b`): NN/g pull-not-push / one-thing-at-a-time / advance-by-doing; CHI-2012 (context-sensitive > forced); shipped analogs Riftbreaker / Cult of the Lamb / DRG / Hades / Helldivers 2 / Remnant 2 all teach **one complete lap then release**, optional+replayable, per-client. The annotated loop diagram is the single highest-value asset (answers the #1 confusion "how do the pillars connect").
|
||||
|
||||
## Architecture — pure client-side presentation, ZERO netcode surface
|
||||
- **`OnboardingSystem`** — client-only observe-only `SystemBase` in `PresentationSystemGroup` (same constraints as `HudSystem`: never mutates sim, never destroys a ghost, reads replicated state once/frame). Owns its own runtime UIDocument (sortingOrder **60** — above HUD 50, below pause 100, root `pickingMode=Ignore`). A bottom-center prompt chip + a world-space `▶` pointer.
|
||||
- **`OnboardingStepMath`** — pure, engine-free step list + `Snapshot` + `IsSatisfied` + prompt copy + pointer kind + mask helpers (the unit-testable core; mirrors the `*Math` discipline).
|
||||
- **`OnboardingState`** — static `Active` flag (HudSystem reads it to blank its own location hint → single prompt voice) + `[RuntimeInitializeOnLoadMethod]` reset-on-play-enter (stale-static rule).
|
||||
- Progress persists per-client in **`GameSettings.OnboardingMask`** (a completed-step bitmask) + `TutorialHints` toggle (v1→v2 **additive**). Client-local JSON — a Join client keeps its own; a save wipe never re-teaches the host.
|
||||
- **Dev replay-each-launch** (`GameSettings.ForceOnboardingEachLaunch`, added 2026-06-29): an additive **0-default** int (no version bump — a v2 file deserializes it to 0/off). When set, `SettingsService.Boot` (a `RuntimeInitializeOnLoadMethod` that runs on **every** editor Play-enter / built-player launch) wipes the mask + forces hints on **in-memory** (not written back — the system re-persists as the player advances, and the next launch wipes again). Surfaced as a "Force Each Launch (Dev)" cycle row in Settings → Onboarding. The menu "Replay Tutorial" remains the one-shot equivalent.
|
||||
|
||||
### The lap (10 beats; soft-gated; scheme-aware glyphs; auto-suppress via absolute counts)
|
||||
Welcome(timed, names the goal) → Move → Mine (attack an Ore node — mining IS combat at the base since Calm has no enemies; pointer→nearest node) → Build a Turret → Fabricator (soft) → Reach the Gate (edge-arrow) → Clear the zone → Return (leave expedition) → Defend the siege (soft) → Done. Each step gated on the **local** player's own first-encounter; count-based steps (Build/Fabricator) test an **absolute** count so a join-client at a built base auto-skips.
|
||||
|
||||
## Wire/bake classification — **LOW blast radius**
|
||||
No `[GhostField]`, no ghost prefab/hash change, **no subscene re-bake**, no RPC, no `GhostRelevancy`, no server-system ordering. Only client-local `GameSettings` v1→v2 (additive JSON). ⇒ no pre-code netcode design review needed; relied on the verify ladder + a post-impl static review instead.
|
||||
|
||||
## Build status — shipped + validated green (built 2026-06-28, validated 2026-06-29)
|
||||
Built **2026-06-28**, written via the **filesystem** (not MCP `create_script`) because the Unity editor was crashing — a **GPU/driver fault** (`dxgi.dll`, RTX 5060 Ti, driver 32.0.16.1062; recurring `Unity.exe.*.dmp` — NOT project code; see the session log + native memory). The feature has **NOT yet been compiled / tested / Play-validated**.
|
||||
|
||||
In lieu of a compiler, a **3-lens adversarial static review** (run `wf_d804925a-f7b`) verified every symbol against the codebase: **Lens 1 (compile/API) = clean PASS** (SystemAPI-in-helpers pattern confirmed valid, the `PlayerInput`/InputSystem CS8377 gotcha respected, asmdef refs + UITK APIs all resolve). Findings fixed:
|
||||
- **[MAJOR, fixed]** the v1→v2 bump activated the lossy `SettingsService.Migrate` (rebuilt from `Defaults()`, dropping returning players' display-mode/quality/v-sync/fps/refresh) → rewrote Migrate to carry forward all old fields and seed only the new ones (Load already Clamps).
|
||||
- **[minor, fixed]** Welcome copy said "Esc: How to Play" but Esc opens Pause, and Esc (via `anyKey`) self-dismissed the framing → reworded to "Esc → Pause → How to Play" + excluded `escapeKey` from the message-beat skip.
|
||||
- **[minor, fixed]** timed beats advanced behind the pause overlay → freeze accumulation/eval on `PauseMenuController.Open`.
|
||||
- **[minor, fixed]** `OnboardingState.Active` not reset on system teardown → reset in `OnDestroy`; HudSystem now `[UpdateAfter(OnboardingSystem)]` for same-frame suppression.
|
||||
- **[L3 watch]** the `▶` pointer glyph's font coverage — verify it renders (swap to a font-independent shape if it's a missing-glyph box).
|
||||
|
||||
**Validation (2026-06-29, editor stable):** `refresh_unity scope=all mode=force` imported the 5 new `.cs` + recompiled → `read_console` clean (1 unrelated AI-assistant warning) → **20/20 EditMode pass** (`OnboardingStepTests` + `OnboardingSettingsTests`, incl. 2 new `ForceOnboardingEachLaunch` cases). Play smoke: seeded a **veteran** mask (`int.MaxValue`) + `force=1` → Boot wiped it → the overlay replayed from Welcome (live `mask` observed back at 0, then 1 as Welcome auto-completed) — proving the dev toggle AND that `HudSystem`'s new `[UpdateAfter(OnboardingSystem)]` introduces **no system sort-cycle** (world booted clean). Captured the Move + Build + Gate prompt chips and the `▶` pointer — the glyph renders as a **clean U+25B6 triangle, not a tofu box** (the flagged L3 watch), and the HUD location hint is correctly suppressed during onboarding. (The `▶` step was forced via reflection on the live `OnboardingSystem` since input can't be injected in an MCP smoke.)
|
||||
|
||||
## Files
|
||||
**New:** `Client/Onboarding/{OnboardingState, OnboardingStepMath, OnboardingSystem}.cs`, `Client/UI/HowToPlayPanel.cs`, `Tests/EditMode/OnboardingStepTests.cs`.
|
||||
**Edited:** `Client/Settings/{GameSettings, SettingsService}.cs`, `Client/UI/{MainMenuController, PauseMenuController, SettingsScreen}.cs`, `Client/Presentation/HudSystem.cs`.
|
||||
|
||||
## Consequences / open
|
||||
- **CLAUDE.md gotcha — parked in the archive, NOT inlined** (resolved 2026-06-29): the reusable lesson (*first-run / has-played / per-player UI flags → client-local `GameSettings`, never host `SaveData`* + the client-onboarding-overlay pattern + the dev force-each-launch wipe) is captured under a dated heading in `_Meta/CLAUDE_Build_Gotchas_Archive.md`. It stayed out of CLAUDE.md by design: the file is at **40884/40960 B** (76 B headroom) and this is a one-time client-overlay pattern, not a high-recurrence hazard, so by CLAUDE.md's own "hottest rules only" budget rule it doesn't earn inline space — evicting a hot rule to make room for a cold one would be a net loss.
|
||||
- Tuning knobs (live): `OnboardingStepMath.{WelcomeSeconds, MoveThreshold, FabricatorSoftSeconds, DefendNoSiegeSeconds, DoneSeconds}`.
|
||||
- Deferred (not built): contextual `?` deep-link from a coach-mark to the card page; target/HUD highlights (operator chose pointers only); a structured guided-tutorial variant.
|
||||
- See [[2026-06-28_First_Run_Onboarding]] · [[DR-042_Loop_Reshape_Expedition_Driven]] · [[DR-038_Slice1_Combat_Readability_HUD_Declutter]] (the discovery-chip/build-mode precedent) · [[DR-019_Frontend_Menu_Settings_Saves_Build]] (settings/save lifecycle).
|
||||
Reference in New Issue
Block a user