W-061: ecs-relations symmetric relations (REL-F01)¶
Type: feature · Size: S · Epic: E-relations-v0 · Target: packages/ecs-relations · Breaking: no Depends: W-060 Refs: spec REL-001..REL-016 (group REL-F01; KL1, KL2, KL4, KL5)
Goal¶
Implement symmetric(world, rel) in packages/ecs-relations: per-world onAdd and onRemove hooks that mirror every pair of rel, with the re-entrancy guard, install-time mirroring, and the unsubscribe function. Headless, no transitive or traversal code yet.
Steps¶
- S1 (REL-001, REL-002, REL-003, REL-008, REL-015):
symmetricmirrors add and remove through per-world hooks; a re-added pair is a no-op; a remove for an absent pair is ignored; files: packages/ecs-relations/src/symmetric.ts, packages/ecs-relations/src/index.ts, packages/ecs-relations/tests/symmetric.test.ts - S2 (REL-004, REL-005, REL-006, REL-007, REL-010, REL-013, REL-016): destroy of either side does not throw; existing pairs are mirrored at install; exclusive replacement and self pairs; every removal route terminates; repeated install gives the same state; an uninitialized world throws naming
symmetricand installs no hook; files: packages/ecs-relations/src/symmetric.ts, packages/ecs-relations/tests/symmetric-edge.test.ts - S3 (REL-009, REL-011, REL-012, REL-014; changeset): user hooks fire once per direction; pair data gets store defaults and is not synced; unsubscribe removes the hooks, is idempotent, and is safe after
world.reset()orworld.destroy(); installs are isolated per relation and per world; files: packages/ecs-relations/tests/symmetric-hooks.test.ts, packages/ecs-relations/src/symmetric.ts, .changeset/w-061-ecs-relations-symmetric.md (patch,@workspace/ecs-relations)
Log¶
- 2026-11-01 created (Triage), split from the approved spec W-008
- 2026-11-01 placed in Later (Depends W-060 not done; ready once the scaffold is done)
- 2026-11-02 moved to Next (Triage): W-060 done, size S, target and Goal filled
- 2026-11-02 moved down in Next behind the demo-critical items (Triage)