diff --git a/AGENTS.md b/AGENTS.md index 3fc76b0..82722d5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -31,3 +31,6 @@ Always keep `.meta` files when adding or moving Unity assets to preserve GUID re Recent history favors concise, descriptive commit messages (often Chinese), e.g. `Feature: add launcher scene and update project settings`. Keep commits focused and include module context (`UI`, `Procedure`, `Entity`) when useful. PRs should include: change summary, affected scenes/modules, test evidence (Test Runner or CLI logs), linked issue/task, and screenshots or short video for UI/visual updates. + +## Encoding +Use UTF8 with BOM diff --git a/Assets/GameMain/Scripts/Entity/EntityLogic/Enemy/EnemyBase.cs b/Assets/GameMain/Scripts/Entity/EntityLogic/Enemy/EnemyBase.cs index 03ae712..86c9e9d 100644 --- a/Assets/GameMain/Scripts/Entity/EntityLogic/Enemy/EnemyBase.cs +++ b/Assets/GameMain/Scripts/Entity/EntityLogic/Enemy/EnemyBase.cs @@ -1,4 +1,4 @@ -using Definition.DataStruct; +using Definition.DataStruct; using Entity; using UnityEngine; @@ -10,9 +10,5 @@ public abstract class EnemyBase : TargetableObject public virtual float AttackRange => 1f; public virtual void SetTarget(Transform target) => _target = target; - - protected bool IsSimulationMovementEnabled() - { - return true; - } } + diff --git a/Assets/GameMain/Scripts/Simulation/SimulationWorld.cs b/Assets/GameMain/Scripts/Simulation/SimulationWorld.cs index 3cfa136..6783643 100644 --- a/Assets/GameMain/Scripts/Simulation/SimulationWorld.cs +++ b/Assets/GameMain/Scripts/Simulation/SimulationWorld.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using CustomDebugger; using UnityEngine; using UnityGameFramework.Runtime; @@ -8,28 +8,28 @@ namespace Simulation public sealed partial class SimulationWorld : GameFrameworkComponent { // Partial layout: - // - SimulationWorld.cs: 核心状态、常量和 Unity 生命周期入口点。 - // - SimulationWorld.RuntimeModules.cs: 运行时域对象、配置和状态代理。 - // - SimulationWorld.SimEntityState.cs: 模拟状态的增删改查和生命周期注册。 - // - SimulationWorld.EntityToSimData.cs: Unity 实体到 sim data 的初始化适配。 - // - SimulationWorld.EntitySync.cs: GameFramework 实体 show/hide 事件桥。 - // - SimulationWorld.TargetSelectionSpatialIndex.cs: 最近敌空间索引查询。 - // - Presentation/SimulationWorld.TransformSync.cs: late-update transform 同步桥。 - // - Presentation/SimulationWorld.HitPresentation.cs: 投射物命中事件表现桥。 - // - DataChannel/SimulationWorld.JobDataChannel.cs: Job 通道共享字段、常量和运行时状态。 - // - DataChannel/SimulationWorld.JobDataLifecycle.cs: Native 通道初始化、清理和 clear。 - // - DataChannel/SimulationWorld.JobDataConversion.cs: sim/job 数据转换与输入输出缓冲准备。 - // - DataChannel/SimulationWorld.CollisionTransient.cs: 碰撞临时通道和运行时统计。 - // - DataChannel/SimulationWorld.EnemySeparationTemporal.cs: 敌人分离的帧间临时状态。 - // - DataChannel/SimulationWorld.JobOutputCommit.cs: Job 输出回写主容器。 - // - Jobs/SimulationWorld.EnemyJobs.cs: 模拟通道 编排 + 敌人移动/分离 顺序执行 - // - Jobs/SimulationWorld.ProjectileJobs.cs: 投射物移动与回收 - // - Jobs/SimulationWorld.CollisionPipeline.cs: 碰撞管线共享配置和状态 - // - Jobs/SimulationWorld.CollisionRequests.cs: area/sector 请求缓冲 - // - Jobs/SimulationWorld.CollisionBroadPhase.cs: broad-phase 候选构建和 Job 调度 - // - Jobs/SimulationWorld.CollisionResolve.cs: 主线程命中结算与 area settle - // - Jobs/SimulationWorld.CollisionPresentation.cs: 命中表现事件和实体/impact 解析 - // - JobStruct/*.cs: burst job 内核和面向 job 的数据结构 + // - SimulationWorld.cs: 鏍稿績鐘舵€併€佸父閲忓拰 Unity 鐢熷懡鍛ㄦ湡鍏ュ彛鐐广€? + // - SimulationWorld.RuntimeModules.cs: 杩愯鏃跺煙瀵硅薄銆侀厤缃拰鐘舵€佷唬鐞嗐€? + // - SimulationWorld.SimEntityState.cs: 妯℃嫙鐘舵€佺殑澧炲垹鏀规煡鍜岀敓鍛藉懆鏈熸敞鍐屻€? + // - SimulationWorld.EntityToSimData.cs: Unity 瀹炰綋鍒?sim data 鐨勫垵濮嬪寲閫傞厤銆? + // - SimulationWorld.EntitySync.cs: GameFramework 瀹炰綋 show/hide 浜嬩欢妗ャ€? + // - SimulationWorld.TargetSelectionSpatialIndex.cs: 鏈€杩戞晫绌洪棿绱㈠紩鏌ヨ銆? + // - Presentation/SimulationWorld.TransformSync.cs: late-update transform 鍚屾妗ャ€? + // - Presentation/SimulationWorld.HitPresentation.cs: 鎶曞皠鐗╁懡涓簨浠惰〃鐜版ˉ銆? + // - DataChannel/SimulationWorld.JobDataChannel.cs: Job 閫氶亾鍏变韩瀛楁銆佸父閲忓拰杩愯鏃剁姸鎬併€? + // - DataChannel/SimulationWorld.JobDataLifecycle.cs: Native 閫氶亾鍒濆鍖栥€佹竻鐞嗗拰 clear銆? + // - DataChannel/SimulationWorld.JobDataConversion.cs: sim/job 鏁版嵁杞崲涓庤緭鍏ヨ緭鍑虹紦鍐插噯澶囥€? + // - DataChannel/SimulationWorld.CollisionTransient.cs: 纰版挒涓存椂閫氶亾鍜岃繍琛屾椂缁熻銆? + // - DataChannel/SimulationWorld.EnemySeparationTemporal.cs: 鏁屼汉鍒嗙鐨勫抚闂翠复鏃剁姸鎬併€? + // - DataChannel/SimulationWorld.JobOutputCommit.cs: Job 杈撳嚭鍥炲啓涓诲鍣ㄣ€? + // - Jobs/SimulationWorld.EnemyJobs.cs: 妯℃嫙閫氶亾 缂栨帓 + 鏁屼汉绉诲姩/鍒嗙 椤哄簭鎵ц + // - Jobs/SimulationWorld.ProjectileJobs.cs: 鎶曞皠鐗╃Щ鍔ㄤ笌鍥炴敹 + // - Jobs/SimulationWorld.CollisionPipeline.cs: 纰版挒绠$嚎鍏变韩閰嶇疆鍜岀姸鎬? + // - Jobs/SimulationWorld.CollisionRequests.cs: area/sector 璇锋眰缂撳啿 + // - Jobs/SimulationWorld.CollisionBroadPhase.cs: broad-phase 鍊欓€夋瀯寤哄拰 Job 璋冨害 + // - Jobs/SimulationWorld.CollisionResolve.cs: 涓荤嚎绋嬪懡涓粨绠椾笌 area settle + // - Jobs/SimulationWorld.CollisionPresentation.cs: 鍛戒腑琛ㄧ幇浜嬩欢鍜屽疄浣?impact 瑙f瀽 + // - JobStruct/*.cs: burst job 鍐呮牳鍜岄潰鍚?job 鐨勬暟鎹粨鏋? private const float DefaultAttackRange = 1f; private const int EnemyStateIdle = 0; private const int EnemyStateChasing = 1; @@ -44,7 +44,6 @@ namespace Simulation public IReadOnlyList Enemies => _enemies; public IReadOnlyList Projectiles => _projectiles; public IReadOnlyList Pickups => _pickups; - public bool UseSimulationMovement => true; #region Lifecycle @@ -92,3 +91,4 @@ namespace Simulation #endregion } } + diff --git a/Assets/GameMain/Scripts/Utility/EnemySeperator/EnemySeparationSolverProvider.cs b/Assets/GameMain/Scripts/Utility/EnemySeperator/EnemySeparationSolverProvider.cs deleted file mode 100644 index 040137f..0000000 --- a/Assets/GameMain/Scripts/Utility/EnemySeperator/EnemySeparationSolverProvider.cs +++ /dev/null @@ -1,163 +0,0 @@ -using System.Collections.Generic; -using UnityEngine; - -namespace CustomUtility -{ - public static class EnemySeparationSolverProvider - { - private enum SolverType - { - GridBucket, - Naive - } - - private struct LegacyRegistration - { - public int AgentId; - public float BodyRadius; - } - - private static SolverType _solverType = SolverType.GridBucket; - private static float _gridCellSize = 1f; - private static IEnemySeparationSolver _legacySolver = CreateSolver(); - private static IEnemySeparationSolver _simulationSolver = CreateSolver(); - - private static readonly Dictionary LegacyRegistrations = new(); - private static readonly List LegacyAgents = new(); - private static readonly List LegacyRecycle = new(); - private static int _legacySnapshotFrame = -1; - private static int _nextLegacyAgentId = 1; - - public static IEnemySeparationSolver Current => _simulationSolver; - public static string CurrentSolverName => _simulationSolver.GetType().Name; - - public static void SetSolver(IEnemySeparationSolver solver) - { - if (solver == null) return; - - _legacySolver = solver; - _simulationSolver = solver; - _legacySnapshotFrame = -1; - } - - public static void UseGridBucketSolver(float cellSize = 1f) - { - _solverType = SolverType.GridBucket; - _gridCellSize = Mathf.Max(0.1f, cellSize); - RecreateSolvers(); - } - - public static void UseNaiveSolver() - { - _solverType = SolverType.Naive; - RecreateSolvers(); - } - - public static void Register(Transform transform, float bodyRadius) - { - if (transform == null) return; - - if (LegacyRegistrations.TryGetValue(transform, out LegacyRegistration registration)) - { - registration.BodyRadius = bodyRadius; - LegacyRegistrations[transform] = registration; - } - else - { - LegacyRegistrations.Add(transform, new LegacyRegistration - { - AgentId = _nextLegacyAgentId++, - BodyRadius = bodyRadius - }); - } - - _legacySnapshotFrame = -1; - } - - public static void Unregister(Transform transform) - { - if (transform == null) return; - if (!LegacyRegistrations.Remove(transform)) return; - - _legacySnapshotFrame = -1; - } - - public static Vector3 Resolve(Transform transform, Vector3 desiredPosition, Vector3 fallbackDirection, - int iterations) - { - if (transform == null) return desiredPosition; - if (!LegacyRegistrations.TryGetValue(transform, out LegacyRegistration registration)) - { - return desiredPosition; - } - - EnsureLegacySnapshot(); - return _legacySolver.Resolve(registration.AgentId, desiredPosition, fallbackDirection, iterations); - } - - public static void SetSimulationAgents(IReadOnlyList agents) - { - _simulationSolver.SetAgents(agents); - } - - public static Vector3 ResolveSimulation(int agentId, Vector3 desiredPosition, Vector3 fallbackDirection, - int iterations) - { - return _simulationSolver.Resolve(agentId, desiredPosition, fallbackDirection, iterations); - } - - private static void EnsureLegacySnapshot() - { - int frame = Time.frameCount; - if (_legacySnapshotFrame == frame) return; - - _legacySnapshotFrame = frame; - LegacyAgents.Clear(); - LegacyRecycle.Clear(); - - foreach (var pair in LegacyRegistrations) - { - Transform transform = pair.Key; - if (transform == null) - { - LegacyRecycle.Add(pair.Key); - continue; - } - - Vector3 position = transform.position; - position.y = 0f; - - LegacyAgents.Add(new EnemySeparationAgent - { - AgentId = pair.Value.AgentId, - Position = position, - Radius = Mathf.Max(0.01f, pair.Value.BodyRadius) - }); - } - - for (int i = 0; i < LegacyRecycle.Count; i++) - { - LegacyRegistrations.Remove(LegacyRecycle[i]); - } - - _legacySolver.SetAgents(LegacyAgents); - } - - private static void RecreateSolvers() - { - _legacySolver = CreateSolver(); - _simulationSolver = CreateSolver(); - _legacySnapshotFrame = -1; - } - - private static IEnemySeparationSolver CreateSolver() - { - if (_solverType == SolverType.Naive) - { - return new NaiveEnemySeparationSolver(); - } - - return new GridBucketEnemySeparationSolver(_gridCellSize); - } - } -} diff --git a/Assets/GameMain/Scripts/Utility/EnemySeperator/EnemySeparationSolverProvider.cs.meta b/Assets/GameMain/Scripts/Utility/EnemySeperator/EnemySeparationSolverProvider.cs.meta deleted file mode 100644 index 5339ad0..0000000 --- a/Assets/GameMain/Scripts/Utility/EnemySeperator/EnemySeparationSolverProvider.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 3cf44095cd7c76043a8e8a44dc5a0888 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Utility/EnemySeperator/GridBucketEnemySeparationSolver.cs b/Assets/GameMain/Scripts/Utility/EnemySeperator/GridBucketEnemySeparationSolver.cs deleted file mode 100644 index f83e050..0000000 --- a/Assets/GameMain/Scripts/Utility/EnemySeperator/GridBucketEnemySeparationSolver.cs +++ /dev/null @@ -1,182 +0,0 @@ -using UnityEngine; - -namespace CustomUtility -{ - public sealed class GridBucketEnemySeparationSolver : IEnemySeparationSolver - { - private struct Agent - { - public float Radius; - public Vector3 Position; - public int CellX; - public int CellZ; - } - - private readonly System.Collections.Generic.Dictionary _agents = new(); - private readonly System.Collections.Generic.Dictionary> _buckets = new(); - private readonly System.Collections.Generic.Stack> _bucketListPool = new(); - private readonly System.Collections.Generic.List _activeBucketKeys = new(); - private readonly float _cellSize; - - private float _maxRadius = 0.45f; - - public GridBucketEnemySeparationSolver(float cellSize = 1f) - { - _cellSize = Mathf.Max(0.1f, cellSize); - } - - public void SetAgents(System.Collections.Generic.IReadOnlyList agents) - { - RecycleBucketsForSnapshot(); - _agents.Clear(); - _maxRadius = 0.01f; - - if (agents == null) return; - - for (int i = 0; i < agents.Count; i++) - { - EnemySeparationAgent input = agents[i]; - Vector3 position = input.Position; - position.y = 0f; - float radius = Mathf.Max(0.01f, input.Radius); - - Agent agent = new Agent - { - Radius = radius, - Position = position, - CellX = ToCell(position.x), - CellZ = ToCell(position.z) - }; - - _agents[input.AgentId] = agent; - AddToBucket(input.AgentId, agent.CellX, agent.CellZ); - - if (radius > _maxRadius) - { - _maxRadius = radius; - } - } - } - - public Vector3 Resolve(int agentId, Vector3 desiredPosition, Vector3 fallbackDirection, int iterations) - { - if (!_agents.TryGetValue(agentId, out var self)) return desiredPosition; - - Vector3 candidate = desiredPosition; - candidate.y = 0f; - - int effectiveIterations = Mathf.Max(1, iterations); - int queryRange = Mathf.Max(1, Mathf.CeilToInt((self.Radius + _maxRadius) / _cellSize)); - Vector3 fallback = fallbackDirection.sqrMagnitude > 0.0001f ? fallbackDirection.normalized : Vector3.right; - fallback.y = 0f; - - for (int iter = 0; iter < effectiveIterations; iter++) - { - int cellX = ToCell(candidate.x); - int cellZ = ToCell(candidate.z); - - for (int dx = -queryRange; dx <= queryRange; dx++) - { - for (int dz = -queryRange; dz <= queryRange; dz++) - { - if (!_buckets.TryGetValue(CellKey(cellX + dx, cellZ + dz), out var bucket)) continue; - - for (int i = 0; i < bucket.Count; i++) - { - int otherAgentId = bucket[i]; - if (otherAgentId == agentId) continue; - if (!_agents.TryGetValue(otherAgentId, out var other)) continue; - - Vector3 toSelf = candidate - other.Position; - float minDistance = self.Radius + other.Radius; - float minDistanceSq = minDistance * minDistance; - float sqrDistance = toSelf.sqrMagnitude; - - if (sqrDistance <= Mathf.Epsilon) - { - candidate += fallback * (self.Radius * 0.25f); - continue; - } - - if (sqrDistance >= minDistanceSq) continue; - - float distance = Mathf.Sqrt(sqrDistance); - float penetration = minDistance - distance; - candidate += (toSelf / distance) * penetration; - } - } - } - } - - SyncAgentPosition(agentId, ref self, candidate); - - candidate.y = desiredPosition.y; - return candidate; - } - - private void RecycleBucketsForSnapshot() - { - for (int i = 0; i < _activeBucketKeys.Count; i++) - { - long key = _activeBucketKeys[i]; - if (!_buckets.TryGetValue(key, out var bucket)) continue; - - bucket.Clear(); - _bucketListPool.Push(bucket); - _buckets.Remove(key); - } - - _activeBucketKeys.Clear(); - } - - private void SyncAgentPosition(int agentId, ref Agent agent, Vector3 position) - { - int newCellX = ToCell(position.x); - int newCellZ = ToCell(position.z); - - if (agent.CellX != newCellX || agent.CellZ != newCellZ) - { - RemoveFromBucket(agentId, agent.CellX, agent.CellZ); - AddToBucket(agentId, newCellX, newCellZ); - agent.CellX = newCellX; - agent.CellZ = newCellZ; - } - - agent.Position = position; - _agents[agentId] = agent; - } - - private void AddToBucket(int agentId, int cellX, int cellZ) - { - long key = CellKey(cellX, cellZ); - if (!_buckets.TryGetValue(key, out var list)) - { - list = _bucketListPool.Count > 0 - ? _bucketListPool.Pop() - : new System.Collections.Generic.List(8); - _buckets.Add(key, list); - _activeBucketKeys.Add(key); - } - - list.Add(agentId); - } - - private void RemoveFromBucket(int agentId, int cellX, int cellZ) - { - long key = CellKey(cellX, cellZ); - if (!_buckets.TryGetValue(key, out var list)) return; - - list.Remove(agentId); - } - - private int ToCell(float value) - { - return Mathf.FloorToInt(value / _cellSize); - } - - private static long CellKey(int x, int z) - { - return ((long)x << 32) ^ (uint)z; - } - } -} diff --git a/Assets/GameMain/Scripts/Utility/EnemySeperator/GridBucketEnemySeparationSolver.cs.meta b/Assets/GameMain/Scripts/Utility/EnemySeperator/GridBucketEnemySeparationSolver.cs.meta deleted file mode 100644 index 7df22f7..0000000 --- a/Assets/GameMain/Scripts/Utility/EnemySeperator/GridBucketEnemySeparationSolver.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: c7c10dca24b508f4fa6726eae7ac2fb1 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Utility/EnemySeperator/IEnemySeparationSolver.cs b/Assets/GameMain/Scripts/Utility/EnemySeperator/IEnemySeparationSolver.cs deleted file mode 100644 index 0d002f1..0000000 --- a/Assets/GameMain/Scripts/Utility/EnemySeperator/IEnemySeparationSolver.cs +++ /dev/null @@ -1,17 +0,0 @@ -using UnityEngine; - -namespace CustomUtility -{ - public struct EnemySeparationAgent - { - public int AgentId; - public Vector3 Position; - public float Radius; - } - - public interface IEnemySeparationSolver - { - void SetAgents(System.Collections.Generic.IReadOnlyList agents); - Vector3 Resolve(int agentId, Vector3 desiredPosition, Vector3 fallbackDirection, int iterations); - } -} diff --git a/Assets/GameMain/Scripts/Utility/EnemySeperator/IEnemySeparationSolver.cs.meta b/Assets/GameMain/Scripts/Utility/EnemySeperator/IEnemySeparationSolver.cs.meta deleted file mode 100644 index bd00780..0000000 --- a/Assets/GameMain/Scripts/Utility/EnemySeperator/IEnemySeparationSolver.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: e3960124c8fe4304493659a13e5a9439 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/GameMain/Scripts/Utility/EnemySeperator/NaiveEnemySeparationSolver.cs b/Assets/GameMain/Scripts/Utility/EnemySeperator/NaiveEnemySeparationSolver.cs deleted file mode 100644 index 8d198cf..0000000 --- a/Assets/GameMain/Scripts/Utility/EnemySeperator/NaiveEnemySeparationSolver.cs +++ /dev/null @@ -1,81 +0,0 @@ -using UnityEngine; - -namespace CustomUtility -{ - public sealed class NaiveEnemySeparationSolver : IEnemySeparationSolver - { - private struct Agent - { - public float Radius; - public Vector3 Position; - } - - private readonly System.Collections.Generic.Dictionary _agents = new(); - private readonly System.Collections.Generic.List _agentKeys = new(); - - public void SetAgents(System.Collections.Generic.IReadOnlyList agents) - { - _agents.Clear(); - _agentKeys.Clear(); - if (agents == null) return; - - for (int i = 0; i < agents.Count; i++) - { - EnemySeparationAgent input = agents[i]; - Vector3 position = input.Position; - position.y = 0f; - - Agent agent = new Agent - { - Radius = Mathf.Max(0.01f, input.Radius), - Position = position - }; - - _agents[input.AgentId] = agent; - _agentKeys.Add(input.AgentId); - } - } - - public Vector3 Resolve(int agentId, Vector3 desiredPosition, Vector3 fallbackDirection, int iterations) - { - if (!_agents.TryGetValue(agentId, out var self)) return desiredPosition; - - Vector3 candidate = desiredPosition; - candidate.y = 0f; - - Vector3 fallback = fallbackDirection.sqrMagnitude > 0.0001f ? fallbackDirection.normalized : Vector3.right; - fallback.y = 0f; - - int effectiveIterations = Mathf.Max(1, iterations); - for (int iter = 0; iter < effectiveIterations; iter++) - { - for (int i = 0; i < _agentKeys.Count; i++) - { - int otherAgentId = _agentKeys[i]; - if (otherAgentId == agentId) continue; - if (!_agents.TryGetValue(otherAgentId, out var other)) continue; - - Vector3 toSelf = candidate - other.Position; - float minDistance = self.Radius + other.Radius; - float minDistanceSq = minDistance * minDistance; - float sqrDistance = toSelf.sqrMagnitude; - - if (sqrDistance <= Mathf.Epsilon) - { - candidate += fallback * (self.Radius * 0.25f); - continue; - } - - if (sqrDistance >= minDistanceSq) continue; - - float distance = Mathf.Sqrt(sqrDistance); - float penetration = minDistance - distance; - candidate += (toSelf / distance) * penetration; - } - } - - candidate.y = desiredPosition.y; - return candidate; - } - } -} diff --git a/Assets/GameMain/Scripts/Utility/EnemySeperator/NaiveEnemySeparationSolver.cs.meta b/Assets/GameMain/Scripts/Utility/EnemySeperator/NaiveEnemySeparationSolver.cs.meta deleted file mode 100644 index d17ca1c..0000000 --- a/Assets/GameMain/Scripts/Utility/EnemySeperator/NaiveEnemySeparationSolver.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: ec8ec1013900437498da4613f680a898 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Tests/Simulation/EditMode/SimulationWorldTickTests.cs b/Assets/Tests/Simulation/EditMode/SimulationWorldTickTests.cs index d8ca668..6358291 100644 --- a/Assets/Tests/Simulation/EditMode/SimulationWorldTickTests.cs +++ b/Assets/Tests/Simulation/EditMode/SimulationWorldTickTests.cs @@ -1,4 +1,4 @@ -using System.Reflection; +using System.Reflection; using Components; using NUnit.Framework; using UnityEngine; @@ -88,7 +88,7 @@ namespace Simulation.Tests.Editor } [Test] - public void SyncEnemyMovementInput_DisablesEnemyMovementOnSimulationPath() + public void SyncEnemyMovementInput_DisablesEnemyMovement() { UpsertEnemy(new EnemySimData { @@ -166,3 +166,4 @@ namespace Simulation.Tests.Editor } } } + diff --git a/Assets/Tests/Simulation/PlayMode/SimulationWorldPlayModeTests.cs b/Assets/Tests/Simulation/PlayMode/SimulationWorldPlayModeTests.cs index 3d6a58f..e08aae1 100644 --- a/Assets/Tests/Simulation/PlayMode/SimulationWorldPlayModeTests.cs +++ b/Assets/Tests/Simulation/PlayMode/SimulationWorldPlayModeTests.cs @@ -1,4 +1,4 @@ -using System.Collections; +using System.Collections; using System.Reflection; using Entity; using Entity.EntityData; @@ -99,7 +99,7 @@ namespace Simulation.Tests.PlayMode } [UnityTest] - public IEnumerator Tick_RespectsEnemyMovementSyncFromComponentShell() + public IEnumerator Tick_RespectsEnemyMovementSync() { _world.SyncEnemyMovementInput(4001, false, Vector3.left, 5f, true, 0.45f, 2); @@ -133,3 +133,4 @@ namespace Simulation.Tests.PlayMode } } } + diff --git a/docs/CodeX-TODO.md b/docs/CodeX-TODO.md index 46b8f95..d68e737 100644 --- a/docs/CodeX-TODO.md +++ b/docs/CodeX-TODO.md @@ -187,21 +187,21 @@ ### 推荐实施顺序 1. 先确认唯一执行路径 -- `SimulationWorld` Burst 管线作为唯一运行时执行路径 -- 实体和组件只保留输入、注册、表现职责 + - `SimulationWorld` Burst 管线作为唯一运行时执行路径 + - 实体和组件只保留输入、注册、表现职责 2. 再处理战斗入口 -- 先改 `GameStateBattle` / `GameEntry` / `ProcedureGame` -- 让运行时明确依赖当前 Burst 管线,不再保留双路径语义 + - 先改 `GameStateBattle` / `GameEntry` / `ProcedureGame` + - 让运行时明确依赖当前 Burst 管线,不再保留双路径语义 3. 再清理旧组件驱动路径 -- 先收敛 `MovementComponent` -- 再删敌人/玩家/投射物实体里的自驱动移动 -- 再删旧 fallback 查询和旧互斥 solver + - 先收敛 `MovementComponent` + - 再删敌人/玩家/投射物实体里的自驱动移动 + - 再删旧 fallback 查询和旧互斥 solver 4. 最后重建测试和文档 -- 先让行为稳定 -- 再补新的回归测试和文档 + - 先让行为稳定 + - 再补新的回归测试和文档 ### 当前建议 @@ -355,72 +355,72 @@ 优先顺序建议: 1. 长枪 / 刺剑 -- 基于 `WeaponKnife` -- 重点调: - - 前刺距离 - - 命中半径 - - 冷却 + - 基于 `WeaponKnife` + - 重点调: + - 前刺距离 + - 命中半径 + - 冷却 2. 大剑 / 半月斩 -- 基于 `WeaponSlash` -- 重点调: - - `SectorAngle` - - 攻击范围 - - 动画时长 + - 基于 `WeaponSlash` + - 重点调: + - `SectorAngle` + - 攻击范围 + - 动画时长 3. 战锤 / 震地锤 -- 基于 `WeaponLightning` 或 `WeaponKnife` -- 重点调: - - 落点半径 - - 前摇 - - 低频高伤 + - 基于 `WeaponLightning` 或 `WeaponKnife` + - 重点调: + - 落点半径 + - 前摇 + - 低频高伤 4. 霰弹枪 -- 基于参数化后的 `WeaponHandgun` -- 重点调: - - 散射 - - 多 pellet - - 近距离爆发 + - 基于参数化后的 `WeaponHandgun` + - 重点调: + - 散射 + - 多 pellet + - 近距离爆发 5. 狙击枪 -- 基于参数化后的 `WeaponHandgun` -- 重点调: - - 单发高伤 - - 超远射程 - - 慢冷却 + - 基于参数化后的 `WeaponHandgun` + - 重点调: + - 单发高伤 + - 超远射程 + - 慢冷却 6. 陨石杖 / 圣光柱 -- 基于 `WeaponLightning` -- 重点调: - - `HoverHeight` - - 爆炸半径 - - 冷却 + - 基于 `WeaponLightning` + - 重点调: + - `HoverHeight` + - 爆炸半径 + - 冷却 ### P3: 中成本扩展 1. 链式闪电 -- 在首目标命中后,继续寻找附近目标 -- 需要新增: - - 连锁次数 - - 连锁半径 - - 每跳衰减 + - 在首目标命中后,继续寻找附近目标 + - 需要新增: + - 连锁次数 + - 连锁半径 + - 每跳衰减 2. 穿透弹 / 火球 -- 复用现有 projectile/simulation 基础 -- 需要明确: - - 穿透次数 - - 命中后是否爆炸 + - 复用现有 projectile/simulation 基础 + - 需要明确: + - 穿透次数 + - 命中后是否爆炸 3. 地雷 / 陷阱 -- 本质是延时触发 area hit -- 需要新增: - - 布置后触发时机 - - 持续时间 - - 触发半径 + - 本质是延时触发 area hit + - 需要新增: + - 布置后触发时机 + - 持续时间 + - 触发半径 4. 回旋镖 -- 需要双阶段投射物状态 -- 成本高于普通枪械/范围武器 + - 需要双阶段投射物状态 + - 成本高于普通枪械/范围武器 ### P4: 暂缓项 @@ -439,29 +439,29 @@ ## 新武器接入步骤模板 1. 在 `Weapon.txt` 新增一行 -- 配好基础字段 -- `Params` 写 JSON 对象 + - 配好基础字段 + - `Params` 写 JSON 对象 2. 新增 `WeaponType` -- 在 `Assets/GameMain/Scripts/Definition/Enum/WeaponType.cs` + - 在 `Assets/GameMain/Scripts/Definition/Enum/WeaponType.cs` 3. 新增武器数据子类 -- 新建 `WeaponXXXData` -- 新建 `WeaponXXXParamsData` -- 在构造里调用 `ParseParams()` + - 新建 `WeaponXXXData` + - 新建 `WeaponXXXParamsData` + - 在构造里调用 `ParseParams()` 4. 新增武器逻辑类 -- 继承 `WeaponBase` -- 接入状态机 -- 读取 `ParamsData` + - 继承 `WeaponBase` + - 接入状态机 + - 读取 `ParamsData` 5. 接入生成入口 -- 玩家初始武器 -- 商店购买武器 -- 其他掉落/奖励入口 + - 玩家初始武器 + - 商店购买武器 + - 其他掉落/奖励入口 6. 验证点 -- 武器生成正确 -- 参数生效正确 -- 描述文本正确 -- Simulation 模式和非 Simulation 模式都能命中 + - 武器生成正确 + - 参数生效正确 + - 描述文本正确 + - Simulation 模式和非 Simulation 模式都能命中 diff --git a/docs/P1.5 Simulation-Supplement.md b/docs/P1.5 Simulation-Supplement.md index f04a1ee..a28e42d 100644 --- a/docs/P1.5 Simulation-Supplement.md +++ b/docs/P1.5 Simulation-Supplement.md @@ -42,7 +42,7 @@ ## 路线收敛说明 - `SimulationWorld.Tick(...)` 已收敛为战斗内唯一仿真执行入口。 -- `UseSimulationMovement` 不再承担运行时双路径路由职责,不应再作为回滚到旧 `MovementComponent` 路径的开关理解。 +- 旧的 `UseSimulationMovement` 兼容属性已删除;运行时不再暴露“是否启用 SimulationWorld 移动”的壳层开关。 - 敌人、投射物与目标查询的运行时行为统一以 `SimulationWorld` 主容器和 Burst Job 管线为准。 - 验证建议:聚焦单一路径下的敌人移动、投射物生命周期、最近敌查询和 area hit 结果,而不是做旧路径 A/B 对照。 @@ -50,3 +50,4 @@ - Job/Burst 第一优先级:`MoveSeperation` 阶段并行化。 - 保持阶段边界不变:继续维持四阶段管线与 `ProfilerMarker`,避免失去对比口径。 - 保持生命周期/索引规则不变:`EntitySync` 与 swap-back/remap 继续作为硬约束。 + diff --git a/docs/TodoList.md b/docs/TodoList.md index 6aa9d80..740a13a 100644 --- a/docs/TodoList.md +++ b/docs/TodoList.md @@ -40,7 +40,7 @@ - [x] Checkpoint 3:建立 Simulation 主更新入口并接入 Battle 状态 - 在 `GameStateBattle.OnUpdate` 中增加 `SimulationWorld.Tick(...)` 调用。 - 先只接“敌人移动/追踪”系统,其他逻辑保持原路径。 - - 路线已收敛:不再维护 `UseSimulationMovement` 作为运行时 A/B 与回滚开关。 + - 路线已收敛:`UseSimulationMovement` 兼容属性已移除,运行时不再保留 A/B 与回滚开关壳层。 - 完成标准:`SimulationWorld.Tick(...)` 成为唯一执行入口,敌人仍能正常追踪玩家。 - [x] Checkpoint 4:迁移敌人核心移动逻辑到 Simulation(去 MonoBehaviour 核心逻辑) @@ -72,13 +72,13 @@ ## 2.5 P1.5 Simulation 收尾(P2 前置) - [x] Checkpoint 1:清理 `TickEnemies` 侧 GC(优先级最高) - 目标:将 `TickEnemies GC` 从当前 `27~108 KB` 降到 `< 5 KB / frame`。 - - 重点文件:`Assets/GameMain/Scripts/Utility/EnemySeperator/GridBucketEnemySeparationSolver.cs`。 + - 历史热点已收口到 `SimulationWorld` 内部敌人分离管线,不再维护独立 legacy solver 文件。 - 处理方式:桶容器与临时列表复用(包含 bucket list 复用池),避免每帧重建集合。 - 完成标准:`2k` 敌人压测下 `TickEnemies GC` 稳定 `< 5 KB / frame`。 - [x] Checkpoint 2:解耦 Simulation 核心与 `Transform` 运行时依赖 - 目标:`SimulationWorld.TickEnemies` 不直接读取或写入 `Transform`。 - - 重点文件:`Assets/GameMain/Scripts/Simulation/SimulationWorld.cs`、`Assets/GameMain/Scripts/Utility/EnemySeperator/IEnemySeparationSolver.cs`、`Assets/GameMain/Scripts/Utility/EnemySeperator/EnemySeparationSolverProvider.cs`。 + - 当前重点文件:`Assets/GameMain/Scripts/Simulation/SimulationWorld.cs` 及其敌人分离/数据通道实现;legacy provider/interface 已删除。 - 处理方式:互斥求解输入改为纯数据(位置/半径/索引),`Transform` 仅在 Presentation 阶段回写。 - 完成标准:`TickEnemies` 热路径中不出现 `Transform` 访问。 @@ -241,3 +241,4 @@ ## 测试命令 - PlayMode: `& "C:\UnityProjects\Unity Editor\2022.3.62f3c1\Editor\Unity.exe" -batchmode -nographics -projectPath . -runTests -testPlatform PlayMode -testResults Logs/playmode-test-results.xml -logFile Logs/playmode-tests.log` - EditMode: `& "C:\UnityProjects\Unity Editor\2022.3.62f3c1\Editor\Unity.exe" -batchmode -nographics -projectPath . -runTests -testPlatform EditMode -testResults Logs/editmode-test-results.xml -logFile Logs/editmode-tests.log` + diff --git a/openspec/changes/archive/2026-04-02-remove-simulationworld-legacy-movement-shims/.openspec.yaml b/openspec/changes/archive/2026-04-02-remove-simulationworld-legacy-movement-shims/.openspec.yaml new file mode 100644 index 0000000..6a5db8c --- /dev/null +++ b/openspec/changes/archive/2026-04-02-remove-simulationworld-legacy-movement-shims/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-04-02 diff --git a/openspec/changes/archive/2026-04-02-remove-simulationworld-legacy-movement-shims/design.md b/openspec/changes/archive/2026-04-02-remove-simulationworld-legacy-movement-shims/design.md new file mode 100644 index 0000000..f0567e7 --- /dev/null +++ b/openspec/changes/archive/2026-04-02-remove-simulationworld-legacy-movement-shims/design.md @@ -0,0 +1,48 @@ +## Context + +`SimulationWorld` 的运行时收敛已经完成,但代码和文档层面仍留有三类误导性遗留:`SimulationWorld.UseSimulationMovement` 这类恒真属性、`EnemyBase.IsSimulationMovementEnabled()` 这类恒真帮助方法,以及 `EnemySeparationSolverProvider` / `IEnemySeparationSolver` 与两个 legacy solver 实现。这些残留类型不再参与真实运行时调度,却继续把当前架构表述成“单路径之上还保留一套可切换兼容层”。 + +本次变更是一次尾部收口,不重新设计仿真数据流,也不扩展新的 solver 能力;目标是让代码表面与已经落地的运行时事实保持一致。 + +## Goals / Non-Goals + +**Goals:** +- 删除仍对外暴露旧路径语义的兼容属性和帮助方法。 +- 删除不再被运行时使用的 enemy separation provider/interface/legacy solver 类型。 +- 让测试和文档表达与当前单一路径实现一致。 +- 把影响收敛在 `SimulationWorld`、enemy runtime、legacy solver 文件和对应回归覆盖内。 + +**Non-Goals:** +- 不在本次 change 中处理 Unity 场景序列化残留字段。 +- 不重做 `SimulationWorld` 的敌人分离算法或数据结构。 +- 不引入新的运行时调试面板、配置项或回滚开关。 + +## Decisions + +### Remove compatibility members instead of renaming them +直接删除 `UseSimulationMovement` 和 `IsSimulationMovementEnabled()`,而不是把它们改名为新的恒真语义成员。原因是这些成员的唯一历史价值就是表达“可选择是否启用 SimulationWorld”,继续保留只会延长错误心智模型。替代方案是保留只读属性并在注释里声明恒真,但这仍会让调用方继续围绕“是否启用”写分支,因此不采用。 + +### Delete legacy solver types rather than keep them as dead abstractions +`EnemySeparationSolverProvider` 与 `IEnemySeparationSolver` 已不再承载运行时能力,继续保留会产生“还有第二套 enemy separation 入口”的假象。本次直接删除 provider、interface 以及两个实现类,而不是把 provider 改成内部空壳。替代方案是保留文件供历史参考,但仓库历史已经足够承担这个角色,不需要源码继续占位。 + +### Tighten regression coverage around absence of legacy entry points +回归重点不是验证某个字段恒真,而是验证调用面已经不再依赖这些兼容入口。因此测试和文档只覆盖单路径可观察行为,并显式移除对旧壳层 API 的引用。替代方案是增加“成员不存在”的反射测试,但那类测试脆弱且价值低,不采用。 + +## Risks / Trade-offs + +- [外部代码仍引用这些壳层成员] → 在实现前用全文检索清理调用点,并通过编译验证所有受影响程序集。 +- [删除 legacy solver 文件后,文档或测试仍残留旧名称] → 同步更新 `docs/` 和 `Assets/Tests/Simulation/` 中的直接引用。 +- [未来有人希望恢复独立 enemy separation 实验入口] → 若确实需要,应以新的 `SimulationWorld` 内部实验点重新设计,而不是恢复旧 provider 抽象。 + +## Migration Plan + +1. 删除 `UseSimulationMovement` 与 `IsSimulationMovementEnabled()` 及其剩余引用。 +2. 删除 `EnemySeparationSolverProvider`、`IEnemySeparationSolver` 与 legacy solver 实现文件。 +3. 更新受影响测试与文档,使其不再依赖这些符号。 +4. 通过编译与相关仿真测试验证仓库仍在单路径语义下工作。 + +无需运行时迁移或数据迁移;这是源码级收口。回滚方式仅为恢复该 change 的提交,不提供运行时开关回退。 + +## Open Questions + +- None. diff --git a/openspec/changes/archive/2026-04-02-remove-simulationworld-legacy-movement-shims/proposal.md b/openspec/changes/archive/2026-04-02-remove-simulationworld-legacy-movement-shims/proposal.md new file mode 100644 index 0000000..9706adf --- /dev/null +++ b/openspec/changes/archive/2026-04-02-remove-simulationworld-legacy-movement-shims/proposal.md @@ -0,0 +1,26 @@ +## Why + +`SimulationWorld` 已经成为唯一运行时执行路径,但仓库里仍保留 `UseSimulationMovement`、`EnemyBase.IsSimulationMovementEnabled()` 以及 `EnemySeparationSolverProvider` / `IEnemySeparationSolver` 这类旧路径壳层。它们不再承载真实运行时能力,却继续制造双路径仍可恢复的错误信号,也增加后续维护和阅读成本。 + +## What Changes + +- 删除 `SimulationWorld.UseSimulationMovement` 这类恒真兼容属性,改为直接暴露单路径语义。 +- 删除 `EnemyBase.IsSimulationMovementEnabled()` 及其调用点,去除敌人运行时代码里残留的旧路径判断壳层。 +- 删除 `EnemySeparationSolverProvider`、`IEnemySeparationSolver` 及其 legacy solver 实现,明确敌人间分离仅由 `SimulationWorld` 负责。 +- 更新测试与文档,确保回归覆盖和架构说明不再引用这些兼容壳层或 legacy solver 接口。 + +## Capabilities + +### New Capabilities + +None. + +### Modified Capabilities + +- `simulationworld-runtime-convergence`: 收紧单路径运行时要求,明确不得保留可被误解为旧路径开关、能力接口或 solver 提供器的兼容壳层。 + +## Impact + +- Affected code: `Assets/GameMain/Scripts/Simulation`, `Assets/GameMain/Scripts/Entity/EntityLogic/Enemy`, `Assets/GameMain/Scripts/Utility/EnemySeperator`, and related tests/docs. +- APIs: removes compatibility-facing members that still imply legacy movement routing or solver substitution. +- Systems: clarifies that enemy separation and movement execution stay exclusively on the `SimulationWorld` path. diff --git a/openspec/changes/archive/2026-04-02-remove-simulationworld-legacy-movement-shims/specs/simulationworld-runtime-convergence/spec.md b/openspec/changes/archive/2026-04-02-remove-simulationworld-legacy-movement-shims/specs/simulationworld-runtime-convergence/spec.md new file mode 100644 index 0000000..4e0ff51 --- /dev/null +++ b/openspec/changes/archive/2026-04-02-remove-simulationworld-legacy-movement-shims/specs/simulationworld-runtime-convergence/spec.md @@ -0,0 +1,31 @@ +## MODIFIED Requirements + +### Requirement: SimulationWorld SHALL be the sole battle simulation executor +The battle runtime MUST execute movement, projectile stepping, collision broad-phase, and related simulation state updates through `SimulationWorld`, and it MUST NOT route these responsibilities through an alternative non-`SimulationWorld` runtime path or expose compatibility switches that imply such a runtime path still exists. + +#### Scenario: Battle tick advances through SimulationWorld +- **WHEN** the battle update loop advances a gameplay frame +- **THEN** `SimulationWorld` executes the simulation pipeline for that frame as the authoritative runtime update path + +#### Scenario: Legacy routing switch does not select an alternate executor +- **WHEN** runtime configuration related to simulation movement is evaluated +- **THEN** it does not select or re-enable a separate legacy movement execution path + +#### Scenario: Runtime API does not expose legacy movement enablement shims +- **WHEN** gameplay runtime code integrates with movement simulation +- **THEN** it does not depend on compatibility members whose purpose is to report whether `SimulationWorld` movement is enabled + +### Requirement: Runtime surfaces SHALL reflect the single-path architecture +Runtime debug surfaces, automated tests, architecture documents, and compatibility-facing runtime APIs MUST reflect that the project supports one authoritative `SimulationWorld` execution path rather than dual-path behavior, and MUST NOT preserve legacy solver provider abstractions that imply an alternate runtime separation path is still supported. + +#### Scenario: Debug panel omits legacy solver controls +- **WHEN** runtime simulation debugging is displayed +- **THEN** it shows current `SimulationWorld` metrics without exposing legacy solver switching or dual-path controls + +#### Scenario: Regression tests validate observable single-path behavior +- **WHEN** simulation regression coverage is maintained +- **THEN** tests validate observable outcomes such as movement, projectile lifetime, hit results, and hide/remove lifecycle instead of asserting private compatibility fields for legacy paths + +#### Scenario: Runtime codebase omits legacy solver provider abstractions +- **WHEN** enemy separation behavior is implemented or documented +- **THEN** it is described as `SimulationWorld`-owned runtime behavior without referencing `EnemySeparationSolverProvider`, `IEnemySeparationSolver`, or equivalent legacy provider abstractions diff --git a/openspec/changes/archive/2026-04-02-remove-simulationworld-legacy-movement-shims/tasks.md b/openspec/changes/archive/2026-04-02-remove-simulationworld-legacy-movement-shims/tasks.md new file mode 100644 index 0000000..28ae9b1 --- /dev/null +++ b/openspec/changes/archive/2026-04-02-remove-simulationworld-legacy-movement-shims/tasks.md @@ -0,0 +1,17 @@ +## 1. Runtime API cleanup + +- [x] 1.1 Remove `SimulationWorld.UseSimulationMovement` and any remaining runtime call sites that branch on that compatibility property. +- [x] 1.2 Remove `EnemyBase.IsSimulationMovementEnabled()` and update enemy runtime code to rely directly on single-path `SimulationWorld` behavior. + +## 2. Legacy solver removal + +- [x] 2.1 Delete `EnemySeparationSolverProvider`, `IEnemySeparationSolver`, and the legacy solver implementations from `Assets/GameMain/Scripts/Utility/EnemySeperator/`. +- [x] 2.2 Clean up any compile-time references, comments, or documentation text that still mention the removed legacy solver provider abstractions. + +## 3. Regression and documentation alignment + +- [x] 3.1 Update simulation/runtime tests so they no longer reference removed compatibility members and still cover observable single-path behavior. +- [x] 3.2 Update architecture and roadmap docs to state that no compatibility movement switch or legacy enemy separation provider remains in the runtime codebase. +- [x] 3.3 Run a build and targeted verification for the affected simulation/runtime surface. + + diff --git a/openspec/specs/simulationworld-runtime-convergence/spec.md b/openspec/specs/simulationworld-runtime-convergence/spec.md index 75acd12..4480c54 100644 --- a/openspec/specs/simulationworld-runtime-convergence/spec.md +++ b/openspec/specs/simulationworld-runtime-convergence/spec.md @@ -7,7 +7,7 @@ Define the battle runtime contract that `SimulationWorld` is the single authorit ## Requirements ### Requirement: SimulationWorld SHALL be the sole battle simulation executor -The battle runtime MUST execute movement, projectile stepping, collision broad-phase, and related simulation state updates through `SimulationWorld`, and it MUST NOT route these responsibilities through an alternative non-`SimulationWorld` runtime path. +The battle runtime MUST execute movement, projectile stepping, collision broad-phase, and related simulation state updates through `SimulationWorld`, and it MUST NOT route these responsibilities through an alternative non-`SimulationWorld` runtime path or expose compatibility switches that imply such a runtime path still exists. #### Scenario: Battle tick advances through SimulationWorld - **WHEN** the battle update loop advances a gameplay frame @@ -17,6 +17,10 @@ The battle runtime MUST execute movement, projectile stepping, collision broad-p - **WHEN** runtime configuration related to simulation movement is evaluated - **THEN** it does not select or re-enable a separate legacy movement execution path +#### Scenario: Runtime API does not expose legacy movement enablement shims +- **WHEN** gameplay runtime code integrates with movement simulation +- **THEN** it does not depend on compatibility members whose purpose is to report whether `SimulationWorld` movement is enabled + ### Requirement: Runtime entities SHALL submit input and consume simulation output only Enemy entities, the player entity, projectile entities, and `MovementComponent` MUST submit movement or behavior input into `SimulationWorld` state and MUST consume position, facing, hit, or lifecycle results from simulation output, rather than computing world-space advancement independently. @@ -40,7 +44,7 @@ Target selection, projectile hits, area hits, and sector hits MUST use `Simulati - **THEN** hit candidates are produced from `SimulationWorld` collision and query capabilities instead of a fallback entity-side path ### Requirement: Runtime surfaces SHALL reflect the single-path architecture -Runtime debug surfaces, automated tests, and architecture documents MUST reflect that the project supports one authoritative `SimulationWorld` execution path rather than dual-path behavior. +Runtime debug surfaces, automated tests, architecture documents, and compatibility-facing runtime APIs MUST reflect that the project supports one authoritative `SimulationWorld` execution path rather than dual-path behavior, and MUST NOT preserve legacy solver provider abstractions that imply an alternate runtime separation path is still supported. #### Scenario: Debug panel omits legacy solver controls - **WHEN** runtime simulation debugging is displayed @@ -49,3 +53,7 @@ Runtime debug surfaces, automated tests, and architecture documents MUST reflect #### Scenario: Regression tests validate observable single-path behavior - **WHEN** simulation regression coverage is maintained - **THEN** tests validate observable outcomes such as movement, projectile lifetime, hit results, and hide/remove lifecycle instead of asserting private compatibility fields for legacy paths + +#### Scenario: Runtime codebase omits legacy solver provider abstractions +- **WHEN** enemy separation behavior is implemented or documented +- **THEN** it is described as `SimulationWorld`-owned runtime behavior without referencing `EnemySeparationSolverProvider`, `IEnemySeparationSolver`, or equivalent legacy provider abstractions