Cleanup 2
This commit is contained in:
parent
1052cc0136
commit
ffcd4e6b54
|
|
@ -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.
|
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.
|
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
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using Definition.DataStruct;
|
using Definition.DataStruct;
|
||||||
using Entity;
|
using Entity;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
|
|
@ -10,9 +10,5 @@ public abstract class EnemyBase : TargetableObject
|
||||||
public virtual float AttackRange => 1f;
|
public virtual float AttackRange => 1f;
|
||||||
|
|
||||||
public virtual void SetTarget(Transform target) => _target = target;
|
public virtual void SetTarget(Transform target) => _target = target;
|
||||||
|
}
|
||||||
|
|
||||||
protected bool IsSimulationMovementEnabled()
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using CustomDebugger;
|
using CustomDebugger;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityGameFramework.Runtime;
|
using UnityGameFramework.Runtime;
|
||||||
|
|
@ -8,28 +8,28 @@ namespace Simulation
|
||||||
public sealed partial class SimulationWorld : GameFrameworkComponent
|
public sealed partial class SimulationWorld : GameFrameworkComponent
|
||||||
{
|
{
|
||||||
// Partial layout:
|
// Partial layout:
|
||||||
// - SimulationWorld.cs: 核心状态、常量和 Unity 生命周期入口点。
|
// - SimulationWorld.cs: 鏍稿績鐘舵€併€佸父閲忓拰 Unity 鐢熷懡鍛ㄦ湡鍏ュ彛鐐广€?
|
||||||
// - SimulationWorld.RuntimeModules.cs: 运行时域对象、配置和状态代理。
|
// - SimulationWorld.RuntimeModules.cs: 杩愯鏃跺煙瀵硅薄銆侀厤缃拰鐘舵€佷唬鐞嗐€?
|
||||||
// - SimulationWorld.SimEntityState.cs: 模拟状态的增删改查和生命周期注册。
|
// - SimulationWorld.SimEntityState.cs: 妯℃嫙鐘舵€佺殑澧炲垹鏀规煡鍜岀敓鍛藉懆鏈熸敞鍐屻€?
|
||||||
// - SimulationWorld.EntityToSimData.cs: Unity 实体到 sim data 的初始化适配。
|
// - SimulationWorld.EntityToSimData.cs: Unity 瀹炰綋鍒?sim data 鐨勫垵濮嬪寲閫傞厤銆?
|
||||||
// - SimulationWorld.EntitySync.cs: GameFramework 实体 show/hide 事件桥。
|
// - SimulationWorld.EntitySync.cs: GameFramework 瀹炰綋 show/hide 浜嬩欢妗ャ€?
|
||||||
// - SimulationWorld.TargetSelectionSpatialIndex.cs: 最近敌空间索引查询。
|
// - SimulationWorld.TargetSelectionSpatialIndex.cs: 鏈€杩戞晫绌洪棿绱㈠紩鏌ヨ銆?
|
||||||
// - Presentation/SimulationWorld.TransformSync.cs: late-update transform 同步桥。
|
// - Presentation/SimulationWorld.TransformSync.cs: late-update transform 鍚屾妗ャ€?
|
||||||
// - Presentation/SimulationWorld.HitPresentation.cs: 投射物命中事件表现桥。
|
// - Presentation/SimulationWorld.HitPresentation.cs: 鎶曞皠鐗╁懡涓簨浠惰〃鐜版ˉ銆?
|
||||||
// - DataChannel/SimulationWorld.JobDataChannel.cs: Job 通道共享字段、常量和运行时状态。
|
// - DataChannel/SimulationWorld.JobDataChannel.cs: Job 閫氶亾鍏变韩瀛楁銆佸父閲忓拰杩愯鏃剁姸鎬併€?
|
||||||
// - DataChannel/SimulationWorld.JobDataLifecycle.cs: Native 通道初始化、清理和 clear。
|
// - DataChannel/SimulationWorld.JobDataLifecycle.cs: Native 閫氶亾鍒濆鍖栥€佹竻鐞嗗拰 clear銆?
|
||||||
// - DataChannel/SimulationWorld.JobDataConversion.cs: sim/job 数据转换与输入输出缓冲准备。
|
// - DataChannel/SimulationWorld.JobDataConversion.cs: sim/job 鏁版嵁杞崲涓庤緭鍏ヨ緭鍑虹紦鍐插噯澶囥€?
|
||||||
// - DataChannel/SimulationWorld.CollisionTransient.cs: 碰撞临时通道和运行时统计。
|
// - DataChannel/SimulationWorld.CollisionTransient.cs: 纰版挒涓存椂閫氶亾鍜岃繍琛屾椂缁熻銆?
|
||||||
// - DataChannel/SimulationWorld.EnemySeparationTemporal.cs: 敌人分离的帧间临时状态。
|
// - DataChannel/SimulationWorld.EnemySeparationTemporal.cs: 鏁屼汉鍒嗙鐨勫抚闂翠复鏃剁姸鎬併€?
|
||||||
// - DataChannel/SimulationWorld.JobOutputCommit.cs: Job 输出回写主容器。
|
// - DataChannel/SimulationWorld.JobOutputCommit.cs: Job 杈撳嚭鍥炲啓涓诲鍣ㄣ€?
|
||||||
// - Jobs/SimulationWorld.EnemyJobs.cs: 模拟通道 编排 + 敌人移动/分离 顺序执行
|
// - Jobs/SimulationWorld.EnemyJobs.cs: 妯℃嫙閫氶亾 缂栨帓 + 鏁屼汉绉诲姩/鍒嗙 椤哄簭鎵ц
|
||||||
// - Jobs/SimulationWorld.ProjectileJobs.cs: 投射物移动与回收
|
// - Jobs/SimulationWorld.ProjectileJobs.cs: 鎶曞皠鐗╃Щ鍔ㄤ笌鍥炴敹
|
||||||
// - Jobs/SimulationWorld.CollisionPipeline.cs: 碰撞管线共享配置和状态
|
// - Jobs/SimulationWorld.CollisionPipeline.cs: 纰版挒绠$嚎鍏变韩閰嶇疆鍜岀姸鎬?
|
||||||
// - Jobs/SimulationWorld.CollisionRequests.cs: area/sector 请求缓冲
|
// - Jobs/SimulationWorld.CollisionRequests.cs: area/sector 璇锋眰缂撳啿
|
||||||
// - Jobs/SimulationWorld.CollisionBroadPhase.cs: broad-phase 候选构建和 Job 调度
|
// - Jobs/SimulationWorld.CollisionBroadPhase.cs: broad-phase 鍊欓€夋瀯寤哄拰 Job 璋冨害
|
||||||
// - Jobs/SimulationWorld.CollisionResolve.cs: 主线程命中结算与 area settle
|
// - Jobs/SimulationWorld.CollisionResolve.cs: 涓荤嚎绋嬪懡涓粨绠椾笌 area settle
|
||||||
// - Jobs/SimulationWorld.CollisionPresentation.cs: 命中表现事件和实体/impact 解析
|
// - Jobs/SimulationWorld.CollisionPresentation.cs: 鍛戒腑琛ㄧ幇浜嬩欢鍜屽疄浣?impact 瑙f瀽
|
||||||
// - JobStruct/*.cs: burst job 内核和面向 job 的数据结构
|
// - JobStruct/*.cs: burst job 鍐呮牳鍜岄潰鍚?job 鐨勬暟鎹粨鏋?
|
||||||
private const float DefaultAttackRange = 1f;
|
private const float DefaultAttackRange = 1f;
|
||||||
private const int EnemyStateIdle = 0;
|
private const int EnemyStateIdle = 0;
|
||||||
private const int EnemyStateChasing = 1;
|
private const int EnemyStateChasing = 1;
|
||||||
|
|
@ -44,7 +44,6 @@ namespace Simulation
|
||||||
public IReadOnlyList<EnemySimData> Enemies => _enemies;
|
public IReadOnlyList<EnemySimData> Enemies => _enemies;
|
||||||
public IReadOnlyList<ProjectileSimData> Projectiles => _projectiles;
|
public IReadOnlyList<ProjectileSimData> Projectiles => _projectiles;
|
||||||
public IReadOnlyList<PickupSimData> Pickups => _pickups;
|
public IReadOnlyList<PickupSimData> Pickups => _pickups;
|
||||||
public bool UseSimulationMovement => true;
|
|
||||||
|
|
||||||
#region Lifecycle
|
#region Lifecycle
|
||||||
|
|
||||||
|
|
@ -92,3 +91,4 @@ namespace Simulation
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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<Transform, LegacyRegistration> LegacyRegistrations = new();
|
|
||||||
private static readonly List<EnemySeparationAgent> LegacyAgents = new();
|
|
||||||
private static readonly List<Transform> 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<EnemySeparationAgent> 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 3cf44095cd7c76043a8e8a44dc5a0888
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
|
|
@ -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<int, Agent> _agents = new();
|
|
||||||
private readonly System.Collections.Generic.Dictionary<long, System.Collections.Generic.List<int>> _buckets = new();
|
|
||||||
private readonly System.Collections.Generic.Stack<System.Collections.Generic.List<int>> _bucketListPool = new();
|
|
||||||
private readonly System.Collections.Generic.List<long> _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<EnemySeparationAgent> 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<int>(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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: c7c10dca24b508f4fa6726eae7ac2fb1
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
|
|
@ -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<EnemySeparationAgent> agents);
|
|
||||||
Vector3 Resolve(int agentId, Vector3 desiredPosition, Vector3 fallbackDirection, int iterations);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: e3960124c8fe4304493659a13e5a9439
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
|
|
@ -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<int, Agent> _agents = new();
|
|
||||||
private readonly System.Collections.Generic.List<int> _agentKeys = new();
|
|
||||||
|
|
||||||
public void SetAgents(System.Collections.Generic.IReadOnlyList<EnemySeparationAgent> 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: ec8ec1013900437498da4613f680a898
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Components;
|
using Components;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
@ -88,7 +88,7 @@ namespace Simulation.Tests.Editor
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void SyncEnemyMovementInput_DisablesEnemyMovementOnSimulationPath()
|
public void SyncEnemyMovementInput_DisablesEnemyMovement()
|
||||||
{
|
{
|
||||||
UpsertEnemy(new EnemySimData
|
UpsertEnemy(new EnemySimData
|
||||||
{
|
{
|
||||||
|
|
@ -166,3 +166,4 @@ namespace Simulation.Tests.Editor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Entity;
|
using Entity;
|
||||||
using Entity.EntityData;
|
using Entity.EntityData;
|
||||||
|
|
@ -99,7 +99,7 @@ namespace Simulation.Tests.PlayMode
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnityTest]
|
[UnityTest]
|
||||||
public IEnumerator Tick_RespectsEnemyMovementSyncFromComponentShell()
|
public IEnumerator Tick_RespectsEnemyMovementSync()
|
||||||
{
|
{
|
||||||
_world.SyncEnemyMovementInput(4001, false, Vector3.left, 5f, true, 0.45f, 2);
|
_world.SyncEnemyMovementInput(4001, false, Vector3.left, 5f, true, 0.45f, 2);
|
||||||
|
|
||||||
|
|
@ -133,3 +133,4 @@ namespace Simulation.Tests.PlayMode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@
|
||||||
|
|
||||||
## 路线收敛说明
|
## 路线收敛说明
|
||||||
- `SimulationWorld.Tick(...)` 已收敛为战斗内唯一仿真执行入口。
|
- `SimulationWorld.Tick(...)` 已收敛为战斗内唯一仿真执行入口。
|
||||||
- `UseSimulationMovement` 不再承担运行时双路径路由职责,不应再作为回滚到旧 `MovementComponent` 路径的开关理解。
|
- 旧的 `UseSimulationMovement` 兼容属性已删除;运行时不再暴露“是否启用 SimulationWorld 移动”的壳层开关。
|
||||||
- 敌人、投射物与目标查询的运行时行为统一以 `SimulationWorld` 主容器和 Burst Job 管线为准。
|
- 敌人、投射物与目标查询的运行时行为统一以 `SimulationWorld` 主容器和 Burst Job 管线为准。
|
||||||
- 验证建议:聚焦单一路径下的敌人移动、投射物生命周期、最近敌查询和 area hit 结果,而不是做旧路径 A/B 对照。
|
- 验证建议:聚焦单一路径下的敌人移动、投射物生命周期、最近敌查询和 area hit 结果,而不是做旧路径 A/B 对照。
|
||||||
|
|
||||||
|
|
@ -50,3 +50,4 @@
|
||||||
- Job/Burst 第一优先级:`MoveSeperation` 阶段并行化。
|
- Job/Burst 第一优先级:`MoveSeperation` 阶段并行化。
|
||||||
- 保持阶段边界不变:继续维持四阶段管线与 `ProfilerMarker`,避免失去对比口径。
|
- 保持阶段边界不变:继续维持四阶段管线与 `ProfilerMarker`,避免失去对比口径。
|
||||||
- 保持生命周期/索引规则不变:`EntitySync` 与 swap-back/remap 继续作为硬约束。
|
- 保持生命周期/索引规则不变:`EntitySync` 与 swap-back/remap 继续作为硬约束。
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@
|
||||||
- [x] Checkpoint 3:建立 Simulation 主更新入口并接入 Battle 状态
|
- [x] Checkpoint 3:建立 Simulation 主更新入口并接入 Battle 状态
|
||||||
- 在 `GameStateBattle.OnUpdate` 中增加 `SimulationWorld.Tick(...)` 调用。
|
- 在 `GameStateBattle.OnUpdate` 中增加 `SimulationWorld.Tick(...)` 调用。
|
||||||
- 先只接“敌人移动/追踪”系统,其他逻辑保持原路径。
|
- 先只接“敌人移动/追踪”系统,其他逻辑保持原路径。
|
||||||
- 路线已收敛:不再维护 `UseSimulationMovement` 作为运行时 A/B 与回滚开关。
|
- 路线已收敛:`UseSimulationMovement` 兼容属性已移除,运行时不再保留 A/B 与回滚开关壳层。
|
||||||
- 完成标准:`SimulationWorld.Tick(...)` 成为唯一执行入口,敌人仍能正常追踪玩家。
|
- 完成标准:`SimulationWorld.Tick(...)` 成为唯一执行入口,敌人仍能正常追踪玩家。
|
||||||
|
|
||||||
- [x] Checkpoint 4:迁移敌人核心移动逻辑到 Simulation(去 MonoBehaviour 核心逻辑)
|
- [x] Checkpoint 4:迁移敌人核心移动逻辑到 Simulation(去 MonoBehaviour 核心逻辑)
|
||||||
|
|
@ -72,13 +72,13 @@
|
||||||
## 2.5 P1.5 Simulation 收尾(P2 前置)
|
## 2.5 P1.5 Simulation 收尾(P2 前置)
|
||||||
- [x] Checkpoint 1:清理 `TickEnemies` 侧 GC(优先级最高)
|
- [x] Checkpoint 1:清理 `TickEnemies` 侧 GC(优先级最高)
|
||||||
- 目标:将 `TickEnemies GC` 从当前 `27~108 KB` 降到 `< 5 KB / frame`。
|
- 目标:将 `TickEnemies GC` 从当前 `27~108 KB` 降到 `< 5 KB / frame`。
|
||||||
- 重点文件:`Assets/GameMain/Scripts/Utility/EnemySeperator/GridBucketEnemySeparationSolver.cs`。
|
- 历史热点已收口到 `SimulationWorld` 内部敌人分离管线,不再维护独立 legacy solver 文件。
|
||||||
- 处理方式:桶容器与临时列表复用(包含 bucket list 复用池),避免每帧重建集合。
|
- 处理方式:桶容器与临时列表复用(包含 bucket list 复用池),避免每帧重建集合。
|
||||||
- 完成标准:`2k` 敌人压测下 `TickEnemies GC` 稳定 `< 5 KB / frame`。
|
- 完成标准:`2k` 敌人压测下 `TickEnemies GC` 稳定 `< 5 KB / frame`。
|
||||||
|
|
||||||
- [x] Checkpoint 2:解耦 Simulation 核心与 `Transform` 运行时依赖
|
- [x] Checkpoint 2:解耦 Simulation 核心与 `Transform` 运行时依赖
|
||||||
- 目标:`SimulationWorld.TickEnemies` 不直接读取或写入 `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 阶段回写。
|
- 处理方式:互斥求解输入改为纯数据(位置/半径/索引),`Transform` 仅在 Presentation 阶段回写。
|
||||||
- 完成标准:`TickEnemies` 热路径中不出现 `Transform` 访问。
|
- 完成标准:`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`
|
- 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`
|
- 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`
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
schema: spec-driven
|
||||||
|
created: 2026-04-02
|
||||||
|
|
@ -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.
|
||||||
|
|
@ -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.
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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.
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -7,7 +7,7 @@ Define the battle runtime contract that `SimulationWorld` is the single authorit
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
### Requirement: SimulationWorld SHALL be the sole battle simulation executor
|
### 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
|
#### Scenario: Battle tick advances through SimulationWorld
|
||||||
- **WHEN** the battle update loop advances a gameplay frame
|
- **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
|
- **WHEN** runtime configuration related to simulation movement is evaluated
|
||||||
- **THEN** it does not select or re-enable a separate legacy movement execution path
|
- **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
|
### 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.
|
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
|
- **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
|
### 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
|
#### Scenario: Debug panel omits legacy solver controls
|
||||||
- **WHEN** runtime simulation debugging is displayed
|
- **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
|
#### Scenario: Regression tests validate observable single-path behavior
|
||||||
- **WHEN** simulation regression coverage is maintained
|
- **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
|
- **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
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue