Maintain C++ ECS: Invariants, Change Impact, and Tests¶
A maintainable design exposes its rules and makes likely mistakes testable. Abstractions help only if the team can preserve the invariants they introduce.
Rules We Already Own¶
| Invariant | Enforcement | Evidence |
|---|---|---|
| Valid dt before mutation | Step entry | Invalid-step tests |
| Acceleration needs movement | Seed validation | Rejected combination |
| Handle belongs to one world | Issuer and resolution | Foreign number collision |
| Deleted entity leaves no data | erase_all | Five store counts become zero |
| Decisions see stable membership | Deferred wrapper | Callback inspection |
| Retry does not replay prefix | Pop successful commands | Controlled second-spawn failure |
| Visibility is not motion | Update/query rules | Invisible-body regression |
Explicit test failures remain enabled in Release. Passing fixtures does not prove every possible execution safe.
Walk a Future Change¶
Consider Drag scaling velocity before movement. This is an exercise, not an implemented feature.
Records need data, validation, update condition, observation, and tests. Composition must decide whether Drag belongs inside Motion. Inheritance must decide whether it is an algorithm variant or independent configuration.
ECS also needs store construction, rollback, cleanup, pass order, and snapshots. A new pass may clarify its writer while increasing consistency obligations.
Do not count only the new loop.
Test Different Mistakes¶
- Independently calculate a trajectory.
- Compare full normalised snapshots.
- Exercise invalid input and stale handles.
- Check final movement and cleanup.
- Reproduce a deliberately wrong condition.
- Inject a queue failure and test recovery.
Independent expectations protect against shared helper bugs. Cross-design comparisons protect against accidental semantic differences.
Failure Boundaries Are API Behaviour¶
Rejected input leaves existing bodies unchanged. Consumed but unpublished IDs are acceptable. A queue can leave an applied prefix: recoverable is not transactional.
A caller must choose retry, discard, or stop without inadvertently running simulation again.
Deliberate Limits¶
No parallel scheduler, generic query API, serialization protocol, or runtime component editing is provided. Owner markers are process-local, not save-file IDs.
Generations and sparse indices impose extra invariants only when their requirements exist. Keep those experiments outside the main course's dependency chain.
Exercise¶
For a proposed component, name the writer, cleanup path, invalid states, observation policy, and order test before debating container choice.
Next: choose a design for your requirements, not for the final chapter.