Files
Project-M/Assets/_Project/Scripts/Simulation/Meta/MetaComponents.cs
T
kronic 304ee8c2a7 Base<->Expedition ties: portal rooms, class-at-base, prep spend + Health.Max, C3/C4/Spitter (DR-046)
Portal-gated rooms: new RunLifecycle.RoomExplore loot window; room+nodes
persist past the last kill; PortalInteractRequest -> server-only PortalCommand
advances (client derives the portal pos). RunDirectorSystem moves teardown off
the InRoom edge, relocates the boss-branch/route-gate into the RoomExplore exit,
and advances an empty expedition immediately (incl. a boss clear).

Class-at-base via a shared ClassSwapUtil (meta-band strip + per-class
MetaTierState replay, so the base RPC and the dev SetClass can't drift);
ClassSelectRequest Staging-gated. Per-run PREP buffs (PrepPurchaseRequest,
PrepCatalog): once-per-run == the row's prep StatModifier band is present,
TotalOf-before-Withdraw atomic, stripped on Returning beside the boon band.

Health.Max promoted to [GhostField] (the one ghost-hash re-bake) so the boss +
floating enemy HP bars read it directly. Feel: melee cone connect-thunk (C3),
hit-stop throttle so a horde wipe can't stutter (C4), Spitter cornered-hold.

456/456 EditMode; two adversarial reviews (pre-code 13-confirmed folded;
post-impl clean bar the RoomExplore boss-clear dead-time, fixed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 10:37:24 -07:00

77 lines
3.7 KiB
C#

using Unity.Entities;
using Unity.NetCode;
namespace ProjectM.Simulation
{
/// <summary>
/// One owned permanent meta-upgrade tier, keyed by (class, upgrade). The CycleDirector's DynamicBuffer of these is
/// the authoritative per-class meta-progression record. A GLOBAL <c>[GhostField]</c> buffer on the ownerless
/// interpolated director ghost (no OwnerSendType — the party invests together and every client reads it for the
/// shop), mirroring <see cref="StorageEntry"/>. Persisted to disk (SaveData v6) and re-applied born-correct at
/// player spawn as meta-band <see cref="StatModifier"/>s. Bytes only (Burst/serialization safe). Sparse — an
/// absent (class, upgrade) row means tier 0.
/// </summary>
[InternalBufferCapacity(24)]
public struct MetaTierState : IBufferElementData
{
/// <summary>Owning class id (Warrior/Ranger — the CharacterId anchor).</summary>
[GhostField] public byte ClassId;
/// <summary>Upgrade id (append-only key into the meta catalog).</summary>
[GhostField] public byte UpgradeId;
/// <summary>Owned tier (&gt;=1; an absent row = 0).</summary>
[GhostField] public byte Tier;
}
/// <summary>
/// Server-only singleton on the CycleDirector: the FIRST-COMMIT latch for the co-op route choice. Written IN-PLACE
/// (immediate SystemAPI.SetComponent, not a deferred ECB) inside <c>RouteSelectSystem</c>'s drain loop so two
/// same-tick picks cannot both observe <see cref="HasPick"/>==0 (the DR-014 atomicity idiom). NOT replicated.
/// </summary>
public struct RouteCommand : IComponentData
{
/// <summary>1 once a route has been committed for the current (RunEpoch, layer).</summary>
public byte HasPick;
/// <summary>The committed option index (into the replicated RouteOpt* set).</summary>
public byte OptionIndex;
/// <summary>Run epoch the pick is for (stale-reject guard).</summary>
public int ForRunEpoch;
/// <summary>Layer the pick is for (stale-reject guard).</summary>
public int ForLayer;
}
/// <summary>
/// Server-only singleton on the CycleDirector: the room-exit PORTAL interact latch (DR-046). PortalInteractReceiveSystem
/// sets <see cref="HasInteract"/> when a player interacts the portal during RoomExplore; RunDirectorSystem (the sole
/// RunInfo/RunRuntime writer) reads it to advance the run + tear the room down, then clears it. NOT replicated.
/// Added unconditionally at director spawn (like RouteCommand).
/// </summary>
public struct PortalCommand : IComponentData
{
/// <summary>1 once a participant has interacted the room-exit portal this RoomExplore.</summary>
public byte HasInteract;
}
/// <summary>
/// Server-only persisted meta counters on the CycleDirector (mirrored to the replicated <see cref="RunInfo"/> for
/// the HUD). Added UNCONDITIONALLY at director spawn (like CycleRuntime/ThreatState/RunPhase) so a New-Game boot
/// has the component the bank block reads; restored values are SetComponent'd only inside the save-present block.
/// NOT replicated.
/// </summary>
public struct MetaCounters : IComponentData
{
public int RunsCompleted;
public int MaxDepthReached;
}
/// <summary>
/// Server-only tag of a player's class id, added at spawn by <c>GoInGameServerSystem</c>. Lets the meta systems
/// resolve which per-class tier record to seed / spend against (class was previously only an <c>AbilityRef</c> /
/// wire concern). NOT replicated.
/// </summary>
public struct PlayerClass : IComponentData
{
public byte ClassId;
}
}