Decouples five cross-cutting concerns from engine core so new presets
compose without special-casing:
- Piece attributes: CORE_PIECE_ATTRS + PresetDef.pieceAttributes;
engine.effectivePieceAttrs unions them. HP is now preset-owned.
- Spawn pipeline: engine.spawnPiece() + onPieceSpawn hook replaces
hand-rolled materialization. Queen-splits seeds HP via the hook,
not via direct knowledge of piece-hp.
- Hook signatures: all hooks now take single context objects
(LifecycleContext, MoveHookContext, CaptureHookContext, DamageHookContext,
SelfCheckFilterContext, GameResultHookContext, PieceSpawnContext,
BeforeMoveContext, TurnStartContext, DescribeMoveEffectContext).
- Piece-type registry: PIECE_TYPE_REGISTRY + core-piece-types.ts
replaces hardcoded FIDE switch-tables in engine/check/checkmate/
stalemate/Piece.tsx. Custom piece types plug in without engine edits.
- Preset-scoped state: engine.presetState<T>(id) backed by facts on
PRESET_STATE_ENTITY. Auto-cleared on deactivate. capture-to-win
migrated off GAME_ENTITY.Winner.
- Move log: engine.moveLog + MoveRecord + describeMoveEffect hook.
- Visual effects: engine.emitEffect/subscribeEffects + VisualEffect +
VisualEffectLayer. Explosion, heal, poison renderers. No-op when
no subscribers.
- Phase hooks: onBeforeMove (with cancel), onTurnStart. Scope-aware
dispatch routes onDamage/onBeforeMove/onTurnStart by target/mover
color.
All 5 production presets migrated. 4 prototype presets (Shield, Cannon,
Berserker, PawnStamina) in integration.test.ts exercise every hook.
Added test-utils.ts and PRESET-API.md for preset authors.
930 tests passing; bun run check clean.