From 342a39a540d66e09f00dcd79388cf2c525c0aa7b Mon Sep 17 00:00:00 2001 From: Luis Gonzalez Date: Wed, 15 Jul 2026 08:45:45 -0700 Subject: [PATCH] =?UTF-8?q?LANTERN=20P1=20step=205=20+=20=C2=A78:=20two-fr?= =?UTF-8?q?ame=20identity=20via=20FrameId=20+=20per-frame=20gym=20loadouts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FrameId (replicated frame/class signal) now WRITTEN server-side at spawn for all players; the two class-HUD readers (ClassPrepPortal, MetaShop) read FrameId (with an AbilityRef fallback for pre-FrameId players) instead of ClassForAbility. The gym seeds a PER-FRAME default Spark loadout via ClassTraits.FrameLoadout — Harpooner (Ranger slot): reel/mobility/skillshots; Bathynaut (Warrior slot): pull-in/dash/zone-control. FrameId uses ecb.AddComponent (baked on the real player; absent on the minimal MetaSeeding test prefab). 497 EditMode green. Deferred to a focused pass (risky wholesale refactor, "still works" via the transition): full removal of legacy AbilityRef/AbilityCooldown/EffectiveAbility Stats (woven through equip/class/meta/StatRecompute + tests) and the CharacterId Bathynaut/Harpooner rename. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../Presentation/ClassPrepPortalHudSystem.cs | 4 ++-- .../Client/Presentation/MetaShopHudSystem.cs | 4 ++-- .../Server/Connection/GoInGameServerSystem.cs | 13 +++++++------ .../Scripts/Simulation/Combat/ClassTraits.cs | 14 ++++++++++++++ 4 files changed, 25 insertions(+), 10 deletions(-) diff --git a/Assets/_Project/Scripts/Client/Presentation/ClassPrepPortalHudSystem.cs b/Assets/_Project/Scripts/Client/Presentation/ClassPrepPortalHudSystem.cs index 1b47fee2f..72f47abf4 100644 --- a/Assets/_Project/Scripts/Client/Presentation/ClassPrepPortalHudSystem.cs +++ b/Assets/_Project/Scripts/Client/Presentation/ClassPrepPortalHudSystem.cs @@ -83,9 +83,9 @@ namespace ProjectM.Client // Local class from the replicated AbilityRef (tracks the dev class-switch; PlayerClass is server-only). byte localClass = ClassTraits.WarriorClass; bool haveLocalPlayer = false; - foreach (var ar in SystemAPI.Query>().WithAll()) + foreach (var (fr, ar) in SystemAPI.Query, RefRO>().WithAll()) { - localClass = ClassTraits.ClassForAbility(ar.ValueRO.Id); + localClass = fr.ValueRO.Value != 0 ? fr.ValueRO.Value : ClassTraits.ClassForAbility(ar.ValueRO.Id); // FrameId signal, AbilityRef fallback haveLocalPlayer = true; break; } diff --git a/Assets/_Project/Scripts/Client/Presentation/MetaShopHudSystem.cs b/Assets/_Project/Scripts/Client/Presentation/MetaShopHudSystem.cs index 6aae6053d..91305f93e 100644 --- a/Assets/_Project/Scripts/Client/Presentation/MetaShopHudSystem.cs +++ b/Assets/_Project/Scripts/Client/Presentation/MetaShopHudSystem.cs @@ -76,9 +76,9 @@ namespace ProjectM.Client // server-only); tiers from the replicated MetaTierState record on the director ghost. byte localClass = ClassTraits.WarriorClass; bool haveLocalPlayer = false; - foreach (var ar in SystemAPI.Query>().WithAll()) + foreach (var (fr, ar) in SystemAPI.Query, RefRO>().WithAll()) { - localClass = ClassTraits.ClassForAbility(ar.ValueRO.Id); + localClass = fr.ValueRO.Value != 0 ? fr.ValueRO.Value : ClassTraits.ClassForAbility(ar.ValueRO.Id); // FrameId signal, AbilityRef fallback haveLocalPlayer = true; break; } diff --git a/Assets/_Project/Scripts/Server/Connection/GoInGameServerSystem.cs b/Assets/_Project/Scripts/Server/Connection/GoInGameServerSystem.cs index a288816a5..ce9eb756d 100644 --- a/Assets/_Project/Scripts/Server/Connection/GoInGameServerSystem.cs +++ b/Assets/_Project/Scripts/Server/Connection/GoInGameServerSystem.cs @@ -88,15 +88,16 @@ namespace ProjectM.Server // Expedition redesign: the server-only class anchor the meta systems key on (born-correct meta // seeding at Step 12a + per-class spend at Step 13 resolve the tier record through this). ecb.AddComponent(player, new PlayerClass { ClassId = classId }); + ecb.AddComponent(player, new FrameId { Value = classId }); // Add (not Set): baked on the real player; absent on the minimal test prefab // replicated frame/class signal (HUD reads this, not AbilityRef) if (isGym) { - // GYM: default Spark socket loadout on keys 1-4 (AbilityFireSystem reads sockets, not AbilityRef). - // LightZone(9) is defined + dispatch-ready but off the default bar (socket it via a swap). + // GYM: per-frame default Spark loadout on keys 1-4 (AbilityFireSystem reads sockets, not AbilityRef). + ClassTraits.FrameLoadout(classId, out byte f0, out byte f1, out byte f2, out byte f3); var gymSockets = ecb.SetBuffer(player); - gymSockets.Add(new AbilitySocket { SparkId = (byte)AbilityId.HookPull }); - gymSockets.Add(new AbilitySocket { SparkId = (byte)AbilityId.Blink }); - gymSockets.Add(new AbilitySocket { SparkId = (byte)AbilityId.DecoyWisp }); - gymSockets.Add(new AbilitySocket { SparkId = (byte)AbilityId.Vortex }); + gymSockets.Add(new AbilitySocket { SparkId = f0 }); + gymSockets.Add(new AbilitySocket { SparkId = f1 }); + gymSockets.Add(new AbilitySocket { SparkId = f2 }); + gymSockets.Add(new AbilitySocket { SparkId = f3 }); } // Step 12a: born-correct PERMANENT meta seeding — replay this class's persisted tiers as // meta-band StatModifiers on the just-instantiated player (same ECB as Instantiate, the diff --git a/Assets/_Project/Scripts/Simulation/Combat/ClassTraits.cs b/Assets/_Project/Scripts/Simulation/Combat/ClassTraits.cs index 4ffafe944..302cc583a 100644 --- a/Assets/_Project/Scripts/Simulation/Combat/ClassTraits.cs +++ b/Assets/_Project/Scripts/Simulation/Combat/ClassTraits.cs @@ -45,6 +45,20 @@ namespace ProjectM.Simulation public static byte ClassForAbility(byte abilityId) => abilityId == (byte)AbilityId.Primary ? RangerClass : WarriorClass; + /// Default 4-socket Spark loadout per frame (Harpooner = the Ranger slot, line-and-iron; Bathynaut = + /// the Warrior slot, anchor-and-crash). Tunable; drives the gym's per-frame default sockets (Build Spec step 5). + public static void FrameLoadout(byte classId, out byte s0, out byte s1, out byte s2, out byte s3) + { + if (classId == RangerClass) // Harpooner: reel primary + mobility + skillshots + { + s0 = (byte)AbilityId.HookPull; s1 = (byte)AbilityId.Blink; s2 = (byte)AbilityId.DecoyWisp; s3 = (byte)AbilityId.LightZone; + } + else // Bathynaut: pull enemies in + dash + zone control (melee via the combo chassis) + { + s0 = (byte)AbilityId.Vortex; s1 = (byte)AbilityId.Blink; s2 = (byte)AbilityId.HookPull; s3 = (byte)AbilityId.LightZone; + } + } + /// True when a modifier's SourceId is in the reserved class-seed range [ClassSourceId, +ClassSeedCount). public static bool IsClassSeed(uint sourceId) => sourceId >= Tuning.ClassSourceId && sourceId < Tuning.ClassSourceId + (uint)ClassSeedCount;