feat(engine): modifier profile types
This commit is contained in:
parent
0f3b28ba55
commit
fea511790b
2 changed files with 150 additions and 1 deletions
|
|
@ -21,7 +21,8 @@
|
||||||
"motion": "^12.38.0",
|
"motion": "^12.38.0",
|
||||||
"react-router-dom": "^7.14.1",
|
"react-router-dom": "^7.14.1",
|
||||||
"sonner": "^2.0.7",
|
"sonner": "^2.0.7",
|
||||||
"tailwindcss": "^4.2.2"
|
"tailwindcss": "^4.2.2",
|
||||||
|
"zod": "^4.3.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/canvas-confetti": "^1.9.0",
|
"@types/canvas-confetti": "^1.9.0",
|
||||||
|
|
|
||||||
148
packages/chess/src/modifiers/types.ts
Normal file
148
packages/chess/src/modifiers/types.ts
Normal file
|
|
@ -0,0 +1,148 @@
|
||||||
|
/**
|
||||||
|
* Type definitions for the piece modifier profile system.
|
||||||
|
*
|
||||||
|
* A ModifierProfile is a first-class entity (orthogonal to layouts and
|
||||||
|
* presets) that attaches rule modifiers to pieces by type ("all white
|
||||||
|
* knights have +1 HP") or by layout slot ("the piece on b1 has +2 range").
|
||||||
|
*
|
||||||
|
* Modifier profiles combine with layouts at game start. They are saved
|
||||||
|
* independently to a library (houserules:modifier-profiles:v1) and can be
|
||||||
|
* hot-swapped mid-game at turn boundaries.
|
||||||
|
*
|
||||||
|
* ADR-2: per-instance modifiers keyed by algebraic-notation square string
|
||||||
|
* (e.g. "b1"). Profile may optionally specify a layoutId for validation.
|
||||||
|
*/
|
||||||
|
import type { EntityId, Session } from "@paratype/rete";
|
||||||
|
import type { PieceType, PieceColor, ChessAttrKey } from "../schema.js";
|
||||||
|
import type { ZodType } from "zod";
|
||||||
|
|
||||||
|
/** The six T1 modifier category identifiers. */
|
||||||
|
export type ModifierKindId =
|
||||||
|
| "hp-bonus"
|
||||||
|
| "range-bonus"
|
||||||
|
| "direction-additions"
|
||||||
|
| "capture-flags"
|
||||||
|
| "promotion-override"
|
||||||
|
| "damage-resistance";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Named movement directions (from the perspective of the moving piece,
|
||||||
|
* color-independent — engine interprets "forward" as toward the opponent's
|
||||||
|
* back rank based on piece color).
|
||||||
|
*
|
||||||
|
* Used by DirectionAdditions modifier to specify additional movement directions.
|
||||||
|
*/
|
||||||
|
export type Direction =
|
||||||
|
| "forward" // toward opponent's back rank (1 square)
|
||||||
|
| "backward" // toward own back rank (1 square)
|
||||||
|
| "left" // queenside (from white's perspective)
|
||||||
|
| "right" // kingside (from white's perspective)
|
||||||
|
| "diagonal-fl" // forward-left
|
||||||
|
| "diagonal-fr" // forward-right
|
||||||
|
| "diagonal-bl" // backward-left
|
||||||
|
| "diagonal-br"; // backward-right
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A modifier that applies to ALL pieces of a given type+color combo.
|
||||||
|
* `value` is the modifier-kind-specific value (number, string[], etc.)
|
||||||
|
*/
|
||||||
|
export interface TypeModifier {
|
||||||
|
readonly kind: ModifierKindId;
|
||||||
|
readonly pieceType: PieceType;
|
||||||
|
readonly color: PieceColor | "both";
|
||||||
|
readonly value: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A modifier that applies to the piece at a specific layout square.
|
||||||
|
* `square` is algebraic notation: "a1" through "h8".
|
||||||
|
* ADR-2: keyed by layout-slot (square string), not EntityId.
|
||||||
|
*/
|
||||||
|
export interface InstanceModifier {
|
||||||
|
readonly kind: ModifierKindId;
|
||||||
|
readonly square: string; // algebraic notation, e.g. "b1"
|
||||||
|
readonly value: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A complete modifier profile.
|
||||||
|
*
|
||||||
|
* Combines with a layout at game start: perType modifiers apply to all
|
||||||
|
* matching pieces; perInstance modifiers apply to pieces at specific squares.
|
||||||
|
*
|
||||||
|
* `layoutId` is optional — only required when perInstance entries are
|
||||||
|
* present, as per-instance modifiers are layout-slot-bound (ADR-2).
|
||||||
|
* When layoutId is absent and perInstance is non-empty, the validator
|
||||||
|
* emits a warning.
|
||||||
|
*/
|
||||||
|
export interface ModifierProfile {
|
||||||
|
readonly id: string;
|
||||||
|
readonly name: string;
|
||||||
|
readonly description: string;
|
||||||
|
/** Layout this profile's per-instance modifiers are bound to. Optional. */
|
||||||
|
readonly layoutId?: string;
|
||||||
|
readonly perType: readonly TypeModifier[];
|
||||||
|
readonly perInstance: readonly InstanceModifier[];
|
||||||
|
readonly version: 1;
|
||||||
|
readonly source: "premade" | "custom";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Descriptor for a modifier category. Each T1 modifier registers one
|
||||||
|
* descriptor into MODIFIER_REGISTRY at module load time (ADR-8).
|
||||||
|
*
|
||||||
|
* `V` is the value type (number for HpBonus, string[] for DirectionAdditions, etc.)
|
||||||
|
*/
|
||||||
|
export interface ModifierDescriptor<V = unknown> {
|
||||||
|
/** Matches ModifierKindId — used as the registry key. */
|
||||||
|
readonly id: ModifierKindId;
|
||||||
|
/** The ChessAttrMap key this modifier seeds on piece entities. */
|
||||||
|
readonly attrName: ChessAttrKey;
|
||||||
|
/** Human-readable display label for UI. */
|
||||||
|
readonly label: string;
|
||||||
|
/** Zod schema for the value field — used for validation and UI form generation. */
|
||||||
|
readonly valueSchema: ZodType<V>;
|
||||||
|
/** How multiple values from different sources stack (ADR-4). */
|
||||||
|
readonly stackingRule: "additive" | "union" | "multiplicative" | "priority-wins";
|
||||||
|
/**
|
||||||
|
* Apply the modifier's effective value to a piece entity in the session.
|
||||||
|
* Called once per (pieceId, kind) pair after all sources have been
|
||||||
|
* collected and the effective value computed via stacking rules.
|
||||||
|
*
|
||||||
|
* @param session - The game session to mutate
|
||||||
|
* @param pieceId - Target piece entity
|
||||||
|
* @param effectiveValue - The already-stacked value to apply
|
||||||
|
*/
|
||||||
|
readonly apply: (session: Session, pieceId: EntityId, effectiveValue: V) => void;
|
||||||
|
/** Return a human-readable description of this modifier value for UI. */
|
||||||
|
readonly describe: (value: V) => string;
|
||||||
|
/**
|
||||||
|
* Hint for the UI editor about which input widget to render for this modifier.
|
||||||
|
* Each uiForm maps to a specific React component in the PerTypePanel/PerInstancePanel.
|
||||||
|
*/
|
||||||
|
readonly uiForm:
|
||||||
|
| "number"
|
||||||
|
| "direction-set"
|
||||||
|
| "capture-flags"
|
||||||
|
| "promotion-target"
|
||||||
|
| "percentage";
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Helper: create a TypeModifier with proper typing. */
|
||||||
|
export function typeModifier(
|
||||||
|
kind: ModifierKindId,
|
||||||
|
pieceType: PieceType,
|
||||||
|
color: PieceColor | "both",
|
||||||
|
value: unknown,
|
||||||
|
): TypeModifier {
|
||||||
|
return { kind, pieceType, color, value };
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Helper: create an InstanceModifier with proper typing. */
|
||||||
|
export function instanceModifier(
|
||||||
|
kind: ModifierKindId,
|
||||||
|
square: string,
|
||||||
|
value: unknown,
|
||||||
|
): InstanceModifier {
|
||||||
|
return { kind, square, value };
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue