T3 Wave 3 (T22 + T23). Two tightly-coupled deliverables landed in one
commit because the second's API surface depends on the first's signature
extensions:
T22 — Custom descriptor application
- new CustomModifierRegistry (per-engine, in custom/registry.ts) — ADR-4
isolation: descriptors registered on engineA never leak to engineB.
- new applyCustomDescriptor(engine, session, pieceId, descriptor) walks
primitive nodes, dispatches each kind through PRIMITIVE_REGISTRY,
and recurses into nested children via childPrimitives() with depth
tracking (mirrors T19's static depth guard at runtime).
- ChessEngine gains a customModifiers field + opts.customModifiers in
EngineOptions for bootstrap registration.
- applyProfileToSession's signature widens to accept (..., engine?,
customRegistry?) — when a profile entry's kind misses MODIFIER_REGISTRY,
the custom registry is consulted as a fallback. Existing T1/T2
callers stay source-compatible (the new params are optional).
T23 — Multi-profile stacking
- collectProfileContributions: pure value-collection helper extracted
from applyProfileToSession's body.
- new applyProfilesToSession(session, profiles[], layout, engine?,
customRegistry?) iterates the helper across every profile in order
before stacking — built-in stacking rules apply across the union.
- new reconcileProfilesSwap mirrors the same generalization for the
retract-then-reapply hot-swap path.
- single-profile applyProfileToSession / reconcileProfileSwap remain as
thin wrappers calling the array versions with [profile].
14 vitest scenarios cover: single-primitive apply, multi-primitive
apply, nested-children walk via on-turn-start, unknown-kind tolerance,
custom-registry fallback in applyProfileToSession, per-engine isolation,
constructor pre-registration, two-profile additive stacking, mixed
built-in + custom across profiles, single-profile passthrough, empty
array no-op, and CustomModifierRegistry CRUD.
Engine wiring (damage pipeline, turn-start hooks, aura recompute) is
deferred to T28 — primitives currently SEED facts that those wires
will observe.