Game Scene Split up

This commit is contained in:
2026-06-04 13:45:46 -07:00
parent dbc4a92a86
commit 16b01bec38
49 changed files with 11976 additions and 188 deletions
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d0bd734fa8e96e047b5651bf8adc98d4
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,64 @@
using Unity.NetCode;
namespace ProjectM.Simulation
{
/// <summary>
/// One-shot dev-tools command from a client to the server (the DebugOverlay / execute_code). A scalar-only
/// <see cref="IRpcCommand"/> (byte opcode + two ints, the project's scalar-RPC convention) so a single wire
/// type covers every dev action via <see cref="DebugOp"/>. **Unconditional (no #if)** — the reflection-built
/// RpcCollection hash must match across release/dev peers, so only the SEND/RECEIVE systems + overlay are
/// #if-gated, never this type. The server applies it authoritatively in DebugCommandReceiveSystem, so the dev
/// buttons exercise the real paths and work over a live connection too.
/// </summary>
public struct DebugCommandRequest : IRpcCommand
{
/// <summary>Which action to perform (see <see cref="DebugOp"/>).</summary>
public byte Op;
/// <summary>First scalar argument (size / item id / region id / amount — per-op).</summary>
public int ArgA;
/// <summary>Second scalar argument (count — per-op).</summary>
public int ArgB;
}
/// <summary>Opcodes for <see cref="DebugCommandRequest.Op"/> (bytes — never an enum on the wire).</summary>
public static class DebugOp
{
/// <summary>Arm + immediately fire a siege of ArgA Husks (ArgA = size).</summary>
public const byte SpawnWave = 0;
/// <summary>Collapse the current siege (cull Husks, stop spawning, clear pending) -> back to Calm.</summary>
public const byte EndSiege = 1;
/// <summary>Cull every living Husk now (leaves the phase alone).</summary>
public const byte ClearEnemies = 2;
/// <summary>Hard-reset the run-state to Calm (clears any siege/pending).</summary>
public const byte SetCalm = 3;
/// <summary>Deposit ArgB of resource ArgA (a <see cref="ResourceId"/>) into the shared ledger.</summary>
public const byte GrantResource = 4;
/// <summary>Grow the sender's damage upgrade by one tier (a debug StatModifier).</summary>
public const byte GrantUpgrade = 5;
/// <summary>Teleport the sender to region ArgA (a <see cref="RegionId"/>).</summary>
public const byte Teleport = 6;
/// <summary>Toggle the sender's <see cref="DebugGodMode"/> (damage immunity).</summary>
public const byte ToggleGod = 7;
/// <summary>Heal the sender to full.</summary>
public const byte Heal = 8;
/// <summary>Kill the sender (Health -> 0; the normal death/respawn loop takes over).</summary>
public const byte KillPlayer = 9;
/// <summary>Add ArgA to the long-arc goal charge.</summary>
public const byte AdvanceGoal = 10;
/// <summary>Set ThreatState.Heat to ArgA (inert until the Heat source ships).</summary>
public const byte SetHeat = 11;
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: bfce4227d5639a84e87b36a1f9b03f50
@@ -0,0 +1,15 @@
using Unity.Entities;
namespace ProjectM.Simulation
{
/// <summary>
/// Dev god-mode marker for a player: while ENABLED, the server skips all damage to this entity
/// (<c>HealthApplyDamageSystem</c> early-outs, exactly like the respawn-invuln window). A server-only
/// ENABLEABLE component (NOT a [GhostField]) — the server is authoritative; the client never needs it.
/// Baked PRESENT but DISABLED on the player prefab (mirrors the <c>Dead</c> gate) so toggling it is a bit
/// flip, never a structural change / sync point. Toggled by the editor-only DebugCommandReceiveSystem. The
/// type is unconditional (it is referenced by the always-compiled HealthApplyDamageSystem); in a player build
/// nothing ever enables it, so the guard is a harmless always-false branch.
/// </summary>
public struct DebugGodMode : IComponentData, IEnableableComponent { }
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 9af8b73ede4319646a3347322999ec36
@@ -26,39 +26,55 @@ namespace ProjectM.Simulation
[GhostField] public int WaveNumber;
}
/// <summary>Phase constants for <see cref="CycleState.Phase"/> (a byte, not an enum, for trivial Burst/serialization).</summary>
/// <summary>Phase constants for <see cref="CycleState.Phase"/> — the GLOBAL shared posture (a byte, not an enum, for trivial Burst/serialization). Being out on an expedition is per-player presence (server-only RegionTag), NOT a global phase.</summary>
public static class CyclePhase
{
/// <summary>Out in the procedural field gathering resources (timed).</summary>
// Re-meaned IN PLACE — the byte VALUES are unchanged from the old Expedition/Defend/Build, so the
// [GhostField] serializer layout is identical (the const re-mean alone forces no re-bake). slot 0 (was
// Expedition) -> Calm; slot 1 (was Defend) -> Siege; slot 2 (was Build) -> retired.
/// <summary>The persistent, unhurried home base — the DEFAULT posture. No countdown; build/prep at your pace.</summary>
public const byte Calm = 0;
/// <summary>The base is under assault by a Husk wave (event-triggered; ends when the wave is cleared).</summary>
public const byte Siege = 1;
// ---- Deprecated aliases (kept so HUD/audio/tests keep compiling through the cut-over; cleaned up later). ----
/// <summary>DEPRECATED alias of <see cref="Calm"/>.</summary>
public const byte Expedition = 0;
/// <summary>The base is under assault by a Husk wave (ends when the wave is cleared).</summary>
/// <summary>DEPRECATED alias of <see cref="Siege"/>.</summary>
public const byte Defend = 1;
/// <summary>Calm at base: spend resources to build/upgrade (timed).</summary>
/// <summary>DEPRECATED, unreachable — the timed Build phase is retired.</summary>
public const byte Build = 2;
/// <summary>Expedition phase duration in server ticks (SimulationTickRate = 60). Tunable; short for the M6 slice.</summary>
public const uint ExpeditionTicks = 3600; // ~60s cap (early return via the gate ends it sooner)
/// <summary>DEPRECATED — the forced Expedition timer is retired (the loop is player-driven). Kept so existing crefs resolve.</summary>
public const uint ExpeditionTicks = 3600;
/// <summary>Build phase duration in server ticks.</summary>
public const uint BuildTicks = 1200; // ~20s
/// <summary>DEPRECATED — the forced Build timer is retired. Kept so existing crefs resolve.</summary>
public const uint BuildTicks = 1200;
}
/// <summary>
/// Server-only bookkeeping for the cycle state machine that must NOT replicate (kept separate from the
/// replicated <see cref="CycleState"/>). Records the wave number captured when the Defend phase began so
/// the director can detect "this Defend's wave has now been spawned and cleared".
/// Server-only bookkeeping for the run-state machine that must NOT replicate (kept separate from the
/// replicated <see cref="CycleState"/>). Records the wave number captured when the current Siege began plus
/// the procedural-expedition-field session epoch (bumped when the expedition region goes empty-&gt;occupied so
/// the field reseeds per sortie).
/// </summary>
public struct CycleRuntime : IComponentData
{
/// <summary>WaveState.WaveNumber captured at the moment the current Defend phase started.</summary>
/// <summary>WaveState.WaveNumber captured the moment the current Siege started (DefendCleared tests &gt; this).</summary>
public int DefendStartWave;
/// <summary>Cycle phase from the previous tick — lets ExpeditionFieldSystem edge-detect entering/leaving Expedition.</summary>
public byte PrevPhase;
/// <summary>Monotonic expedition-field session counter; bumped on the expedition region's empty-&gt;occupied edge so each sortie reseeds. RNG seed (never tick math; never 0, via max(1, ...)).</summary>
public int ExpeditionEpoch;
/// <summary>CycleNumber the expedition field was last seeded for (compared by int equality, never tick math).</summary>
public int LastSpawnedCycle;
/// <summary>The <see cref="ExpeditionEpoch"/> the field was last seeded for (compared by int equality).</summary>
public int LastSpawnedEpoch;
/// <summary>Previous-tick expedition occupancy (1 = at least one player out), for the empty&lt;-&gt;occupied edge.</summary>
public byte PrevExpeditionOccupied;
}
}
@@ -0,0 +1,85 @@
using Unity.Entities;
namespace ProjectM.Simulation
{
/// <summary>
/// Baked, server-only tuning for the composite ThreatDirector (the data-driven base-attack scheduler). Lives
/// on the global CycleDirector entity but is NOT a [GhostField] — the server alone decides when a siege
/// fires; clients learn about it only through the replicated <see cref="CycleState.Phase"/> flip to Siege.
/// Flat scalar fields (never an enum/array — dodges the MCP authoring-drop gotcha + stays Burst-trivial). This
/// slice wires only the POST-EXPEDITION source; the Heat/Schedule fields are baked-but-inert so those sources
/// drop in later additively with NO re-bake (server-only layout, not a ghost serializer change).
/// </summary>
public struct ThreatConfig : IComponentData
{
// ---- Post-expedition retaliation (the only source wired this slice) ----
/// <summary>1 = a completed expedition (a player returning to base) can draw a retaliation siege.</summary>
public byte PostExpeditionEnabled;
/// <summary>Telegraph/arming delay (server ticks) between the trigger and the siege actually spawning.</summary>
public uint PostExpeditionDelayTicks;
/// <summary>Siege size floor (Husk count) for a post-expedition retaliation.</summary>
public int SizeBase;
/// <summary>Extra Husks per unit of resources hauled back this run (0 = a flat <see cref="SizeBase"/> siege).</summary>
public int SizePerExpeditionResource;
/// <summary>How a pending siege starts (see <see cref="ThreatStartCondition"/>).</summary>
public byte StartCondition;
/// <summary>Max server ticks a Siege may run before it auto-collapses (remaining Husks culled) so an unattended/empty-base siege can never soft-lock. 0 = no cap.</summary>
public uint SiegeTimeoutTicks;
// ---- Reserved, present-but-inert this slice (additive later, no re-bake) ----
public byte HeatEnabled;
public float HeatPerTickAtBase;
public float HeatPerHarvest;
public float HeatThreshold;
public byte ScheduleEnabled;
public uint ScheduleIntervalTicks;
}
/// <summary>Start-condition constants for <see cref="ThreatConfig.StartCondition"/> (bytes — never an enum, never in an RPC).</summary>
public static class ThreatStartCondition
{
/// <summary>DEFAULT: arm via the telegraph countdown (<see cref="ThreatState.ArmTick"/>) then fire — even at an empty base.</summary>
public const byte Immediate = 0;
/// <summary>Hold the pending siege until ≥1 player is in the base region OR the arm tick + a grace window elapses (bounded — never a soft-lock).</summary>
public const byte RequirePlayerAtBase = 1;
}
/// <summary>
/// Server-only runtime state of the ThreatDirector, on the global CycleDirector entity beside
/// <see cref="CycleRuntime"/>. NOT replicated. <see cref="PendingSiegeSize"/> is the single documented entry
/// point: any source (post-expedition, dev tools, later Heat/Schedule) sets it; <c>CyclePhaseSystem</c>
/// consumes it on the Calm→Siege edge and zeroes it. All stored ticks are wrap-safe (TickUtil.NonZero +
/// NetworkTick compares), never raw uint.
/// </summary>
public struct ThreatState : IComponentData
{
/// <summary>Husk count of the armed siege; 0 = none pending. Consumed (zeroed) by CyclePhaseSystem at Siege entry.</summary>
public int PendingSiegeSize;
/// <summary>Server tick the pending siege fires (telegraph). 0 = fire as soon as seen. Routed through TickUtil.NonZero.</summary>
public uint ArmTick;
/// <summary>Server tick the current Siege began (0 = not under siege). The bounded-resolution timeout measures from here (TickUtil.NonZero) so an unattended/empty-base siege can never soft-lock.</summary>
public uint SiegeStartTick;
/// <summary>Count of expeditions completed (a player returned to base). Drives the post-expedition source + stats.</summary>
public int ExpeditionsCompleted;
/// <summary>Return events the gate has signalled but the director has not yet consumed (the gate teleports the player out of its radius, so one increment per return — natural de-dup).</summary>
public int PendingReturns;
/// <summary>Accumulated heat (inert this slice; the Heat source reads/writes it later).</summary>
public float Heat;
/// <summary>Next scheduled-siege tick (inert this slice; the Schedule source uses it later). TickUtil.NonZero when used.</summary>
public uint NextScheduledTick;
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 2e66b1e7c715ceb418459c9323853271