diff --git a/Assets/_Project/Scripts/Authoring/BakerStructureExt.cs b/Assets/_Project/Scripts/Authoring/BakerStructureExt.cs
deleted file mode 100644
index 5962a3b66..000000000
--- a/Assets/_Project/Scripts/Authoring/BakerStructureExt.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-using ProjectM.Simulation;
-using Unity.Entities;
-using UnityEngine;
-
-namespace ProjectM.Authoring
-{
- ///
- /// Baker helpers shared by the build-structure / automation-machine bakers. These emit the SAME component data
- /// the bakers wrote inline before (no serialized authoring field or baked component change) — they only
- /// deduplicate the repeated stamp and the damageable triad
- /// ( + buffer + ). Extension methods on
- /// the concrete (not IBaker, whose AddComponent/AddBuffer are obsolete) so
- /// the non-obsolete public Baker API resolves.
- ///
- public static class BakerStructureExt
- {
- /// Stamp PlacedStructure{Type=type} with the standard baked defaults (Cell/NextTick/LastProcessedTick set at placement).
- public static void AddPlacedStructure(this Baker baker, Entity e, byte type)
- where TAuthoring : Component
- {
- baker.AddComponent(e, new PlacedStructure
- {
- Type = type,
- Cell = default,
- NextTick = 0u,
- LastProcessedTick = 0u,
- });
- }
-
- /// Make an entity damageable/destructible: Health{Current=Max=maxHp} + the required DamageEvent buffer + Destructible tag.
- public static void AddDamageable(this Baker baker, Entity e, float maxHp)
- where TAuthoring : Component
- {
- baker.AddComponent(e, new Health { Current = maxHp, Max = maxHp });
- baker.AddBuffer(e);
- baker.AddComponent(e);
- }
- }
-}
diff --git a/Assets/_Project/Scripts/Authoring/BakerStructureExt.cs.meta b/Assets/_Project/Scripts/Authoring/BakerStructureExt.cs.meta
deleted file mode 100644
index dde9dad55..000000000
--- a/Assets/_Project/Scripts/Authoring/BakerStructureExt.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: a54e2710fbe8cca4a9e84e9bbca4f7b9
\ No newline at end of file
diff --git a/Assets/_Project/Scripts/Authoring/Building/StructureAuthoring.cs b/Assets/_Project/Scripts/Authoring/Building/StructureAuthoring.cs
deleted file mode 100644
index 365c344d2..000000000
--- a/Assets/_Project/Scripts/Authoring/Building/StructureAuthoring.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using ProjectM.Simulation;
-using Unity.Entities;
-using UnityEngine;
-
-namespace ProjectM.Authoring
-{
- ///
- /// Generic authoring for a non-functional build-structure ghost prefab (Wall / Pylon) — duplicate
- /// Turret.prefab so the ownerless interpolated GhostAuthoringComponent on PlacedStructure.Type comes free,
- /// then swap TurretAuthoring for this. Bakes ONLY {Type=}
- /// (no stats, so TurretFireSystem ignores it). BuildPlaceSystem overrides Cell +
- /// LastProcessedTick and adds RegionTag{Base} at placement. is a byte (StructureType.*) to
- /// dodge the cross-assembly enum-in-Burst hazard and the MCP enum-drop gotcha.
- ///
- public class StructureAuthoring : MonoBehaviour
- {
- [Tooltip("StructureType byte: 5 = Wall, 6 = Pylon (do NOT use 1-4: Turret + reserved M7 automation).")]
- public byte Kind = StructureType.Wall;
-
- [Min(1f)] public float MaxHp = 150f;
-
- private class StructureBaker : Baker
- {
- public override void Bake(StructureAuthoring authoring)
- {
- var entity = GetEntity(authoring, TransformUsageFlags.Dynamic);
- this.AddPlacedStructure(entity, authoring.Kind);
- // EB-1: Wall/Pylon are damageable + destructible AI targets (a wall soaks Husk strikes that would
- // otherwise hit a turret). DamageEvent buffer MUST exist or an AI strike crashes at ECB playback.
- // No HitRadius -> ProjectileDamageSystem ignores them (no friendly projectile fire).
- this.AddDamageable(entity, authoring.MaxHp);
- }
- }
- }
-}
diff --git a/Assets/_Project/Scripts/Authoring/Building/StructureAuthoring.cs.meta b/Assets/_Project/Scripts/Authoring/Building/StructureAuthoring.cs.meta
deleted file mode 100644
index 090a937b9..000000000
--- a/Assets/_Project/Scripts/Authoring/Building/StructureAuthoring.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: 3f03349205fb1fe43bf6aaff14fce0b7
\ No newline at end of file
diff --git a/Assets/_Project/Scripts/Authoring/Building/StructureCatalogAuthoring.cs b/Assets/_Project/Scripts/Authoring/Building/StructureCatalogAuthoring.cs
deleted file mode 100644
index e5ebbff9c..000000000
--- a/Assets/_Project/Scripts/Authoring/Building/StructureCatalogAuthoring.cs
+++ /dev/null
@@ -1,60 +0,0 @@
-using ProjectM.Simulation;
-using Unity.Entities;
-using UnityEngine;
-using UnityEngine.Serialization;
-
-namespace ProjectM.Authoring
-{
- ///
- /// Authoring for the baked singleton (the build cost/prefab table). Flat
- /// prefab + cost fields per buildable type (the type + cost-resource are byte consts in the baker —
- /// enum-via-MCP is unreliable, and bytes dodge the cross-assembly enum-in-Burst hazard); the runtime
- /// buffer is the data-driven shape. Place once in the gameplay subscene.
- ///
- public class StructureCatalogAuthoring : MonoBehaviour
- {
- [Tooltip("Wall structure ghost prefab (StructureAuthoring{Wall} + GhostAuthoring).")]
- public GameObject WallPrefab;
-
- [Tooltip("Biomass cost to build a wall.")]
- [Min(0)] [FormerlySerializedAs("WallCostOre")] public int WallCostBiomass = 4;
-
- [Tooltip("Pylon cosmetic-beacon ghost prefab (StructureAuthoring{Pylon} + GhostAuthoring).")]
- public GameObject PylonPrefab;
-
- [Tooltip("Ore cost to build a pylon.")]
- [Min(0)] public int PylonCostOre = 2;
-
- private class StructureCatalogBaker : Baker
- {
- public override void Bake(StructureCatalogAuthoring authoring)
- {
- var entity = GetEntity(authoring, TransformUsageFlags.None);
- AddComponent(entity);
- var buf = AddBuffer(entity);
-
- if (authoring.WallPrefab != null)
- {
- buf.Add(new StructureCatalogEntry
- {
- Type = StructureType.Wall,
- Prefab = GetEntity(authoring.WallPrefab, TransformUsageFlags.Dynamic),
- CostResourceId = ResourceId.Biomass, // DR-042 C6b: walls cost Biomass (the dead currency's only sink)
- CostAmount = authoring.WallCostBiomass,
- });
- }
-
- if (authoring.PylonPrefab != null)
- {
- buf.Add(new StructureCatalogEntry
- {
- Type = StructureType.Pylon,
- Prefab = GetEntity(authoring.PylonPrefab, TransformUsageFlags.Dynamic),
- CostResourceId = ResourceId.Ore,
- CostAmount = authoring.PylonCostOre,
- });
- }
- }
- }
- }
-}
diff --git a/Assets/_Project/Scripts/Authoring/Building/StructureCatalogAuthoring.cs.meta b/Assets/_Project/Scripts/Authoring/Building/StructureCatalogAuthoring.cs.meta
deleted file mode 100644
index 400430a9a..000000000
--- a/Assets/_Project/Scripts/Authoring/Building/StructureCatalogAuthoring.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: 40093ed42072f5a4889f5f62f510aa27
\ No newline at end of file
diff --git a/Assets/_Project/Scripts/Authoring/Combat/BoonCatalogAuthoring.cs b/Assets/_Project/Scripts/Authoring/Combat/BoonCatalogAuthoring.cs
deleted file mode 100644
index 9b1ae3dc6..000000000
--- a/Assets/_Project/Scripts/Authoring/Combat/BoonCatalogAuthoring.cs
+++ /dev/null
@@ -1,77 +0,0 @@
-using System;
-using System.Collections.Generic;
-using ProjectM.Simulation;
-using Unity.Collections;
-using Unity.Entities;
-using UnityEngine;
-
-namespace ProjectM.Authoring
-{
- ///
- /// Authoring for the boon-catalog config singleton (place ONE in the gameplay subscene). Designers can author
- /// rows in the inspector; an EMPTY list bakes the code-default v1 table ()
- /// verbatim — so the subscene object needs zero property assignment through the tooling (the
- /// component_properties enum/array-drop hazard). Ids are APPEND-ONLY (they ride the replicated BoonOffer bytes
- /// and, later, per-run analytics).
- ///
- public class BoonCatalogAuthoring : MonoBehaviour
- {
- [Serializable]
- public struct BoonRow
- {
- public byte Id;
- public StatTarget Target;
- public ModOp Op;
- public float Value;
- [Tooltip("Rarity draw weight: common 100 / rare 30 / epic 10. 0 removes the boon from the pool.")]
- public byte Weight;
- [Tooltip("bit0 = Warrior, bit1 = Ranger, 3 = both.")]
- public byte ClassMask;
- public string Name;
- public string Desc;
- }
-
- [Tooltip("Leave EMPTY to bake the code-default v1 table; fill to fully replace it.")]
- public List Rows = new List();
-
- private class BoonCatalogBaker : Baker
- {
- public override void Bake(BoonCatalogAuthoring authoring)
- {
- var entity = GetEntity(authoring, TransformUsageFlags.None);
-
- BlobAssetReference blob;
- if (authoring.Rows == null || authoring.Rows.Count == 0)
- {
- blob = BoonCatalogData.BuildDefault();
- }
- else
- {
- var builder = new BlobBuilder(Allocator.Temp);
- ref var root = ref builder.ConstructRoot();
- var defs = builder.Allocate(ref root.Defs, authoring.Rows.Count);
- for (int i = 0; i < authoring.Rows.Count; i++)
- {
- var r = authoring.Rows[i];
- defs[i] = new BoonDefBlob
- {
- Id = r.Id,
- Target = (byte)r.Target,
- Op = (byte)r.Op,
- Value = r.Value,
- Weight = r.Weight,
- ClassMask = r.ClassMask,
- Name = new FixedString64Bytes(r.Name ?? string.Empty),
- Desc = new FixedString128Bytes(r.Desc ?? string.Empty),
- };
- }
- blob = builder.CreateBlobAssetReference(Allocator.Persistent);
- builder.Dispose();
- }
-
- AddBlobAsset(ref blob, out _);
- AddComponent(entity, new BoonCatalog { Value = blob });
- }
- }
- }
-}
diff --git a/Assets/_Project/Scripts/Authoring/Combat/BoonCatalogAuthoring.cs.meta b/Assets/_Project/Scripts/Authoring/Combat/BoonCatalogAuthoring.cs.meta
deleted file mode 100644
index 53d5ebbd4..000000000
--- a/Assets/_Project/Scripts/Authoring/Combat/BoonCatalogAuthoring.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: 075a570afb8ef9541b6307280b53f4e7
\ No newline at end of file
diff --git a/Assets/_Project/Scripts/Authoring/Combat/ChargerAuthoring.cs b/Assets/_Project/Scripts/Authoring/Combat/ChargerAuthoring.cs
deleted file mode 100644
index 6c0b588a6..000000000
--- a/Assets/_Project/Scripts/Authoring/Combat/ChargerAuthoring.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-using ProjectM.Simulation;
-using Unity.Entities;
-using UnityEngine;
-
-namespace ProjectM.Authoring
-{
- ///
- /// MC-1 — marks a Husk prefab as a CHARGER variant. Compose this WITH on the
- /// prefab root (both bakers share the primary entity): EnemyAuthoring bakes the common Husk components and
- /// Charger-tuned stats; this bakes the server-only (zeroed = not lunging).
- /// Component-PRESENCE is the discriminator EnemyAISystem branches on — no enum/brain byte (the Burst
- /// cross-assembly-enum hazard) — routing the Charger to the commit→lunge→whiff-stagger pass while the Grunt
- /// pass excludes it via .WithNone<LungeState>(). NOT a [GhostField]: the lunged position
- /// replicates via stock LocalTransform like every Husk.
- ///
- public class ChargerAuthoring : MonoBehaviour
- {
- private class ChargerBaker : Baker
- {
- public override void Bake(ChargerAuthoring authoring)
- {
- var entity = GetEntity(authoring, TransformUsageFlags.Dynamic);
- AddComponent(entity);
- // Slice 1 (Feature D): the replicated mid-lunge cue, baked DISABLED (a Charger spawns not-lunging).
- // EnemyAISystem derives the bit each tick from LungeState.UntilTick (visiting disabled entities via
- // .WithPresent()). Adding this [GhostEnabledBit] changes the Charger ghost hash -> RE-BAKE.
- AddComponent(entity);
- SetComponentEnabled(entity, false);
- }
- }
- }
-}
diff --git a/Assets/_Project/Scripts/Authoring/Combat/ChargerAuthoring.cs.meta b/Assets/_Project/Scripts/Authoring/Combat/ChargerAuthoring.cs.meta
deleted file mode 100644
index 505df6336..000000000
--- a/Assets/_Project/Scripts/Authoring/Combat/ChargerAuthoring.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: 9565191e0ea7fc94db934ae91a43a4cf
\ No newline at end of file
diff --git a/Assets/_Project/Scripts/Authoring/Combat/EnemyAuthoring.cs b/Assets/_Project/Scripts/Authoring/Combat/EnemyAuthoring.cs
index 8e611bfc5..7043766ec 100644
--- a/Assets/_Project/Scripts/Authoring/Combat/EnemyAuthoring.cs
+++ b/Assets/_Project/Scripts/Authoring/Combat/EnemyAuthoring.cs
@@ -60,16 +60,11 @@ namespace ProjectM.Authoring
// denominator per variant; IsCharger lets the client pick the Charger look (LungeState is server-only).
// Kind byte (client telegraph look) — derived from the sibling variant authoring (EnemyBaker is the
// SOLE EnemyTelegraph writer). Grunt=0 / Charger=1 / Spitter=2 / Swarmer=3 (ZoneEnemyMath.Kind*).
- byte kind = ZoneEnemyMath.KindGrunt;
+ // 2026-08-07 audit purge: the Charger/Spitter/Swarmer variant authoring is gone — it was attached
+ // to ZERO prefabs, so every enemy already baked Kind=Grunt and the variant AI passes matched
+ // nothing. One kind, one windup, until the LANTERN bestiary reintroduces variety via CreatureKit.
+ const byte kind = ZoneEnemyMath.KindGrunt;
byte windup = (byte)Tuning.AttackWindupTicks;
- var spitter = GetComponent();
- // Bake-time guard (DR-041 sole-Position-writer invariant): a prefab must carry at most ONE of
- // {ChargerAuthoring(LungeState), SpitterAuthoring(SpitterState)} — both would match ZERO AI passes.
- if (GetComponent() != null && spitter != null)
- Debug.LogError($"Enemy '{authoring.name}' has BOTH ChargerAuthoring and SpitterAuthoring; it would match no AI pass and never move. Remove one.", authoring);
- if (GetComponent() != null) { kind = ZoneEnemyMath.KindCharger; windup = (byte)Tuning.ChargerWindupTicks; }
- else if (spitter != null) { kind = ZoneEnemyMath.KindSpitter; windup = (byte)Mathf.Clamp(spitter.WindupTicks, 1, 255); }
- else if (GetComponent() != null) { kind = ZoneEnemyMath.KindSwarmer; windup = (byte)Tuning.AttackWindupTicks; /* B4: match the server windup (grunt-path swarmers use GruntWindupTicks); baked 6 was a snap-ramp lie */ }
AddComponent(entity, new EnemyTelegraph { WindupTicks = windup, Kind = kind });
}
}
diff --git a/Assets/_Project/Scripts/Authoring/Combat/EnemyProjectileAuthoring.cs b/Assets/_Project/Scripts/Authoring/Combat/EnemyProjectileAuthoring.cs
deleted file mode 100644
index 60cdf5c95..000000000
--- a/Assets/_Project/Scripts/Authoring/Combat/EnemyProjectileAuthoring.cs
+++ /dev/null
@@ -1,44 +0,0 @@
-using ProjectM.Simulation;
-using Unity.Entities;
-using Unity.Mathematics;
-using UnityEngine;
-
-namespace ProjectM.Authoring
-{
- ///
- /// MC-2 — authoring for the hostile Spitter projectile prefab (an ownerless INTERPOLATED ghost, duplicated from an
- /// existing interpolated ghost so the GhostAuthoringComponent comes free). Bakes with
- /// the spit's default Speed/Damage/Range; the firing Spitter OVERRIDES Direction + Speed + Damage + Region at spawn
- /// and ADDS the RegionTag (so this prefab MUST NOT bake RegionTag — AddComponent would throw on a baked one).
- /// NO Health (so it is invisible to every player hit-test) and NO [GhostField] beyond the stock LocalTransform.
- ///
- public class EnemyProjectileAuthoring : MonoBehaviour
- {
- [Min(0f), Tooltip("Default muzzle speed (the firing Spitter overrides this per-variant).")]
- public float Speed = 11f;
-
- [Min(0f), Tooltip("Default damage (the firing Spitter overrides this from its AttackDamage).")]
- public float Damage = 8f;
-
- [Min(0f), Tooltip("Max travel distance before the spit expires (world units).")]
- public float Range = 16f;
-
- private class EnemyProjectileBaker : Baker
- {
- public override void Bake(EnemyProjectileAuthoring authoring)
- {
- var entity = GetEntity(authoring, TransformUsageFlags.Dynamic);
- AddComponent(entity, new EnemyProjectile
- {
- Speed = authoring.Speed,
- Damage = authoring.Damage,
- Range = authoring.Range,
- Direction = new float2(0f, 1f),
- DistanceTravelled = 0f,
- LastStep = 0f,
- Region = 0,
- });
- }
- }
- }
-}
diff --git a/Assets/_Project/Scripts/Authoring/Combat/EnemyProjectileAuthoring.cs.meta b/Assets/_Project/Scripts/Authoring/Combat/EnemyProjectileAuthoring.cs.meta
deleted file mode 100644
index af179adb4..000000000
--- a/Assets/_Project/Scripts/Authoring/Combat/EnemyProjectileAuthoring.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: ff79c8fbcacb8c34faad37d59836b5ac
\ No newline at end of file
diff --git a/Assets/_Project/Scripts/Authoring/Combat/SpitterAuthoring.cs b/Assets/_Project/Scripts/Authoring/Combat/SpitterAuthoring.cs
deleted file mode 100644
index 3dd46d24f..000000000
--- a/Assets/_Project/Scripts/Authoring/Combat/SpitterAuthoring.cs
+++ /dev/null
@@ -1,49 +0,0 @@
-using ProjectM.Simulation;
-using Unity.Entities;
-using UnityEngine;
-
-namespace ProjectM.Authoring
-{
- ///
- /// MC-2 — marks a Husk prefab as a SPITTER variant (the ranged "reposition" question). Compose WITH
- /// on the prefab root: EnemyAuthoring bakes the common Husk components + the spit's
- /// damage/cooldown (EnemyStats.AttackDamage / AttackCooldownTicks), this bakes the server-only
- /// (zeroed NextShotTick = ready). Component-PRESENCE is the discriminator EnemyAISystem
- /// branches on (no enum); the Grunt + Charger passes exclude it via .WithNone<SpitterState>(). The
- /// actual spit projectile is a SEPARATE ghost configured by the SpitterProjectilePrefab subscene singleton.
- ///
- public class SpitterAuthoring : MonoBehaviour
- {
- [Min(0f), Tooltip("Distance the Spitter tries to hold from its target (band centre).")]
- public float PreferredRange = 9f;
-
- [Min(0f), Tooltip("Half-width dead-zone around PreferredRange where it holds and fires.")]
- public float RangeTolerance = 1.5f;
-
- [Min(0f), Tooltip("Muzzle speed of the spit (world units/second). Slow enough to be dodgeable at range.")]
- public float ProjectileSpeed = 11f;
-
- [Min(0f), Tooltip("If the target closes within this AND the Spitter can't retreat, it fires point-blank.")]
- public float CorneredRange = 3f;
-
- [Min(1), Tooltip("Telegraph wind-up before the spit fires (ticks). Keep >= ~24 (> interp delay) to stay dodgeable.")]
- public int WindupTicks = 26;
-
- private class SpitterBaker : Baker
- {
- public override void Bake(SpitterAuthoring authoring)
- {
- var entity = GetEntity(authoring, TransformUsageFlags.Dynamic);
- AddComponent(entity, new SpitterState
- {
- PreferredRange = authoring.PreferredRange,
- RangeTolerance = authoring.RangeTolerance,
- ProjectileSpeed = authoring.ProjectileSpeed,
- CorneredRange = authoring.CorneredRange,
- WindupTicks = authoring.WindupTicks,
- NextShotTick = 0,
- });
- }
- }
- }
-}
diff --git a/Assets/_Project/Scripts/Authoring/Combat/SpitterAuthoring.cs.meta b/Assets/_Project/Scripts/Authoring/Combat/SpitterAuthoring.cs.meta
deleted file mode 100644
index deb89a591..000000000
--- a/Assets/_Project/Scripts/Authoring/Combat/SpitterAuthoring.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: 55fe00810b31aa54abd577b6a07192e2
\ No newline at end of file
diff --git a/Assets/_Project/Scripts/Authoring/Combat/SpitterProjectilePrefabAuthoring.cs b/Assets/_Project/Scripts/Authoring/Combat/SpitterProjectilePrefabAuthoring.cs
deleted file mode 100644
index bdfd51784..000000000
--- a/Assets/_Project/Scripts/Authoring/Combat/SpitterProjectilePrefabAuthoring.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using ProjectM.Simulation;
-using Unity.Entities;
-using UnityEngine;
-
-namespace ProjectM.Authoring
-{
- ///
- /// MC-2 — authoring for the subscene singleton. Place ONE on a GameObject in
- /// the gameplay subscene; the server EnemyAISystem Spitter pass reads it via GetSingleton to know which spit
- /// ghost to instantiate and the concurrent soft-cap. The referenced prefab is the EnemyProjectile ghost
- /// (EnemyProjectileAuthoring); MaxLiveProjectiles bounds the relevancy loop — a Spitter at/over the cap soft-fails
- /// its shot (no cooldown burn). The carrying entity has no transform; only the referenced prefab needs one.
- ///
- public class SpitterProjectilePrefabAuthoring : MonoBehaviour
- {
- [Tooltip("The EnemyProjectile ghost prefab that Spitters fire (must carry EnemyProjectileAuthoring + an interpolated GhostAuthoringComponent).")]
- public GameObject ProjectilePrefab;
-
- [Min(1), Tooltip("Max concurrent live spit projectiles across all Spitters (soft-cap; over it a Spitter soft-fails its shot).")]
- public int MaxLiveProjectiles = 24;
-
- private class SpitterProjectilePrefabBaker : Baker
- {
- public override void Bake(SpitterProjectilePrefabAuthoring authoring)
- {
- var entity = GetEntity(authoring, TransformUsageFlags.None);
- AddComponent(entity, new SpitterProjectilePrefab
- {
- Prefab = authoring.ProjectilePrefab != null
- ? GetEntity(authoring.ProjectilePrefab, TransformUsageFlags.Dynamic) : Entity.Null,
- MaxLiveProjectiles = authoring.MaxLiveProjectiles,
- });
- }
- }
- }
-}
diff --git a/Assets/_Project/Scripts/Authoring/Combat/SpitterProjectilePrefabAuthoring.cs.meta b/Assets/_Project/Scripts/Authoring/Combat/SpitterProjectilePrefabAuthoring.cs.meta
deleted file mode 100644
index 4986c335c..000000000
--- a/Assets/_Project/Scripts/Authoring/Combat/SpitterProjectilePrefabAuthoring.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: 4ce5223c5fd56694c81991e1bb1232de
\ No newline at end of file
diff --git a/Assets/_Project/Scripts/Authoring/Combat/SwarmerAuthoring.cs b/Assets/_Project/Scripts/Authoring/Combat/SwarmerAuthoring.cs
deleted file mode 100644
index 28a0499bd..000000000
--- a/Assets/_Project/Scripts/Authoring/Combat/SwarmerAuthoring.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using ProjectM.Simulation;
-using Unity.Entities;
-using UnityEngine;
-
-namespace ProjectM.Authoring
-{
- ///
- /// MC-2 — marks a Husk prefab as a SWARMER variant (the "surround" question). Compose WITH
- /// on the prefab root, tuned fast + low-HP + fast frequent low-chip bites (via the
- /// EnemyAuthoring fields). This bakes only the marker: a Swarmer has NO AI branch (it
- /// falls through the Grunt seek+strike pass); the tag drives the director's CLUSTER spawn (a pack per slot) + a
- /// client tint. Keeps EnemyTag + RegionTag like every Husk.
- ///
- public class SwarmerAuthoring : MonoBehaviour
- {
- private class SwarmerBaker : Baker
- {
- public override void Bake(SwarmerAuthoring authoring)
- {
- var entity = GetEntity(authoring, TransformUsageFlags.Dynamic);
- AddComponent(entity);
- }
- }
- }
-}
diff --git a/Assets/_Project/Scripts/Authoring/Combat/SwarmerAuthoring.cs.meta b/Assets/_Project/Scripts/Authoring/Combat/SwarmerAuthoring.cs.meta
deleted file mode 100644
index 6725a0f9b..000000000
--- a/Assets/_Project/Scripts/Authoring/Combat/SwarmerAuthoring.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: b6a84b442d0535642abc303c01546a15
\ No newline at end of file
diff --git a/Assets/_Project/Scripts/Authoring/HomeBase/SharedStorageContainerAuthoring.cs b/Assets/_Project/Scripts/Authoring/HomeBase/SharedStorageContainerAuthoring.cs
deleted file mode 100644
index ab7c927df..000000000
--- a/Assets/_Project/Scripts/Authoring/HomeBase/SharedStorageContainerAuthoring.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-using ProjectM.Simulation;
-using Unity.Entities;
-using UnityEngine;
-
-namespace ProjectM.Authoring
-{
- ///
- /// Authoring for the shared storage-container ghost prefab: an ownerless INTERPOLATED ghost whose
- /// replicated buffer is the shared inventory any player deposits into /
- /// withdraws from (server-authoritative, applied by StorageOpReceiveSystem). Add a
- /// GhostAuthoringComponent (Interpolated) to the prefab so clients see its contents replicate.
- /// GetEntity(TransformUsageFlags.Dynamic) gives it a runtime world transform, set at spawn to
- /// the base cell center.
- ///
- public class SharedStorageContainerAuthoring : MonoBehaviour
- {
- [Min(0f)]
- [Tooltip("Interaction radius (world units) for the deposit/withdraw test; reserved for proximity gating.")]
- public float InteractRadius = 2f;
-
- private class SharedStorageContainerBaker : Baker
- {
- public override void Bake(SharedStorageContainerAuthoring authoring)
- {
- var entity = GetEntity(authoring, TransformUsageFlags.Dynamic);
- AddComponent(entity);
- AddComponent(entity, new HitRadius { Value = authoring.InteractRadius });
- AddBuffer(entity);
- }
- }
- }
-}
diff --git a/Assets/_Project/Scripts/Authoring/HomeBase/SharedStorageContainerAuthoring.cs.meta b/Assets/_Project/Scripts/Authoring/HomeBase/SharedStorageContainerAuthoring.cs.meta
deleted file mode 100644
index f4df69d92..000000000
--- a/Assets/_Project/Scripts/Authoring/HomeBase/SharedStorageContainerAuthoring.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: 8cc6285a9b8958a47a61a07550b7f792
\ No newline at end of file
diff --git a/Assets/_Project/Scripts/Authoring/HomeBase/StorageSpawnerAuthoring.cs b/Assets/_Project/Scripts/Authoring/HomeBase/StorageSpawnerAuthoring.cs
deleted file mode 100644
index d1b06b32e..000000000
--- a/Assets/_Project/Scripts/Authoring/HomeBase/StorageSpawnerAuthoring.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-using ProjectM.Simulation;
-using Unity.Entities;
-using Unity.Mathematics;
-using UnityEngine;
-
-namespace ProjectM.Authoring
-{
- ///
- /// Authoring for the baked singleton (mirrors UpgradePickupSpawnerAuthoring).
- /// Place once in the gameplay subscene; the server-only SharedStorageSpawnSystem reads it, instantiates
- /// the storage-container ghost at the base-grid cell center (BaseGridMath.CellToWorld), then destroys
- /// the singleton so it fires exactly once. The entity carries no transform; only the prefab needs one.
- ///
- public class StorageSpawnerAuthoring : MonoBehaviour
- {
- [Tooltip("Storage-container ghost prefab to instantiate. Must carry SharedStorageContainerAuthoring + a GhostAuthoringComponent.")]
- public GameObject ContainerPrefab;
-
- [Tooltip("Build-grid cell at which to place the container (cell center, on the base plane).")]
- public Vector2Int Cell = new Vector2Int(16, 22);
-
- private class StorageSpawnerBaker : Baker
- {
- public override void Bake(StorageSpawnerAuthoring authoring)
- {
- var entity = GetEntity(authoring, TransformUsageFlags.None);
-
- AddComponent(entity, new StorageSpawner
- {
- Prefab = authoring.ContainerPrefab != null
- ? GetEntity(authoring.ContainerPrefab, TransformUsageFlags.Dynamic)
- : Entity.Null,
- Cell = new int2(authoring.Cell.x, authoring.Cell.y),
- });
- }
- }
- }
-}
diff --git a/Assets/_Project/Scripts/Authoring/HomeBase/StorageSpawnerAuthoring.cs.meta b/Assets/_Project/Scripts/Authoring/HomeBase/StorageSpawnerAuthoring.cs.meta
deleted file mode 100644
index a66f9937e..000000000
--- a/Assets/_Project/Scripts/Authoring/HomeBase/StorageSpawnerAuthoring.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: 968b8c85b6f69ae438e56cb1f19a2450
\ No newline at end of file
diff --git a/Assets/_Project/Scripts/Authoring/Items/ItemDatabaseAuthoring.cs b/Assets/_Project/Scripts/Authoring/Items/ItemDatabaseAuthoring.cs
deleted file mode 100644
index 42234f80d..000000000
--- a/Assets/_Project/Scripts/Authoring/Items/ItemDatabaseAuthoring.cs
+++ /dev/null
@@ -1,69 +0,0 @@
-using System.Collections.Generic;
-using ProjectM.Simulation;
-using Unity.Collections;
-using Unity.Entities;
-using UnityEngine;
-
-namespace ProjectM.Authoring
-{
- ///
- /// Bakes the designer-authored item definitions into a single ItemDatabase blob singleton (immutable,
- /// shared, Burst-fast), mirroring AbilityDatabaseAuthoring. Place ONE in the gameplay subscene; it streams
- /// identically into the client and server worlds (config, not replicated). DependsOn each definition so a
- /// value change re-bakes the blob. Runtime lookup is ID-keyed (ItemDatabaseBlob.TryGetItem), so the list
- /// order here does not matter and inserting an item never renumbers existing ids.
- ///
- public class ItemDatabaseAuthoring : MonoBehaviour
- {
- [Tooltip("All item definitions in the game (resources + tools/gear). Looked up at runtime by ItemId.")]
- public List Items = new List();
-
- private class DatabaseBaker : Baker
- {
- public override void Bake(ItemDatabaseAuthoring authoring)
- {
- var entity = GetEntity(TransformUsageFlags.None);
-
- int count = authoring.Items != null ? authoring.Items.Count : 0;
-
- var builder = new BlobBuilder(Allocator.Temp);
- ref var root = ref builder.ConstructRoot();
- var arr = builder.Allocate(ref root.Items, count);
- for (int i = 0; i < count; i++)
- {
- var def = authoring.Items[i];
- if (def == null) { arr[i] = default; continue; }
- DependsOn(def);
- arr[i] = new ItemDefBlob
- {
- ItemId = (ushort)def.ItemId,
- Category = def.Category,
- Tier = def.Tier,
- StackMax = def.StackMax,
- EquipSlot = def.EquipSlot,
- Mod0 = ModAt(def, 0),
- Mod1 = ModAt(def, 1),
- Mod2 = ModAt(def, 2),
- Mod3 = ModAt(def, 3),
- Name = def.DisplayName,
- };
- }
-
- var blob = builder.CreateBlobAssetReference(Allocator.Persistent);
- builder.Dispose();
- AddBlobAsset(ref blob, out _);
- AddComponent(entity, new ItemDatabase { Value = blob });
-
- static ItemModSpec ModAt(ItemDefinition def, int i)
- {
- if (def.Mods != null && i < def.Mods.Count)
- {
- var m = def.Mods[i];
- return new ItemModSpec { Target = m.Target, Op = m.Op, Value = m.Value };
- }
- return new ItemModSpec { Target = 255 };
- }
- }
- }
- }
-}
diff --git a/Assets/_Project/Scripts/Authoring/Items/ItemDatabaseAuthoring.cs.meta b/Assets/_Project/Scripts/Authoring/Items/ItemDatabaseAuthoring.cs.meta
deleted file mode 100644
index 7dcd14c91..000000000
--- a/Assets/_Project/Scripts/Authoring/Items/ItemDatabaseAuthoring.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: 5ee44dc3bc9f3164592195d4068be8d1
\ No newline at end of file
diff --git a/Assets/_Project/Scripts/Authoring/Items/ItemDefinition.cs b/Assets/_Project/Scripts/Authoring/Items/ItemDefinition.cs
deleted file mode 100644
index 358e3a794..000000000
--- a/Assets/_Project/Scripts/Authoring/Items/ItemDefinition.cs
+++ /dev/null
@@ -1,50 +0,0 @@
-using System.Collections.Generic;
-using ProjectM.Simulation;
-using UnityEngine;
-
-namespace ProjectM.Authoring
-{
- ///
- /// Designer-facing definition of one item (resource, tool, weapon, gear, consumable). Numeric/byte fields
- /// are baked into the ItemDatabase blob (immutable, Burst-fast runtime config). Category and Tier are
- /// BYTES, not enums, to dodge BOTH the MCP enum-drop hazard (manage_* silently drop enum fields when set
- /// via tooling) AND the cross-assembly enum-in-Burst hazard. UI icon/description are deferred to a later
- /// managed lookup keyed by id, exactly like AbilityDefinition.
- ///
- [CreateAssetMenu(menuName = "Project M/Item Definition", fileName = "Item_")]
- public class ItemDefinition : ScriptableObject
- {
- [Tooltip("Stable item id (ushort range). Keep 1=Aether, 2=Ore, 3=Biomass; reserve >3 for new items; 0 = none.")]
- public int ItemId = 4;
-
- public string DisplayName = "Item";
-
- [Tooltip("ItemCategory byte: 0=Resource, 1=Tool, 2=Weapon, 3=Gear, 4=Consumable.")]
- public byte Category = ItemCategory.Resource;
-
- [Tooltip("Progression tier (0 = base). Higher-tier tools harvest higher-tier nodes / hit harder.")]
- public byte Tier = 0;
-
- [Min(1)]
- [Tooltip("Max units that stack in one inventory slot (1 for non-stacking equipment).")]
- public int StackMax = 999;
-
- [Header("Equipment (Phase 1)")]
- [Tooltip("EquipSlotId byte: 0=Weapon, 1=Armor, 2=Trinket, 3=Tool, 255=not equippable.")]
- public byte EquipSlot = 255;
-
- [Tooltip("Stat modifiers granted while equipped (first 4 used).")]
- public List Mods = new List();
- }
-
- /// Designer-facing stat-mod grant on an equippable item; the baker writes the first 4 into ItemDefBlob's inline mod slots.
- [System.Serializable]
- public struct ItemModAuthoring
- {
- [Tooltip("StatTarget byte: 0=Damage,1=CooldownTicks,2=Range,3=ProjectileSpeed,4=AutoTargetRange,5=AutoTargetCone,6=MoveSpeed,7=TurnRate,8=MaxHealth.")]
- public byte Target;
- [Tooltip("ModOp byte: 0=Flat, 1=PercentAdd, 2=PercentMult.")]
- public byte Op;
- public float Value;
- }
-}
diff --git a/Assets/_Project/Scripts/Authoring/Items/ItemDefinition.cs.meta b/Assets/_Project/Scripts/Authoring/Items/ItemDefinition.cs.meta
deleted file mode 100644
index 0a7a91fba..000000000
--- a/Assets/_Project/Scripts/Authoring/Items/ItemDefinition.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: 84295e2f852afac4fa4b7384857281d9
\ No newline at end of file
diff --git a/Assets/_Project/Scripts/Authoring/Meta/MetaCatalogAuthoring.cs b/Assets/_Project/Scripts/Authoring/Meta/MetaCatalogAuthoring.cs
deleted file mode 100644
index 5b6ba8c8a..000000000
--- a/Assets/_Project/Scripts/Authoring/Meta/MetaCatalogAuthoring.cs
+++ /dev/null
@@ -1,85 +0,0 @@
-using System;
-using System.Collections.Generic;
-using ProjectM.Simulation;
-using Unity.Collections;
-using Unity.Entities;
-using UnityEngine;
-
-namespace ProjectM.Authoring
-{
- ///
- /// Authoring for the PERMANENT meta-upgrade catalog singleton (place ONE in the gameplay subscene — the
- /// BoonCatalogAuthoring pattern). An EMPTY row list bakes the code-default v1 table
- /// () verbatim, so the subscene object needs zero property assignment
- /// through the tooling. Ids are APPEND-ONLY (persisted in SaveData v6; a removed id's saved rows are preserved
- /// and skipped, never crashed on).
- ///
- public class MetaCatalogAuthoring : MonoBehaviour
- {
- [Serializable]
- public struct MetaRow
- {
- public byte Id;
- [Tooltip("bit0 = Warrior, bit1 = Ranger, 3 = both.")]
- public byte ClassMask;
- public StatTarget Target;
- public ModOp Op;
- public byte MaxTier;
- public float ValuePerTier;
- public int BaseCost;
- public int CostGrowth;
- [Tooltip("0xFF (255) = no prerequisite.")]
- public byte PrereqId;
- public byte PrereqTier;
- public string Name;
- public string Desc;
- }
-
- [Tooltip("Leave EMPTY to bake the code-default v1 table; fill to fully replace it.")]
- public List Rows = new List();
-
- private class MetaCatalogBaker : Baker
- {
- public override void Bake(MetaCatalogAuthoring authoring)
- {
- var entity = GetEntity(authoring, TransformUsageFlags.None);
-
- BlobAssetReference blob;
- if (authoring.Rows == null || authoring.Rows.Count == 0)
- {
- blob = MetaCatalogData.BuildDefault();
- }
- else
- {
- var builder = new BlobBuilder(Allocator.Temp);
- ref var root = ref builder.ConstructRoot();
- var defs = builder.Allocate(ref root.Defs, authoring.Rows.Count);
- for (int i = 0; i < authoring.Rows.Count; i++)
- {
- var r = authoring.Rows[i];
- defs[i] = new MetaUpgradeDefBlob
- {
- Id = r.Id,
- ClassMask = r.ClassMask,
- Target = (byte)r.Target,
- Op = (byte)r.Op,
- MaxTier = r.MaxTier,
- ValuePerTier = r.ValuePerTier,
- BaseCost = r.BaseCost,
- CostGrowth = r.CostGrowth,
- PrereqId = r.PrereqId,
- PrereqTier = r.PrereqTier,
- Name = new FixedString64Bytes(r.Name ?? string.Empty),
- Desc = new FixedString128Bytes(r.Desc ?? string.Empty),
- };
- }
- blob = builder.CreateBlobAssetReference(Allocator.Persistent);
- builder.Dispose();
- }
-
- AddBlobAsset(ref blob, out _);
- AddComponent(entity, new MetaUpgradeCatalog { Value = blob });
- }
- }
- }
-}
diff --git a/Assets/_Project/Scripts/Authoring/Meta/MetaCatalogAuthoring.cs.meta b/Assets/_Project/Scripts/Authoring/Meta/MetaCatalogAuthoring.cs.meta
deleted file mode 100644
index 38bc113ff..000000000
--- a/Assets/_Project/Scripts/Authoring/Meta/MetaCatalogAuthoring.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: 4f007f7870c0afd4e93ea5b86fd21c8b
\ No newline at end of file
diff --git a/Assets/_Project/Scripts/Authoring/Player/PlayerAuthoring.cs b/Assets/_Project/Scripts/Authoring/Player/PlayerAuthoring.cs
index e5dbe302e..582b81027 100644
--- a/Assets/_Project/Scripts/Authoring/Player/PlayerAuthoring.cs
+++ b/Assets/_Project/Scripts/Authoring/Player/PlayerAuthoring.cs
@@ -60,12 +60,9 @@ namespace ProjectM.Authoring
// Empty replicated modifier stack (grown by upgrades/pickups/debug hook, server-authoritative).
AddBuffer(entity);
- // Empty replicated personal inventory (server-authoritative; harvest yield + deposit RPC land here).
- AddBuffer(entity);
- // Equipment loadout: one replicated row per slot in FIXED order (buffer index = EquipSlotId), empty.
- var equip = AddBuffer(entity);
- for (int s = 0; s < EquipSlotId.Count; s++)
- equip.Add(new EquipmentSlot { ItemId = 0 });
+ // 2026-08-07 audit purge: the replicated personal InventorySlot bag and the EquipmentSlot loadout
+ // went with the shell (CLAUDE.md already recorded inventory/equipment as PAUSED). Harvest now
+ // credits the shared ledger directly.
// Server-only expiry tracker for timed buffs (paired with a StatModifier by SourceId; not replicated).
AddBuffer(entity);
@@ -94,14 +91,9 @@ namespace ProjectM.Authoring
AddComponent(entity, new RespawnState { RespawnTick = 0, DelayTicks = authoring.RespawnDelayTicks, InvulnTicks = authoring.RespawnInvulnTicks });
AddComponent(entity, new RespawnInvuln { UntilTick = 0 });
- // Expedition redesign (the ONE player-ghost re-bake, front-loaded): the send-to-all ready-check
- // flag + the owner-only choice-of-3 boon offer (inert until Step 9's BoonOfferSystem lights it up).
- AddComponent(entity);
- AddComponent(entity);
- // Phase 1.7 boon overhaul: the mechanic-changer state (replicated SendToOwner, baked inert, zeroed on
- // the Returning edge) + the server-only Blade-Dash per-dash dedup accumulator (non-replicated).
- AddComponent(entity);
- AddComponent(entity);
+ // 2026-08-07 audit purge: PlayerReady (ready-check), BoonOffer/BoonEffects (Phase-1.7 boons) and
+ // DashTrailState (Blade Dash) were baked onto every player ghost for the superseded base/expedition
+ // loop. All four are gone; the ghost archetype shrinks accordingly.
// LANTERN Phase 1 (Step 1): 4-socket kit data model — THE ability model (the legacy single
// AbilityRef/AbilityCooldown path is deleted). AbilitySocket = cold per-socket loadout
// (EquipmentSlot-modelled, 4 empty rows; GoInGameServerSystem seeds the frame loadout at spawn);
diff --git a/Assets/_Project/Scripts/Authoring/World/CycleDirectorAuthoring.cs b/Assets/_Project/Scripts/Authoring/World/CycleDirectorAuthoring.cs
index afce26453..65017e32e 100644
--- a/Assets/_Project/Scripts/Authoring/World/CycleDirectorAuthoring.cs
+++ b/Assets/_Project/Scripts/Authoring/World/CycleDirectorAuthoring.cs
@@ -23,15 +23,10 @@ namespace ProjectM.Authoring
AddComponent(entity);
AddBuffer(entity);
- // DR-042 C7b: replicated expedition-objective summary (the HUD 'enemies remaining / cleared' readout).
- // Born Idle; RoomEnemyDirectorSystem is the sole writer.
- AddComponent(entity, new ExpeditionObjective { State = ExpeditionObjectiveState.Idle, Remaining = 0 });
-
- // Expedition redesign: the replicated run-lifecycle FSM (RunInfo) + the per-class permanent-meta
- // tier buffer (MetaTierState). Born Staging/empty; server RunDirectorSystem / MetaSpendSystem are
- // the sole writers.
- AddComponent(entity, new RunInfo { Lifecycle = RunLifecycle.Staging });
- AddBuffer(entity);
+ // 2026-08-07 audit purge: ExpeditionObjective (the 'enemies remaining / cleared' readout),
+ // RunInfo (the run-lifecycle FSM) and MetaTierState (per-frame permanent upgrades) were all baked
+ // here for the superseded base/expedition loop. Their sole writers are deleted; the director now
+ // carries only the global resource ledger.
}
}
}
diff --git a/Assets/_Project/Scripts/Client/Building/BuildPaletteState.cs b/Assets/_Project/Scripts/Client/Building/BuildPaletteState.cs
deleted file mode 100644
index 951b3617d..000000000
--- a/Assets/_Project/Scripts/Client/Building/BuildPaletteState.cs
+++ /dev/null
@@ -1,41 +0,0 @@
-namespace ProjectM.Client
-{
- ///
- /// Client-local build-mode state shared between the HUD build palette (sets the selected buildable), the
- /// build placement input (ground ghost preview + click-to-place + conveyor rotation), and the input gather
- /// (suppresses Fire while a build is selected, so a left-click places instead of firing). Pure UI state —
- /// never replicated, never touches the sim. Reset on play-enter (statics survive fast-enter domain reloads).
- ///
- public static class BuildPaletteState
- {
- /// Selected structure type (StructureType.*); 0 = none / no slot selected.
- public static byte Selected;
-
- /// Pending conveyor facing (0=+X,1=-X,2=+Z,3=-Z); rotated by [ / ] or R.
- public static byte Direction;
-
- /// True while the build PALETTE panel is open (toggled by Tab / gamepad Y). Slice 1 HUD declutter:
- /// the palette is hidden by default; this gates its visibility, orthogonal to .
- public static bool PaletteOpen;
-
- /// True while a buildable SLOT is selected (placement is armed). The palette must also be open.
- public static bool Active => Selected != 0;
-
- /// Toggle the palette panel open/closed; closing also cancels any active slot selection.
- public static void TogglePalette()
- {
- PaletteOpen = !PaletteOpen;
- if (!PaletteOpen) { Selected = 0; Direction = 0; }
- }
-
- /// Select a type (or 0 to deselect), resetting the pending conveyor facing; auto-opens the palette
- /// so a slot click never leaves the panel hidden.
- public static void Select(byte type) { Selected = type; Direction = 0; if (type != 0) PaletteOpen = true; }
-
- /// Cancel the current selection and close the palette.
- public static void Clear() { Selected = 0; Direction = 0; PaletteOpen = false; }
-
- [UnityEngine.RuntimeInitializeOnLoadMethod(UnityEngine.RuntimeInitializeLoadType.SubsystemRegistration)]
- static void ResetStatics() { Selected = 0; Direction = 0; PaletteOpen = false; }
- }
-}
diff --git a/Assets/_Project/Scripts/Client/Building/BuildPaletteState.cs.meta b/Assets/_Project/Scripts/Client/Building/BuildPaletteState.cs.meta
deleted file mode 100644
index 99674e144..000000000
--- a/Assets/_Project/Scripts/Client/Building/BuildPaletteState.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: b0f12fac7a937bf418aaf47eba57cc74
\ No newline at end of file
diff --git a/Assets/_Project/Scripts/Client/Building/BuildSendSystem.cs b/Assets/_Project/Scripts/Client/Building/BuildSendSystem.cs
deleted file mode 100644
index f787e88a4..000000000
--- a/Assets/_Project/Scripts/Client/Building/BuildSendSystem.cs
+++ /dev/null
@@ -1,267 +0,0 @@
-using ProjectM.Simulation;
-using Unity.Entities;
-using Unity.Mathematics;
-using Unity.NetCode;
-using Unity.Transforms;
-using UnityEngine;
-
-namespace ProjectM.Client
-{
- ///
- /// Client-only build input + RPC sender. Two ways to build:
- /// (1) the HUD build PALETTE (primary): a selected buildable () drives a ground
- /// GHOST preview at the cursor cell — green when valid (in-plot, unoccupied, affordable; the same legality the
- /// server re-validates), red otherwise — and a LEFT-CLICK places it; right-click / Esc cancels; [ / ] or R
- /// rotates a conveyor's facing. Fire is suppressed while build mode is active (PlayerInputGatherSystem reads
- /// ), so the place-click never also fires. Build mode is suspended while
- /// the pause overlay is open, and the frame a palette button changes the selection never also places.
- /// (2) keyboard hotkeys (fallback, suppressed in palette mode): B/V/F place at the local player's cell.
- /// Editor-only statics (PlaceStructure / PlaceTurret / ...) drive the same RPC path from execute_code for
- /// headless validation. Managed SystemBase; UnityEngine.InputSystem types are fully qualified to avoid the
- /// ProjectM.Simulation.PlayerInput name collision. The server re-validates legality + cost authoritatively.
- ///
- [WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]
- public partial class BuildSendSystem : SystemBase
- {
-
- // Dev hotkey fallback: key -> buildable.
- static readonly (UnityEngine.InputSystem.Key Key, byte Type)[] s_BuildHotkeys =
- {
- (UnityEngine.InputSystem.Key.V, StructureType.Wall),
- // LANTERN purge: the automation buildables (Fabricator/Harvester/Conveyor) are deleted; Pylon stays
- // hidden from the hotkey fallback to match the build palette. PlaceStructure execute_code statics remain.
- };
-
- UnityEngine.Camera _camera; // cursor -> ground re-raycast for click-to-place (resolved lazily)
- GameObject _ghost; // translucent ground preview cube
- Material _ghostMat;
- MeshFilter _ghostMf; // ghost mesh swaps to the selected structure's preview mesh (HudTheme)
- MeshRenderer _ghostMr;
- Mesh _cubeMesh; // procedural fallback (the original preview cube)
- byte _ghostType = 255; // last-applied preview type (255 = unset)
- byte _lastSelected; // skip placing on the frame a palette click changes the selection
-
- // (Step 11: UpgradeAbility/s_PendingUpgrades RETIRED with the AbilityUpgradeRequest wire — in-run boons +
- // the base meta-shop replaced the Aether damage upgrade.)
-
-#if UNITY_EDITOR
- struct PendingBuild { public byte Type; public int CellX; public int CellZ; public byte Direction; }
- static readonly System.Collections.Generic.Queue s_PendingBuild =
- new System.Collections.Generic.Queue();
-
- /// EDITOR / execute_code hook: queue a structure placement at a specific cell.
- public static void PlaceStructure(byte type, int cellX, int cellZ, byte direction = 0) =>
- s_PendingBuild.Enqueue(new PendingBuild { Type = type, CellX = cellX, CellZ = cellZ, Direction = direction });
-
-
- /// EDITOR / execute_code hook: queue a wall placement at a specific cell.
- public static void PlaceWall(int cellX, int cellZ) => PlaceStructure(StructureType.Wall, cellX, cellZ);
-
-#endif
-
- protected override void OnCreate()
- {
- RequireForUpdate();
- }
-
- protected override void OnDestroy()
- {
- if (_ghost != null) Object.Destroy(_ghost);
- if (_ghostMat != null) Object.Destroy(_ghostMat);
- }
-
- protected override void OnUpdate()
- {
- if (!SystemAPI.TryGetSingletonEntity(out var connection))
- return;
-
- HandleBuildMode(connection);
-
- // --- Build-palette toggle (Tab / gamepad Y): Slice 1 HUD declutter — the palette is hidden by default ---
- var keyboard = UnityEngine.InputSystem.Keyboard.current;
- var gamepad = UnityEngine.InputSystem.Gamepad.current;
- bool togglePressed =
- (keyboard != null && keyboard.tabKey.wasPressedThisFrame) ||
- (gamepad != null && gamepad.buttonNorth.wasPressedThisFrame);
- if (togglePressed && !PauseMenuController.Open)
- BuildPaletteState.TogglePalette();
-
- // Hotkey fallback (suppressed while the palette build mode is active).
- if (keyboard != null && !BuildPaletteState.Active)
- {
- foreach (var (key, type) in s_BuildHotkeys)
- if (keyboard[key].wasPressedThisFrame && TryGetLocalPlayerCell(out int2 cell))
- SendBuild(connection, type, cell.x, cell.y, (byte)0);
- }
-
-#if UNITY_EDITOR
- while (s_PendingBuild.Count > 0)
- {
- var b = s_PendingBuild.Dequeue();
- SendBuild(connection, b.Type, b.CellX, b.CellZ, b.Direction);
- }
-#endif
- }
-
- // ---- Palette-driven build mode: ground ghost preview + click-to-place ----
- void HandleBuildMode(Entity connection)
- {
- byte sel = BuildPaletteState.Selected;
- bool justSelected = sel != _lastSelected; // the selecting click must not also place
- _lastSelected = sel;
-
- bool active = BuildPaletteState.Active && !PauseMenuController.Open;
- AimPresentation.ForceCursorVisible = active;
- if (!active) { HideGhost(); return; }
-
- var keyboard = UnityEngine.InputSystem.Keyboard.current;
- var mouse = UnityEngine.InputSystem.Mouse.current;
-
- // Cancel build mode (right-click / Esc).
- if ((mouse != null && mouse.rightButton.wasPressedThisFrame) ||
- (keyboard != null && keyboard.escapeKey.wasPressedThisFrame))
- {
- BuildPaletteState.Clear();
- HideGhost();
- return;
- }
-
- // Rotate a conveyor's facing ([ / ] or R).
- if (keyboard != null)
- {
- if (keyboard.leftBracketKey.wasPressedThisFrame)
- BuildPaletteState.Direction = (byte)((BuildPaletteState.Direction + 3) % 4);
- if (keyboard.rightBracketKey.wasPressedThisFrame || keyboard.rKey.wasPressedThisFrame)
- BuildPaletteState.Direction = (byte)((BuildPaletteState.Direction + 1) % 4);
- }
-
- if (!SystemAPI.TryGetSingleton(out var anchor)) { HideGhost(); return; }
- if (_camera == null) _camera = CameraResolver.Resolve();
- if (_camera == null || mouse == null) { HideGhost(); return; }
-
- // Cursor -> ground -> cell.
- UnityEngine.Vector2 sp = mouse.position.ReadValue();
- UnityEngine.Ray ray = _camera.ScreenPointToRay(new UnityEngine.Vector3(sp.x, sp.y, 0f));
- if (!AimMath.TryGroundHit((float3)ray.origin, (float3)ray.direction, anchor.GridOrigin.y, out var groundPoint))
- { HideGhost(); return; }
- int2 targetCell = BaseGridMath.WorldToCell(anchor, groundPoint);
-
- // Validity — client mirror of the server check (in-plot, unoccupied, affordable).
- bool occupied = false;
- foreach (var (ps, xf) in SystemAPI.Query, RefRO>())
- if (math.all(BaseGridMath.WorldToCell(anchor, xf.ValueRO.Position) == targetCell)) { occupied = true; break; }
-
- int cost = CatalogCost(sel);
- byte reason = BuildPreviewMath.Evaluate(anchor, targetCell, occupied, LedgerOre(), cost);
- bool valid = reason == BuildPreviewMath.Valid;
-
- ShowGhost(BaseGridMath.CellToWorld(anchor, targetCell), anchor.CellSize, valid, sel);
-
- // Place on a left-click (valid, not the selecting click).
- if (valid && !justSelected && mouse.leftButton.wasPressedThisFrame)
- SendBuild(connection, sel, targetCell.x, targetCell.y, BuildPaletteState.Direction);
- }
-
- int LedgerOre()
- {
- if (!SystemAPI.TryGetSingletonEntity(out var le)) return 0;
- var buf = SystemAPI.GetBuffer(le);
- for (int i = 0; i < buf.Length; i++) if (buf[i].ItemId == ResourceId.Ore) return buf[i].Count;
- return 0;
- }
-
- int CatalogCost(byte type)
- {
- if (type == 0 || !SystemAPI.TryGetSingletonEntity(out var ce)) return int.MaxValue;
- var cat = SystemAPI.GetBuffer(ce);
- for (int i = 0; i < cat.Length; i++) if (cat[i].Type == type) return cat[i].CostAmount;
- return int.MaxValue;
- }
-
- // ---- Ground ghost preview: the selected structure's REAL mesh (HudTheme, build-safe serialized refs)
- // tinted translucent green/red; falls back to the original procedural cube when no mesh is authored. ----
- void ShowGhost(float3 center, float cellSize, bool valid, byte type)
- {
- EnsureGhost();
- ApplyGhostMesh(type);
- if (_ghostMf.sharedMesh == _cubeMesh)
- {
- _ghost.transform.position = (Vector3)center + Vector3.up * 0.5f;
- _ghost.transform.localScale = new Vector3(cellSize * 0.9f, 1f, cellSize * 0.9f);
- }
- else
- {
- // preview meshes are authored real-size with a ground pivot (SM_Turret_01 / SM_Wall_01 / SM_Fabricator_01)
- _ghost.transform.position = (Vector3)center;
- _ghost.transform.localScale = Vector3.one;
- }
- _ghostMat.color = valid ? new Color(0.3f, 1f, 0.45f, 0.35f) : new Color(1f, 0.32f, 0.26f, 0.35f);
- if (!_ghost.activeSelf) _ghost.SetActive(true);
- }
-
- // Swap the ghost's mesh when the palette selection changes (255 = unset sentinel; no selection is 0 -> cube).
- void ApplyGhostMesh(byte type)
- {
- if (type == _ghostType) return;
- _ghostType = type;
- var theme = HudTheme.Get();
- Mesh mesh = theme != null ? theme.StructureGhostMesh(type) : null;
- if (mesh == null) mesh = _cubeMesh;
- _ghostMf.sharedMesh = mesh;
- if (_ghostMr.sharedMaterials.Length != mesh.subMeshCount)
- {
- var mats = new Material[mesh.subMeshCount]; // one translucent mat per submesh so the whole preview tints
- for (int i = 0; i < mats.Length; i++) mats[i] = _ghostMat;
- _ghostMr.sharedMaterials = mats;
- }
- }
-
- void HideGhost()
- {
- if (_ghost != null && _ghost.activeSelf) _ghost.SetActive(false);
- }
-
- void EnsureGhost()
- {
- if (_ghost != null) return;
- var shader = Shader.Find("Sprites/Default");
- if (shader == null) shader = Shader.Find("Universal Render Pipeline/Unlit");
- _ghostMat = new Material(shader) { color = new Color(0.3f, 1f, 0.45f, 0.35f) };
- _ghost = GameObject.CreatePrimitive(PrimitiveType.Cube);
- _ghost.name = "~BuildGhost";
- var col = _ghost.GetComponent();
- if (col != null) Object.Destroy(col);
- _ghostMf = _ghost.GetComponent();
- _cubeMesh = _ghostMf.sharedMesh;
- _ghostType = 255;
- _ghostMr = _ghost.GetComponent();
- _ghostMr.sharedMaterial = _ghostMat;
- _ghostMr.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
- _ghostMr.receiveShadows = false;
- _ghost.SetActive(false);
- }
-
-
-
- bool TryGetLocalPlayerCell(out int2 cell)
- {
- cell = default;
- if (!SystemAPI.TryGetSingleton(out var anchor))
- return false;
- foreach (var xform in SystemAPI.Query>().WithAll())
- {
- cell = BaseGridMath.WorldToCell(anchor, xform.ValueRO.Position);
- return true;
- }
- return false;
- }
-
- void SendBuild(Entity connection, byte type, int cellX, int cellZ, byte direction)
- {
- var e = EntityManager.CreateEntity();
- EntityManager.AddComponentData(e, new BuildPlaceRequest { StructureType = type, CellX = cellX, CellZ = cellZ, Direction = direction });
- EntityManager.AddComponentData(e, new SendRpcCommandRequest { TargetConnection = connection });
- }
- // (Step 11: the Aether ability-upgrade sender was RETIRED with AbilityUpgradeRequest — boons replaced it.)
- }
-}
diff --git a/Assets/_Project/Scripts/Client/Building/BuildSendSystem.cs.meta b/Assets/_Project/Scripts/Client/Building/BuildSendSystem.cs.meta
deleted file mode 100644
index 2f4de9bf2..000000000
--- a/Assets/_Project/Scripts/Client/Building/BuildSendSystem.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: 765356fd6c5e64c4e9ab588f99d3388f
\ No newline at end of file
diff --git a/Assets/_Project/Scripts/Client/Combat/BoonSendSystem.cs b/Assets/_Project/Scripts/Client/Combat/BoonSendSystem.cs
deleted file mode 100644
index f92747cdd..000000000
--- a/Assets/_Project/Scripts/Client/Combat/BoonSendSystem.cs
+++ /dev/null
@@ -1,49 +0,0 @@
-using ProjectM.Simulation;
-using Unity.Entities;
-using Unity.NetCode;
-using UnityEngine;
-
-namespace ProjectM.Client
-{
- ///
- /// Client-side boon-pick sender: a static enqueue (the Step-14 3-card modal / execute_code) drained into
- /// RPCs. Carries only the option INDEX — the server resolves it against the
- /// sender's own authoritative BoonOffer and validates lifecycle/pending, so a stale or forged pick is
- /// simply dropped. Statics reset on play-enter (the stale-bridge hazard).
- ///
- [WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]
- public partial class BoonSendSystem : SystemBase
- {
- static int s_Pending;
- static byte s_PendingIndex;
-
- [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
- static void ResetStatics()
- {
- s_Pending = 0;
- s_PendingIndex = 0;
- }
-
- /// Queue a boon pick (0/1/2). The HUD card click + execute_code drive this.
- public static void PickBoon(byte optionIndex)
- {
- s_PendingIndex = optionIndex;
- s_Pending++;
- }
-
- protected override void OnCreate()
- {
- RequireForUpdate();
- }
-
- protected override void OnUpdate()
- {
- while (s_Pending > 0)
- {
- s_Pending--;
- var req = EntityManager.CreateEntity(typeof(BoonPickRequest), typeof(SendRpcCommandRequest));
- EntityManager.SetComponentData(req, new BoonPickRequest { Index = s_PendingIndex });
- }
- }
- }
-}
diff --git a/Assets/_Project/Scripts/Client/Combat/BoonSendSystem.cs.meta b/Assets/_Project/Scripts/Client/Combat/BoonSendSystem.cs.meta
deleted file mode 100644
index ad7427a4f..000000000
--- a/Assets/_Project/Scripts/Client/Combat/BoonSendSystem.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: d7e90bc7230614845817b81f0f7b70f0
\ No newline at end of file
diff --git a/Assets/_Project/Scripts/Client/Economy/EquipSendSystem.cs b/Assets/_Project/Scripts/Client/Economy/EquipSendSystem.cs
deleted file mode 100644
index 9866d6380..000000000
--- a/Assets/_Project/Scripts/Client/Economy/EquipSendSystem.cs
+++ /dev/null
@@ -1,116 +0,0 @@
-using ProjectM.Simulation;
-using Unity.Entities;
-using Unity.NetCode;
-
-namespace ProjectM.Client
-{
- ///
- /// Client-only sender for / RPCs. One-off actions, so
- /// RPCs (not per-tick input); the server applies them authoritatively in
- /// . Number keys 1-9 equip the Nth EQUIPPABLE item in the local bag
- /// (resources are skipped via the catalog); U unequips the weapon. Managed SystemBase because it reads the
- /// managed Input System; Input System types are fully qualified and using UnityEngine.InputSystem; is
- /// omitted (it defines a colliding PlayerInput type). An #if UNITY_EDITOR static hook drives the same
- /// path from execute_code for headless validation. The HUD click-to-equip (HudSystem) is the primary UX;
- /// these keys are the reachable fallback. Wire types are unconditional; only this send SYSTEM is gated.
- ///
- [WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]
- public partial class EquipSendSystem : SystemBase
- {
- struct PendingEquip { public bool Unequip; public ushort ItemId; public byte Slot; }
-
- static readonly System.Collections.Generic.Queue s_Pending =
- new System.Collections.Generic.Queue();
-
- /// EDITOR / execute_code hook: queue an equip of from the bag.
- public static void Equip(ushort itemId) =>
- s_Pending.Enqueue(new PendingEquip { Unequip = false, ItemId = itemId });
-
- /// EDITOR / execute_code hook: queue an unequip of (an EquipSlotId).
- public static void Unequip(byte slot) =>
- s_Pending.Enqueue(new PendingEquip { Unequip = true, Slot = slot });
-
- protected override void OnCreate()
- {
- RequireForUpdate();
- }
-
- protected override void OnUpdate()
- {
- if (!SystemAPI.TryGetSingletonEntity(out var connection))
- return;
-
- var keyboard = UnityEngine.InputSystem.Keyboard.current;
- if (keyboard != null)
- {
- int n = NumberKeyPressed(keyboard);
- if (n >= 0)
- {
- ushort itemId = NthEquippableBagItem(n);
- if (itemId != 0) SendEquip(connection, itemId);
- }
- if (keyboard.uKey.wasPressedThisFrame)
- SendUnequip(connection, EquipSlotId.Weapon);
- }
-
- while (s_Pending.Count > 0)
- {
- var p = s_Pending.Dequeue();
- if (p.Unequip) SendUnequip(connection, p.Slot);
- else SendEquip(connection, p.ItemId);
- }
- }
-
- static int NumberKeyPressed(UnityEngine.InputSystem.Keyboard kb)
- {
- if (kb.digit1Key.wasPressedThisFrame) return 0;
- if (kb.digit2Key.wasPressedThisFrame) return 1;
- if (kb.digit3Key.wasPressedThisFrame) return 2;
- if (kb.digit4Key.wasPressedThisFrame) return 3;
- if (kb.digit5Key.wasPressedThisFrame) return 4;
- if (kb.digit6Key.wasPressedThisFrame) return 5;
- if (kb.digit7Key.wasPressedThisFrame) return 6;
- if (kb.digit8Key.wasPressedThisFrame) return 7;
- if (kb.digit9Key.wasPressedThisFrame) return 8;
- return -1;
- }
-
- /// Resolve the Nth EQUIPPABLE distinct item in the local player's bag (skips resources via the catalog).
- ushort NthEquippableBagItem(int n)
- {
- bool haveDb = SystemAPI.TryGetSingleton(out var db);
- foreach (var bag in SystemAPI.Query>().WithAll())
- {
- int idx = 0;
- for (int i = 0; i < bag.Length; i++)
- {
- ushort id = bag[i].ItemId;
- if (id == 0 || bag[i].Count <= 0) continue;
- if (haveDb && db.Value.IsCreated)
- {
- ref var b = ref db.Value.Value;
- if (!b.TryGetItem(id, out var def) || def.EquipSlot >= EquipSlotId.Count) continue;
- }
- if (idx == n) return id;
- idx++;
- }
- break;
- }
- return 0;
- }
-
- void SendEquip(Entity connection, ushort itemId)
- {
- var req = EntityManager.CreateEntity();
- EntityManager.AddComponentData(req, new EquipRequest { ItemId = itemId });
- EntityManager.AddComponentData(req, new SendRpcCommandRequest { TargetConnection = connection });
- }
-
- void SendUnequip(Entity connection, byte slot)
- {
- var req = EntityManager.CreateEntity();
- EntityManager.AddComponentData(req, new UnequipRequest { Slot = slot });
- EntityManager.AddComponentData(req, new SendRpcCommandRequest { TargetConnection = connection });
- }
- }
-}
diff --git a/Assets/_Project/Scripts/Client/Economy/EquipSendSystem.cs.meta b/Assets/_Project/Scripts/Client/Economy/EquipSendSystem.cs.meta
deleted file mode 100644
index 99070f18c..000000000
--- a/Assets/_Project/Scripts/Client/Economy/EquipSendSystem.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: 7a9bec24b84553746aec834c1b56dfd6
\ No newline at end of file
diff --git a/Assets/_Project/Scripts/Client/Economy/InventoryDepositSendSystem.cs b/Assets/_Project/Scripts/Client/Economy/InventoryDepositSendSystem.cs
deleted file mode 100644
index f777e4c16..000000000
--- a/Assets/_Project/Scripts/Client/Economy/InventoryDepositSendSystem.cs
+++ /dev/null
@@ -1,64 +0,0 @@
-using ProjectM.Simulation;
-using Unity.Entities;
-using Unity.NetCode;
-
-namespace ProjectM.Client
-{
- ///
- /// Client-only sender for RPCs (move the local player's PERSONAL
- /// inventory into the shared base stockpile / global ledger). A one-off action (not per-tick predicted
- /// input), so it is an RPC: on the deposit key edge (G = deposit ALL) it creates the request entity
- /// targeted at the server connection, and the server applies it authoritatively in
- /// . Managed SystemBase because it reads the managed
- /// Input System; Input System types are fully qualified and using UnityEngine.InputSystem; is
- /// intentionally omitted (that namespace defines a PlayerInput type that collides with
- /// ). An editor-only static hook ()
- /// drives the same path from execute_code for headless validation without a focused Game view. The wire
- /// type is unconditional; only this send SYSTEM is build-time managed.
- ///
- [WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]
- public partial class InventoryDepositSendSystem : SystemBase
- {
-#if UNITY_EDITOR
- struct PendingDeposit { public ushort ItemId; public int Count; }
-
- static readonly System.Collections.Generic.Queue s_Pending =
- new System.Collections.Generic.Queue();
-
- /// EDITOR / execute_code hook: queue a deposit (ItemId 0 = deposit all; Count <= 0 = all of that item).
- public static void Deposit(ushort itemId = 0, int count = 0) =>
- s_Pending.Enqueue(new PendingDeposit { ItemId = itemId, Count = count });
-#endif
-
- protected override void OnCreate()
- {
- RequireForUpdate();
- }
-
- protected override void OnUpdate()
- {
- // Need the server connection to target the RPC; bail (keeping any queued ops) until connected.
- if (!SystemAPI.TryGetSingletonEntity(out var connection))
- return;
-
- var keyboard = UnityEngine.InputSystem.Keyboard.current;
- if (keyboard != null && keyboard.gKey.wasPressedThisFrame)
- Send(connection, 0, 0); // G -> deposit everything to the base stockpile
-
-#if UNITY_EDITOR
- while (s_Pending.Count > 0)
- {
- var d = s_Pending.Dequeue();
- Send(connection, d.ItemId, d.Count);
- }
-#endif
- }
-
- void Send(Entity connection, ushort itemId, int count)
- {
- var request = EntityManager.CreateEntity();
- EntityManager.AddComponentData(request, new InventoryDepositRequest { ItemId = itemId, Count = count });
- EntityManager.AddComponentData(request, new SendRpcCommandRequest { TargetConnection = connection });
- }
- }
-}
diff --git a/Assets/_Project/Scripts/Client/Economy/InventoryDepositSendSystem.cs.meta b/Assets/_Project/Scripts/Client/Economy/InventoryDepositSendSystem.cs.meta
deleted file mode 100644
index d63db2d3b..000000000
--- a/Assets/_Project/Scripts/Client/Economy/InventoryDepositSendSystem.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: 9298a5924b4920b4db8ff2f48732a662
\ No newline at end of file
diff --git a/Assets/_Project/Scripts/Client/HomeBase/StorageOpSendSystem.cs b/Assets/_Project/Scripts/Client/HomeBase/StorageOpSendSystem.cs
deleted file mode 100644
index 43cfa3f4d..000000000
--- a/Assets/_Project/Scripts/Client/HomeBase/StorageOpSendSystem.cs
+++ /dev/null
@@ -1,76 +0,0 @@
-using ProjectM.Simulation;
-using Unity.Entities;
-using Unity.NetCode;
-
-namespace ProjectM.Client
-{
- ///
- /// Client-only sender for shared-storage deposit/withdraw RPCs. A
- /// one-off action (not per-tick predicted input), so it is an RPC: on an interact key edge (E =
- /// deposit, Q = withdraw a default test item) it creates the request entity targeted at the server
- /// connection, and the server applies it authoritatively in StorageOpReceiveSystem. Managed
- /// SystemBase because it reads the managed Input System. Input System types are fully qualified and
- /// using UnityEngine.InputSystem; is intentionally omitted (that namespace defines a
- /// PlayerInput type that collides with ). An editor-only
- /// static hook (Deposit/Withdraw) drives the same path from execute_code for headless validation
- /// without a focused Game view.
- ///
- [WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]
- public partial class StorageOpSendSystem : SystemBase
- {
- // Default test item used by the keyboard interact and the parameterless debug hooks.
- const ushort DefaultItemId = 1;
- const int DefaultCount = 1;
-
-#if UNITY_EDITOR
- struct PendingStorageOp { public byte Op; public ushort ItemId; public int Count; }
-
- static readonly System.Collections.Generic.Queue s_Pending =
- new System.Collections.Generic.Queue();
-
- /// EDITOR / execute_code hook: queue a deposit of of .
- public static void Deposit(ushort itemId = DefaultItemId, int count = DefaultCount) =>
- s_Pending.Enqueue(new PendingStorageOp { Op = StorageOp.Deposit, ItemId = itemId, Count = count });
-
- /// EDITOR / execute_code hook: queue a withdraw of of .
- public static void Withdraw(ushort itemId = DefaultItemId, int count = DefaultCount) =>
- s_Pending.Enqueue(new PendingStorageOp { Op = StorageOp.Withdraw, ItemId = itemId, Count = count });
-#endif
-
- protected override void OnCreate()
- {
- RequireForUpdate();
- }
-
- protected override void OnUpdate()
- {
- // Need the server connection to target the RPC; bail (keeping any queued ops) until connected.
- if (!SystemAPI.TryGetSingletonEntity(out var connection))
- return;
-
- var keyboard = UnityEngine.InputSystem.Keyboard.current;
- if (keyboard != null)
- {
- if (keyboard.eKey.wasPressedThisFrame)
- Send(connection, StorageOp.Deposit, DefaultItemId, DefaultCount);
- if (keyboard.qKey.wasPressedThisFrame)
- Send(connection, StorageOp.Withdraw, DefaultItemId, DefaultCount);
- }
-
-#if UNITY_EDITOR
- while (s_Pending.Count > 0)
- {
- var op = s_Pending.Dequeue();
- Send(connection, op.Op, op.ItemId, op.Count);
- }
-#endif
- }
-
- void Send(Entity connection, byte op, ushort itemId, int count)
- {
- var request = EntityManager.CreateEntity();
- EntityManager.AddComponentData(request, new StorageOpRequest { Op = op, ItemId = itemId, Count = count });
- EntityManager.AddComponentData(request, new SendRpcCommandRequest { TargetConnection = connection });
- }
- }
-}
diff --git a/Assets/_Project/Scripts/Client/HomeBase/StorageOpSendSystem.cs.meta b/Assets/_Project/Scripts/Client/HomeBase/StorageOpSendSystem.cs.meta
deleted file mode 100644
index 5c3c38a44..000000000
--- a/Assets/_Project/Scripts/Client/HomeBase/StorageOpSendSystem.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: d59f540925fe24a439bad6f7a77907fe
\ No newline at end of file
diff --git a/Assets/_Project/Scripts/Client/Input/PlayerInputGatherSystem.cs b/Assets/_Project/Scripts/Client/Input/PlayerInputGatherSystem.cs
index 6c7d76248..5eb983dea 100644
--- a/Assets/_Project/Scripts/Client/Input/PlayerInputGatherSystem.cs
+++ b/Assets/_Project/Scripts/Client/Input/PlayerInputGatherSystem.cs
@@ -77,16 +77,16 @@ namespace ProjectM.Client
var gamepad = UnityEngine.InputSystem.Gamepad.current;
var mouse = UnityEngine.InputSystem.Mouse.current;
var keyboard = UnityEngine.InputSystem.Keyboard.current;
- bool dashPressed = ((keyboard != null && keyboard.leftShiftKey.wasPressedThisFrame) || (gamepad != null && gamepad.buttonEast.wasPressedThisFrame)) && !BuildPaletteState.Active;
- // MC-4 offense rebind: melee combo = PRIMARY (left-click / pad West); ranged projectile demoted to right-click / pad left-trigger. Both suppressed while placing a build (like dash/old fire).
- bool attackPressed = ((mouse != null && mouse.leftButton.wasPressedThisFrame) || (gamepad != null && gamepad.buttonWest.wasPressedThisFrame)) && !BuildPaletteState.Active;
- bool firePressed = ((mouse != null && mouse.rightButton.wasPressedThisFrame) || (gamepad != null && gamepad.leftTrigger.wasPressedThisFrame)) && !BuildPaletteState.Active;
+ bool dashPressed = ((keyboard != null && keyboard.leftShiftKey.wasPressedThisFrame) || (gamepad != null && gamepad.buttonEast.wasPressedThisFrame));
+ // MC-4 offense rebind: melee combo = PRIMARY (left-click / pad West); ranged demoted to right-click / pad LT.
+ bool attackPressed = ((mouse != null && mouse.leftButton.wasPressedThisFrame) || (gamepad != null && gamepad.buttonWest.wasPressedThisFrame));
+ bool firePressed = ((mouse != null && mouse.rightButton.wasPressedThisFrame) || (gamepad != null && gamepad.leftTrigger.wasPressedThisFrame));
// LANTERN 4-socket kit bindings: keyboard 1..4 (+ gamepad RT/LB/RB); socket 0 also fires on the
- // legacy primary (right-click / pad LT). Suppressed while placing a build.
- bool socket0Pressed = firePressed || ((keyboard != null && keyboard.digit1Key.wasPressedThisFrame) && !BuildPaletteState.Active);
- bool socket1Pressed = ((keyboard != null && keyboard.digit2Key.wasPressedThisFrame) || (gamepad != null && gamepad.rightTrigger.wasPressedThisFrame)) && !BuildPaletteState.Active;
- bool socket2Pressed = ((keyboard != null && keyboard.digit3Key.wasPressedThisFrame) || (gamepad != null && gamepad.leftShoulder.wasPressedThisFrame)) && !BuildPaletteState.Active;
- bool socket3Pressed = ((keyboard != null && keyboard.digit4Key.wasPressedThisFrame) || (gamepad != null && gamepad.rightShoulder.wasPressedThisFrame)) && !BuildPaletteState.Active;
+ // legacy primary (right-click / pad LT). Build-palette suppression removed 2026-08-07.
+ bool socket0Pressed = firePressed || ((keyboard != null && keyboard.digit1Key.wasPressedThisFrame));
+ bool socket1Pressed = ((keyboard != null && keyboard.digit2Key.wasPressedThisFrame) || (gamepad != null && gamepad.rightTrigger.wasPressedThisFrame));
+ bool socket2Pressed = ((keyboard != null && keyboard.digit3Key.wasPressedThisFrame) || (gamepad != null && gamepad.leftShoulder.wasPressedThisFrame));
+ bool socket3Pressed = ((keyboard != null && keyboard.digit4Key.wasPressedThisFrame) || (gamepad != null && gamepad.rightShoulder.wasPressedThisFrame));
float2 rightStick = float2.zero;
bool gamepadActive = false;
diff --git a/Assets/_Project/Scripts/Client/Meta/MetaSpendSendSystem.cs b/Assets/_Project/Scripts/Client/Meta/MetaSpendSendSystem.cs
deleted file mode 100644
index 37262116f..000000000
--- a/Assets/_Project/Scripts/Client/Meta/MetaSpendSendSystem.cs
+++ /dev/null
@@ -1,41 +0,0 @@
-using System.Collections.Generic;
-using ProjectM.Simulation;
-using Unity.Entities;
-using Unity.NetCode;
-using UnityEngine;
-
-namespace ProjectM.Client
-{
- ///
- /// Client-side meta-purchase sender: a static enqueue (the Step-14 base meta-shop panel / execute_code) drained
- /// into RPCs. Carries only the upgrade ID — the tier is server-computed and the
- /// server re-validates everything (Staging gate, class mask, MaxTier, prereq, Aether affordability), so a stale
- /// or forged request is simply dropped. A real QUEUE (unlike the single-slot boon pick) — two rapid clicks on
- /// two different shop rows must both arrive. Statics reset on play-enter (the stale-bridge hazard).
- ///
- [WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]
- public partial class MetaSpendSendSystem : SystemBase
- {
- static readonly Queue s_Queue = new();
-
- [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
- static void ResetStatics() => s_Queue.Clear();
-
- /// Queue a permanent-upgrade purchase by catalog id. The shop row click + execute_code drive this.
- public static void RequestPurchase(byte upgradeId) => s_Queue.Enqueue(upgradeId);
-
- protected override void OnCreate()
- {
- RequireForUpdate();
- }
-
- protected override void OnUpdate()
- {
- while (s_Queue.Count > 0)
- {
- var req = EntityManager.CreateEntity(typeof(MetaSpendRequest), typeof(SendRpcCommandRequest));
- EntityManager.SetComponentData(req, new MetaSpendRequest { UpgradeId = s_Queue.Dequeue() });
- }
- }
- }
-}
diff --git a/Assets/_Project/Scripts/Client/Meta/MetaSpendSendSystem.cs.meta b/Assets/_Project/Scripts/Client/Meta/MetaSpendSendSystem.cs.meta
deleted file mode 100644
index 06dc91328..000000000
--- a/Assets/_Project/Scripts/Client/Meta/MetaSpendSendSystem.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: 9f3197b4ede6dea41a6c4c93ffa51b42
\ No newline at end of file
diff --git a/Assets/_Project/Scripts/Client/Presentation/AmbientAudioSystem.cs b/Assets/_Project/Scripts/Client/Presentation/AmbientAudioSystem.cs
index 390c965af..0fde265e7 100644
--- a/Assets/_Project/Scripts/Client/Presentation/AmbientAudioSystem.cs
+++ b/Assets/_Project/Scripts/Client/Presentation/AmbientAudioSystem.cs
@@ -80,36 +80,8 @@ namespace ProjectM.Client
}
}
- // Launch countdown beeps (3-2-1) + the boss-arrival roar — replicated-state observations only.
- if (SystemAPI.TryGetSingleton(out var runAudio))
- {
- int sec = -1;
- if (runAudio.Lifecycle == RunLifecycle.Launching && runAudio.LaunchTick != 0
- && SystemAPI.TryGetSingleton(out var antime) && antime.ServerTick.IsValid)
- {
- int tl = new NetworkTick(runAudio.LaunchTick).TicksSince(antime.ServerTick);
- if (tl > 0) sec = tl / 60 + 1;
- }
- if (sec > 0 && sec != _lastCountdownSec && sec <= 3)
- _ambient.PlayOneShot(_stingBeep, 0.5f * GameVolume.Sfx);
- _lastCountdownSec = sec;
-
- bool inBossRoom = runAudio.Lifecycle == RunLifecycle.InRoom
- && runAudio.CurrentRoomType == RoomTypeId.Boss;
- if (!inBossRoom)
- {
- _bossRoared = false; // re-arm for the next boss room
- }
- else if (!_bossRoared)
- {
- foreach (var _ in SystemAPI.Query>().WithAll())
- {
- _ambient.PlayOneShot(_stingRoar, 0.9f * GameVolume.Sfx);
- _bossRoared = true;
- break;
- }
- }
- }
+ // 2026-08-07 audit purge: the 3-2-1 launch countdown beeps and the boss-arrival roar keyed off
+ // RunInfo.Lifecycle / RoomTypeId. Both went with the run FSM and the boss.
}
// ---- Procedural audio (asset-free; mirrors CombatFeedbackSystem.MakeClip) ----
diff --git a/Assets/_Project/Scripts/Client/Presentation/AmbientLifeSystem.cs b/Assets/_Project/Scripts/Client/Presentation/AmbientLifeSystem.cs
index 012606a1f..8d6391f6c 100644
--- a/Assets/_Project/Scripts/Client/Presentation/AmbientLifeSystem.cs
+++ b/Assets/_Project/Scripts/Client/Presentation/AmbientLifeSystem.cs
@@ -109,13 +109,8 @@ namespace ProjectM.Client
if (_cam.transform.position.x > 500f)
{
key = 1; // arid default in the expedition region
- if (SystemAPI.TryGetSingleton(out var ri) && ri.Lifecycle != RunLifecycle.Staging)
- switch (ri.CurrentBiome)
- {
- case RoomBiomeId.Meadow: key = 0; break;
- case RoomBiomeId.Cavern: key = 2; break;
- case RoomBiomeId.Blight: key = 3; break;
- }
+ // 2026-08-07 audit purge: per-room biome selection keyed off RunInfo.CurrentBiome. The room
+ // biomes went with the run FSM; the expedition region keeps its single ambient set.
}
return key;
}
diff --git a/Assets/_Project/Scripts/Client/Presentation/AmbientMotionSystem.cs b/Assets/_Project/Scripts/Client/Presentation/AmbientMotionSystem.cs
index cb533fd51..f2bf661f5 100644
--- a/Assets/_Project/Scripts/Client/Presentation/AmbientMotionSystem.cs
+++ b/Assets/_Project/Scripts/Client/Presentation/AmbientMotionSystem.cs
@@ -152,15 +152,8 @@ namespace ProjectM.Client
if (_cam.transform.position.x > 500f)
{
key = 1; // arid default
- if (SystemAPI.TryGetSingleton(out var ri) && ri.Lifecycle != RunLifecycle.Staging)
- {
- switch (ri.CurrentBiome)
- {
- case RoomBiomeId.Meadow: key = 0; break;
- case RoomBiomeId.Cavern: key = 2; break;
- case RoomBiomeId.Blight: key = 3; break;
- }
- }
+ // 2026-08-07 audit purge: per-room biome selection keyed off RunInfo.CurrentBiome, which went
+ // with the run FSM. The expedition region keeps its single motion set.
}
if (key != _biomeKey)
{
diff --git a/Assets/_Project/Scripts/Client/Presentation/BoonModalHudSystem.cs b/Assets/_Project/Scripts/Client/Presentation/BoonModalHudSystem.cs
deleted file mode 100644
index 0449c7574..000000000
--- a/Assets/_Project/Scripts/Client/Presentation/BoonModalHudSystem.cs
+++ /dev/null
@@ -1,164 +0,0 @@
-using System.Collections.Generic;
-using ProjectM.Simulation;
-using Unity.Entities;
-using Unity.NetCode;
-using Unity.Transforms;
-using Unity.Mathematics;
-using UnityEngine;
-using UnityEngine.UIElements;
-
-namespace ProjectM.Client
-{
- ///
- /// The choice-of-3 boon modal (RoomReward) — extracted from into its own client-only,
- /// observe-only presentation in . Owns its own
- /// runtime UIDocument sharing (sortingOrder 55) so it composes into the
- /// same UITK panel + event dispatcher as the HUD (50) / markers (48) / onboarding (60). Reads the local player's
- /// replicated + the blob; card clicks enqueue through
- /// . Built lazily on first show.
- ///
- [WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]
- [UpdateInGroup(typeof(PresentationSystemGroup))]
- public partial class BoonModalHudSystem : SystemBase
- {
- GameObject _go;
- UIDocument _doc;
- bool _built;
-
- VisualElement _boonModal, _boonCardRow;
- int _boonShownFor; // last exact (Option0|Option1<<8|Option2<<16)+1 signature the modal was built for
- bool _boonModalBuilt;
-
- protected override void OnStartRunning()
- {
- if (_go != null) return;
- MenuUi.EnsureEventSystem();
- _go = new GameObject("~HUDBoonModal");
- _doc = _go.AddComponent();
- _doc.panelSettings = MenuUi.LoadPanelSettings();
- _doc.sortingOrder = 55;
- }
-
- protected override void OnDestroy()
- {
- if (_go != null) Object.Destroy(_go);
- }
-
- protected override void OnUpdate()
- {
- if (_doc == null) return;
- var root = _doc.rootVisualElement;
- if (root == null) return; // panel not initialised yet (next frame)
- if (!_built)
- {
- root.style.position = Position.Absolute;
- root.style.left = 0; root.style.right = 0; root.style.top = 0; root.style.bottom = 0;
- root.pickingMode = PickingMode.Ignore; // never eat game-world clicks
- _built = true;
- }
-
- bool haveRun = SystemAPI.TryGetSingleton(out var runInfo);
-
- BoonOffer localOffer = default;
- bool hasOffer = false;
- foreach (var off in SystemAPI.Query>().WithAll())
- {
- localOffer = off.ValueRO;
- hasOffer = true;
- break;
- }
- BlobAssetReference boonPool = default;
- if (SystemAPI.TryGetSingleton(out var bcat))
- boonPool = bcat.Value;
- // Lifecycle gate (post-impl review): even a stale replicated Pending never shows the modal outside
- // the reward window.
- UpdateBoonModal(localOffer, hasOffer && localOffer.Pending == 1
- && haveRun && runInfo.Lifecycle == RunLifecycle.RoomReward, boonPool);
- }
-
- void UpdateBoonModal(BoonOffer offer, bool show, BlobAssetReference pool)
- {
- if (!show || !pool.IsCreated)
- {
- if (_boonModal != null) _boonModal.style.display = DisplayStyle.None;
- _boonShownFor = 0;
- return;
- }
- var root = _doc != null ? _doc.rootVisualElement : null;
- if (root == null) return;
- if (!_boonModalBuilt)
- {
- BuildBoonModal(root);
- _boonModalBuilt = true;
- }
-
- // Rebuild the three cards only when the offer actually changes (a new room's deal).
- // Exact signature (post-impl review): the old lossy byte XOR could collide across consecutive
- // rooms and leave stale card labels. +1 keeps 0 as the hidden/reset sentinel.
- int sig = 1 + (offer.Option0 | (offer.Option1 << 8) | (offer.Option2 << 16));
- if (_boonShownFor != sig)
- {
- _boonCardRow.Clear();
- ref var defs = ref pool.Value;
- for (byte k = 0; k < 3; k++)
- {
- byte id = k == 2 ? offer.Option2 : k == 1 ? offer.Option1 : offer.Option0;
- int idx = BoonMath.FindDef(ref defs, id);
- string title = idx >= 0 ? defs.Defs[idx].Name.ToString() : ("BOON " + id);
- string desc = idx >= 0 ? defs.Defs[idx].Desc.ToString() : "";
- byte weight = idx >= 0 ? defs.Defs[idx].Weight : (byte)100;
- byte pick = k; // capture a COPY into the closure, never the loop variable
- var card = MenuUi.Button(title + "\n" + desc, () => BoonSendSystem.PickBoon(pick));
- card.style.width = 200;
- card.style.height = StyleKeyword.Auto; // long descs grow the card
- card.style.minHeight = 96;
- // Rarity from the draw weight (100 common / 60 uncommon / 30 rare / 10 epic).
- var rare = weight <= 10 ? new Color(1f, 0.82f, 0.30f)
- : weight <= 30 ? new Color(0.65f, 0.50f, 1f)
- : weight <= 60 ? new Color(0.45f, 0.95f, 0.55f)
- : new Color(1f, 1f, 1f, 0.30f);
- MenuUi.Border(card, rare, weight <= 30 ? 2.5f : 1.5f);
- card.style.marginLeft = 8;
- card.style.marginRight = 8;
- card.style.whiteSpace = WhiteSpace.Normal;
- _boonCardRow.Add(card);
- }
- _boonShownFor = sig;
- }
- _boonModal.style.display = DisplayStyle.Flex;
- }
-
- void BuildBoonModal(VisualElement root)
- {
- _boonModal = new VisualElement { pickingMode = PickingMode.Ignore };
- _boonModal.style.position = Position.Absolute;
- _boonModal.style.left = 0; _boonModal.style.right = 0;
- _boonModal.style.top = 0; _boonModal.style.bottom = 0;
- _boonModal.style.alignItems = Align.Center;
- _boonModal.style.justifyContent = Justify.Center;
- _boonModal.style.display = DisplayStyle.None;
-
- var box = new VisualElement();
- box.style.backgroundColor = new Color(0.07f, 0.09f, 0.12f, 0.96f);
- box.style.borderTopLeftRadius = 10; box.style.borderTopRightRadius = 10;
- box.style.borderBottomLeftRadius = 10; box.style.borderBottomRightRadius = 10;
- box.style.paddingLeft = 18; box.style.paddingRight = 18;
- box.style.paddingTop = 14; box.style.paddingBottom = 16;
- box.style.alignItems = Align.Center;
-
- var title = new Label("ROOM CLEARED — CHOOSE A BOON");
- title.style.color = new Color(0.6f, 1f, 0.7f);
- title.style.fontSize = 18;
- title.style.unityFontStyleAndWeight = FontStyle.Bold;
- title.style.marginBottom = 12;
- box.Add(title);
-
- _boonCardRow = new VisualElement();
- _boonCardRow.style.flexDirection = FlexDirection.Row;
- box.Add(_boonCardRow);
-
- _boonModal.Add(box);
- root.Add(_boonModal);
- }
- }
-}
diff --git a/Assets/_Project/Scripts/Client/Presentation/BoonModalHudSystem.cs.meta b/Assets/_Project/Scripts/Client/Presentation/BoonModalHudSystem.cs.meta
deleted file mode 100644
index 4f29e033c..000000000
--- a/Assets/_Project/Scripts/Client/Presentation/BoonModalHudSystem.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: 90c2dc6391260794dbdf466b2f8887e5
\ No newline at end of file
diff --git a/Assets/_Project/Scripts/Client/Presentation/ClassPrepPortalHudSystem.cs b/Assets/_Project/Scripts/Client/Presentation/ClassPrepPortalHudSystem.cs
deleted file mode 100644
index a3c5f33d1..000000000
--- a/Assets/_Project/Scripts/Client/Presentation/ClassPrepPortalHudSystem.cs
+++ /dev/null
@@ -1,240 +0,0 @@
-using System.Collections.Generic;
-using ProjectM.Simulation;
-using Unity.Entities;
-using Unity.NetCode;
-using Unity.Transforms;
-using Unity.Mathematics;
-using UnityEngine;
-using UnityEngine.UIElements;
-
-namespace ProjectM.Client
-{
- ///
- /// DR-046 base class-select + prep-loadout panels (Staging) + the room-exit portal prompt (RoomExplore) —
- /// extracted from into their own client-only, observe-only presentation
- /// in . Owns its own runtime UIDocument sharing
- /// (sortingOrder 54). Recomputes the local class + ore/bio/aether + the
- /// Staging gate + the RoomExplore portal proximity locally; clicks enqueue through
- /// / / .
- /// NOTE (behavior-preserving): the class/prep Staging gate reproduces the original's FULL condition — it also
- /// requires the + buffer to be present (the panels
- /// were gated on the same metaShow boolean as the meta shop).
- ///
- [WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]
- [UpdateInGroup(typeof(PresentationSystemGroup))]
- public partial class ClassPrepPortalHudSystem : SystemBase
- {
- GameObject _go;
- UIDocument _doc;
- bool _built;
-
- VisualElement _classPanel, _prepPanel, _prepRowsHost;
- Label _classTitle, _prepTitle, _portalPrompt;
- Button _classWarBtn, _classRangerBtn;
- bool _classPanelBuilt, _prepPanelBuilt, _portalBuilt;
- int _classShownFor, _prepShownFor;
-
- protected override void OnStartRunning()
- {
- if (_go != null) return;
- MenuUi.EnsureEventSystem();
- _go = new GameObject("~HUDClassPrepPortal");
- _doc = _go.AddComponent();
- _doc.panelSettings = MenuUi.LoadPanelSettings();
- _doc.sortingOrder = 54;
- }
-
- protected override void OnDestroy()
- {
- if (_go != null) Object.Destroy(_go);
- }
-
- protected override void OnUpdate()
- {
- if (_doc == null) return;
- var root = _doc.rootVisualElement;
- if (root == null) return; // panel not initialised yet (next frame)
- if (!_built)
- {
- root.style.position = Position.Absolute;
- root.style.left = 0; root.style.right = 0; root.style.top = 0; root.style.bottom = 0;
- root.pickingMode = PickingMode.Ignore; // never eat game-world clicks
- _built = true;
- }
-
- bool haveRun = SystemAPI.TryGetSingleton(out var runInfo);
-
- // Resources from the ledger (last entry per type wins, matching the core loop).
- int aether = 0, ore = 0, bio = 0;
- if (SystemAPI.TryGetSingletonEntity(out var ledgerE))
- {
- var buf = SystemAPI.GetBuffer(ledgerE);
- for (int i = 0; i < buf.Length; i++)
- {
- var en = buf[i];
- if (en.ItemId == ResourceId.Aether) aether = en.Count;
- else if (en.ItemId == ResourceId.Ore) ore = en.Count;
- else if (en.ItemId == ResourceId.Biomass) bio = en.Count;
- }
- }
-
- // Local class from the replicated AbilityRef (tracks the dev class-switch; PlayerClass is server-only).
- byte localClass = ClassTraits.WarriorClass;
- bool haveLocalPlayer = false;
- foreach (var fr in SystemAPI.Query>().WithAll())
- {
- localClass = ClassTraits.Normalize(fr.ValueRO.Value); // FrameId is the sole class signal (legacy AbilityRef deleted)
- haveLocalPlayer = true;
- break;
- }
-
- // Faithful reproduction of the original `metaShow` gate: class/prep were shown on the SAME condition as
- // the meta shop, which requires the meta catalog + tier buffer to exist.
- DynamicBuffer metaRecord = default;
- bool metaShow = haveRun && runInfo.Lifecycle == RunLifecycle.Staging && haveLocalPlayer
- && SystemAPI.TryGetSingleton(out var metaCat) && metaCat.Value.IsCreated
- && SystemAPI.TryGetSingletonBuffer(out metaRecord, true);
-
- UpdateClassPanel(metaShow, localClass); // DR-046: base class pick (Staging)
- UpdatePrepPanel(metaShow, ore, bio, aether); // DR-046: base prep loadout (Staging)
- UpdatePortalPrompt(haveRun ? runInfo : default, haveRun); // DR-046: room-exit portal prompt (RoomExplore)
- }
-
- void UpdateClassPanel(bool show, byte classId)
- {
- if (!show) { if (_classPanel != null) _classPanel.style.display = DisplayStyle.None; _classShownFor = 0; return; }
- var root = _doc != null ? _doc.rootVisualElement : null; if (root == null) return;
- if (!_classPanelBuilt) { BuildClassPanel(root); _classPanelBuilt = true; }
- int sig = classId + 1;
- if (_classShownFor != sig)
- {
- bool ranger = classId == ClassTraits.RangerClass;
- _classWarBtn.text = ranger ? "WARRIOR" : "WARRIOR ✓";
- _classRangerBtn.text = ranger ? "RANGER ✓" : "RANGER";
- _classWarBtn.SetEnabled(ranger);
- _classRangerBtn.SetEnabled(!ranger);
- _classShownFor = sig;
- }
- _classPanel.style.display = DisplayStyle.Flex;
- }
-
- void BuildClassPanel(VisualElement root)
- {
- _classPanel = new VisualElement { pickingMode = PickingMode.Ignore };
- _classPanel.style.position = Position.Absolute;
- _classPanel.style.left = 12; _classPanel.style.top = Length.Percent(22);
- _classPanel.style.display = DisplayStyle.None;
- var box = new VisualElement();
- box.style.backgroundColor = new Color(0.07f, 0.09f, 0.12f, 0.92f);
- MenuUi.Round(box, 10);
- box.style.paddingLeft = 12; box.style.paddingRight = 12; box.style.paddingTop = 10; box.style.paddingBottom = 10;
- _classTitle = new Label("CLASS");
- _classTitle.style.color = MenuUi.Accent; _classTitle.style.fontSize = 14;
- _classTitle.style.unityFontStyleAndWeight = FontStyle.Bold; _classTitle.style.marginBottom = 8;
- box.Add(_classTitle);
- _classWarBtn = MenuUi.Button("WARRIOR", () => ClassSelectSendSystem.RequestClass(ClassTraits.WarriorClass));
- _classWarBtn.style.marginBottom = 4; box.Add(_classWarBtn);
- _classRangerBtn = MenuUi.Button("RANGER", () => ClassSelectSendSystem.RequestClass(ClassTraits.RangerClass));
- box.Add(_classRangerBtn);
- _classPanel.Add(box); root.Add(_classPanel);
- }
-
- void UpdatePrepPanel(bool show, int ore, int bio, int aether)
- {
- if (!show) { if (_prepPanel != null) _prepPanel.style.display = DisplayStyle.None; _prepShownFor = 0; return; }
- var root = _doc != null ? _doc.rootVisualElement : null; if (root == null) return;
- if (!_prepPanelBuilt) { BuildPrepPanel(root); _prepPanelBuilt = true; }
- uint boughtMask = 0;
- foreach (var mods in SystemAPI.Query>().WithAll())
- {
- for (int m = 0; m < mods.Length; m++)
- {
- uint sid = mods[m].SourceId;
- if (sid >= Tuning.PrepSourceIdBase && sid < Tuning.PrepSourceIdBase + Tuning.PrepSourceIdSpan)
- boughtMask |= (uint)(1 << (int)(sid - Tuning.PrepSourceIdBase));
- }
- break;
- }
- int sig = ore * 7 ^ bio * 13 ^ aether * 31 ^ (int)boughtMask * 101;
- if (sig == 0) sig = 1;
- if (_prepShownFor != sig)
- {
- _prepRowsHost.Clear();
- for (int i = 0; i < PrepCatalog.Count; i++)
- {
- var r = PrepCatalog.Rows[i];
- int have = r.CostResId == ResourceId.Aether ? aether : r.CostResId == ResourceId.Biomass ? bio : ore;
- bool bought = (boughtMask & (uint)(1 << r.Id)) != 0;
- string resName = r.CostResId == ResourceId.Aether ? "Aether" : r.CostResId == ResourceId.Biomass ? "Biomass" : "Ore";
- string label = PrepLabel(r.Id) + (bought ? " BOUGHT" : " - " + r.Cost + " " + resName);
- byte buyId = r.Id;
- var row = MenuUi.Button(label, () => PrepPurchaseSendSystem.RequestPrep(buyId));
- row.style.width = 240; row.style.marginBottom = 4;
- row.style.whiteSpace = WhiteSpace.Normal; row.style.unityTextAlign = TextAnchor.MiddleLeft;
- row.SetEnabled(!bought && have >= r.Cost);
- _prepRowsHost.Add(row);
- }
- _prepShownFor = sig;
- }
- _prepPanel.style.display = DisplayStyle.Flex;
- }
-
- static string PrepLabel(byte id) => id == 0 ? "+30 Max HP" : id == 1 ? "+12% Move Speed"
- : id == 2 ? "+20% Melee Damage" : "+20% Ranged Damage";
-
- void BuildPrepPanel(VisualElement root)
- {
- _prepPanel = new VisualElement { pickingMode = PickingMode.Ignore };
- _prepPanel.style.position = Position.Absolute;
- _prepPanel.style.left = 12; _prepPanel.style.top = Length.Percent(45);
- _prepPanel.style.display = DisplayStyle.None;
- var box = new VisualElement();
- box.style.backgroundColor = new Color(0.07f, 0.09f, 0.12f, 0.92f);
- MenuUi.Round(box, 10);
- box.style.paddingLeft = 12; box.style.paddingRight = 12; box.style.paddingTop = 10; box.style.paddingBottom = 10;
- _prepTitle = new Label("PREP LOADOUT (lasts the run)");
- _prepTitle.style.color = MenuUi.Accent; _prepTitle.style.fontSize = 14;
- _prepTitle.style.unityFontStyleAndWeight = FontStyle.Bold; _prepTitle.style.marginBottom = 8;
- box.Add(_prepTitle);
- _prepRowsHost = new VisualElement(); box.Add(_prepRowsHost);
- _prepPanel.Add(box); root.Add(_prepPanel);
- }
-
- void UpdatePortalPrompt(RunInfo runInfo, bool haveRun)
- {
- var root = _doc != null ? _doc.rootVisualElement : null; if (root == null) return;
- if (!_portalBuilt) { BuildPortalPrompt(root); _portalBuilt = true; }
- bool show = false, inRange = false;
- if (haveRun && runInfo.Lifecycle == RunLifecycle.RoomExplore
- && SystemAPI.TryGetSingleton(out var anchor))
- {
- show = true; // room cleared -> ALWAYS steer the player to the (now visible) portal, not only when in range
- float3 portalPos = RegionMath.ExpeditionPortalPos(BaseGridMath.PlotCenter(anchor), (byte)(runInfo.CurrentRoom & 1));
- foreach (var lt in SystemAPI.Query>().WithAll())
- {
- inRange = math.distance(lt.ValueRO.Position.xz, portalPos.xz) <= Tuning.PortalInteractRange;
- if (inRange)
- {
- var kb = UnityEngine.InputSystem.Keyboard.current;
- if (kb != null && kb.eKey.wasPressedThisFrame) PortalInteractSendSystem.Interact();
- }
- break;
- }
- _portalPrompt.text = inRange
- ? "PRESS E TO LEAVE — the haul comes home"
- : "ROOM CLEAR — reach the glowing portal to move on";
- }
- _portalPrompt.style.display = show ? DisplayStyle.Flex : DisplayStyle.None;
- }
-
- void BuildPortalPrompt(VisualElement root)
- {
- _portalPrompt = HudUi.Display("PRESS E TO LEAVE — the haul comes home", 20, new Color(0.55f, 0.95f, 1f), TextAnchor.MiddleCenter);
- _portalPrompt.style.position = Position.Absolute;
- _portalPrompt.style.left = 0; _portalPrompt.style.right = 0; _portalPrompt.style.bottom = 240;
- _portalPrompt.pickingMode = PickingMode.Ignore;
- _portalPrompt.style.display = DisplayStyle.None;
- root.Add(_portalPrompt);
- }
- }
-}
diff --git a/Assets/_Project/Scripts/Client/Presentation/ClassPrepPortalHudSystem.cs.meta b/Assets/_Project/Scripts/Client/Presentation/ClassPrepPortalHudSystem.cs.meta
deleted file mode 100644
index 1781d7f1f..000000000
--- a/Assets/_Project/Scripts/Client/Presentation/ClassPrepPortalHudSystem.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: 32e011d68689e89488879377a7fb4c3a
\ No newline at end of file
diff --git a/Assets/_Project/Scripts/Client/Presentation/CombatFeedbackSystem.cs b/Assets/_Project/Scripts/Client/Presentation/CombatFeedbackSystem.cs
index e25e4c153..f2f24c173 100644
--- a/Assets/_Project/Scripts/Client/Presentation/CombatFeedbackSystem.cs
+++ b/Assets/_Project/Scripts/Client/Presentation/CombatFeedbackSystem.cs
@@ -225,7 +225,7 @@ namespace ProjectM.Client
bool isEnemy = SystemAPI.HasComponent(entity);
uint windup = isEnemy && SystemAPI.HasComponent(entity) ? SystemAPI.GetComponent(entity).WindUpUntilTick : 0u;
bool isLocalPlayer = entity == _localPlayer;
- bool isStructure = SystemAPI.HasComponent(entity); // EB-1: suppress combat cues -> StructureFeedbackSystem
+ const bool isStructure = false; // structures deleted 2026-08-07 (audit purge)
if (_cache.TryGetValue(entity, out var prev))
{
diff --git a/Assets/_Project/Scripts/Client/Presentation/DynamicLightSystem.cs b/Assets/_Project/Scripts/Client/Presentation/DynamicLightSystem.cs
index 12f929760..ad9f55894 100644
--- a/Assets/_Project/Scripts/Client/Presentation/DynamicLightSystem.cs
+++ b/Assets/_Project/Scripts/Client/Presentation/DynamicLightSystem.cs
@@ -148,23 +148,10 @@ namespace ProjectM.Client
}
PruneUnseen(_barrelLights);
- // ---- portal light (RoomExplore only) ----
- bool portalOn = false;
- if (SystemAPI.TryGetSingleton(out var runInfo)
- && runInfo.Lifecycle == RunLifecycle.RoomExplore
- && SystemAPI.TryGetSingleton(out var anchor))
- {
- portalOn = true;
- var pos = RegionMath.ExpeditionPortalPos(BaseGridMath.PlotCenter(anchor), (byte)(runInfo.CurrentRoom & 1));
- if (_portalLight == null) _portalLight = Rent();
- float baseI = cfg != null ? cfg.PortalIntensity : 3.5f;
- float amp = cfg != null ? cfg.PortalPulseAmp : 1.2f;
- _portalLight.color = cfg != null ? cfg.PortalColor : new Color(0.55f, 0.95f, 1f, 1f);
- _portalLight.range = cfg != null ? cfg.PortalRange : 13f;
- _portalLight.intensity = baseI + amp * Mathf.Sin(UnityEngine.Time.time * 4f);
- _portalLight.transform.position = new Vector3(pos.x, pos.y + 2.2f, pos.z);
- }
- if (!portalOn && _portalLight != null) { Return(_portalLight); _portalLight = null; }
+ // 2026-08-07 audit purge: the room-exit portal light keyed off RunInfo.Lifecycle == RoomExplore.
+ // Portals went with the run FSM; release any pooled light so nothing leaks.
+ if (_portalLight != null) { Return(_portalLight); _portalLight = null; }
+
// ---- impact flashes ----
float now = UnityEngine.Time.time;
diff --git a/Assets/_Project/Scripts/Client/Presentation/EnemyAnimationDriveSystem.cs b/Assets/_Project/Scripts/Client/Presentation/EnemyAnimationDriveSystem.cs
index da7f7e107..5f7dcd1ea 100644
--- a/Assets/_Project/Scripts/Client/Presentation/EnemyAnimationDriveSystem.cs
+++ b/Assets/_Project/Scripts/Client/Presentation/EnemyAnimationDriveSystem.cs
@@ -85,7 +85,6 @@ namespace ProjectM.Client
dt = dt,
prevPos = _prevPos,
seen = seen,
- isLunging = SystemAPI.GetComponentLookup(true),
};
Dependency = job.Schedule(Dependency); // .Schedule (not parallel): mutates _prevPos
@@ -111,7 +110,7 @@ namespace ProjectM.Client
{
public FastAnimatorParameter moveX, moveZ, speed, isAttacking, isDead, isHit, isHitHeavy;
public float el, reactSeconds, staggerSeconds, staggerDamage; // 07-21 hit-react (reactSeconds 0 = off)
- [Unity.Collections.ReadOnly] public ComponentLookup isLunging; // A7: a lunge has no AttackWindup; OR it in so the boss/Charger attack anim plays during the committed lunge
+
public float dt;
public NativeParallelHashMap prevPos;
@@ -149,7 +148,7 @@ namespace ProjectM.Client
float2 facing = AnimParamMath.PlanarForward(xform.Rotation);
float3 p = AnimParamMath.LocomotionParams(vel, facing, stats.MoveSpeed);
- bool attacking = windup.WindUpUntilTick != 0 || (isLunging.HasComponent(e) && isLunging.IsComponentEnabled(e)); // A7: the committed lunge (which zeroes AttackWindup) still animates as an attack
+ bool attacking = windup.WindUpUntilTick != 0; // the IsLunging OR went with the Charger purge (2026-08-07)
// B3: the corpse window — Health.Current is replicated, so <=0 IS the death read. A corpse
// plays the Death state and nothing else (no jog, no frozen attack).
diff --git a/Assets/_Project/Scripts/Client/Presentation/EnemyDangerTelegraphSystem.cs b/Assets/_Project/Scripts/Client/Presentation/EnemyDangerTelegraphSystem.cs
index 64e76bad3..95b987953 100644
--- a/Assets/_Project/Scripts/Client/Presentation/EnemyDangerTelegraphSystem.cs
+++ b/Assets/_Project/Scripts/Client/Presentation/EnemyDangerTelegraphSystem.cs
@@ -98,7 +98,6 @@ namespace ProjectM.Client
EntityManager.CompleteDependencyBeforeRO();
EntityManager.CompleteDependencyBeforeRO();
EntityManager.CompleteDependencyBeforeRO();
- EntityManager.CompleteDependencyBeforeRO();
// Local player (strike-beep proximity gate).
_localPlayer = Entity.Null;
@@ -122,7 +121,6 @@ namespace ProjectM.Client
Unity.NetCode.NetworkTick serverTick = SystemAPI.TryGetSingleton(out var nt) ? nt.ServerTick : default;
_dangerSeen.Clear();
_enemySeen.Clear();
- bool bossRoom = SystemAPI.TryGetSingleton(out var dangerRi) && dangerRi.Lifecycle == RunLifecycle.InRoom && dangerRi.CurrentRoomType == RoomTypeId.Boss; // A7: in a Boss room the Charger-kind enemy IS the boss (adds are swarmers)
if (serverTick.IsValid)
{
@@ -151,26 +149,16 @@ namespace ProjectM.Client
}
_prevWindup[entity] = until;
- // Feature D: a committed Charger lunge keeps the cue ALIVE past windup (AttackWindup zeroes at commit).
- bool lunging = SystemAPI.HasComponent(entity) && SystemAPI.IsComponentEnabled(entity);
- bool isBoss = bossRoom && tele.ValueRO.Kind == ZoneEnemyMath.KindCharger; // A7: boss radial SLAM telegraph
+ if (until == 0u) continue;
- if (until == 0u && !lunging) continue;
-
- float intensity;
- if (lunging)
- {
- intensity = 1f; // mid-lunge: max danger, persistent until IsLunging clears
- }
- else
- {
- var untilTick = new Unity.NetCode.NetworkTick(until);
- if (!untilTick.IsValid || !untilTick.IsNewerThan(serverTick)) continue; // windup already elapsed
- int remaining = untilTick.TicksSince(serverTick);
- // Feature C: per-enemy windup duration (baked, client-safe) -> ramps 0->1 ending AT impact for
- // any windup length (fixes the Charger plateauing early under the old hard-coded 22).
- float windupDur = isBoss ? Tuning.BossSlamWindupTicks : math.max(1f, tele.ValueRO.WindupTicks); // A7: ramp over the boss's real slam wind-up
- intensity = math.saturate(1f - remaining / windupDur);
+ var untilTick = new Unity.NetCode.NetworkTick(until);
+ if (!untilTick.IsValid || !untilTick.IsNewerThan(serverTick)) continue; // windup already elapsed
+ int remaining = untilTick.TicksSince(serverTick);
+ // Feature C: per-enemy windup duration (baked, client-safe) -> ramps 0->1 ending AT impact for
+ // any windup length.
+ float windupDur = math.max(1f, tele.ValueRO.WindupTicks);
+ float intensity = math.saturate(1f - remaining / windupDur);
+ {;
// Near-impact strike beep (deferred-items pass): a "dodge NOW" cue once per windup, gated to
// enemies near the local player (the danger cone already proves it's winding up to strike).
@@ -211,33 +199,9 @@ namespace ProjectM.Client
_dangerZones[entity] = go;
}
float coneRange = math.max(1f, stats.ValueRO.AttackRange + 0.6f);
- if (lunging) coneRange += 1.5f; // forward-stretch the wedge to read the committed travel
- if (isBoss && !lunging)
- {
- // A7: the boss SLAM is RADIAL (Tuning.BossSlamRadius) -> paint a FULL ground ring so the tell
- // matches the hit area (a forward wedge sized to melee reach would lie about a radial AoE).
- BuildDangerMesh(go.GetComponent().sharedMesh, Tuning.BossSlamRadius, 3.14159f, intensity);
- }
- else if (isBoss)
- {
- // B4: the boss LUNGE is a committed forward gap-closer (IsLunging bit on through windup +
- // travel) - a radial ring would lie about the threat shape; paint a long narrow travel wedge.
- BuildDangerMesh(go.GetComponent().sharedMesh, math.max(coneRange, 8f), 0.45f, intensity);
- }
- else if (tele.ValueRO.Kind == ZoneEnemyMath.KindSpitter)
- {
- // MC-3: a Spitter is a RANGED threat — a melee wedge at its feet is useless. Paint a thin aim
- // LANE along its (face-locked) facing out to projectile reach during wind-up, brightening as the
- // shot nears so the player reads the line to dodge/dash across it.
- float laneLen = 12f;
- if (SystemAPI.HasComponent(entity))
- {
- var ss = SystemAPI.GetComponent(entity);
- laneLen = math.max(4f, ss.PreferredRange + ss.RangeTolerance + 2f);
- }
- BuildLaneMesh(go.GetComponent().sharedMesh, laneLen, 0.28f, intensity);
- }
- else BuildDangerMesh(go.GetComponent().sharedMesh, coneRange, 0.7f, intensity);
+ // 2026-08-07 audit purge: the boss radial-slam ring, the boss lunge wedge and the Spitter aim
+ // lane are gone with BossState / IsLunging / SpitterState. One enemy kind, one melee wedge.
+ BuildDangerMesh(go.GetComponent().sharedMesh, coneRange, 0.7f, intensity);
float2 fwd = AnimParamMath.PlanarForward(xf.ValueRO.Rotation);
var tr = go.transform;
tr.position = (Vector3)xf.ValueRO.Position + Vector3.up * 0.06f;
diff --git a/Assets/_Project/Scripts/Client/Presentation/EnemyMarkerSystem.cs b/Assets/_Project/Scripts/Client/Presentation/EnemyMarkerSystem.cs
index a63d039d0..9a78bb84a 100644
--- a/Assets/_Project/Scripts/Client/Presentation/EnemyMarkerSystem.cs
+++ b/Assets/_Project/Scripts/Client/Presentation/EnemyMarkerSystem.cs
@@ -76,7 +76,7 @@ namespace ProjectM.Client
// Collect living enemies within range (nearest-capped by MaxMarkers).
_positions.Clear();
float rangeSq = FeelConfig.EnemyMarkerRange * FeelConfig.EnemyMarkerRange;
- foreach (var lt in SystemAPI.Query>().WithAll().WithNone())
+ foreach (var lt in SystemAPI.Query>().WithAll().WithNone())
{
float3 p = lt.ValueRO.Position;
if (haveLocal && math.distancesq(p, localPos) > rangeSq) continue;
diff --git a/Assets/_Project/Scripts/Client/Presentation/HudSystem.cs b/Assets/_Project/Scripts/Client/Presentation/HudSystem.cs
index d6e8cb959..3a76942b5 100644
--- a/Assets/_Project/Scripts/Client/Presentation/HudSystem.cs
+++ b/Assets/_Project/Scripts/Client/Presentation/HudSystem.cs
@@ -142,134 +142,10 @@ namespace ProjectM.Client
bool haveTick = SystemAPI.TryGetSingleton(out var nt);
int huskCount = _huskQuery.CalculateEntityCount();
- // ---- Macro banner: run-lifecycle header (the siege/cycle machinery is retired — LANTERN purge) ----
- bool haveRun = SystemAPI.TryGetSingleton(out var runInfo);
- bool onRun = haveRun && runInfo.Lifecycle != RunLifecycle.Staging;
- if (haveRun)
- {
- var col = onRun ? new Color(1f, 0.8f, 0.4f) : new Color(0.45f, 0.9f, 0.7f);
- _phaseText.text = onRun ? "ON EXPEDITION" : "AT BASE";
- _phaseText.style.color = col;
- _banner.style.borderBottomColor = col;
- RetintPanel(_banner, PanelDark);
- }
- else
- {
- _phaseText.text = "";
- }
-
- // ---- Location line (banner sub-line) — Step 14: driven by the replicated RunInfo lifecycle FSM ----
- var cam = Camera.main; // camera-X region signal still feeds downstream panels (atmosphere/threat)
- bool onExpedition = cam != null && cam.transform.position.x > ExpeditionRegionXMin;
- SystemAPI.TryGetSingleton(out var obj);
- if (haveRun)
- {
- switch (runInfo.Lifecycle)
- {
- case RunLifecycle.Staging:
- // The READY panel (bottom-center) owns the action + N/M count; the top line frames intent.
- _locationText.text = "AT THE BASE - build defenses, buy upgrades, READY UP to launch";
- _locationText.style.color = new Color(0.55f, 0.85f, 1f);
- break;
- case RunLifecycle.Launching:
- {
- // Wrap-safe countdown (post-impl review): the client's PREDICTED tick passes LaunchTick
- // near zero while Lifecycle is still Launching — signed TicksSince, never raw uint math.
- int secs = 0;
- if (runInfo.LaunchTick != 0 && SystemAPI.TryGetSingleton(out var ntime)
- && ntime.ServerTick.IsValid)
- {
- int ticksLeft = new NetworkTick(runInfo.LaunchTick).TicksSince(ntime.ServerTick);
- if (ticksLeft > 0) secs = ticksLeft / 60 + 1;
- }
- _locationText.text = "LAUNCHING IN " + secs + " - un-ready [T] to abort";
- _locationText.style.color = new Color(1f, 0.9f, 0.4f);
- break;
- }
- case RunLifecycle.InRoom:
- {
- string room = "ROOM " + (runInfo.CurrentRoom + 1) + "/" + runInfo.RoomCount
- + " " + RoomTypeLabel(runInfo.CurrentRoomType);
- _locationText.text = obj.State == ExpeditionObjectiveState.Active
- ? room + " - " + obj.Remaining + " enemies remaining"
- : room + " - clear it to advance";
- _locationText.style.color = new Color(1f, 0.8f, 0.4f);
- break;
- }
- case RunLifecycle.RoomReward:
- _locationText.text = "ROOM CLEARED - choose your boon";
- _locationText.style.color = new Color(0.5f, 1f, 0.6f);
- break;
- case RunLifecycle.RoomExplore: // Phase 0: without this case the RoomReward text stuck through the loot window
- _locationText.text = "ROOM CLEAR - grab the loot, take the portal to move on";
- _locationText.style.color = new Color(0.5f, 1f, 0.6f);
- break;
- case RunLifecycle.RouteSelect:
- _locationText.text = "CHOOSE YOUR PATH";
- _locationText.style.color = new Color(0.55f, 0.85f, 1f);
- break;
- case RunLifecycle.Returning:
- _locationText.text = "RETURNING HOME...";
- _locationText.style.color = new Color(0.7f, 0.9f, 1f);
- break;
- }
- }
- else
- {
- _locationText.text = "";
- }
-
-
-
- // The clickable READY panel (Staging/Launching). Counts are the replicated send-to-all PlayerReady flags.
- int rTotal = 0, rReady = 0;
- bool localReady = false;
- int launchSecs = 0;
- bool readyShow = haveRun
- && (runInfo.Lifecycle == RunLifecycle.Staging || runInfo.Lifecycle == RunLifecycle.Launching);
- if (readyShow)
- {
- foreach (var pr in SystemAPI.Query>().WithAll())
- {
- rTotal++;
- if (pr.ValueRO.Value != 0) rReady++;
- }
- foreach (var pr in SystemAPI.Query>().WithAll())
- localReady = pr.ValueRO.Value != 0;
- if (runInfo.Lifecycle == RunLifecycle.Launching && runInfo.LaunchTick != 0
- && SystemAPI.TryGetSingleton(out var lnt) && lnt.ServerTick.IsValid)
- {
- int tl = new NetworkTick(runInfo.LaunchTick).TicksSince(lnt.ServerTick);
- if (tl > 0) launchSecs = tl / 60 + 1;
- }
- }
- UpdateReadyPanel(readyShow, runInfo, rTotal, rReady, localReady, launchSecs);
-
- // Boss presence bar. The boss is a scaled Charger (EnemyTelegraph.Kind==KindCharger, baked/client-safe)
- // in the EXPEDITION region — filtering on both excludes phase-two summoned swarmers AND a base-region
- // siege enemy a dead teammate can see. Health.Max is now a [GhostField] (replicated x8 for the boss), so
- // the fraction reads true directly.
- bool bossAlive = false;
- float bossHp = 0f, bossMax = 0f;
- if (haveRun && runInfo.Lifecycle == RunLifecycle.InRoom && runInfo.CurrentRoomType == RoomTypeId.Boss)
- {
- foreach (var (bhq, tele, blt) in
- SystemAPI.Query, RefRO, RefRO>().WithAll())
- {
- if (tele.ValueRO.Kind != ZoneEnemyMath.KindCharger) continue; // the boss is a Charger; skip summoned swarmers
- if (blt.ValueRO.Position.x <= ExpeditionRegionXMin) continue; // expedition only (not a base siege enemy)
- if (bhq.ValueRO.Max > bossMax)
- {
- bossMax = bhq.ValueRO.Max;
- bossHp = bhq.ValueRO.Current;
- bossAlive = bhq.ValueRO.Current > 0f;
- }
- }
- }
- UpdateBossBar(bossAlive, bossHp, bossMax);
-
- // Run-depth dots — keeps the roguelite spine visible while fighting (the map only shows at gates).
- UpdateRunDepth(haveRun ? runInfo : default, haveRun);
+ // 2026-08-07 audit purge: the macro run banner, the location sub-line, the ready-check panel, the
+ // boss bar and the run-depth dots were all driven by RunInfo / ExpeditionObjective / PlayerReady —
+ // the superseded base/expedition FSM. All deleted; the HUD is now vitals + threat + resources + the
+ // ability bar (its own system).
// ---- Resources (feed palette affordability) ----
@@ -307,23 +183,6 @@ namespace ProjectM.Client
RetintPanel(_threatPanel, PanelDark);
}
- // ---- Build palette + control hints (bottom-center) ----
- UpdatePalette(aether, ore, bio, onExpedition);
- bool paletteOpen = BuildPaletteState.PaletteOpen && !onExpedition && _paletteBuilt;
- bool buildActive = paletteOpen && BuildPaletteState.Active;
- if (buildActive)
- {
- byte scheme = AimPresentation.Scheme;
- if (!_hintBuilt || _hintScheme != scheme) RebuildHints(scheme);
- _hintBar.style.display = DisplayStyle.Flex;
- }
- else
- {
- _hintBar.style.display = DisplayStyle.None;
- }
- // Build-mode discovery chip: a subtle "Tab/Y — BUILD" hint when the palette is hidden at base (Slice 1).
- _buildDiscoveryChip.style.display = (!onExpedition && !BuildPaletteState.PaletteOpen)
- ? DisplayStyle.Flex : DisplayStyle.None;
// ---- Per-player vitals ----
bool found = false;
@@ -348,7 +207,7 @@ namespace ProjectM.Client
break;
}
- _doc.rootVisualElement.style.display = (found || haveRun) ? DisplayStyle.Flex : DisplayStyle.None;
+ _doc.rootVisualElement.style.display = found ? DisplayStyle.Flex : DisplayStyle.None;
// ---- Low-health vignette + hurt flash (full-screen) ----
_flash = HudVisualMath.DecayFlash(_flash, dt);
@@ -389,51 +248,6 @@ namespace ProjectM.Client
_vignette.style.display = DisplayStyle.None;
_downed.style.display = DisplayStyle.None;
}
- // ---- Personal inventory (read-only; toggle with I, deposit-all with G via InventoryDepositSendSystem) ----
- var invKb = UnityEngine.InputSystem.Keyboard.current;
- if (invKb != null && invKb.iKey.wasPressedThisFrame) _invOpen = !_invOpen;
- if (_invOpen && found)
- {
- EntityManager.CompleteDependencyBeforeRO();
- EntityManager.CompleteDependencyBeforeRO();
- bool haveItemDb = SystemAPI.TryGetSingleton(out var itemDb);
- _invPanel.style.display = DisplayStyle.Flex;
-
- _invList.Clear();
- int shown = 0;
- foreach (var bag in SystemAPI.Query>()
- .WithAll())
- {
- for (int i = 0; i < bag.Length; i++)
- {
- var slot = bag[i];
- if (slot.ItemId == 0 || slot.Count <= 0) continue;
- AddInvRow(slot.ItemId, ItemName(haveItemDb, itemDb, slot.ItemId), ItemTint(slot.ItemId), slot.Count,
- IsEquippable(haveItemDb, itemDb, slot.ItemId));
- shown++;
- }
- break;
- }
- if (shown == 0)
- _invList.Add(HudUi.Text("(empty)", 13, MenuUi.SubCol, TextAnchor.MiddleLeft));
-
- _equipList.Clear();
- foreach (var slots in SystemAPI.Query>()
- .WithAll())
- {
- for (byte s = 0; s < EquipSlotId.Count && s < slots.Length; s++)
- {
- ushort id = slots[s].ItemId;
- string label = SlotName(s) + ": " + (id == 0 ? "-" : ItemName(haveItemDb, itemDb, id));
- AddEquipRow(s, label, id != 0);
- }
- break;
- }
- }
- else
- {
- _invPanel.style.display = DisplayStyle.None;
- }
}
// ---- per-frame helpers ----
@@ -446,94 +260,9 @@ namespace ProjectM.Client
- // LANTERN purge: the automation buildables are deleted; Pylon stays hidden from the build palette (cosmetic-only).
- static bool IsPaletteType(byte type) => type != StructureType.Pylon;
- void UpdatePalette(int aether, int ore, int bio, bool onExpedition)
- {
- if (!_paletteBuilt && SystemAPI.TryGetSingletonEntity(out var catE))
- {
- var cat = SystemAPI.GetBuffer(catE);
- for (int i = 0; i < cat.Length; i++)
- if (IsPaletteType(cat[i].Type))
- AddPaletteItem(cat[i].Type, cat[i].CostAmount, cat[i].CostResourceId);
- _paletteBuilt = true;
- }
- if (!_paletteBuilt) { _paletteRow.style.display = DisplayStyle.None; return; }
-
- bool showPalette = !onExpedition && BuildPaletteState.PaletteOpen;
- _paletteRow.style.display = showPalette ? DisplayStyle.Flex : DisplayStyle.None;
- foreach (var kv in _palette)
- {
- var item = kv.Value;
- int have = item.CostRes == ResourceId.Aether ? aether : item.CostRes == ResourceId.Biomass ? bio : ore;
- bool affordable = have >= item.CostAmount;
- bool selected = BuildPaletteState.Selected == kv.Key;
- item.Root.style.opacity = affordable ? 1f : 0.5f;
- item.Cost.style.color = affordable ? new Color(0.7f, 0.95f, 0.8f) : new Color(1f, 0.5f, 0.4f);
- if (item.Icon != null)
- item.Icon.style.unityBackgroundImageTintColor = affordable ? AetherCyan : new Color(0.5f, 0.55f, 0.6f);
- MenuUi.Border(item.Root, selected ? MenuUi.Accent : SlotIdleBorder, selected ? 2 : 1);
- item.Root.style.backgroundColor = selected ? SlotSelBg : SlotIdleBg;
- if (item.Glow != null) item.Glow.style.opacity = selected ? 0.6f : 0f;
- }
- }
-
- void AddPaletteItem(byte type, int cost, byte costRes)
- {
- if (type == 0 || _palette.ContainsKey(type)) return;
- var theme = HudTheme.Get();
-
- var root = new VisualElement();
- root.style.width = 86;
- root.style.marginLeft = 4; root.style.marginRight = 4;
- root.style.paddingTop = 8; root.style.paddingBottom = 6;
- root.style.alignItems = Align.Center;
- root.style.backgroundColor = SlotIdleBg;
- root.pickingMode = PickingMode.Position;
- MenuUi.Round(root, 6);
- MenuUi.Border(root, SlotIdleBorder, 1);
-
- // selection glow: a soft Synty glow filling the slot behind everything, opacity toggled on select.
- var glow = new VisualElement();
- glow.style.position = Position.Absolute;
- glow.style.left = 3; glow.style.right = 3; glow.style.top = 4; glow.style.bottom = 4;
- glow.pickingMode = PickingMode.Ignore;
- glow.style.opacity = 0f;
- if (theme != null && theme.Glow != null)
- {
- glow.style.backgroundImage = new StyleBackground(Background.FromSprite(theme.Glow));
- glow.style.unityBackgroundImageTintColor = AetherCyan;
- glow.style.backgroundSize = new StyleBackgroundSize(new BackgroundSize(BackgroundSizeType.Cover));
- }
- root.Add(glow);
-
- var iconEl = HudUi.Icon(theme != null ? theme.StructureIcon(type) : null, 44, AetherCyan);
- root.Add(iconEl);
-
- var nameLabel = HudUi.Text(StructureName(type), 12, MenuUi.TextCol, TextAnchor.MiddleCenter);
- nameLabel.style.marginTop = 2;
- root.Add(nameLabel);
-
- var costRow = new VisualElement();
- costRow.style.flexDirection = FlexDirection.Row;
- costRow.style.alignItems = Align.Center;
- costRow.style.marginTop = 2;
- costRow.pickingMode = PickingMode.Ignore;
- var costIcon = HudUi.Icon(ResourceSprite(theme, costRes), 14, ResourceTint(costRes));
- costIcon.style.marginRight = 3;
- costRow.Add(costIcon);
- var costLabel = HudUi.Display(cost.ToString(), 13, new Color(0.7f, 0.95f, 0.8f), TextAnchor.MiddleCenter);
- costRow.Add(costLabel);
- root.Add(costRow);
-
- byte t = type;
- root.RegisterCallback(_ =>
- BuildPaletteState.Select(BuildPaletteState.Selected == t ? (byte)0 : t));
-
- _paletteRow.Add(root);
- _palette[type] = new PaletteItem { Root = root, Cost = costLabel, CostAmount = cost, CostRes = costRes, Glow = glow, Icon = iconEl };
- }
+ // 2026-08-07 audit purge: AddPaletteItem built one build-palette slot (icon, cost row, selection glow,
+ // click-to-select). The build palette went with the structures layer.
void RebuildHints(byte scheme)
{
@@ -583,11 +312,7 @@ namespace ProjectM.Client
BuildThreat(root);
BuildMacro(root);
BuildResources(root);
- BuildPaletteRow(root);
- BuildHintBar(root);
- BuildDiscoveryChip(root);
BuildDowned(root);
- BuildInventory(root);
}
void BuildVignette(VisualElement root)
@@ -827,119 +552,9 @@ namespace ProjectM.Client
- void BuildInventory(VisualElement root)
- {
- _invPanel = HudUi.Panel(PanelDark);
- _invPanel.style.position = Position.Absolute;
- _invPanel.style.right = 40; _invPanel.style.bottom = 40;
- _invPanel.style.minWidth = 224;
- _invPanel.style.paddingLeft = 14; _invPanel.style.paddingRight = 14;
- _invPanel.style.paddingTop = 10; _invPanel.style.paddingBottom = 10;
- _invPanel.style.alignItems = Align.FlexStart;
- _invPanel.pickingMode = PickingMode.Ignore;
-
- var header = HudUi.Display("INVENTORY", 16, AetherCyan, TextAnchor.MiddleLeft);
- header.style.marginBottom = 6;
- _invPanel.Add(header);
-
- _invList = new VisualElement();
- _invList.pickingMode = PickingMode.Ignore;
- _invPanel.Add(_invList);
-
- var equipHeader = HudUi.Display("EQUIPMENT", 14, AetherCyan, TextAnchor.MiddleLeft);
- equipHeader.style.marginTop = 8; equipHeader.style.marginBottom = 4;
- _invPanel.Add(equipHeader);
-
- _equipList = new VisualElement();
- _equipList.pickingMode = PickingMode.Ignore;
- _invPanel.Add(_equipList);
-
- var hint = HudUi.Text("I close - click item=equip / slot=unequip - G deposit", 11, MenuUi.SubCol, TextAnchor.MiddleLeft);
- hint.style.marginTop = 8;
- _invPanel.Add(hint);
-
- _invPanel.style.display = DisplayStyle.None;
- root.Add(_invPanel);
- }
-
- void AddInvRow(ushort itemId, string name, Color tint, int count, bool equippable)
- {
- var row = new VisualElement();
- row.style.flexDirection = FlexDirection.Row;
- row.style.justifyContent = Justify.SpaceBetween;
- row.style.minWidth = 196;
- row.style.marginTop = 2;
- row.Add(HudUi.Text(name + (equippable ? " (equip)" : ""), 13, tint, TextAnchor.MiddleLeft));
- row.Add(HudUi.Display("x" + count, 13, Color.white, TextAnchor.MiddleRight));
- if (equippable)
- {
- row.pickingMode = PickingMode.Position;
- ushort id = itemId;
- row.RegisterCallback(_ => EquipSendSystem.Equip(id));
- }
- else row.pickingMode = PickingMode.Ignore;
- _invList.Add(row);
- }
-
- static string ItemName(bool haveDb, ItemDatabase db, ushort id)
- {
- if (haveDb && db.Value.IsCreated)
- {
- ref var blob = ref db.Value.Value;
- if (blob.TryGetItem(id, out var def)) return def.Name.ToString();
- }
- if (id == ResourceId.Aether) return "Aether";
- if (id == ResourceId.Ore) return "Ore";
- if (id == ResourceId.Biomass) return "Biomass";
- return "Item " + id;
- }
-
- static Color ItemTint(ushort id)
- {
- if (id == ResourceId.Aether) return AetherCyan;
- if (id == ResourceId.Ore) return OreAmber;
- if (id == ResourceId.Biomass) return BioGreen;
- return new Color(0.85f, 0.85f, 0.9f);
- }
- static bool IsEquippable(bool haveDb, ItemDatabase db, ushort id)
- {
- if (!haveDb || !db.Value.IsCreated) return false;
- ref var b = ref db.Value.Value;
- return b.TryGetItem(id, out var def) && def.EquipSlot < EquipSlotId.Count;
- }
-
- static string SlotName(byte slot)
- {
- switch (slot)
- {
- case EquipSlotId.Weapon: return "Weapon";
- case EquipSlotId.Armor: return "Armor";
- case EquipSlotId.Trinket: return "Trinket";
- case EquipSlotId.Tool: return "Tool";
- default: return "Slot " + slot;
- }
- }
-
- void AddEquipRow(byte slot, string label, bool occupied)
- {
- var row = new VisualElement();
- row.style.flexDirection = FlexDirection.Row;
- row.style.justifyContent = Justify.SpaceBetween;
- row.style.minWidth = 196;
- row.style.marginTop = 2;
- row.Add(HudUi.Text(label, 13, occupied ? AetherCyan : MenuUi.SubCol, TextAnchor.MiddleLeft));
- if (occupied)
- {
- row.pickingMode = PickingMode.Position;
- byte s = slot;
- row.RegisterCallback(_ => EquipSendSystem.Unequip(s));
- row.Add(HudUi.Text("unequip", 11, new Color(1f, 0.6f, 0.5f), TextAnchor.MiddleRight));
- }
- else row.pickingMode = PickingMode.Ignore;
- _equipList.Add(row);
- }
-
-
+ // 2026-08-07 audit purge: BuildInventory / AddInvRow / ItemName / ItemTint / IsEquippable / SlotName /
+ // AddEquipRow drove the personal-inventory + equipment strip. That layer was already PAUSED in CLAUDE.md
+ // and went with the shell.
static Color ResourceTint(byte resId)
=> resId == ResourceId.Aether ? AetherCyan : resId == ResourceId.Biomass ? BioGreen : OreAmber;
@@ -954,213 +569,10 @@ namespace ProjectM.Client
- static string StructureName(byte type)
- {
- switch (type)
- {
- case StructureType.Wall: return "Wall";
- case StructureType.Pylon: return "Pylon";
- default: return "?";
- }
- }
-
- // ==== Step 14: boon modal + route panel (lazy-built overlays; clicks -> client send statics) ====
-
-
- static string RoomTypeLabel(byte roomType) => roomType == RoomTypeId.Boss ? "[BOSS]"
- : roomType == RoomTypeId.Elite ? "[ELITE]"
- : roomType == RoomTypeId.Reward ? "[REWARD]" : "[COMBAT]";
-
-
-
-
-
- // ---- the drawn branching route map (Slay-the-Spire style; display regen from RunSeed, clicks bind
- // to the authoritative RouteOpt* bytes) ----
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- // ---- the clickable READY panel (Staging: toggle + party pips; Launching: countdown + abort) ----
-
- void UpdateReadyPanel(bool show, RunInfo runInfo, int total, int ready, bool localReady, int launchSecs)
- {
- if (!show)
- {
- if (_readyPanel != null) _readyPanel.style.display = DisplayStyle.None;
- _readyShownFor = 0;
- return;
- }
- var root = _doc != null ? _doc.rootVisualElement : null;
- if (root == null) return;
- if (!_readyPanelBuilt)
- {
- BuildReadyPanel(root);
- _readyPanelBuilt = true;
- }
-
- bool launching = runInfo.Lifecycle == RunLifecycle.Launching;
- int sig = 1 + ready + (total << 4) + (localReady ? 1 << 8 : 0) + (launchSecs << 9) + (launching ? 1 << 16 : 0);
- if (_readyShownFor != sig)
- {
- _readyTitle.text = launching
- ? "LAUNCHING IN " + launchSecs
- : "EXPEDITION — " + ready + "/" + Mathf.Max(total, 1) + " READY";
- _readyTitle.style.color = launching ? new Color(1f, 0.9f, 0.4f) : new Color(0.55f, 0.85f, 1f);
- _readyBtn.text = launching ? "ABORT [T]" : localReady ? "UNREADY [T]" : "READY UP [T]";
- _readyPipRow.Clear();
- for (int i = 0; i < total; i++)
- {
- var pip = new VisualElement();
- pip.style.width = 14; pip.style.height = 14;
- pip.style.marginLeft = 3; pip.style.marginRight = 3;
- MenuUi.Round(pip, 7f);
- pip.style.backgroundColor = i < ready
- ? new Color(0.45f, 0.95f, 0.55f) : new Color(1f, 1f, 1f, 0.15f);
- _readyPipRow.Add(pip);
- }
- _readyShownFor = sig;
- }
- _readyPanel.style.display = DisplayStyle.Flex;
- }
-
- void BuildReadyPanel(VisualElement root)
- {
- _readyPanel = new VisualElement { pickingMode = PickingMode.Ignore };
- _readyPanel.style.position = Position.Absolute;
- _readyPanel.style.left = 0; _readyPanel.style.right = 0;
- _readyPanel.style.bottom = 170; // clear of the build palette row + hint bar
- _readyPanel.style.alignItems = Align.Center;
- _readyPanel.style.display = DisplayStyle.None;
-
- var box = new VisualElement { pickingMode = PickingMode.Position };
- box.style.backgroundColor = new Color(0.07f, 0.09f, 0.12f, 0.92f);
- box.style.borderTopLeftRadius = 10; box.style.borderTopRightRadius = 10;
- box.style.borderBottomLeftRadius = 10; box.style.borderBottomRightRadius = 10;
- box.style.paddingLeft = 18; box.style.paddingRight = 18;
- box.style.paddingTop = 10; box.style.paddingBottom = 12;
- box.style.alignItems = Align.Center;
-
- _readyTitle = new Label("EXPEDITION");
- _readyTitle.style.fontSize = 16;
- _readyTitle.style.unityFontStyleAndWeight = FontStyle.Bold;
- box.Add(_readyTitle);
-
- _readyPipRow = new VisualElement();
- _readyPipRow.style.flexDirection = FlexDirection.Row;
- _readyPipRow.style.justifyContent = Justify.Center;
- _readyPipRow.style.marginTop = 6; _readyPipRow.style.marginBottom = 8;
- box.Add(_readyPipRow);
-
- _readyBtn = MenuUi.Button("READY UP [T]", ReadySendSystem.ToggleReady);
- box.Add(_readyBtn);
-
- _readyPanel.Add(box);
- root.Add(_readyPanel);
- }
-
- // ---- boss presence bar (Boss rooms only; red, top-center, under the macro banner) ----
-
- void UpdateBossBar(bool alive, float hp, float max)
- {
- if (!alive || max <= 0f)
- {
- if (_bossPanel != null) _bossPanel.style.display = DisplayStyle.None;
- return;
- }
- var root = _doc != null ? _doc.rootVisualElement : null;
- if (root == null) return;
- if (!_bossBarBuilt)
- {
- BuildBossBar(root);
- _bossBarBuilt = true;
- }
- HudUi.SetFill(_bossFill, Mathf.Clamp01(hp / max));
- _bossText.text = "ALPHA HUSK " + Mathf.CeilToInt(Mathf.Max(0f, hp)) + " / " + Mathf.CeilToInt(max);
- _bossPanel.style.display = DisplayStyle.Flex;
- }
-
- void BuildBossBar(VisualElement root)
- {
- _bossPanel = new VisualElement { pickingMode = PickingMode.Ignore };
- _bossPanel.style.position = Position.Absolute;
- _bossPanel.style.left = 0; _bossPanel.style.right = 0;
- _bossPanel.style.top = 168;
- _bossPanel.style.alignItems = Align.Center;
- _bossPanel.style.display = DisplayStyle.None;
-
- var col = HudUi.Group(Align.Center);
- _bossText = HudUi.Display("ALPHA HUSK", 22, new Color(1f, 0.35f, 0.3f), TextAnchor.MiddleCenter);
- col.Add(_bossText);
- var bar = HudUi.Bar(420, 12, new Color(0.92f, 0.22f, 0.18f), out _bossFill);
- bar.style.marginTop = 4;
- col.Add(bar);
- _bossPanel.Add(col);
- root.Add(_bossPanel);
- }
-
- // ---- run-depth dots (visible through the whole run; the current room pulses bigger) ----
-
- void UpdateRunDepth(RunInfo runInfo, bool haveRun)
- {
- bool show = haveRun && runInfo.RoomCount > 0
- && (runInfo.Lifecycle == RunLifecycle.InRoom
- || runInfo.Lifecycle == RunLifecycle.RoomReward
- || runInfo.Lifecycle == RunLifecycle.RouteSelect);
- if (!show)
- {
- if (_depthPanel != null) _depthPanel.style.display = DisplayStyle.None;
- _depthShownFor = 0;
- return;
- }
- var root = _doc != null ? _doc.rootVisualElement : null;
- if (root == null) return;
- if (!_depthBuilt)
- {
- _depthPanel = new VisualElement { pickingMode = PickingMode.Ignore };
- _depthPanel.style.position = Position.Absolute;
- _depthPanel.style.left = 0; _depthPanel.style.right = 0;
- _depthPanel.style.top = 208; // below the macro cluster (goal/core) AND the boss bar (168)
- _depthPanel.style.flexDirection = FlexDirection.Row;
- _depthPanel.style.justifyContent = Justify.Center;
- root.Add(_depthPanel);
- _depthBuilt = true;
- }
- int sig = 1 + runInfo.CurrentRoom * 37 + runInfo.RoomCount * 3;
- if (_depthShownFor != sig)
- {
- _depthPanel.Clear();
- for (int i = 0; i < runInfo.RoomCount; i++)
- {
- bool current = i == runInfo.CurrentRoom;
- bool done = i < runInfo.CurrentRoom;
- var dot = new VisualElement { pickingMode = PickingMode.Ignore };
- float size = current ? 12f : 8f;
- dot.style.width = size; dot.style.height = size;
- dot.style.marginLeft = 3; dot.style.marginRight = 3;
- dot.style.alignSelf = Align.Center;
- MenuUi.Round(dot, size * 0.5f);
- dot.style.backgroundColor = current ? new Color(0.55f, 0.85f, 1f)
- : done ? new Color(0.55f, 0.85f, 1f, 0.55f)
- : new Color(1f, 1f, 1f, 0.16f);
- _depthPanel.Add(dot);
- }
- _depthShownFor = sig;
- }
- _depthPanel.style.display = DisplayStyle.Flex;
- }
+ // 2026-08-07 audit purge: StructureName, RoomTypeLabel, the branching route map, the ready panel, the
+ // boss presence bar and the run-depth dots all belonged to the superseded base/expedition loop and are
+ // deleted along with RunInfo / PlayerReady / BossState / StructureCatalog. Recover from git if the
+ // roguelite spine returns.
diff --git a/Assets/_Project/Scripts/Client/Presentation/MetaShopHudSystem.cs b/Assets/_Project/Scripts/Client/Presentation/MetaShopHudSystem.cs
deleted file mode 100644
index 32de8a67c..000000000
--- a/Assets/_Project/Scripts/Client/Presentation/MetaShopHudSystem.cs
+++ /dev/null
@@ -1,197 +0,0 @@
-using System.Collections.Generic;
-using ProjectM.Simulation;
-using Unity.Entities;
-using Unity.NetCode;
-using Unity.Transforms;
-using Unity.Mathematics;
-using UnityEngine;
-using UnityEngine.UIElements;
-
-namespace ProjectM.Client
-{
- ///
- /// The Staging-only permanent-upgrade shop (meta shop) — extracted from into its own
- /// client-only, observe-only presentation in .
- /// Owns its own runtime UIDocument sharing (sortingOrder 51). Recomputes
- /// its inputs locally: the local class from the replicated
- /// (), Aether from the buffer, siege from
- /// , and the Staging gate from . Row clicks enqueue through
- /// — the server re-validates everything.
- ///
- [WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]
- [UpdateInGroup(typeof(PresentationSystemGroup))]
- public partial class MetaShopHudSystem : SystemBase
- {
- GameObject _go;
- UIDocument _doc;
- bool _built;
-
- VisualElement _metaPanel, _metaRowsHost;
- Label _metaShopTitle;
- bool _metaShopBuilt;
- int _metaShownFor; // last (class, tiers, aether) signature the shop rows were built for
-
- protected override void OnStartRunning()
- {
- if (_go != null) return;
- MenuUi.EnsureEventSystem();
- _go = new GameObject("~HUDMetaShop");
- _doc = _go.AddComponent();
- _doc.panelSettings = MenuUi.LoadPanelSettings();
- _doc.sortingOrder = 51;
- }
-
- protected override void OnDestroy()
- {
- if (_go != null) Object.Destroy(_go);
- }
-
- protected override void OnUpdate()
- {
- if (_doc == null) return;
- var root = _doc.rootVisualElement;
- if (root == null) return; // panel not initialised yet (next frame)
- if (!_built)
- {
- root.style.position = Position.Absolute;
- root.style.left = 0; root.style.right = 0; root.style.top = 0; root.style.bottom = 0;
- root.pickingMode = PickingMode.Ignore; // never eat game-world clicks
- _built = true;
- }
-
- bool haveRun = SystemAPI.TryGetSingleton(out var runInfo);
-
- // Aether from the ledger (the sole meta-shop currency; last entry wins, matching the core loop).
- int aether = 0;
- if (SystemAPI.TryGetSingletonEntity(out var ledgerE))
- {
- var buf = SystemAPI.GetBuffer(ledgerE);
- for (int i = 0; i < buf.Length; i++)
- if (buf[i].ItemId == ResourceId.Aether) aether = buf[i].Count;
- }
-
- // Local class derives from the replicated AbilityRef (tracks the dev class-switch; PlayerClass is
- // server-only); tiers from the replicated MetaTierState record on the director ghost.
- byte localClass = ClassTraits.WarriorClass;
- bool haveLocalPlayer = false;
- foreach (var fr in SystemAPI.Query>().WithAll())
- {
- localClass = ClassTraits.Normalize(fr.ValueRO.Value); // FrameId is the sole class signal (legacy AbilityRef deleted)
- haveLocalPlayer = true;
- break;
- }
-
- bool metaShow = false;
- BlobAssetReference metaPool = default;
- DynamicBuffer metaRecord = default;
- if (haveRun && runInfo.Lifecycle == RunLifecycle.Staging && haveLocalPlayer
- && SystemAPI.TryGetSingleton(out var metaCat) && metaCat.Value.IsCreated
- && SystemAPI.TryGetSingletonBuffer(out metaRecord, true))
- {
- metaPool = metaCat.Value;
- metaShow = true;
- }
- UpdateMetaShop(metaShow, localClass, aether, metaPool, metaRecord);
- }
-
- void UpdateMetaShop(bool show, byte classId, int aether,
- BlobAssetReference pool, DynamicBuffer record)
- {
- if (!show)
- {
- if (_metaPanel != null) _metaPanel.style.display = DisplayStyle.None;
- _metaShownFor = 0;
- return;
- }
- var root = _doc != null ? _doc.rootVisualElement : null;
- if (root == null) return;
- if (!_metaShopBuilt)
- {
- BuildMetaShop(root);
- _metaShopBuilt = true;
- }
-
- // Rebuild the rows only when class / owned tiers / affordability actually change (Staging-only, <=8 rows).
- int sig = classId * 131 ^ aether * 31;
- for (int i = 0; i < record.Length; i++)
- sig ^= (record[i].ClassId * 7 + record[i].UpgradeId * 13 + record[i].Tier) * (i + 3);
- if (sig == 0) sig = 1;
- if (_metaShownFor != sig)
- {
- _metaRowsHost.Clear();
- _metaShopTitle.text = (classId == ClassTraits.RangerClass ? "RANGER" : "WARRIOR")
- + " PERMANENT UPGRADES - AETHER " + aether;
- ref var defs = ref pool.Value;
- byte classBit = BoonMath.MaskFor(classId);
- for (int d = 0; d < defs.Defs.Length; d++)
- {
- if ((defs.Defs[d].ClassMask & classBit) == 0) continue;
- byte id = defs.Defs[d].Id;
- byte owned = MetaMath.TierOf(record, classId, id);
- if (owned > defs.Defs[d].MaxTier) owned = defs.Defs[d].MaxTier; // D-F5 display clamp (seed AND spend AND shop)
- bool maxed = owned >= defs.Defs[d].MaxTier;
- int cost = MetaMath.CostForTier(in defs.Defs[d], owned);
- string label = defs.Defs[d].Name.ToString()
- + (maxed ? " MAXED" : " - " + cost + " Aether")
- + "\n" + defs.Defs[d].Desc.ToString();
- byte buyId = id; // closure copy, never the loop variable
- var row = MenuUi.Button(label, () => MetaSpendSendSystem.RequestPurchase(buyId));
- row.style.width = 290;
- row.style.height = StyleKeyword.Auto; // two-line labels must grow the row (overlap fix)
- row.style.paddingTop = 6; row.style.paddingBottom = 6;
- row.style.marginBottom = 4;
- row.style.whiteSpace = WhiteSpace.Normal;
- row.style.unityTextAlign = TextAnchor.MiddleLeft;
- row.SetEnabled(!maxed && aether >= cost); // honest UI; the server re-validates everything anyway
- // Owned-tier pips (replaces the "[2/5]" text — reads at a glance).
- var pipRow = new VisualElement { pickingMode = PickingMode.Ignore };
- pipRow.style.flexDirection = FlexDirection.Row;
- pipRow.style.marginTop = 3;
- for (int p = 0; p < defs.Defs[d].MaxTier; p++)
- {
- var tp = new VisualElement { pickingMode = PickingMode.Ignore };
- tp.style.width = 9; tp.style.height = 9;
- tp.style.marginRight = 3;
- MenuUi.Round(tp, 4.5f);
- tp.style.backgroundColor = p < owned ? MenuUi.Accent : new Color(1f, 1f, 1f, 0.14f);
- pipRow.Add(tp);
- }
- row.Add(pipRow);
- _metaRowsHost.Add(row);
- }
- _metaShownFor = sig;
- }
- _metaPanel.style.display = DisplayStyle.Flex;
- }
-
- void BuildMetaShop(VisualElement root)
- {
- _metaPanel = new VisualElement { pickingMode = PickingMode.Ignore };
- _metaPanel.style.position = Position.Absolute;
- _metaPanel.style.right = 12;
- _metaPanel.style.top = Length.Percent(22);
- _metaPanel.style.alignItems = Align.FlexEnd;
- _metaPanel.style.display = DisplayStyle.None;
-
- var box = new VisualElement();
- box.style.backgroundColor = new Color(0.07f, 0.09f, 0.12f, 0.92f);
- box.style.borderTopLeftRadius = 10; box.style.borderTopRightRadius = 10;
- box.style.borderBottomLeftRadius = 10; box.style.borderBottomRightRadius = 10;
- box.style.paddingLeft = 12; box.style.paddingRight = 12;
- box.style.paddingTop = 10; box.style.paddingBottom = 10;
-
- _metaShopTitle = new Label("PERMANENT UPGRADES");
- _metaShopTitle.style.color = MenuUi.Accent;
- _metaShopTitle.style.fontSize = 14;
- _metaShopTitle.style.unityFontStyleAndWeight = FontStyle.Bold;
- _metaShopTitle.style.marginBottom = 8;
- box.Add(_metaShopTitle);
-
- _metaRowsHost = new VisualElement();
- box.Add(_metaRowsHost);
-
- _metaPanel.Add(box);
- root.Add(_metaPanel);
- }
- }
-}
diff --git a/Assets/_Project/Scripts/Client/Presentation/MetaShopHudSystem.cs.meta b/Assets/_Project/Scripts/Client/Presentation/MetaShopHudSystem.cs.meta
deleted file mode 100644
index 95d6ce937..000000000
--- a/Assets/_Project/Scripts/Client/Presentation/MetaShopHudSystem.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: 2551683f48286ae41980c5bb4e48d6e1
\ No newline at end of file
diff --git a/Assets/_Project/Scripts/Client/Presentation/MusicSystem.cs b/Assets/_Project/Scripts/Client/Presentation/MusicSystem.cs
index a363d6093..cc000d825 100644
--- a/Assets/_Project/Scripts/Client/Presentation/MusicSystem.cs
+++ b/Assets/_Project/Scripts/Client/Presentation/MusicSystem.cs
@@ -78,30 +78,9 @@ namespace ProjectM.Client
// ---- pick the mix from replicated state (defaults = quiet staging bed) ----
float tBass = 0.50f, tPad = 0.55f, tArp = 0.12f, tPulse = 0f;
- if (SystemAPI.TryGetSingleton(out var run))
- {
- switch (run.Lifecycle)
- {
- case RunLifecycle.Launching:
- tBass = 0.55f; tPad = 0.50f; tArp = 0.35f; tPulse = 0.20f; // anticipation swell
- break;
- case RunLifecycle.InRoom:
- bool boss = run.CurrentRoomType == RoomTypeId.Boss;
- tBass = boss ? 0.75f : 0.65f;
- tPad = 0.40f;
- tArp = boss ? 0.80f : 0.65f;
- tPulse = boss ? 0.75f : 0.35f;
- break;
- case RunLifecycle.RoomReward:
- case RunLifecycle.RouteSelect:
- tBass = 0.45f; tPad = 0.55f; tArp = 0.25f; tPulse = 0.08f; // between-rooms lull
- break;
- case RunLifecycle.Returning:
- tBass = 0.45f; tPad = 0.60f; tArp = 0.15f; tPulse = 0f; // resolution
- break;
- // Staging keeps the defaults.
- }
- }
+ // 2026-08-07 audit purge: the mix used to key off RunInfo.Lifecycle (staging / launching / in-room /
+ // boss / returning). With the run FSM deleted the bed holds its defaults; re-key it off LANTERN's
+ // descent state when Phase 2 lands.
float dt = SystemAPI.Time.DeltaTime * FadePerSecond;
_vBass = Mathf.MoveTowards(_vBass, tBass, dt);
diff --git a/Assets/_Project/Scripts/Client/Presentation/RoomDressingConfig.cs b/Assets/_Project/Scripts/Client/Presentation/RoomDressingConfig.cs
deleted file mode 100644
index 73acb30ff..000000000
--- a/Assets/_Project/Scripts/Client/Presentation/RoomDressingConfig.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-using UnityEngine;
-
-namespace ProjectM.Client
-{
- ///
- /// Live-tunable knobs + build-safe prefab references for — the Phase 1.5b
- /// per-room cosmetic dressing scatter. Mirrors the /
- /// bridge idiom: a MonoBehaviour in the gameplay scene with a static the client
- /// presentation system reads. Prefab arrays are serialized scene references (never Resources.Load — that is
- /// build-stripped). Absent config or an empty set = no dressing (the feature is soft-off).
- ///
- public sealed class RoomDressingConfig : MonoBehaviour
- {
- public static RoomDressingConfig Instance;
-
- [Header("Master")]
- public bool Enabled = true;
- [Min(0)] public int CountPerRoom = 26;
- [Min(0.1f)] public float ScaleMin = 0.85f;
- [Min(0.1f)] public float ScaleMax = 1.35f;
-
- [Header("Per-biome cosmetic prop sets (RoomBiomeId: Meadow/Arid/Cavern/Blight)")]
- public GameObject[] Meadow;
- public GameObject[] Arid;
- public GameObject[] Cavern;
- public GameObject[] Blight;
-
- void OnEnable() { Instance = this; }
- void OnDisable() { if (Instance == this) Instance = null; }
-
- [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
- static void ResetStatics() { Instance = null; }
- }
-}
diff --git a/Assets/_Project/Scripts/Client/Presentation/RoomDressingConfig.cs.meta b/Assets/_Project/Scripts/Client/Presentation/RoomDressingConfig.cs.meta
deleted file mode 100644
index e34a7e4ba..000000000
--- a/Assets/_Project/Scripts/Client/Presentation/RoomDressingConfig.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: 4ef612b567d6f6746bc03f764274740e
\ No newline at end of file
diff --git a/Assets/_Project/Scripts/Client/Presentation/RoomDressingSystem.cs b/Assets/_Project/Scripts/Client/Presentation/RoomDressingSystem.cs
deleted file mode 100644
index 51dd99e2f..000000000
--- a/Assets/_Project/Scripts/Client/Presentation/RoomDressingSystem.cs
+++ /dev/null
@@ -1,172 +0,0 @@
-using ProjectM.Simulation;
-using Unity.Entities;
-using Unity.Mathematics;
-using UnityEngine;
-
-namespace ProjectM.Client
-{
- ///
- /// Phase 1.5b (ground bundle) — client-only, observe-only PER-ROOM DRESSING SCATTER. A managed
- /// in that OBSERVES replicated
- /// and instantiates biome-flavoured cosmetic ground props (pebbles, tufts, bones,
- /// mushrooms) inside the room's ACTUAL shape — the same authority the server
- /// scatter uses, on a DISTINCT hash stream so dressing never co-locates with nodes (0x0DE) or clutter
- /// (0xC17). Deterministic from the replicated run seed: every co-op client (and a late-joiner) sees the
- /// same floor. Torn down when the room changes or the run ends. Colliders + rigidbodies are stripped on
- /// spawn — the aim-reticle raycast and the physics world must never see dressing. Never mutates the sim.
- ///
- /// Bundle 2 (decals): also lays a few persistent feathered crack/scorch DETAIL scars at the arena centre
- /// (higher-frequency detail the baked ground splat can't resolve), on its own hash sub-stream, parented to
- /// the same dressing root so they tear down together. Base POIs are skipped — Part O baked their wear in.
- ///
- ///
- [WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]
- [UpdateInGroup(typeof(PresentationSystemGroup))]
- public partial class RoomDressingSystem : SystemBase
- {
- Transform _root;
- uint _activeKey;
-
- // Bundle 2 arena-scar decal assets (reused across rebuilds; the scar GameObjects are children of _root and
- // torn down each room change, these shared assets persist until OnDestroy).
- Material _decalMat;
- Mesh[] _decalMeshes;
- MaterialPropertyBlock _decalMpb;
- static readonly int DecalColorId = Shader.PropertyToID("_Color");
-
- protected override void OnStartRunning()
- {
- if (_root == null) _root = new GameObject("~RoomDressing").transform;
- }
-
- protected override void OnDestroy()
- {
- if (_root != null) Object.Destroy(_root.gameObject);
- if (_decalMat != null) Object.Destroy(_decalMat);
- if (_decalMeshes != null)
- for (int i = 0; i < _decalMeshes.Length; i++)
- if (_decalMeshes[i] != null) Object.Destroy(_decalMeshes[i]);
- }
-
- protected override void OnUpdate()
- {
- if (!ScenePolicy.IsGameplayScene()) return;
- if (_root == null) return;
-
- var cfg = RoomDressingConfig.Instance;
- bool inRoom = SystemAPI.TryGetSingleton(out var ri)
- && (ri.Lifecycle == RunLifecycle.InRoom
- || ri.Lifecycle == RunLifecycle.RoomReward
- || ri.Lifecycle == RunLifecycle.RoomExplore);
- bool on = cfg != null && cfg.Enabled && cfg.CountPerRoom > 0;
- if (!inRoom || !on || !SystemAPI.TryGetSingleton(out var anchor))
- {
- Clear();
- return;
- }
-
- uint key = RunMapMath.Hash(ri.RunSeed, (uint)(ri.CurrentRoom + 1), 0xD2E5u);
- if (key == _activeKey) return;
- Clear();
- Build(in ri, in anchor, cfg);
- _activeKey = key;
- }
-
- void Clear()
- {
- if (_activeKey == 0 || _root == null) { _activeKey = 0; return; }
- for (int i = _root.childCount - 1; i >= 0; i--)
- Object.Destroy(_root.GetChild(i).gameObject);
- _activeKey = 0;
- }
-
- void Build(in RunInfo ri, in BaseAnchor anchor, RoomDressingConfig cfg)
- {
- GameObject[] set = ri.CurrentBiome switch
- {
- RoomBiomeId.Meadow => cfg.Meadow,
- RoomBiomeId.Cavern => cfg.Cavern,
- RoomBiomeId.Blight => cfg.Blight,
- _ => cfg.Arid,
- };
- if (set == null || set.Length == 0) return;
-
- float3 baseCenter = BaseGridMath.PlotCenter(anchor);
- byte subSlot = (byte)(ri.CurrentRoom & 1);
- float3 origin = RegionMath.ExpeditionRoomOrigin(baseCenter, subSlot);
- float3 portal = RegionMath.ExpeditionPortalPos(baseCenter, subSlot);
-
- var map = RunMapMath.Generate(ri.RunSeed);
- var node = map.NodeAt(RunMap.NodeId(ri.CurrentRoom, ri.CurrentCol));
- var plan = RoomLayoutMath.Plan(node, ri.CurrentRoom, ri.RoomCount);
- var rng = new Unity.Mathematics.Random(RunMapMath.Hash(ri.RunSeed, (uint)(ri.CurrentRoom + 1), 0xD2E55u) | 1u);
-
- int count = cfg.CountPerRoom;
- for (int i = 0; i < count; i++)
- {
- float3 pos = RoomLayoutMath.ScatterInShape(plan.ShapeId, origin, i, count, ref rng);
- for (int attempt = 0; attempt < 4; attempt++)
- {
- bool nearLanding = math.distancesq(pos.xz, origin.xz) < 9f; // party lands at the origin
- bool nearPortal = math.distancesq(pos.xz, portal.xz) < 6.25f; // keep the exit beacon clear
- if (!nearLanding && !nearPortal) break;
- pos = RoomLayoutMath.ScatterInShape(plan.ShapeId, origin, i, count, ref rng);
- }
- var prefab = set[rng.NextInt(0, set.Length)];
- if (prefab == null) continue;
- var go = Object.Instantiate(prefab, _root, false);
- go.transform.SetPositionAndRotation(
- new Vector3(pos.x, 0f, pos.z), // terrain y=0 (origin.y is the CC capsule plane, 1 u up)
- Quaternion.Euler(0f, rng.NextFloat(0f, 360f), 0f));
- go.transform.localScale = Vector3.one * rng.NextFloat(cfg.ScaleMin, cfg.ScaleMax);
- foreach (var col in go.GetComponentsInChildren(true)) Object.Destroy(col);
- foreach (var rb in go.GetComponentsInChildren(true)) Object.Destroy(rb);
- }
-
- // Bundle 2 arena scars: distinct hash sub-stream (0xD2E56) so they never co-locate with the prop
- // scatter (0xD2E55); clustered around the arena centre. Persistent (no fade); torn down with dressing.
- if (DecalConfig.Enabled && DecalConfig.RoomArenaDecalCount > 0)
- {
- EnsureDecalAssets();
- var srng = new Unity.Mathematics.Random(RunMapMath.Hash(ri.RunSeed, (uint)(ri.CurrentRoom + 1), 0xD2E56u) | 1u);
- for (int i = 0; i < DecalConfig.RoomArenaDecalCount; i++)
- SpawnArenaScar(origin, i, ref srng);
- }
- }
-
- void EnsureDecalAssets()
- {
- if (_decalMat == null) _decalMat = FeedbackFx.MakeDecalMaterial("RoomScarDecal");
- if (_decalMpb == null) _decalMpb = new MaterialPropertyBlock();
- if (_decalMeshes == null)
- {
- _decalMeshes = new Mesh[4];
- _decalMeshes[0] = FeedbackFx.BuildScorchMesh(20, 0.32f, 11);
- _decalMeshes[1] = FeedbackFx.BuildScorchMesh(20, 0.34f, 23);
- _decalMeshes[2] = FeedbackFx.BuildCrackMesh(3, 31);
- _decalMeshes[3] = FeedbackFx.BuildCrackMesh(2, 47);
- }
- }
-
- void SpawnArenaScar(float3 origin, int index, ref Unity.Mathematics.Random rng)
- {
- var go = new GameObject("ArenaScar");
- go.transform.SetParent(_root, false);
- go.AddComponent().sharedMesh = _decalMeshes[rng.NextInt(0, _decalMeshes.Length)];
- var mr = go.AddComponent();
- mr.sharedMaterial = _decalMat;
- mr.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
- mr.receiveShadows = false;
- mr.lightProbeUsage = UnityEngine.Rendering.LightProbeUsage.Off;
- _decalMpb.SetColor(DecalColorId, DecalConfig.RoomDecalColor);
- mr.SetPropertyBlock(_decalMpb);
- float ang = rng.NextFloat(0f, math.PI * 2f);
- float rad = rng.NextFloat(0f, 4.5f); // clustered around the arena centre where the fight happens
- float scale = rng.NextFloat(1.4f, 3.0f);
- go.transform.SetPositionAndRotation(
- new Vector3(origin.x + math.cos(ang) * rad, 0.05f + 0.003f * index, origin.z + math.sin(ang) * rad),
- Quaternion.Euler(0f, rng.NextFloat(0f, 360f), 0f));
- go.transform.localScale = new Vector3(scale, 1f, scale);
- }
- }
-}
diff --git a/Assets/_Project/Scripts/Client/Presentation/RoomDressingSystem.cs.meta b/Assets/_Project/Scripts/Client/Presentation/RoomDressingSystem.cs.meta
deleted file mode 100644
index 692b3e35b..000000000
--- a/Assets/_Project/Scripts/Client/Presentation/RoomDressingSystem.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: 6eb0d1fcbd0a69244842b4eb2fbcd49f
\ No newline at end of file
diff --git a/Assets/_Project/Scripts/Client/Presentation/RoomPortalBeaconSystem.cs b/Assets/_Project/Scripts/Client/Presentation/RoomPortalBeaconSystem.cs
deleted file mode 100644
index 3a855294d..000000000
--- a/Assets/_Project/Scripts/Client/Presentation/RoomPortalBeaconSystem.cs
+++ /dev/null
@@ -1,102 +0,0 @@
-using ProjectM.Simulation;
-using Unity.Entities;
-using Unity.Mathematics;
-using UnityEngine;
-using static ProjectM.Client.FeedbackFx;
-
-namespace ProjectM.Client
-{
- ///
- /// DR-046 — client-only, observe-only presentation of the room-exit PORTAL made visible. A managed
- /// in that OBSERVES replicated
- /// and never mutates the sim. During the loot window it shows a glowing cyan
- /// pillar (or the authored effect when wired) at the client-derived portal position
- /// so the player has an unmistakable "go here to continue" target; hidden whenever the run isn't in RoomExplore.
- /// Position resolves through the SAME authority the HUD prompt uses, so
- /// the beacon and the "PRESS E" range always agree. Extracted from CombatFeedbackSystem (owns its own FX-root +
- /// beacon material); no Entity-keyed cache.
- ///
- [WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]
- [UpdateInGroup(typeof(PresentationSystemGroup))]
- public partial class RoomPortalBeaconSystem : SystemBase
- {
- Transform _fxRoot;
- GameObject _portalFx; // Phase 1: authored portal effect (VFXConfig.Portal) replacing the procedural pillar when wired
- Material _portalMat; // DR-046: room-exit portal beacon glow (mutated for the pulse; beacon-only mat)
- GameObject _portalBeacon; // DR-046: pooled world-space "go here" pillar, shown only during RoomExplore
-
- protected override void OnStartRunning()
- {
- if (_fxRoot != null) return;
- _fxRoot = new GameObject("~RoomPortalBeaconFX").transform;
- _portalMat = MakeParticleMaterial();
- _portalMat.name = "RoomPortal";
- _portalMat.color = new Color(0.25f, 1.2f, 1.55f, 0.85f); // DR-046: HDR cyan portal glow (Phase 0: tamed — 2.6/3.4 bloomed to a white blob)
- }
-
- protected override void OnDestroy()
- {
- if (_fxRoot != null) Object.Destroy(_fxRoot.gameObject);
- if (_portalMat != null) Object.Destroy(_portalMat);
- }
-
- protected override void OnUpdate()
- {
- UpdatePortalBeacon();
- }
-
- // DR-046: the room-exit PORTAL made VISIBLE. During the RoomExplore loot window a glowing cyan pillar marks the
- // client-derived portal position so the player has an unmistakable "go here to continue" target — the HUD prompt
- // alone left the exit invisible, so players waited out the ~30s grace timeout ("nothing happens for a while").
- // Client-only, observe-only; one pooled GameObject, hidden whenever the run isn't in RoomExplore. Position
- // resolves through the SAME RegionMath.ExpeditionPortalPos authority the HUD prompt uses -> beacon + "PRESS E"
- // range always agree.
- void UpdatePortalBeacon()
- {
- if (_fxRoot == null || _portalMat == null) return;
- bool inExplore = SystemAPI.TryGetSingleton(out var ri) && ri.Lifecycle == RunLifecycle.RoomExplore;
- if (!inExplore || !SystemAPI.TryGetSingleton(out var anchor))
- {
- if (_portalBeacon != null && _portalBeacon.activeSelf) _portalBeacon.SetActive(false);
- if (_portalFx != null && _portalFx.activeSelf) _portalFx.SetActive(false);
- return;
- }
- float3 pos = RegionMath.ExpeditionPortalPos(BaseGridMath.PlotCenter(anchor), (byte)(ri.CurrentRoom & 1));
- // Phase 1: prefer the authored portal effect (VFXConfig.Portal, PolygonParticleFX) over the
- // procedural pillar; the pillar remains the asset-free fallback.
- var vfx = VFXConfig.Instance;
- if (vfx != null && vfx.Portal != null)
- {
- if (_portalFx == null)
- {
- _portalFx = Object.Instantiate(vfx.Portal, _fxRoot, false);
- _portalFx.name = "~RoomPortalFx";
- }
- _portalFx.transform.position = new Vector3(pos.x, 0f, pos.z); // terrain y=0 (pos.y is the capsule plane)
- if (!_portalFx.activeSelf) _portalFx.SetActive(true);
- if (_portalBeacon != null && _portalBeacon.activeSelf) _portalBeacon.SetActive(false);
- return;
- }
- if (_portalBeacon == null)
- {
- _portalBeacon = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
- _portalBeacon.name = "~RoomPortalBeacon";
- var col = _portalBeacon.GetComponent(); if (col != null) Object.Destroy(col); // cosmetic only
- _portalBeacon.transform.SetParent(_fxRoot, false);
- var mr = _portalBeacon.GetComponent();
- mr.sharedMaterial = _portalMat;
- mr.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
- mr.receiveShadows = false;
- }
- if (!_portalBeacon.activeSelf) _portalBeacon.SetActive(true);
- float t = (float)SystemAPI.Time.ElapsedTime;
- float breathe = 0.5f + 0.5f * math.sin(t * 3.5f);
- var tr = _portalBeacon.transform;
- // Cylinder is 2u tall in local space -> scale.y=2.2 gives a 4.4u pillar; lift the centre so the base sits
- // on the TERRAIN (y=0) — pos.y is the CC capsule-center plane (GridOrigin.y=1), 1 u above the ground.
- tr.position = new Vector3(pos.x, 2.2f, pos.z);
- tr.localScale = new Vector3(0.9f + 0.12f * breathe, 2.2f, 0.9f + 0.12f * breathe);
- _portalMat.color = new Color(0.25f, 1.2f, 1.55f, 0.45f + 0.3f * breathe); // glow throb (beacon-only mat; Phase 0: tamed + slimmed — the fat 6u pillar bloomed to a white egg swallowing the prompt)
- }
- }
-}
diff --git a/Assets/_Project/Scripts/Client/Presentation/RoomPortalBeaconSystem.cs.meta b/Assets/_Project/Scripts/Client/Presentation/RoomPortalBeaconSystem.cs.meta
deleted file mode 100644
index 5dfa01fb9..000000000
--- a/Assets/_Project/Scripts/Client/Presentation/RoomPortalBeaconSystem.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: 587c50c122f52954da052232f28a6052
\ No newline at end of file
diff --git a/Assets/_Project/Scripts/Client/Presentation/RouteMapHudSystem.cs b/Assets/_Project/Scripts/Client/Presentation/RouteMapHudSystem.cs
deleted file mode 100644
index 938e22fad..000000000
--- a/Assets/_Project/Scripts/Client/Presentation/RouteMapHudSystem.cs
+++ /dev/null
@@ -1,259 +0,0 @@
-using System.Collections.Generic;
-using ProjectM.Simulation;
-using Unity.Entities;
-using Unity.NetCode;
-using Unity.Transforms;
-using Unity.Mathematics;
-using UnityEngine;
-using UnityEngine.UIElements;
-
-namespace ProjectM.Client
-{
- ///
- /// The drawn branching route map (RouteSelect) — extracted from into its own client-only,
- /// observe-only presentation in . Owns its own
- /// runtime UIDocument sharing (sortingOrder 55). The map is regenerated
- /// client-side from RunInfo.RunSeed for DISPLAY only; the clickable next-layer nodes bind to the
- /// authoritative RouteOpt* bytes (never the regen) via . Also owns the
- /// client-local visited-path trace (nodeIds; reset per RunSeed) that lights walked edges.
- ///
- [WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]
- [UpdateInGroup(typeof(PresentationSystemGroup))]
- public partial class RouteMapHudSystem : SystemBase
- {
- GameObject _go;
- UIDocument _doc;
- bool _built;
-
- VisualElement _routePanel;
- Label _routeTitle;
- bool _routePanelBuilt;
- VisualElement _routeMapHost; // node circles + Painter2D edges
- int _routeMapSig; // (seed, room, col, options) signature the map was drawn for
- readonly List _routeVisited = new(); // client-local path trace (nodeIds), reset per RunSeed
- uint _routeVisitedSeed;
-
- protected override void OnStartRunning()
- {
- if (_go != null) return;
- MenuUi.EnsureEventSystem();
- _go = new GameObject("~HUDRouteMap");
- _doc = _go.AddComponent();
- _doc.panelSettings = MenuUi.LoadPanelSettings();
- _doc.sortingOrder = 55;
- }
-
- protected override void OnDestroy()
- {
- if (_go != null) Object.Destroy(_go);
- }
-
- protected override void OnUpdate()
- {
- if (_doc == null) return;
- var root = _doc.rootVisualElement;
- if (root == null) return; // panel not initialised yet (next frame)
- if (!_built)
- {
- root.style.position = Position.Absolute;
- root.style.left = 0; root.style.right = 0; root.style.top = 0; root.style.bottom = 0;
- root.pickingMode = PickingMode.Ignore; // never eat game-world clicks
- _built = true;
- }
-
- bool haveRun = SystemAPI.TryGetSingleton(out var runInfo);
- UpdateRoutePanel(haveRun ? runInfo : default);
-
- // Client-local path trace for the route map (nodeIds visited this run; display-only).
- if (haveRun && runInfo.RunSeed != _routeVisitedSeed)
- {
- _routeVisited.Clear();
- _routeVisitedSeed = runInfo.RunSeed;
- }
- if (haveRun && runInfo.Lifecycle == RunLifecycle.InRoom)
- {
- int visitedNode = RunMap.NodeId(runInfo.CurrentRoom, runInfo.CurrentCol);
- if (_routeVisited.Count == 0 || _routeVisited[^1] != visitedNode) _routeVisited.Add(visitedNode);
- }
- }
-
- // ---- the drawn branching route map (Slay-the-Spire style; display regen from RunSeed, clicks bind
- // to the authoritative RouteOpt* bytes) ----
-
- const float MapStrideX = 58f, MapStrideY = 46f, MapNodeSize = 34f, MapPad = 14f;
-
- static Vector2 MapNodePos(int layer, int col, byte layerWidth)
- {
- float x = MapPad + layer * MapStrideX;
- float y = MapPad + MapStrideY + (col - (layerWidth - 1) * 0.5f) * MapStrideY;
- return new Vector2(x, y);
- }
-
- static string RoomGlyph(byte t) => t == RoomTypeId.Boss ? "B"
- : t == RoomTypeId.Elite ? "E" : t == RoomTypeId.Reward ? "R" : "C";
-
- static Color RoomColor(byte t) => t == RoomTypeId.Boss ? new Color(0.92f, 0.28f, 0.22f)
- : t == RoomTypeId.Elite ? new Color(0.80f, 0.45f, 1f)
- : t == RoomTypeId.Reward ? new Color(0.45f, 0.95f, 0.55f) : new Color(1f, 0.72f, 0.35f);
-
- void UpdateRoutePanel(RunInfo runInfo)
- {
- // Keyed on the LIFECYCLE (never RouteOptionCount alone — the review's D-F6 criterion).
- bool show = runInfo.Lifecycle == RunLifecycle.RouteSelect && runInfo.RouteOptionCount > 0;
- if (!show)
- {
- if (_routePanel != null) _routePanel.style.display = DisplayStyle.None;
- _routeMapSig = 0;
- return;
- }
- var root = _doc != null ? _doc.rootVisualElement : null;
- if (root == null) return;
- if (!_routePanelBuilt)
- {
- BuildRoutePanel(root);
- _routePanelBuilt = true;
- }
-
- int sig = (int)runInfo.RunSeed ^ (runInfo.CurrentRoom + 1) * 131 ^ runInfo.CurrentCol * 31
- ^ (runInfo.RouteOptionCount << 24) ^ (runInfo.RouteOpt0Col << 16)
- ^ (runInfo.RouteOpt1Col << 18) ^ (runInfo.RouteOpt2Col << 20);
- if (sig == 0) sig = 1;
- if (_routeMapSig != sig)
- {
- RebuildRouteMap(runInfo);
- _routeTitle.text = "CHOOSE YOUR PATH — room " + (runInfo.CurrentRoom + 2) + "/" + runInfo.RoomCount;
- _routeMapSig = sig;
- }
- _routePanel.style.display = DisplayStyle.Flex;
- }
-
- void BuildRoutePanel(VisualElement root)
- {
- _routePanel = new VisualElement { pickingMode = PickingMode.Ignore };
- _routePanel.style.position = Position.Absolute;
- _routePanel.style.left = 0; _routePanel.style.right = 0;
- _routePanel.style.top = 0; _routePanel.style.bottom = 0;
- _routePanel.style.alignItems = Align.Center;
- _routePanel.style.justifyContent = Justify.Center;
- _routePanel.style.display = DisplayStyle.None;
-
- var box = new VisualElement { pickingMode = PickingMode.Position }; // swallow world clicks under the map
- box.style.backgroundColor = new Color(0.07f, 0.09f, 0.12f, 0.95f);
- box.style.borderTopLeftRadius = 10; box.style.borderTopRightRadius = 10;
- box.style.borderBottomLeftRadius = 10; box.style.borderBottomRightRadius = 10;
- box.style.paddingLeft = 18; box.style.paddingRight = 18;
- box.style.paddingTop = 12; box.style.paddingBottom = 12;
- box.style.alignItems = Align.Center;
-
- _routeTitle = new Label("CHOOSE YOUR PATH");
- _routeTitle.style.color = new Color(0.55f, 0.85f, 1f);
- _routeTitle.style.fontSize = 16;
- _routeTitle.style.unityFontStyleAndWeight = FontStyle.Bold;
- _routeTitle.style.marginBottom = 10;
- box.Add(_routeTitle);
-
- _routeMapHost = new VisualElement { pickingMode = PickingMode.Ignore };
- _routeMapHost.style.position = Position.Relative;
- box.Add(_routeMapHost);
-
- var cap = HudUi.Text("your path is lit — click a highlighted room to commit the party", 13,
- MenuUi.SubCol, TextAnchor.MiddleCenter);
- cap.style.marginTop = 10;
- box.Add(cap);
-
- _routePanel.Add(box);
- root.Add(_routePanel);
- }
-
- void RebuildRouteMap(RunInfo runInfo)
- {
- _routeMapHost.Clear();
- var map = RunMapMath.Generate(runInfo.RunSeed);
- _routeMapHost.style.width = MapPad * 2f + (map.LayerCount - 1) * MapStrideX + MapNodeSize;
- _routeMapHost.style.height = MapPad * 2f + 2f * MapStrideY + MapNodeSize;
-
- // Edges under the nodes (Painter2D); walked segments glow, the rest are faint.
- var edges = new VisualElement { pickingMode = PickingMode.Ignore };
- edges.style.position = Position.Absolute;
- edges.style.left = 0; edges.style.top = 0; edges.style.right = 0; edges.style.bottom = 0;
- var mapCopy = map;
- var visited = new List(_routeVisited);
- edges.generateVisualContent += ctx =>
- {
- var p = ctx.painter2D;
- p.lineWidth = 2f;
- var c = new Vector2(MapNodeSize * 0.5f, MapNodeSize * 0.5f);
- for (int layer = 0; layer < mapCopy.LayerCount - 1; layer++)
- for (int col = 0; col < mapCopy.LayerWidths[layer]; col++)
- {
- var node = mapCopy.Node(layer, col);
- if (node.NextMask == 0) continue;
- var a = MapNodePos(layer, col, mapCopy.LayerWidths[layer]);
- for (int j = 0; j < mapCopy.LayerWidths[layer + 1]; j++)
- {
- if ((node.NextMask & (1 << j)) == 0) continue;
- var b = MapNodePos(layer + 1, j, mapCopy.LayerWidths[layer + 1]);
- bool walked = visited.Contains(RunMap.NodeId(layer, col))
- && visited.Contains(RunMap.NodeId(layer + 1, j));
- p.strokeColor = walked ? new Color(0.55f, 0.85f, 1f, 0.9f) : new Color(1f, 1f, 1f, 0.16f);
- p.BeginPath();
- p.MoveTo(a + c);
- p.LineTo(b + c);
- p.Stroke();
- }
- }
- };
- _routeMapHost.Add(edges);
-
- int nextLayer = runInfo.CurrentRoom + 1;
- for (int layer = 0; layer < map.LayerCount; layer++)
- for (int col = 0; col < map.LayerWidths[layer]; col++)
- {
- var node = map.Node(layer, col);
- bool isCurrent = layer == runInfo.CurrentRoom && col == runInfo.CurrentCol;
- bool wasVisited = _routeVisited.Contains(RunMap.NodeId(layer, col));
- byte opt = 255;
- if (layer == nextLayer)
- {
- if (runInfo.RouteOptionCount > 0 && col == runInfo.RouteOpt0Col) opt = 0;
- else if (runInfo.RouteOptionCount > 1 && col == runInfo.RouteOpt1Col) opt = 1;
- else if (runInfo.RouteOptionCount > 2 && col == runInfo.RouteOpt2Col) opt = 2;
- }
- _routeMapHost.Add(MakeMapNode(node.RoomType,
- MapNodePos(layer, col, map.LayerWidths[layer]), isCurrent, wasVisited, opt, layer <= runInfo.CurrentRoom));
- }
- }
-
- VisualElement MakeMapNode(byte roomType, Vector2 pos, bool isCurrent, bool visited, byte optionIndex, bool past)
- {
- bool clickable = optionIndex != 255;
- var n = new VisualElement { pickingMode = clickable ? PickingMode.Position : PickingMode.Ignore };
- n.style.position = Position.Absolute;
- n.style.left = pos.x; n.style.top = pos.y;
- n.style.width = MapNodeSize; n.style.height = MapNodeSize;
- MenuUi.Round(n, MapNodeSize * 0.5f);
- var c = RoomColor(roomType);
- float bgA = clickable ? 0.95f : visited || isCurrent ? 0.85f : past ? 0.20f : 0.40f;
- var restBg = new Color(c.r * 0.35f, c.g * 0.35f, c.b * 0.35f, bgA);
- n.style.backgroundColor = restBg;
- MenuUi.Border(n, isCurrent ? new Color(0.55f, 0.85f, 1f) : clickable ? c : new Color(1f, 1f, 1f, 0.18f),
- isCurrent || clickable ? 2.5f : 1.2f);
- var lbl = new Label(RoomGlyph(roomType)) { pickingMode = PickingMode.Ignore };
- lbl.style.unityTextAlign = TextAnchor.MiddleCenter;
- lbl.style.flexGrow = 1;
- lbl.style.color = clickable || visited || isCurrent ? c : new Color(1f, 1f, 1f, 0.35f);
- lbl.style.fontSize = 15;
- lbl.style.unityFontStyleAndWeight = FontStyle.Bold;
- n.Add(lbl);
- if (clickable)
- {
- byte pick = optionIndex; // closure copy, never the loop variable
- n.RegisterCallback(_ => RouteSendSystem.PickRoute(pick));
- n.RegisterCallback(_ =>
- n.style.backgroundColor = new Color(c.r * 0.55f, c.g * 0.55f, c.b * 0.55f, 1f));
- n.RegisterCallback(_ => n.style.backgroundColor = restBg);
- }
- return n;
- }
- }
-}
diff --git a/Assets/_Project/Scripts/Client/Presentation/RouteMapHudSystem.cs.meta b/Assets/_Project/Scripts/Client/Presentation/RouteMapHudSystem.cs.meta
deleted file mode 100644
index a5d84fe6b..000000000
--- a/Assets/_Project/Scripts/Client/Presentation/RouteMapHudSystem.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: e2330bd90a6294442959883258d476b4
\ No newline at end of file
diff --git a/Assets/_Project/Scripts/Client/Presentation/StructureFeedbackSystem.cs b/Assets/_Project/Scripts/Client/Presentation/StructureFeedbackSystem.cs
deleted file mode 100644
index 87384c754..000000000
--- a/Assets/_Project/Scripts/Client/Presentation/StructureFeedbackSystem.cs
+++ /dev/null
@@ -1,142 +0,0 @@
-using System.Collections.Generic;
-using ProjectM.Simulation;
-using Unity.Entities;
-using Unity.Mathematics;
-using Unity.NetCode;
-using Unity.Transforms;
-using UnityEngine;
-using static ProjectM.Client.FeedbackFx;
-
-namespace ProjectM.Client
-{
- ///
- /// EB-1 — client-only WORLD JUICE for player-built structures taking damage + dying ("loses have weight"). A
- /// managed in that OBSERVES replicated state and
- /// never mutates the sim: it edge-detects each structure ghost's [GhostField] Health.Current — a decrease
- /// spawns a small amber chip (camera-SILENT so a siege's many hits never clamp the shake), and a destruction
- /// (an HP<=0 edge OR a despawn) spawns a LOUD red-orange burst + camera punch. A PROXIMITY GATE suppresses the
- /// destruction burst unless the structure was near the local player, so the base->expedition RegionRelevancy
- /// despawn (every base structure drops from this client at once) stays SILENT. De-duped: a structure fires its
- /// death burst AT MOST once (the HP<=0 edge sets DeathFired so the prune-cleanup skips it; the server destroys
- /// a structure the same tick it hits 0, so the prune is usually the path that fires). CombatFeedbackSystem
- /// suppresses structures, so this is the SOLE structure cue. Procedural particles + SFX (mirrors
- /// WorldFeedbackSystem; self-contained). Never destroys a ghost (GhostDespawnSystem owns despawn); prunes the
- /// cache EVERY frame (no [RequireMatchingQueriesForUpdate] — else a cache entry leaks per kill).
- ///
- [WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]
- [UpdateInGroup(typeof(PresentationSystemGroup))]
- public partial class StructureFeedbackSystem : SystemBase
- {
- struct Cache { public float Hp; public float3 Pos; public bool DeathFired; }
-
- readonly Dictionary _cache = new();
- readonly HashSet _seen = new();
- readonly List _stale = new();
-
- Transform _fxRoot;
- ParticleSystem _chipFx;
- ParticleSystem _deathFx;
- AudioClip _chipClip;
- AudioClip _deathClip;
-
- protected override void OnCreate()
- {
- _chipClip = MakeClip("struct_chip", 700f, 500f, 0.05f, 0.30f);
- _deathClip = MakeClip("struct_death", 220f, 60f, 0.35f, 0.55f);
- }
-
- protected override void OnStartRunning()
- {
- if (_fxRoot != null) return;
- _fxRoot = new GameObject("~StructureFeedbackFX").transform;
- var mat = MakeParticleMaterial();
- _chipFx = MakeBurst(_fxRoot, "StructChips", mat, StructureFeelConfig.DamageTint, 0.12f, 5f, 0.30f, 256, 0.3f, 0.18f, 0.15f);
- _deathFx = MakeBurst(_fxRoot, "StructDeath", mat, StructureFeelConfig.DeathTint, 0.20f, 8f, 0.55f, 512, 0.3f, 0.18f, 0.15f);
- }
-
- protected override void OnDestroy()
- {
- if (_fxRoot != null) Object.Destroy(_fxRoot.gameObject);
- }
-
- protected override void OnUpdate()
- {
- if (!StructureFeelConfig.Enabled) { _cache.Clear(); return; }
-
- EntityManager.CompleteDependencyBeforeRO();
- EntityManager.CompleteDependencyBeforeRO();
- EntityManager.CompleteDependencyBeforeRO();
-
- bool haveLocal = false;
- float3 localPos = default;
- foreach (var xf in SystemAPI.Query>().WithAll())
- {
- localPos = xf.ValueRO.Position;
- haveLocal = true;
- }
- float rangeSq = StructureFeelConfig.ProximityRange * StructureFeelConfig.ProximityRange;
-
- _seen.Clear();
- foreach (var (health, xf, e) in
- SystemAPI.Query, RefRO>().WithAll().WithEntityAccess())
- {
- _seen.Add(e);
- float cur = health.ValueRO.Current;
- float3 pos = xf.ValueRO.Position;
- bool nearby = haveLocal && math.distancesq(pos, localPos) <= rangeSq;
-
- if (_cache.TryGetValue(e, out var prev))
- {
- if (cur <= 0f && prev.Hp > 0f && !prev.DeathFired)
- {
- if (nearby) FireDeath(pos);
- _cache[e] = new Cache { Hp = cur, Pos = pos, DeathFired = true };
- continue;
- }
- if (cur < prev.Hp - 0.001f && cur > 0f && nearby)
- {
- EmitTinted(_chipFx, (Vector3)pos + Vector3.up * 0.7f, StructureFeelConfig.ChipBurstCount, StructureFeelConfig.DamageTint);
- PlayClip(_chipClip, (Vector3)pos, StructureFeelConfig.ChipSfxVolume);
- }
- }
- _cache[e] = new Cache { Hp = cur, Pos = pos, DeathFired = _cache.TryGetValue(e, out var c2) && c2.DeathFired };
- }
-
- // Prune: a despawn = destroyed (or a region-transit drop). Proximity-gated so the +1000 base->expedition
- // despawn stays silent; de-duped against an HP<=0 edge that already fired this structure's death.
- if (_cache.Count != _seen.Count)
- {
- _stale.Clear();
- foreach (var kv in _cache)
- if (!_seen.Contains(kv.Key)) _stale.Add(kv.Key);
- for (int i = 0; i < _stale.Count; i++)
- {
- var c = _cache[_stale[i]];
- if (!c.DeathFired && haveLocal && math.distancesq(c.Pos, localPos) <= rangeSq)
- FireDeath(c.Pos);
- _cache.Remove(_stale[i]);
- }
- }
- }
-
- void FireDeath(float3 pos)
- {
- EmitTinted(_deathFx, (Vector3)pos + Vector3.up * 0.6f, StructureFeelConfig.DeathBurstCount, StructureFeelConfig.DeathTint);
- PlayClip(_deathClip, (Vector3)pos, StructureFeelConfig.DeathSfxVolume);
- PrototypeCameraRig.PunchFov(StructureFeelConfig.DeathFovKick, 110f);
- PrototypeCameraRig.AddShake(StructureFeelConfig.DeathShake);
- }
-
- // ---- procedural particles + SFX (mirrors WorldFeedbackSystem; self-contained) ----
-
-
-
-
-
-
-
-
-
-
- }
-}
diff --git a/Assets/_Project/Scripts/Client/Presentation/StructureFeedbackSystem.cs.meta b/Assets/_Project/Scripts/Client/Presentation/StructureFeedbackSystem.cs.meta
deleted file mode 100644
index 50177ed1f..000000000
--- a/Assets/_Project/Scripts/Client/Presentation/StructureFeedbackSystem.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: 61153a58a80eb0542bbdc62085cce81b
\ No newline at end of file
diff --git a/Assets/_Project/Scripts/Client/Presentation/StructureFeelConfig.cs b/Assets/_Project/Scripts/Client/Presentation/StructureFeelConfig.cs
deleted file mode 100644
index d460d51ff..000000000
--- a/Assets/_Project/Scripts/Client/Presentation/StructureFeelConfig.cs
+++ /dev/null
@@ -1,47 +0,0 @@
-using UnityEngine;
-
-namespace ProjectM.Client
-{
- ///
- /// EB-1 — static live-tunable knobs for (structure damage chips +
- /// destruction bursts). A presentation-only bridge (mirrors WorldFeelConfig); reset on play-enter via
- /// so poked values never leak across fast-enter-playmode sessions.
- /// Read only on the main thread by the managed feedback system, never from Burst.
- ///
- public static class StructureFeelConfig
- {
- public static bool Enabled = true;
-
- /// A despawn farther than this from the local player does NOT fire a death burst — so the
- /// base->expedition RegionRelevancy despawn (all base structures drop at once) stays silent.
- public static float ProximityRange = 45f;
-
- public static int ChipBurstCount = 8;
- public static int DeathBurstCount = 40;
- public static float ChipSfxVolume = 0.25f;
- public static float DeathSfxVolume = 0.6f;
-
- // A LOUD, low-frequency punch is reserved for a structure DEATH only; per-chip feedback is camera-silent so
- // a wave of hits never sustains a nauseating shake (AddShake clamps cumulatively, PunchFov takes a max).
- public static float DeathFovKick = 5.5f;
- public static float DeathShake = 0.35f;
-
- public static Color DamageTint = new Color(2.4f, 1.4f, 0.4f); // amber HDR spark on a hit
- public static Color DeathTint = new Color(3.0f, 0.7f, 0.25f); // red-orange HDR loss burst
-
- [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
- static void ResetDefaults()
- {
- Enabled = true;
- ProximityRange = 45f;
- ChipBurstCount = 8;
- DeathBurstCount = 40;
- ChipSfxVolume = 0.25f;
- DeathSfxVolume = 0.6f;
- DeathFovKick = 5.5f;
- DeathShake = 0.35f;
- DamageTint = new Color(2.4f, 1.4f, 0.4f);
- DeathTint = new Color(3.0f, 0.7f, 0.25f);
- }
- }
-}
diff --git a/Assets/_Project/Scripts/Client/Presentation/StructureFeelConfig.cs.meta b/Assets/_Project/Scripts/Client/Presentation/StructureFeelConfig.cs.meta
deleted file mode 100644
index daf0ff099..000000000
--- a/Assets/_Project/Scripts/Client/Presentation/StructureFeelConfig.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: c868d6648bec9fd4199c44fcf8330326
\ No newline at end of file
diff --git a/Assets/_Project/Scripts/Client/Presentation/WorldAtmosphereSystem.cs b/Assets/_Project/Scripts/Client/Presentation/WorldAtmosphereSystem.cs
index 1a3ac30cf..60570a7d9 100644
--- a/Assets/_Project/Scripts/Client/Presentation/WorldAtmosphereSystem.cs
+++ b/Assets/_Project/Scripts/Client/Presentation/WorldAtmosphereSystem.cs
@@ -39,28 +39,11 @@ namespace ProjectM.Client
float expDen = cfg != null ? cfg.ExpeditionFogDensity : 0.04f;
Color expAmb = cfg != null ? cfg.ExpeditionAmbientSky : new Color(0.02f, 0.045f, 0.07f, 1f);
- // Per-room murk flavors (cosmetic code consts; all stay inside the deep-sea band — hue shifts are
- // subtle, value stays dark so dynamic lights keep carrying readability).
- bool haveRun = SystemAPI.TryGetSingleton(out var runInfo);
- if (haveRun && runInfo.Lifecycle != ProjectM.Simulation.RunLifecycle.Staging)
- {
- switch (runInfo.CurrentBiome)
- {
- case ProjectM.Simulation.RoomBiomeId.Meadow: // kelp shallows: a touch greener
- expFog = new Color(0.02f, 0.10f, 0.09f, 1f); expDen = 0.035f;
- expAmb = new Color(0.025f, 0.06f, 0.06f, 1f);
- break;
- case ProjectM.Simulation.RoomBiomeId.Cavern: // crush-dark trench: colder + denser
- expFog = new Color(0.01f, 0.05f, 0.08f, 1f); expDen = 0.05f;
- expAmb = new Color(0.015f, 0.035f, 0.06f, 1f);
- break;
- case ProjectM.Simulation.RoomBiomeId.Blight: // gloam bloom: a sick green-violet cast
- expFog = new Color(0.045f, 0.075f, 0.09f, 1f); expDen = 0.045f;
- expAmb = new Color(0.045f, 0.05f, 0.075f, 1f);
- break;
- // default keeps the config/fallback out-shelf murk above.
- }
- }
+ // 2026-08-07 audit purge: per-room murk flavors (kelp shallows / crush-dark trench / gloam bloom)
+ // keyed off RunInfo.CurrentBiome. The run FSM is deleted, so the out-shelf murk config above is the
+ // whole look. Re-key this off LANTERN's pocket type when Phase 2 lands — the colour constants are in
+ // git if the three flavors are wanted back.
+
float x = _cam.transform.position.x;
float t = Mathf.Clamp01((x - (boundary - half)) / (2f * half));
diff --git a/Assets/_Project/Scripts/Client/UI/HudTheme.cs b/Assets/_Project/Scripts/Client/UI/HudTheme.cs
index cb0aa25d2..fd0977f82 100644
--- a/Assets/_Project/Scripts/Client/UI/HudTheme.cs
+++ b/Assets/_Project/Scripts/Client/UI/HudTheme.cs
@@ -76,25 +76,10 @@ namespace ProjectM.Client
}
/// Icon for a byte (null → caller falls back to the structure name text).
- public Sprite StructureIcon(byte type)
- {
- switch (type)
- {
- case StructureType.Wall: return WallIcon;
- case StructureType.Pylon: return PylonIcon;
- default: return null;
- }
- }
+
/// Placement-ghost preview mesh for a byte (null → the cube fallback).
- public Mesh StructureGhostMesh(byte type)
- {
- switch (type)
- {
- case StructureType.Wall: return WallGhostMesh;
- default: return null;
- }
- }
+
// ---- cached SDF font definitions (one FontAsset per font, built once, reset per play session) ----
static FontAsset _displayFa, _bodyFa, _bodyLightFa;
diff --git a/Assets/_Project/Scripts/Client/UI/WorldLauncher.cs b/Assets/_Project/Scripts/Client/UI/WorldLauncher.cs
index 0d3cf8013..470f5ec95 100644
--- a/Assets/_Project/Scripts/Client/UI/WorldLauncher.cs
+++ b/Assets/_Project/Scripts/Client/UI/WorldLauncher.cs
@@ -179,19 +179,11 @@ namespace ProjectM.Client
var st = tq.GetSingleton().ServerTick;
if (st.IsValid) nowTick = st.TickIndexForValidTick;
}
- // v6: the permanent-meta slice via the ONE shared collector — omitting it HERE (the most common
- // exit path) would silently WIPE all meta progression on quit (the meta review's top blocker).
- MetaSaveScan.Collect(em, dir, out var metaRows, out var runsCompleted, out var maxDepth);
- SaveStructureScan.Collect(em, nowTick, out var structures);
-
+ // 2026-08-07 audit purge: the quit-to-menu save used to collect the permanent-meta slice and the
+ // placed structures too. Both layers are deleted; the ledger is the whole save now.
SaveService.Save(new SaveData
{
- RunsCompleted = runsCompleted,
- MaxDepthReached = maxDepth,
- MetaUpgrades = metaRows,
-
Ledger = rows,
- Structures = structures,
SavedAtMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
});
}
diff --git a/Assets/_Project/Scripts/Client/World/ClassSelectSendSystem.cs b/Assets/_Project/Scripts/Client/World/ClassSelectSendSystem.cs
deleted file mode 100644
index 05b02bc2f..000000000
--- a/Assets/_Project/Scripts/Client/World/ClassSelectSendSystem.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using System.Collections.Generic;
-using ProjectM.Simulation;
-using Unity.Entities;
-using Unity.NetCode;
-using UnityEngine;
-
-namespace ProjectM.Client
-{
- ///
- /// Client-side class-pick sender: a static enqueue (the Staging class-select HUD buttons) drained into
- /// RPCs (the MetaSpendSendSystem idiom). Carries only the class id; the server
- /// re-validates the phase + applies the full swap. Statics reset on play-enter (the stale-bridge hazard).
- ///
- [WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]
- public partial class ClassSelectSendSystem : SystemBase
- {
- static readonly Queue s_Queue = new();
-
- [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
- static void ResetStatics() => s_Queue.Clear();
-
- /// Queue a class pick (0=Warrior, 1=Ranger). The Staging class buttons drive this.
- public static void RequestClass(byte classId) => s_Queue.Enqueue(classId);
-
- protected override void OnCreate() => RequireForUpdate();
-
- protected override void OnUpdate()
- {
- while (s_Queue.Count > 0)
- {
- var req = EntityManager.CreateEntity(typeof(ClassSelectRequest), typeof(SendRpcCommandRequest));
- EntityManager.SetComponentData(req, new ClassSelectRequest { ClassId = s_Queue.Dequeue() });
- }
- }
- }
-}
diff --git a/Assets/_Project/Scripts/Client/World/ClassSelectSendSystem.cs.meta b/Assets/_Project/Scripts/Client/World/ClassSelectSendSystem.cs.meta
deleted file mode 100644
index 167c72eb9..000000000
--- a/Assets/_Project/Scripts/Client/World/ClassSelectSendSystem.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: e0d67e293cdeb454fbef8414d4aeb813
\ No newline at end of file
diff --git a/Assets/_Project/Scripts/Client/World/PortalInteractSendSystem.cs b/Assets/_Project/Scripts/Client/World/PortalInteractSendSystem.cs
deleted file mode 100644
index 836f911d6..000000000
--- a/Assets/_Project/Scripts/Client/World/PortalInteractSendSystem.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-using ProjectM.Simulation;
-using Unity.Entities;
-using Unity.NetCode;
-using UnityEngine;
-
-namespace ProjectM.Client
-{
- ///
- /// Client-side portal-interact sender: a static flag (the portal prompt / E-key) drained into a single
- /// RPC. Coalesced (one per drain — repeat E while the server is still in
- /// RoomExplore is harmless, the server latches once). Statics reset on play-enter.
- ///
- [WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]
- public partial class PortalInteractSendSystem : SystemBase
- {
- static bool s_pending;
-
- [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
- static void ResetStatics() => s_pending = false;
-
- /// Request leaving via the portal (the HUD prompt / E-key near the portal drives this).
- public static void Interact() => s_pending = true;
-
- protected override void OnCreate() => RequireForUpdate();
-
- protected override void OnUpdate()
- {
- if (!s_pending) return;
- s_pending = false;
- EntityManager.CreateEntity(typeof(PortalInteractRequest), typeof(SendRpcCommandRequest));
- }
- }
-}
diff --git a/Assets/_Project/Scripts/Client/World/PortalInteractSendSystem.cs.meta b/Assets/_Project/Scripts/Client/World/PortalInteractSendSystem.cs.meta
deleted file mode 100644
index efc660402..000000000
--- a/Assets/_Project/Scripts/Client/World/PortalInteractSendSystem.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: eac0f1b2340c0b344b430363311d3be5
\ No newline at end of file
diff --git a/Assets/_Project/Scripts/Client/World/PrepPurchaseSendSystem.cs b/Assets/_Project/Scripts/Client/World/PrepPurchaseSendSystem.cs
deleted file mode 100644
index ec1ea6c37..000000000
--- a/Assets/_Project/Scripts/Client/World/PrepPurchaseSendSystem.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using System.Collections.Generic;
-using ProjectM.Simulation;
-using Unity.Entities;
-using Unity.NetCode;
-using UnityEngine;
-
-namespace ProjectM.Client
-{
- ///
- /// Client-side prep-loadout sender: a static enqueue (the Staging PREP panel buttons) drained into
- /// RPCs (the MetaSpendSendSystem idiom). Carries only the option id; the server
- /// prices + re-validates (Staging, affordability, once-per-run). Statics reset on play-enter.
- ///
- [WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]
- public partial class PrepPurchaseSendSystem : SystemBase
- {
- static readonly Queue s_Queue = new();
-
- [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
- static void ResetStatics() => s_Queue.Clear();
-
- /// Queue a prep-loadout purchase by catalog option id. The Staging PREP rows drive this.
- public static void RequestPrep(byte optionId) => s_Queue.Enqueue(optionId);
-
- protected override void OnCreate() => RequireForUpdate();
-
- protected override void OnUpdate()
- {
- while (s_Queue.Count > 0)
- {
- var req = EntityManager.CreateEntity(typeof(PrepPurchaseRequest), typeof(SendRpcCommandRequest));
- EntityManager.SetComponentData(req, new PrepPurchaseRequest { OptionId = s_Queue.Dequeue() });
- }
- }
- }
-}
diff --git a/Assets/_Project/Scripts/Client/World/PrepPurchaseSendSystem.cs.meta b/Assets/_Project/Scripts/Client/World/PrepPurchaseSendSystem.cs.meta
deleted file mode 100644
index 4b3daac76..000000000
--- a/Assets/_Project/Scripts/Client/World/PrepPurchaseSendSystem.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: 5b60ac8ed7ee3ce4081c55b2188be142
\ No newline at end of file
diff --git a/Assets/_Project/Scripts/Client/World/ReadySendSystem.cs b/Assets/_Project/Scripts/Client/World/ReadySendSystem.cs
deleted file mode 100644
index b37857ecd..000000000
--- a/Assets/_Project/Scripts/Client/World/ReadySendSystem.cs
+++ /dev/null
@@ -1,59 +0,0 @@
-using ProjectM.Simulation;
-using Unity.Entities;
-using Unity.NetCode;
-using UnityEngine;
-
-namespace ProjectM.Client
-{
- ///
- /// Client-side ready-toggle sender: a static enqueue (HUD button at Step 14 / the T dev key / execute_code)
- /// drained into RPC entities — the BuildSendSystem queue+drain idiom. The local
- /// bool tracks only the toggle DIRECTION; the server-replicated is the truth the HUD
- /// renders. Statics reset on play-enter (statics survive fast-enter-playmode reloads — the stale-bridge hazard).
- ///
- [WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]
- public partial class ReadySendSystem : SystemBase
- {
- static int s_Pending; // queued explicit sets
- static byte s_PendingValue;
- static bool s_LocalReady; // last requested state (toggle direction only, not authority)
-
- [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
- static void ResetStatics()
- {
- s_Pending = 0;
- s_PendingValue = 0;
- s_LocalReady = false;
- }
-
- /// Queue an explicit ready set (HUD button / execute_code).
- public static void SetReady(bool ready)
- {
- s_PendingValue = (byte)(ready ? 1 : 0);
- s_Pending++;
- s_LocalReady = ready;
- }
-
- /// Queue a toggle of the last requested state (the T dev key; HUD replaces this at Step 14).
- public static void ToggleReady() => SetReady(!s_LocalReady);
-
- protected override void OnCreate()
- {
- RequireForUpdate();
- }
-
- protected override void OnUpdate()
- {
- var keyboard = UnityEngine.InputSystem.Keyboard.current;
- if (keyboard != null && keyboard.tKey.wasPressedThisFrame && !PauseMenuController.Open)
- ToggleReady();
-
- while (s_Pending > 0)
- {
- s_Pending--;
- var req = EntityManager.CreateEntity(typeof(ReadyToggleRequest), typeof(SendRpcCommandRequest));
- EntityManager.SetComponentData(req, new ReadyToggleRequest { Ready = s_PendingValue });
- }
- }
- }
-}
diff --git a/Assets/_Project/Scripts/Client/World/ReadySendSystem.cs.meta b/Assets/_Project/Scripts/Client/World/ReadySendSystem.cs.meta
deleted file mode 100644
index 47b3260f2..000000000
--- a/Assets/_Project/Scripts/Client/World/ReadySendSystem.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: 7047cb8f6861ba8498f33698c9948dc8
\ No newline at end of file
diff --git a/Assets/_Project/Scripts/Client/World/RouteSendSystem.cs b/Assets/_Project/Scripts/Client/World/RouteSendSystem.cs
deleted file mode 100644
index 3bcf2ae35..000000000
--- a/Assets/_Project/Scripts/Client/World/RouteSendSystem.cs
+++ /dev/null
@@ -1,60 +0,0 @@
-using ProjectM.Simulation;
-using Unity.Entities;
-using Unity.NetCode;
-using UnityEngine;
-
-namespace ProjectM.Client
-{
- ///
- /// Client-side route-pick sender: a static enqueue (the Step-14 map panel's option buttons / execute_code)
- /// drained into RPCs. The request is stamped from the CLIENT's replicated
- /// : ForRunEpoch = (int)RunSeed (the re-meaned run-identity token — the server-only
- /// RunEpoch is not client-knowable) and ForLayer = CurrentRoom VERBATIM (during a gate that is still the
- /// just-cleared layer — never +1). The server re-validates everything; a stale/possessed pick is simply dropped.
- /// Statics reset on play-enter (the stale-bridge hazard).
- ///
- [WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]
- public partial class RouteSendSystem : SystemBase
- {
- static int s_Pending;
- static byte s_PendingIndex;
-
- [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
- static void ResetStatics()
- {
- s_Pending = 0;
- s_PendingIndex = 0;
- }
-
- /// Queue a route pick (0..RouteOptionCount-1). HUD map panel + execute_code drive this.
- public static void PickRoute(byte optionIndex)
- {
- s_PendingIndex = optionIndex;
- s_Pending++;
- }
-
- protected override void OnCreate()
- {
- RequireForUpdate();
- RequireForUpdate();
- }
-
- protected override void OnUpdate()
- {
- if (s_Pending == 0)
- return;
- var runInfo = SystemAPI.GetSingleton();
- while (s_Pending > 0)
- {
- s_Pending--;
- var req = EntityManager.CreateEntity(typeof(RouteSelectRequest), typeof(SendRpcCommandRequest));
- EntityManager.SetComponentData(req, new RouteSelectRequest
- {
- OptionIndex = s_PendingIndex,
- ForRunEpoch = (int)runInfo.RunSeed, // the re-meaned replicated run token
- ForLayer = runInfo.CurrentRoom, // the gate's un-incremented cleared layer
- });
- }
- }
- }
-}
diff --git a/Assets/_Project/Scripts/Client/World/RouteSendSystem.cs.meta b/Assets/_Project/Scripts/Client/World/RouteSendSystem.cs.meta
deleted file mode 100644
index f0b761e07..000000000
--- a/Assets/_Project/Scripts/Client/World/RouteSendSystem.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: c22921cccedc3564b86d12491d88488e
\ No newline at end of file
diff --git a/Assets/_Project/Scripts/Server/Automation/BaseRestoreSystem.cs b/Assets/_Project/Scripts/Server/Automation/BaseRestoreSystem.cs
deleted file mode 100644
index dbfc3e672..000000000
--- a/Assets/_Project/Scripts/Server/Automation/BaseRestoreSystem.cs
+++ /dev/null
@@ -1,101 +0,0 @@
-using ProjectM.Simulation;
-using Unity.Burst;
-using Unity.Collections;
-using Unity.Entities;
-using Unity.Mathematics;
-using Unity.NetCode;
-using Unity.Transforms;
-
-namespace ProjectM.Server
-{
- ///
- /// One-shot server restore of player-built structures for a "Continue" session. The menu (WorldLauncher) stages a
- /// carrier in the fresh ServerWorld BEFORE the gameplay subscene streams; this
- /// system waits (RequireForUpdate) for the streamed + +
- /// a valid NetworkTime, then replays each saved structure CHARGE-FREE: Instantiate the catalog prefab at the
- /// saved cell (preserving the baked Scale), restore the wounded HP born-correct, re-tag RegionTag{Base} +
- /// RuntimePlacedTag, then DESTROY the carrier so it never runs again. The ledger restores separately +
- /// absolutely via CycleDirectorSpawnSystem's born-correct load (no double-spend, no Withdraw here).
- ///
- [BurstCompile]
- [WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
- public partial struct BaseRestoreSystem : ISystem
- {
- ComponentLookup m_TransformLookup;
- ComponentLookup m_HealthLookup;
-
- [BurstCompile]
- public void OnCreate(ref SystemState state)
- {
- m_TransformLookup = state.GetComponentLookup(isReadOnly: true);
- m_HealthLookup = state.GetComponentLookup(isReadOnly: true);
- state.RequireForUpdate();
- state.RequireForUpdate();
- state.RequireForUpdate();
- state.RequireForUpdate(state.GetEntityQuery(ComponentType.ReadOnly()));
- }
-
- [BurstCompile]
- public void OnUpdate(ref SystemState state)
- {
- var serverTick = SystemAPI.GetSingleton().ServerTick;
- if (!serverTick.IsValid)
- return;
- uint now = serverTick.TickIndexForValidTick;
-
- m_TransformLookup.Update(ref state);
- m_HealthLookup.Update(ref state);
-
- var anchor = SystemAPI.GetSingleton();
- var catalog = SystemAPI.GetBuffer(SystemAPI.GetSingletonEntity());
-
- var ecb = new EntityCommandBuffer(Allocator.Temp);
-
- foreach (var (pending, carrier) in
- SystemAPI.Query>().WithEntityAccess())
- {
- for (int s = 0; s < pending.Length; s++)
- {
- var p = pending[s];
-
- int entryIdx = -1;
- for (int i = 0; i < catalog.Length; i++)
- if (catalog[i].Type == p.Type) { entryIdx = i; break; }
- if (entryIdx < 0 || catalog[entryIdx].Prefab == Entity.Null)
- continue; // type not in the catalog (e.g. a save from a newer build) -> skip, don't crash
-
- var prefab = catalog[entryIdx].Prefab;
- var structure = ecb.Instantiate(prefab);
-
- int2 cell = new int2(p.CellX, p.CellZ);
- var xform = m_TransformLookup[prefab];
- xform.Position = BaseGridMath.CellToWorld(anchor, cell); // preserve baked Scale (FromPosition would reset it)
- ecb.SetComponent(structure, xform);
-
- ecb.SetComponent(structure, new PlacedStructure
- {
- Type = p.Type,
- Cell = cell,
- NextTick = 0u, // cooldown restore retired with the automation chain (LANTERN purge)
- LastProcessedTick = TickUtil.NonZero(now),
- });
- // EB-1: restore the wounded HP born-correct in the SAME ecb as Instantiate (Health.Current is a
- // [GhostField]; a deferred set would leak baked Max to clients for one snapshot). Max + the
- // 0->full fallback come from the BAKED prefab, never the save.
- if (m_HealthLookup.HasComponent(prefab))
- {
- var hm = m_HealthLookup[prefab];
- ecb.SetComponent(structure, new Health { Current = p.HP > 0f ? p.HP : hm.Max, Max = hm.Max });
- }
- ecb.AddComponent(structure, new RegionTag { Region = RegionId.Base });
- ecb.AddComponent(structure);
- }
-
- ecb.DestroyEntity(carrier);
- }
-
- ecb.Playback(state.EntityManager);
- ecb.Dispose();
- }
- }
-}
diff --git a/Assets/_Project/Scripts/Server/Automation/BaseRestoreSystem.cs.meta b/Assets/_Project/Scripts/Server/Automation/BaseRestoreSystem.cs.meta
deleted file mode 100644
index cfc8e2198..000000000
--- a/Assets/_Project/Scripts/Server/Automation/BaseRestoreSystem.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: 4003027ade5ccd5418e300d87e5c5e14
\ No newline at end of file
diff --git a/Assets/_Project/Scripts/Server/Building/BuildPlaceSystem.cs b/Assets/_Project/Scripts/Server/Building/BuildPlaceSystem.cs
deleted file mode 100644
index 7339cb44f..000000000
--- a/Assets/_Project/Scripts/Server/Building/BuildPlaceSystem.cs
+++ /dev/null
@@ -1,106 +0,0 @@
-using ProjectM.Simulation;
-using Unity.Burst;
-using Unity.Collections;
-using Unity.Entities;
-using Unity.Mathematics;
-using Unity.NetCode;
-using Unity.Transforms;
-
-namespace ProjectM.Server
-{
- ///
- /// Server-authoritative structure placement (handles RPCs). Derives
- /// occupancy by scanning live ghosts into a Temp NativeHashSet (structures
- /// are the source of truth — no cached buffer on the immutable BaseAnchor). For each request it validates
- /// catalog/legality/occupancy/cost, and on success commits IN-PLACE (StorageMath.Withdraw on the global
- /// ledger + reserve the cell in the set) so two same-tick requests for one cell can't both pass — the
- /// StorageOpReceiveSystem in-place idiom — then instantiates the catalog prefab at the cell center
- /// (RegionTag{Base}, world-owned, NextTick=0, LastProcessedTick stamped). Plain server SimulationSystemGroup
- /// (not predicted → applied once). Rejects invalid requests silently.
- ///
- [BurstCompile]
- [WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
- public partial struct BuildPlaceSystem : ISystem
- {
- ComponentLookup m_TransformLookup;
-
- [BurstCompile]
- public void OnCreate(ref SystemState state)
- {
- m_TransformLookup = state.GetComponentLookup(isReadOnly: true);
- state.RequireForUpdate();
- state.RequireForUpdate();
- state.RequireForUpdate();
- state.RequireForUpdate();
- var builder = new EntityQueryBuilder(Allocator.Temp)
- .WithAll();
- state.RequireForUpdate(state.GetEntityQuery(builder));
- }
-
- [BurstCompile]
- public void OnUpdate(ref SystemState state)
- {
- m_TransformLookup.Update(ref state);
- uint now = SystemAPI.GetSingleton().ServerTick.TickIndexForValidTick;
- var anchor = SystemAPI.GetSingleton();
-
- var catalog = SystemAPI.GetBuffer(SystemAPI.GetSingletonEntity());
- var ledger = SystemAPI.GetBuffer(SystemAPI.GetSingletonEntity());
-
- // Derive occupancy from the live structure set (authoritative).
- var occupied = new NativeHashSet