Skip to content

Tutorial onboarding (Ember Reach) — design

Tutorial onboarding (Ember Reach) — design

Section titled “Tutorial onboarding (Ember Reach) — design”

Status: brainstormed with Chris 2026-07-21. This is a deliberate v0.1 feature-freeze exception (see docs/RELEASE-0.1.md — feature freeze is normally bug fixes/QA/docs/release tasks only) — Chris wants new players taught the game properly before landing in Survival, and approved re-enabling + extending the built-but-gated Story/Tutorial system to do it.

Every new player should be taught, in order: moving the ship (left stick), that they have both an auto-aiming weapon and a stronger manual weapon aimed with the right stick, how the dash/warp dodge works and how to read its readiness off the engine glow, and how to launch the decoy. Then they should land in Survival — the mode v0.1 actually ships as the main loop — without being walled behind the full campaign.

Context (what already exists — verified via code, not memory)

Section titled “Context (what already exists — verified via code, not memory)”

Storyline mode (“Ascent: The Spectrum”) is not vaporware. It’s a fully built, tested campaign system — StoryDirector, StoryData, StoryState, dialogue/region-title UI, NPCs, 4 elemental domains (Ember → Hush → LiveWire → Null) each ending in a Warden boss — currently hidden from the v0.1 menu by one flag:

  • ui/start_menu.gd’s const V01_CRYSTALS_ONLY := true collapses the 3-entry mode picker (tutorial/story/survival) down to Survival only. The other two modes still exist, still pass their tests, and are fully wired end-to-end (main.gd’s _mode var and _is_story() already branch on all three).
  • A dedicated "tutorial" mode already exists (distinct from "story"): it replays the same Ember→Null campaign but pauses the sim during dialogue, uses a fixed seed (sim.enable_story(sdata, not is_tutorial, not is_tutorial) — suppress_tutorial=false, randomize=false), and currently has no stopping point — a player can walk straight from Ember into Hush and play the whole campaign under _mode == "tutorial".
  • MetaState.tutorial_done flips true once story.regions_seen.size() > 1 or story.complete (main.gd:1179-1180) — i.e. today it only flips on reaching region 2 or finishing the entire campaign. There is no “first act cleared” checkpoint.
  • In story mode, active_weapon_ids starts empty (sim.gd:965, “story starts weaponless”) but PilotArsenal always instantiates the manual WeaponAim regardless of mode — every pilot has the right-stick weapon from the first tick. The player’s very first NPC interaction (echo_intro, room ember_entry) grants the auto-firing blade (give_blade reward) and its paired dialogue (ember_intro) already explains both weapon types: “Your weapons fight on their own — just fly, and they find their marks. The right stick throws an aimed bolt.”
  • The decoy is already taught: echo_decoy (on_enter for room ember_a2, the second arena) explains the meter-fueled, always-available decoy. This is not the same thing as the Drone Bay companion drone, which requires a gold-shop purchase (unlock-drone-<klass>) before it can ever be deployed — a fresh save owns none, so it can’t be the thing taught here.
  • Dash/warp has no teaching content at all today. PlayerState.dash_cd (0 = ready) already drives render/player_renderer.gd’s thruster glow via a dash_ready boolean (line 191) — the “glow tells you when it’s ready” visual the player asked for already exists in the renderer, it’s just never explained to the player.
  • ui/control_hint.gd (ControlHint) is a platform-tailored (touch/stick/mouse), self-dismissing movement reminder. Correction from an earlier pass of this design: it is not dead code — main.gd’s _new_run() originally created it gated if not _is_story() (commit c736b354, 2026-06-30 — i.e. it was only ever meant for Survival, since Tutorial/Story teach movement via dialogue), then Chris explicitly asked for it to be removed entirely (commit c7ef9d9, 2026-07-05, “per Chris’s request”). main.gd:908 today is just control_hint = null with a comment recording that removal. Verified via git history, not assumed.
  • Narrow V01_CRYSTALS_ONLY’s effect from “Survival only” to “Tutorial + Survival” (Story stays hidden — a bigger, separate commitment Chris explicitly deferred past v0.1).
  • Fresh save (meta.tutorial_done == false): skip the menu picker entirely on first launch, start a Tutorial run directly. Returning players (tutorial_done == true) see the normal 2-entry menu; Tutorial stays selectable for a replay.
  • Tutorial is capped to Ember Reach only (the first of the 4 domains), not the full campaign. The instant the player defeats the Ember Warden, a short victory beat plays, then the run ends and a Survival run auto-launches — no menu stop in between, no hard lock (a player who wants to bail into Survival before finishing Ember can already do so via pause/quit, same as any run today; nothing about this design blocks that). The auto-launched Survival run starts exactly as it would from the menu — default ship/loadout, since this is necessarily the player’s first Survival run on a fresh save.
  • tutorial_done gets a new flip condition: Ember cleared while _mode == "tutorial", checked alongside (not replacing) the existing region/complete check, since the latter still matters for the separate "story" mode’s suppress_tutorial/randomize behavior once Story is re-exposed post-v0.1.

Most of what’s needed is already-shipped dialogue; the actual gap is small.

Mechanic Status Plan
Movement (left stick) Untaught in Tutorial (Chris removed ControlHint everywhere 2026-07-05) Re-instantiate ControlHint, but only for Tutorial-mode runs — respects the 2026-07-05 Survival removal, which stays as-is. Shows before the player reaches Echo; reaching Echo to trigger ember_intro also requires the player to have moved regardless.
Auto weapon vs. manual weapon Already taught (ember_intro) Keep as-is; light copy polish only if playtesting shows it’s unclear.
Dash/warp Not taught anywhere Add 1-2 new lines to the existing ember_intro speech (same beat that already covers weapons + healing) — explain the dash button, that it grants a moment of invulnerability, and to watch the engine glow for readiness. No new dialogue node or trigger — ember_intro already fires before the player’s first real fight (ember_a1), which is the right moment.
Decoy launch Already taught (echo_decoy) Unchanged.
  • ui/start_menu.gd — narrow the V01_CRYSTALS_ONLY mode list to [tutorial, survival].
  • main.gd — on meta.tutorial_done == false, bypass _show_start_menu() and start a tutorial run directly.
  • main.gd / sim/story_director.gd — new branch: when _mode == "tutorial" and the Ember Warden dies, override the normal “portal to Hush” transition (ember_portal’s ember_outro dialogue currently promises “The Hush is next”) with a short tutorial-specific victory beat, then end the run and auto-launch Survival.
  • main.gd — new tutorial_done flip condition for the capped-tutorial case.
  • main.gd — re-instantiate ControlHint at run start, gated to Tutorial-mode runs only (Survival’s 2026-07-05 removal is untouched). Verify it doesn’t visually clash with ember_intro’s dialogue box, which fires shortly after (should be fine — the hint is only on screen before the player reaches Echo).
  • data/story.json — add the dash/warp lines to ember_intro.
  • Extend tests/test_story_*.gd for the new capped-tutorial→Survival handoff and the new tutorial_done flip condition.
  • This is all story != null / render-only territory (ControlHint is pure UI, no sim state) — the Survival determinism baseline is untouched, no re-pin expected.
  • Manual playtest on Mac (fresh save → forced tutorial → Ember Warden → auto-Survival → subsequent launch shows the normal 2-mode menu) before an Apple TV device deploy, per the project’s existing on-device verification convention.
  • Re-exposing the full 4-domain Story campaign on the v0.1 menu.
  • Teaching the Drone Bay companion drone (requires a shop purchase a fresh save can’t have).
  • Any new dash/warp mechanic — this uses the existing dash exactly as implemented today, just narrated for the first time.
  • Reactive, just-in-time teaching triggers (e.g. “you took a hit you could’ve dashed away from”) — the design uses the existing scripted on_enter/NPC-interaction dialogue pattern instead, matching how every other lesson in Ember Reach already works.