At the start of every conversation, read docs/README.md to load the project documentation map. Skills are automatically loaded by Claude Code from .claude/skills/ — do not manually read them.
Before writing or modifying any code, follow the code-standards skill for naming conventions, member ordering, formatting rules, and test patterns. For edge cases, Explorer/.editorconfig is the authoritative formatting reference.
For expanded patterns and code examples, see the ecs-system-and-component-design skill. For async patterns, see the async-programming skill. For component cleanup lifecycle, see both the ECS and sdk-component-implementation skills.
-
Systems are the sole logic entry point for entities.
- Entity manipulation outside systems is strictly forbidden.
-
Systems:
- Must not hold persistent entity/component collections.
- Can use temporary collections for per-frame aggregation.
- Must not contain state — all state goes into ECS.
-
Systems must inherit from
BaseUnityLoopSystem. -
Constructors:
- Marked
internal. - Accept shared dependencies only (settings, pools, utilities, factories).
- Marked
-
Follow single responsibility principle.
- Split systems >200 lines into static counterparts.
- Group systems into features with a defined execution order.
-
Assign each system to a
SystemGroupbased on purpose (e.g. Physics, Presentation). -
Consider creating custom groups for better dependency control.
- Prefer source-generated queries.
- Avoid
World.Query(last resort due to delegate/closure overhead). - No nested queries.
- Use
TryGetfor known entities over queries. - Always filter out
DeleteEntityIntention.
- System
Update()must be allocation-free. - Keep logic minimal due to multiple world executions per frame.
- Consolidate queries sharing filters.
- Use centralized throttling (
ThrottlingEnabled) where appropriate. - Do not use LINQ — it allocates too much memory.
- Use
ref varto modify components, nevervaralone. - NEVER perform structural changes (Add/Remove that trigger archetype moves) after obtaining a
ref,in, oroutreference to a component. Structural changes relocate entity data in memory, invalidating all outstandingref,in, andoutpointers. Components may also hold references to managed objects whose state depends on the current memory layout. Always complete allref/in/outreads/writes first, then apply structural changes. - Prefer passing
Entityby value, notin Entity, if modifying. - Clarify
refvsinintent; useref readonlyfor immutable refs.
-
Handle cleanup when:
- Component is removed
- Entity is destroyed (
DeleteEntityIntention) - World is disposed (via
IFinalizeWorldSystem)
-
Clean-up tasks include:
- Pool returns
- Promise invalidation
- Cache dereferencing
- Custom logic (e.g., avatar teardown)
-
Prefer
ReleasePoolableComponentSystem<T, TProvider>for pooled disposals.
- SDK and custom components treated equally in ECS.
- Add extra data using separate components.
- Prefer stateful components over frequent structural changes.
- Use
AssetPromise<T>for async-loaded assets. - Always operate
AssetPromiseviaref.
- Cache singleton components like
Input,Player,Camera,Time. - Use
TryGetfor mutation,Hasfor presence checks,Queryfor grouped access. - Prefer
TryGetoverQueryfor performance (2× faster).
-
Minimize detached
UniTask/UniTaskVoidcalls. -
Always catch exceptions:
- Ignore
OperationCanceledException - Log/report all others via
ReportHub.LogException
- Ignore
-
Use
SuppressToResultAsync()to simplify exception handling. -
Handle cancellation with
ct.IsCancellationRequested, neverThrowIfCancellationRequested().
- Use
UnitySystemTestBase<T>for world lifecycle in tests. - Expose system constructors via
[InternalsVisibleTo]. - Use NUnit + NSubstitute.
ObjectProxywas introduced to resolve circular dependencies. While effective, consider this an anti-pattern. Favor clearer dependency injection.- Interfaces or abstract classes with only one implementation and no test coverage should be avoided or merged.
- Use
ReportHubinstead ofDebug.Logfor all logging. - Minimize GC pressure: reuse objects, use object pooling, avoid boxing/unboxing, use
StringBuilderfor string concatenation.