LANTERN P1 step 5 + §8: two-frame identity via FrameId + per-frame gym loadouts

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) <noreply@anthropic.com>
This commit is contained in:
2026-07-15 08:45:45 -07:00
parent 8f099c32ba
commit 342a39a540
4 changed files with 25 additions and 10 deletions
@@ -45,6 +45,20 @@ namespace ProjectM.Simulation
public static byte ClassForAbility(byte abilityId)
=> abilityId == (byte)AbilityId.Primary ? RangerClass : WarriorClass;
/// <summary>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).</summary>
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;
}
}
/// <summary>True when a modifier's SourceId is in the reserved class-seed range [ClassSourceId, +ClassSeedCount).</summary>
public static bool IsClassSeed(uint sourceId)
=> sourceId >= Tuning.ClassSourceId && sourceId < Tuning.ClassSourceId + (uint)ClassSeedCount;