This commit is contained in:
2026-07-04 16:57:40 -07:00
parent 16e396841e
commit c6b7890212
47 changed files with 1713 additions and 209 deletions
@@ -9,6 +9,12 @@ namespace ProjectM.Client
/// observable client state each step reads, the deterministic per-step completion test, prompt copy, the
/// spatial-cue kind, and the persisted-mask helpers.
///
/// RE-AUTHORED for the expedition redesign (demo polish): base nodes are gone (mining happens IN the run),
/// so the old at-base Mine step is dead — the first lap is now Build (grubstake Ore) → Fabricator →
/// READY UP → fight rooms (mine in-run) → boon → boss/return → defend. Step BITS are re-meant, never
/// renamed-in-place semantics: a veteran's full mask stays dormant; a partial first-run mask at worst
/// replays one beat.
///
/// Pacing (operator-locked = soft-gated): a step shows until its action is performed (no per-step timeout),
/// EXCEPT two info beats — <see cref="Fabricator"/> and <see cref="Defend"/> — which also auto-advance, plus
/// the timed <see cref="Welcome"/> strip. Veteran / co-op auto-suppress falls out for free: the count-based
@@ -20,12 +26,12 @@ namespace ProjectM.Client
// ---- ordered steps (byte ids; bit i of GameSettings.OnboardingMask = step i complete) ----
public const byte Welcome = 0; // tiny win-condition framing strip (timed)
public const byte Move = 1;
public const byte Mine = 2; // attack an Ore node — mining IS combat at the base (Calm = no enemies yet)
public const byte Build = 3; // open palette + place a Turret
public const byte Fabricator = 4; // Ore -> Charge (soft info beat)
public const byte Gate = 5; // reach the Expedition Gate
public const byte Clear = 6; // clear the expedition wave (the first real enemies)
public const byte Return = 7; // walk back to base to bank +1 charge
public const byte Build = 2; // open palette + place a Turret (the 50-Ore grubstake covers it)
public const byte Fabricator = 3; // Ore -> Charge (soft info beat)
public const byte ReadyUp = 4; // press T / click READY — the party launches together
public const byte Rooms = 5; // fight the rooms; attack crystal nodes to haul resources
public const byte Boon = 6; // clear a room -> pick 1 of 3 boons + choose the path
public const byte Return = 7; // fell the boss — the haul + the Engine charge come home
public const byte Defend = 8; // survive the retaliation siege (soft info beat)
public const byte Done = 9; // closing beat
public const byte StepCount = 10;
@@ -40,18 +46,18 @@ namespace ProjectM.Client
// ---- spatial-cue kinds the System resolves to a live world target ----
public const byte PointerNone = 0;
public const byte PointerOreNode = 1;
public const byte PointerBaseGate = 2; // the base-region gate (go to the expedition)
public const byte PointerExpeditionGate = 3; // the expedition-region gate (return home)
public const byte PointerBaseGate = 2; // RETIRED target (walk-in gate died with the redesign); byte kept
public const byte PointerExpeditionGate = 3; // RETIRED target; byte kept for mask/step stability
/// <summary>Observable client state for one evaluation. Built by the System from ECS + input each frame.</summary>
public struct Snapshot
{
public float StepElapsed; // seconds the current step has been shown
public float MoveDistance; // accumulated player movement since the Move step began
public int OreNow; // shared-ledger Ore right now
public int OreBaseline; // ledger Ore captured when the Mine step began
public int TurretCount; // live Turret structures (absolute)
public int FabricatorCount; // live Fabricator structures (absolute)
public bool LocalReady; // the LOCAL player's replicated PlayerReady flag
public byte Lifecycle; // replicated RunInfo.Lifecycle (RunLifecycle.*)
public bool OnExpedition; // local player is in the expedition region
public byte ObjectiveState; // ExpeditionObjective.State (Idle/Active/Cleared)
public bool SawSiege; // a Siege phase was observed while the Defend step was showing
@@ -65,12 +71,14 @@ namespace ProjectM.Client
{
case Welcome: return s.StepElapsed >= WelcomeSeconds;
case Move: return s.MoveDistance >= MoveThreshold;
case Mine: return s.OreNow > s.OreBaseline;
case Build: return s.TurretCount >= 1;
case Fabricator: return s.FabricatorCount >= 1 || s.StepElapsed >= FabricatorSoftSeconds;
case Gate: return s.OnExpedition || s.ObjectiveState == ExpeditionObjectiveState.Active;
case Clear: return s.ObjectiveState == ExpeditionObjectiveState.Cleared;
case Return: return !s.OnExpedition; // entered while on expedition; satisfied on crossing home
case ReadyUp: return s.LocalReady || s.Lifecycle != RunLifecycle.Staging;
case Rooms: return s.OnExpedition || s.ObjectiveState == ExpeditionObjectiveState.Active;
case Boon: return s.ObjectiveState == ExpeditionObjectiveState.Cleared
|| s.Lifecycle == RunLifecycle.RoomReward
|| s.Lifecycle == RunLifecycle.RouteSelect;
case Return: return !s.OnExpedition; // entered mid-run; satisfied on coming home
case Defend: return s.SawSiege ? s.Phase == CyclePhase.Calm : s.StepElapsed >= DefendNoSiegeSeconds;
case Done: return s.StepElapsed >= DoneSeconds;
default: return true;
@@ -82,10 +90,8 @@ namespace ProjectM.Client
{
switch (step)
{
case Mine: return PointerOreNode;
case Gate: return PointerBaseGate;
case Return: return PointerExpeditionGate;
default: return PointerNone;
case Rooms: return PointerOreNode; // in-run crystal nodes (base nodes no longer exist)
default: return PointerNone;
}
}
@@ -93,19 +99,18 @@ namespace ProjectM.Client
public static string Prompt(byte step, bool gamepad)
{
string move = gamepad ? "Left Stick" : "WASD";
string attack = gamepad ? "RT" : "LMB";
string build = gamepad ? "Y" : "Tab"; // matches the existing HUD build-discovery chip glyph
switch (step)
{
case Welcome: return "CLEAR EXPEDITIONS to charge the Engine — defend the Core while you do. (Esc → Pause → How to Play)";
case Move: return move + " — Move";
case Mine: return attack + " — Attack the glowing Ore to mine it";
case Build: return build + " — open Build, then place a Turret by your Core";
case Build: return build + " — open Build, place a Turret by your Core (your 50 starting Ore covers it)";
case Fabricator: return "Build a Fabricator — turrets need Charge (Ore → ammo)";
case Gate: return "Press T to READY UP — when everyone is ready, the run launches";
case Clear: return "Clear each room — pick a boon, choose your path, reach the boss";
case Return: return "Fell the bossyou return home and the Engine charges (+1)";;
case Defend: return "Defend the Core! — hold the line through the siege";
case ReadyUp: return "Press T (or click READY UP) — when everyone is ready, the party launches";
case Rooms: return "Fight through the roomsattack the glowing crystals to haul resources home";
case Boon: return "Clear the roompick 1 of 3 BOONS, then choose your path on the map";
case Return: return "Reach the boss and fell it — the haul comes home and the Engine charges (+1)";
case Defend: return "Defend the Core! — a completed run provokes a retaliation siege";
case Done: return "You've got it. Clear expeditions to fill the Engine and win.";
default: return "";
}