refactor(modifiers): use baseAttr for preset source mapping

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
Joey Yakimowich-Payne 2026-04-19 14:02:32 -06:00
commit 3d49cd2792
No known key found for this signature in database
4 changed files with 21 additions and 7 deletions

View file

@ -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.

View file

@ -9,6 +9,7 @@ type Value = z.infer<typeof schema>;
const descriptor: ModifierDescriptor<Value> = {
id: "hp-bonus",
attrName: "HpBonus",
baseAttr: "Hp",
label: "HP Bonus",
valueSchema: schema,
stackingRule: "additive",

View file

@ -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 };
}
}

View file

@ -98,6 +98,17 @@ export interface ModifierDescriptor<V = unknown> {
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. */