diff --git a/.sisyphus/notepads/polish-t2/learnings.md b/.sisyphus/notepads/polish-t2/learnings.md index a2952e3..086f2f6 100644 --- a/.sisyphus/notepads/polish-t2/learnings.md +++ b/.sisyphus/notepads/polish-t2/learnings.md @@ -64,3 +64,8 @@ Probable file: add test to `solo-smoke.spec.ts` (T2 added it as the canary) or e - Implemented the adapter path in `modifiers/registry.ts`: stored descriptors now wrap typed `apply/describe` in `unknown`-accepting closures at registration time. - Result: removed the `descriptor as unknown as ModifierDescriptor` storage cast; `schema.ts` parse return also reduced from double-cast to `as ModifierProfile`. + +## [2026-04-19 14:01] Task: commit-3 preset source attribution + +- Added optional `baseAttr?: ChessAttrKey` to `ModifierDescriptor` and set `hp-bonus` to `baseAttr: "Hp"`. +- Removed hardcoded `HpBonus`/`RangeBonus` aliasing from `getModifierSource`; source matching now uses `descriptor.baseAttr ?? descriptor.attrName` directly. diff --git a/packages/chess/src/modifiers/descriptors/hp-bonus.ts b/packages/chess/src/modifiers/descriptors/hp-bonus.ts index 5ccffee..522b06b 100644 --- a/packages/chess/src/modifiers/descriptors/hp-bonus.ts +++ b/packages/chess/src/modifiers/descriptors/hp-bonus.ts @@ -9,6 +9,7 @@ type Value = z.infer; const descriptor: ModifierDescriptor = { id: "hp-bonus", attrName: "HpBonus", + baseAttr: "Hp", label: "HP Bonus", valueSchema: schema, stackingRule: "additive", diff --git a/packages/chess/src/modifiers/source.ts b/packages/chess/src/modifiers/source.ts index 340e3b7..74aeb6a 100644 --- a/packages/chess/src/modifiers/source.ts +++ b/packages/chess/src/modifiers/source.ts @@ -32,7 +32,7 @@ export function getModifierSource( // 1. Check per-instance modifiers if (algebraicSq) { const instanceMatch = profile.perInstance.find( - (m) => m.kind === modifierKind && m.square === algebraicSq + (m) => m.kind === modifierKind && m.square === algebraicSq, ); if (instanceMatch) { return { kind: "per-instance", square: instanceMatch.square }; @@ -45,7 +45,7 @@ export function getModifierSource( (m) => m.kind === modifierKind && m.pieceType === pieceType && - (m.color === "both" || m.color === color) + (m.color === "both" || m.color === color), ); if (typeMatch) { return { kind: "per-type", pieceType: typeMatch.pieceType, color: typeMatch.color }; @@ -56,13 +56,10 @@ export function getModifierSource( // 3. Check active presets const descriptor = MODIFIER_REGISTRY.get(modifierKind); if (descriptor) { - let attrName = descriptor.attrName as string; - if (attrName === "HpBonus") attrName = "Hp"; - else if (attrName === "RangeBonus") attrName = "Range"; - + const presetAttr = descriptor.baseAttr ?? descriptor.attrName; for (const presetEntry of engine.activePresets.list()) { const def = PRESET_REGISTRY.get(presetEntry.id); - if (def?.pieceAttributes?.includes(attrName as keyof import("../schema.js").ChessAttrMap)) { + if (def?.pieceAttributes?.includes(presetAttr)) { return { kind: "preset", presetId: presetEntry.id, presetName: def.name }; } } diff --git a/packages/chess/src/modifiers/types.ts b/packages/chess/src/modifiers/types.ts index d668135..d6a0cdf 100644 --- a/packages/chess/src/modifiers/types.ts +++ b/packages/chess/src/modifiers/types.ts @@ -98,6 +98,17 @@ export interface ModifierDescriptor { readonly id: ModifierKindId; /** The ChessAttrMap key this modifier seeds on piece entities. */ readonly attrName: ChessAttrKey; + /** + * Optional: the base piece attribute this modifier augments. When set, + * `getModifierSource` uses this (not `attrName`) to match against a + * preset's `pieceAttributes` list — e.g. `hp-bonus` augments the `Hp` + * attribute declared by the `piece-hp` preset, even though the + * modifier itself writes to `HpBonus`. + * + * Omit for modifiers whose `attrName` is the authoritative attribute + * (no preset declares it separately). + */ + readonly baseAttr?: ChessAttrKey; /** Human-readable display label for UI. */ readonly label: string; /** Zod schema for the value field — used for validation and UI form generation. */