LANTERN P1: enemy-test gym tooling — on-demand spawn + GymTag clean-spawn

GymTag + GymEnemyRoster (+ GymRosterAuthoring) bake the gym singletons. New
DebugOp.SpawnEnemy (client SpawnEnemy wrapper + server receive case) spawns a
chosen enemy KIND (Drowner/Grindylow) from the baked roster near the sender —
replacing the wave-roster hack per the roadmap's Enemy-test GYM direction.
GoInGameServerSystem takes a GymTag branch: bypass the meta-catalog spawn guard
(a fresh gym has no CycleDirector) + seed a default Spark loadout on keys 1-4.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-15 00:01:13 -07:00
parent b011a367e3
commit b92cc7196b
9 changed files with 148 additions and 2 deletions
@@ -68,5 +68,8 @@ namespace ProjectM.Simulation
/// Strips the old class trait seeds, re-seeds the new ones, swaps the Fire ability, and heals a living
/// player to the new class's max. Editor-only dev tool (class-switch); 0 / unknown -> Warrior.</summary>
public const byte SetClass = 13;
/// <summary>GYM (editor): spawn <see cref="GymEnemyKind"/> ArgA near the sender, ArgB times (default 1). Reads the baked GymEnemyRoster.</summary>
public const byte SpawnEnemy = 14;
}
}
@@ -0,0 +1,35 @@
using Unity.Entities;
namespace ProjectM.Simulation
{
/// <summary>
/// Singleton tag baked into the Enemy-test GYM subscene (see the LANTERN roadmap A1 gym note). Marks the world
/// as the gym so <c>GoInGameServerSystem</c> takes the CLEAN gym spawn path — bypassing the base/meta/cycle
/// seeding (the gym has no CycleDirector, so the meta-catalog spawn guard must not block it) and seeding a
/// default Spark socket loadout instead. Purely a mode switch; the gym never runs the base siege/economy.
/// </summary>
public struct GymTag : IComponentData { }
/// <summary>
/// Baked gym enemy roster — one row per on-demand-spawnable enemy KIND (buffer index is arbitrary; match on
/// <see cref="Kind"/>). <c>DebugCommandReceiveSystem</c>'s SpawnEnemy op instantiates the row whose Kind ==
/// ArgA near the requesting player. Entity refs can't live in a blob, so this is a companion buffer on the
/// gym-roster entity (the <see cref="AbilityPrefabElement"/> idiom). Server-read only.
/// </summary>
[InternalBufferCapacity(4)]
public struct GymEnemyRoster : IBufferElementData
{
/// <summary>Stable enemy-kind key (0 = Drowner, 1 = Grindylow; extend as the roster grows).</summary>
public byte Kind;
/// <summary>The baked enemy ghost prefab entity to instantiate for this kind.</summary>
public Entity Prefab;
}
/// <summary>Stable kind ids for <see cref="GymEnemyRoster.Kind"/> / the SpawnEnemy op's ArgA.</summary>
public static class GymEnemyKind
{
public const byte Drowner = 0;
public const byte Grindylow = 1;
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: f39498494d08f66458393a87b395cd62