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",
|
||||
"react-router-dom": "^7.14.1",
|
||||
"sonner": "^2.0.7",
|
||||
"tailwindcss": "^4.2.2"
|
||||
"tailwindcss": "^4.2.2",
|
||||
"zod": "^4.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@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