feat(engine): range-bonus modifier descriptor

Add RANGE_BONUS_DESCRIPTOR that seeds RangeBonus fact on piece entities,
and update getSlidingMoves to respect it via a per-ray maxSteps cap.
Clamped to [0,7] in apply(); standard boards are unaffected (RangeBonus=0
→ maxSteps=7, identical to prior behaviour). Foundation for range-limit presets.
This commit is contained in:
Joey Yakimowich-Payne 2026-04-18 22:20:24 -06:00
commit 761adfb699
No known key found for this signature in database
2 changed files with 92 additions and 0 deletions

View file

@ -0,0 +1,67 @@
import { describe, it, expect } from "vitest";
import { Session, type EntityId } from "@paratype/rete";
import { RANGE_BONUS_DESCRIPTOR } from "./range-bonus.js";
import { MODIFIER_REGISTRY } from "../registry.js";
function setupSession(): Session {
return new Session({ autoFire: false });
}
describe("RANGE_BONUS_DESCRIPTOR", () => {
it("registers in MODIFIER_REGISTRY with id 'range-bonus'", () => {
expect(MODIFIER_REGISTRY.has("range-bonus")).toBe(true);
expect(MODIFIER_REGISTRY.get("range-bonus")).toBe(RANGE_BONUS_DESCRIPTOR);
});
it("has correct metadata", () => {
expect(RANGE_BONUS_DESCRIPTOR.id).toBe("range-bonus");
expect(RANGE_BONUS_DESCRIPTOR.attrName).toBe("RangeBonus");
expect(RANGE_BONUS_DESCRIPTOR.stackingRule).toBe("additive");
expect(RANGE_BONUS_DESCRIPTOR.uiForm).toBe("number");
});
describe("describe()", () => {
it("returns 'Range +N' for non-negative values", () => {
expect(RANGE_BONUS_DESCRIPTOR.describe(2)).toBe("Range +2");
expect(RANGE_BONUS_DESCRIPTOR.describe(0)).toBe("Range +0");
expect(RANGE_BONUS_DESCRIPTOR.describe(7)).toBe("Range +7");
});
it("returns 'Range -N' for negative values", () => {
expect(RANGE_BONUS_DESCRIPTOR.describe(-1)).toBe("Range -1");
expect(RANGE_BONUS_DESCRIPTOR.describe(-7)).toBe("Range -7");
});
});
describe("apply()", () => {
it("inserts RangeBonus fact on the piece entity", () => {
const session = setupSession();
RANGE_BONUS_DESCRIPTOR.apply(session, 1 as EntityId, 3);
expect(session.get(1 as EntityId, "RangeBonus")).toBe(3);
});
it("clamps effectiveValue above RANGE_MAX (7) down to 7", () => {
const session = setupSession();
RANGE_BONUS_DESCRIPTOR.apply(session, 1 as EntityId, 10 as number);
expect(session.get(1 as EntityId, "RangeBonus")).toBe(7);
});
it("clamps negative effectiveValue to 0", () => {
const session = setupSession();
RANGE_BONUS_DESCRIPTOR.apply(session, 1 as EntityId, -5 as number);
expect(session.get(1 as EntityId, "RangeBonus")).toBe(0);
});
it("applies exactly RANGE_MAX (7) without clamping", () => {
const session = setupSession();
RANGE_BONUS_DESCRIPTOR.apply(session, 2 as EntityId, 7);
expect(session.get(2 as EntityId, "RangeBonus")).toBe(7);
});
it("applies zero without clamping", () => {
const session = setupSession();
RANGE_BONUS_DESCRIPTOR.apply(session, 3 as EntityId, 0);
expect(session.get(3 as EntityId, "RangeBonus")).toBe(0);
});
});
});

View file

@ -0,0 +1,25 @@
import { z } from "zod";
import { MODIFIER_REGISTRY } from "../registry.js";
import type { ModifierDescriptor } from "../types.js";
const RANGE_MAX = 7;
const schema = z.number().int().min(-7).max(7);
type Value = z.infer<typeof schema>;
export const RANGE_BONUS_DESCRIPTOR: ModifierDescriptor<Value> = {
id: "range-bonus",
attrName: "RangeBonus",
label: "Range Bonus",
valueSchema: schema,
stackingRule: "additive",
uiForm: "number",
apply(session, pieceId, effectiveValue) {
const clamped = Math.max(0, Math.min(RANGE_MAX, effectiveValue));
session.insert(pieceId, "RangeBonus", clamped);
},
describe(value) {
return `Range ${value >= 0 ? "+" : ""}${value}`;
},
};
MODIFIER_REGISTRY.register(RANGE_BONUS_DESCRIPTOR);