LANTERN purge: delete the superseded base/expedition shell (audit H1/H3/M5)

The 2026-08-06 audit found the shipping scene was still the abandoned
co-op-Hades game with LANTERN combat bolted on, and that a third of the
codebase was live code for a direction abandoned on 2026-07-13. Operator
chose deletion over freezing: "everything is saved in source control if
needed. I want the project to be clean."

DELETED (~140 source files, Scripts 335->231, Tests 77->43):
- Enemy variants + boss (H3). ChargerAuthoring / SpitterAuthoring /
  SwarmerAuthoring were attached to ZERO prefabs, so LungeState /
  SpitterState / SwarmerTag were never baked: ~272 lines of Bursted AI
  passes, BossAISystem (261 lines) and the whole MixBands escalation
  curve could not match a single chunk at runtime, while 734 lines of
  green tests certified them. Both shipping enemy prefabs were already
  byte-identical in stats.
- Run/room lifecycle: RunDirector FSM, RunInfo/RunMap/RoomPlan/RoomTag,
  route select, portal interact, ready-check, room field/teardown.
- Meta shop, prep loadout, boons (incl. KillRewardSystem and
  DashTrailDamageSystem, which existed only to serve boon flags).
- Build palette + structures, shared storage, inventory/equipment
  (already recorded PAUSED in CLAUDE.md).
- The HUD panels driving all of the above (HudSystem 1168 -> 610).

KEPT deliberately: BaseGridMath + BaseAnchor (8 systems use PlotCenter
for spawn rings, respawn and dynamic light), the resource ledger +
StorageMath, the save system, region/relevancy. Three of these were in
the delete set until I checked their consumers — worth remembering that
the file-level manifest was wrong about them.

Also folds in audit finding M5: PlayerClass was a second, server-only
copy of the byte FrameId already replicates. It existed for the meta
shop; with that gone, FrameId is the single frame identity.

Harvest is now single-sink (ledger). HarvestMath keeps its shape so
LANTERN's carried-vs-banked cargo split lands in one place, not two.

295/295 EditMode green, zero compile errors. Subscene re-bake and Play
validation follow in the next commit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-07 12:59:39 -07:00
parent 6a412fe3e7
commit 62e48a3b0b
304 changed files with 260 additions and 14591 deletions
@@ -21,8 +21,6 @@ namespace ProjectM.Simulation
public uint Stamp;
public uint KnockUntil;
public bool IsFinisher; // Phase 1.7: this swing is the combo finisher
public bool Detonate; // Phase 1.7: attacker has the FinisherDetonate boon
public bool Pull; // Phase 1.7: attacker has the KnockToPull boon
}
/// <summary>
@@ -52,12 +50,8 @@ namespace ProjectM.Simulation
public partial struct MeleeComboSystem : ISystem
{
ComponentLookup<KnockbackState> m_KnockbackLookup;
ComponentLookup<BossState> m_BossLookup; // A4: the boss is knockback-immune (no melee stunlock out of its slams)
ComponentLookup<RegionTag> m_RegionLookup;
BufferLookup<InventorySlot> m_InvLookup;
BufferLookup<StatModifier> m_StatModLookup;
ComponentLookup<BoonEffects> m_BoonEffectsLookup; // Phase 1.7 (player query is at the 7-type cap -> lookup)
ComponentLookup<MeleeCleavePending> m_PendingLookup; // 07-20 G2.1 scheduled cleave (query at cap -> lookup)
/// <summary>Phase 1.7 Detonating Finisher blast radius (planar, tunable).</summary>
@@ -69,7 +63,7 @@ namespace ProjectM.Simulation
/// (G2.2); damage/knockback keep the classic finisher mult.</summary>
static PendingCleave BuildCleave(byte step, byte comboLen, float baseDamage, float baseRange, float knockSpeed,
float finisherMult, float finisherRangeMult, uint stamp, uint knockUntil, float3 from, float2 aim,
float2 facingDir, int ownerId, byte bflags, bool hasMods, DynamicBuffer<StatModifier> mods)
float2 facingDir, int ownerId, bool hasMods, DynamicBuffer<StatModifier> mods)
{
bool fin = step >= comboLen;
float d = math.max(0f, hasMods ? StatMath.Apply(baseDamage, StatTarget.MeleeDamage, mods) : baseDamage);
@@ -85,8 +79,7 @@ namespace ProjectM.Simulation
Stamp = stamp,
KnockUntil = knockUntil,
IsFinisher = fin,
Detonate = (bflags & BoonFlag.FinisherDetonate) != 0,
Pull = (bflags & BoonFlag.KnockToPull) != 0,
// Detonate/Pull came from boon flags (deleted 2026-08-07 audit purge).
};
}
@@ -94,12 +87,8 @@ namespace ProjectM.Simulation
public void OnCreate(ref SystemState state)
{
m_KnockbackLookup = state.GetComponentLookup<KnockbackState>(isReadOnly: false);
m_BossLookup = state.GetComponentLookup<BossState>(isReadOnly: true);
m_RegionLookup = state.GetComponentLookup<RegionTag>(isReadOnly: true);
m_InvLookup = state.GetBufferLookup<InventorySlot>(isReadOnly: false);
m_StatModLookup = state.GetBufferLookup<StatModifier>(isReadOnly: true);
m_BoonEffectsLookup = state.GetComponentLookup<BoonEffects>(isReadOnly: true);
m_PendingLookup = state.GetComponentLookup<MeleeCleavePending>(isReadOnly: false);
state.RequireForUpdate<NetworkTime>();
}
@@ -134,7 +123,6 @@ namespace ProjectM.Simulation
// when at least one swing actually started — no per-tick enemy gather on idle/client ticks).
var cleaves = isServer ? new NativeList<PendingCleave>(Allocator.Temp) : default;
m_StatModLookup.Update(ref state);
m_BoonEffectsLookup.Update(ref state); // Phase 1.7: per-player boon flags (read inside the player loop)
m_PendingLookup.Update(ref state); // 07-20 G2.1: scheduled cleave slots (server-only writes)
foreach (var (mc, control, input, facing, xform, owner, ds, entity) in
@@ -164,7 +152,6 @@ namespace ProjectM.Simulation
cleaves.Add(BuildCleave(pend.Step, comboLen, baseDamage, baseRange, knockSpeed,
finisherMult, finisherRangeMult, stamp, knockUntil, xform.ValueRO.Position,
input.ValueRO.Aim, facing.ValueRO.Direction, owner.ValueRO.NetworkId,
m_BoonEffectsLookup.HasComponent(entity) ? m_BoonEffectsLookup[entity].Flags : (byte)0,
m_StatModLookup.HasBuffer(entity), m_StatModLookup.HasBuffer(entity) ? m_StatModLookup[entity] : default));
m_PendingLookup[entity] = default;
}
@@ -237,7 +224,6 @@ namespace ProjectM.Simulation
cleaves.Add(BuildCleave(pend.Step, comboLen, baseDamage, baseRange, knockSpeed,
finisherMult, finisherRangeMult, stamp, knockUntil, xform.ValueRO.Position,
input.ValueRO.Aim, facing.ValueRO.Direction, owner.ValueRO.NetworkId,
m_BoonEffectsLookup.HasComponent(entity) ? m_BoonEffectsLookup[entity].Flags : (byte)0,
m_StatModLookup.HasBuffer(entity), m_StatModLookup.HasBuffer(entity) ? m_StatModLookup[entity] : default));
m_PendingLookup[entity] = new MeleeCleavePending { ResolveTick = TickUtil.NonZero(now + ct), Step = swingStep };
}
@@ -246,7 +232,6 @@ namespace ProjectM.Simulation
cleaves.Add(BuildCleave(swingStep, comboLen, baseDamage, baseRange, knockSpeed,
finisherMult, finisherRangeMult, stamp, knockUntil, xform.ValueRO.Position,
input.ValueRO.Aim, facing.ValueRO.Direction, owner.ValueRO.NetworkId,
m_BoonEffectsLookup.HasComponent(entity) ? m_BoonEffectsLookup[entity].Flags : (byte)0,
m_StatModLookup.HasBuffer(entity), m_StatModLookup.HasBuffer(entity) ? m_StatModLookup[entity] : default));
}
}
@@ -273,14 +258,8 @@ namespace ProjectM.Simulation
// pool) just like a base projectile hit. SERVER-ONLY (this whole block) — interpolated node ghosts
// are never rolled back, so the deposit + destroy fire exactly once per swing.
bool haveLedger = SystemAPI.TryGetSingletonEntity<ResourceLedger>(out var ledgerEntity);
bool haveDb = SystemAPI.TryGetSingleton<ItemDatabase>(out var itemDb);
DynamicBuffer<StorageEntry> ledger = default;
if (haveLedger) ledger = SystemAPI.GetBuffer<StorageEntry>(ledgerEntity);
m_RegionLookup.Update(ref state);
m_InvLookup.Update(ref state);
var meleePlayerByConn = new NativeHashMap<int, Entity>(8, Allocator.Temp);
foreach (var (po, pe) in SystemAPI.Query<RefRO<GhostOwner>>().WithAll<PlayerTag, InventorySlot>().WithEntityAccess())
meleePlayerByConn[po.ValueRO.NetworkId] = pe;
var harvEntity = new NativeList<Entity>(Allocator.Temp);
var harvPos = new NativeList<float3>(Allocator.Temp);
var harvRemaining = new NativeList<int>(Allocator.Temp);
@@ -288,7 +267,6 @@ namespace ProjectM.Simulation
var harvPerHit = new NativeList<float>(Allocator.Temp);
var harvIsClutter = new NativeList<bool>(Allocator.Temp);
var harvVariant = new NativeList<byte>(Allocator.Temp);
var harvToLedger = new NativeList<bool>(Allocator.Temp);
foreach (var (hx, node, he) in
SystemAPI.Query<RefRO<LocalTransform>, RefRO<ResourceNode>>().WithEntityAccess())
{
@@ -301,7 +279,6 @@ namespace ProjectM.Simulation
harvPerHit.Add(node.ValueRO.HarvestPerHit);
harvIsClutter.Add(false);
harvVariant.Add(0);
harvToLedger.Add(m_RegionLookup.HasComponent(he) && m_RegionLookup[he].Region == RegionId.Base);
}
foreach (var (hx, clutter, he) in
SystemAPI.Query<RefRO<LocalTransform>, RefRO<BlightClutter>>().WithEntityAccess())
@@ -315,13 +292,11 @@ namespace ProjectM.Simulation
harvPerHit.Add(clutter.ValueRO.ScrapPerHit);
harvIsClutter.Add(true);
harvVariant.Add(clutter.ValueRO.Variant);
harvToLedger.Add(m_RegionLookup.HasComponent(he) && m_RegionLookup[he].Region == RegionId.Base);
}
var harvDestroyed = new NativeArray<bool>(harvEntity.Length, Allocator.Temp);
m_KnockbackLookup.Update(ref state);
m_BossLookup.Update(ref state);
var ecb = new EntityCommandBuffer(Allocator.Temp);
for (int s = 0; s < cleaves.Length; s++)
@@ -339,29 +314,8 @@ namespace ProjectM.Simulation
SourceTick = c.Stamp,
});
if (c.KnockSpeed > 0f)
KnockbackUtil.Stamp(ref m_KnockbackLookup, m_BossLookup, target,
c.From, enemyPositions[i], c.Face, c.KnockSpeed, c.KnockUntil, c.Pull);
}
}
// Phase 1.7 Detonating Finisher: a finisher swing with the boon blasts a planar AoE around its
// origin (mirrors HazardExplosionSystem). Cone+blast overlap is the normal DamageEvent-summation.
for (int s = 0; s < cleaves.Length; s++)
{
var dc = cleaves[s];
if (!dc.IsFinisher || !dc.Detonate)
continue;
float detRadSq = k_DetonateRadius * k_DetonateRadius;
for (int i = 0; i < enemyEntities.Length; i++)
{
float2 dd = new float2(enemyPositions[i].x - dc.From.x, enemyPositions[i].z - dc.From.z);
if (math.lengthsq(dd) > detRadSq)
continue;
ecb.AppendToBuffer(enemyEntities[i], new DamageEvent
{
Amount = dc.Damage,
SourceNetworkId = dc.OwnerId,
SourceTick = dc.Stamp,
});
KnockbackUtil.Stamp(ref m_KnockbackLookup, target,
c.From, enemyPositions[i], c.Face, c.KnockSpeed, c.KnockUntil);
}
}
@@ -383,17 +337,12 @@ namespace ProjectM.Simulation
// zero means ZERO (cover); any POSITIVE yield still credits >= 1 (the fractional-yield guard).
int deposit = harvPerHit[i] > 0f ? amount : 0;
byte yieldId = harvYieldId[i];
// Route by region: Base nodes credit the shared ledger DIRECTLY (the build pool); an
// expedition / un-tagged target goes to the swinging player's PERSONAL inventory (spill to
// ledger), mirroring ResourceHarvestSystem. Only deplete if the yield landed somewhere —
// never consume a node for zero credit (e.g. no ledger singleton present).
Entity meleeHarvester = Entity.Null;
if (meleePlayerByConn.TryGetValue(hc.OwnerId, out var meleePlayer))
meleeHarvester = meleePlayer;
// All yield credits the shared ledger — the PERSONAL inventory sink was deleted with the
// superseded shell (2026-08-07 audit). Only deplete if the yield landed somewhere; never
// consume a node for zero credit (e.g. no ledger singleton present).
if (deposit > 0)
{
bool deposited = HarvestMath.DepositYield(yieldId, deposit, harvToLedger[i], meleeHarvester,
m_InvLookup, ledger, haveLedger, haveDb, itemDb);
bool deposited = HarvestMath.DepositYield(yieldId, deposit, ledger, haveLedger);
if (!deposited)
continue; // never consume a YIELDING target for zero credit
}
@@ -455,8 +404,6 @@ namespace ProjectM.Simulation
harvIsClutter.Dispose();
harvVariant.Dispose();
harvDestroyed.Dispose();
harvToLedger.Dispose();
meleePlayerByConn.Dispose();
}
if (cleaves.IsCreated)