From 99c9d1629c061a77e62729f174c26fda447e0dff Mon Sep 17 00:00:00 2001 From: Joey Yakimowich-Payne Date: Tue, 21 Apr 2026 17:47:02 -0600 Subject: [PATCH] test(chess/ui): extend ParamField snapshot suite for 7 new trigger primitives MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds SAMPLE_PARAMS entries for on-move, on-turn-end, on-promotion, on-check-received, on-check-delivered, on-moved-onto-square, and on-captured so the Record exhaustive type remains satisfied after the union grew from 15 to 22 kinds. Captures golden snapshots for each new kind's rendering via the existing renderToStaticMarkup harness — each snapshot is one-line HTML because the ParamField inspector falls back to a plain JSON textarea for trigger primitives with nested-array params (complex-schema branch). No ParamField behavior change; this is purely consumer-side wiring required by the PrimitiveKind union extension. --- .../chess/src/ui/ParamField.snapshot.test.tsx | 37 ++++ .../ParamField.snapshot.test.tsx.snap | 195 ++++++++++++++++++ 2 files changed, 232 insertions(+) diff --git a/packages/chess/src/ui/ParamField.snapshot.test.tsx b/packages/chess/src/ui/ParamField.snapshot.test.tsx index c818db4..3bfd086 100644 --- a/packages/chess/src/ui/ParamField.snapshot.test.tsx +++ b/packages/chess/src/ui/ParamField.snapshot.test.tsx @@ -45,14 +45,51 @@ const SAMPLE_PARAMS: Record = { { kind: "add-to-attribute", params: { attr: "Hp", delta: 1 } }, ], }, + "on-turn-end": { + color: "both", + primitives: [ + { kind: "add-to-attribute", params: { attr: "Hp", delta: -1 } }, + ], + }, "on-capture": { primitives: [ { kind: "add-to-attribute", params: { attr: "Hp", delta: 1 } }, ], }, + "on-move": { + primitives: [ + { kind: "add-to-attribute", params: { attr: "AttackBonus", delta: 1 } }, + ], + }, "on-damaged": { primitives: [{ kind: "reflect-damage", params: { percentage: 25 } }], }, + "on-check-delivered": { + primitives: [ + { kind: "add-to-attribute", params: { attr: "Hp", delta: 1 } }, + ], + }, + "on-check-received": { + primitives: [ + { kind: "add-to-attribute", params: { attr: "Hp", delta: 1 } }, + ], + }, + "on-promotion": { + primitives: [ + { kind: "seed-attribute", params: { attr: "Hp", value: 5 } }, + ], + }, + "on-moved-onto-square": { + filter: { kind: "squares", squares: [28, 35] }, + primitives: [ + { kind: "add-to-attribute", params: { attr: "Hp", delta: 1 } }, + ], + }, + "on-captured": { + primitives: [ + { kind: "add-to-attribute", params: { attr: "Hp", delta: -2 } }, + ], + }, conditional: { condition: { type: "attr-lt", attr: "Hp", value: 2 }, then: [{ kind: "set-capture-flag", params: { flag: 2 } }], diff --git a/packages/chess/src/ui/__snapshots__/ParamField.snapshot.test.tsx.snap b/packages/chess/src/ui/__snapshots__/ParamField.snapshot.test.tsx.snap index 9579c1e..08bb4c1 100644 --- a/packages/chess/src/ui/__snapshots__/ParamField.snapshot.test.tsx.snap +++ b/packages/chess/src/ui/__snapshots__/ParamField.snapshot.test.tsx.snap @@ -194,3 +194,198 @@ exports[`ParamField rendering (T14 regression baseline) > set-capture-flag rende "flag": 1 }

Sets CAN_CAPTURE_OWN — the piece may capture its own color's pieces.

" `; + +exports[`ParamField rendering (T14 regression baseline) absorb-damage-with-attribute renders attr + rate 1`] = ` +"

Declares that incoming damage should first deplete a user-chosen counter (rate points per damage) before touching HP. You must seed the counter itself with seed-attribute — this primitive only wires the absorb mechanic, not the charge supply.

Examples
3-charge shield (pair with seed-attribute)
{
+  "attr": "ShieldCharges",
+  "rate": 1
+}

Pair with seed-attribute {attr: 'ShieldCharges', value: 3}. Each damage point consumes one charge; after 3 damage, HP starts taking hits.

Hardened armor (rate=2)
{
+  "attr": "ArmorPlates",
+  "rate": 2
+}

Each damage point consumes 2 ArmorPlates instead of HP — makes plates deplete twice as fast but with the same absorption curve.

not seeded
" +`; + +exports[`ParamField rendering (T14 regression baseline) add-aura renders radius + targetAttr + delta 1`] = ` +"

Radiates a numeric contribution to targetAttr onto every piece within \`radius\` (Chebyshev / king-move distance — radius 1 = 8 neighbours). Recomputes after every move; pieces moving out of range lose the contribution on the next pass. Self-application is skipped. Multiple auras to the same targetAttr from different sources accumulate additively.

Examples
King aura: +1 HP within 2 squares
{
+  "radius": 2,
+  "targetAttr": "HpBonus",
+  "delta": 1
+}

Every friendly or enemy piece within 2 squares of this piece gains +1 HpBonus while in range.

Adjacent range buff
{
+  "radius": 1,
+  "targetAttr": "RangeBonus",
+  "delta": 1
+}

Anyone standing next to this piece (8 neighbouring squares) gets +1 to range.

" +`; + +exports[`ParamField rendering (T14 regression baseline) add-direction renders directions array fallback 1`] = ` +"

Appends one or more color-relative named directions into the piece's DirectionAdditions array, deduplicated by name. Composes with the built-in Direction Additions modifier — both write to the same fact. Valid directions: forward, backward, left, right, diagonal-fl, diagonal-fr, diagonal-bl, diagonal-br.

Examples
Backward-capable pawn
{
+  "directions": [
+    "backward"
+  ]
+}

Lets a pawn step backward as well as forward.

Full omnidirectional king-lite
{
+  "directions": [
+    "forward",
+    "backward",
+    "left",
+    "right"
+  ]
+}

Adds all 4 orthogonal directions in one primitive. Diagonal names are listed separately if you need them.

" +`; + +exports[`ParamField rendering (T14 regression baseline) add-to-attribute renders attr + delta fields 1`] = ` +"

Reads the current numeric value of attr (0 if unset) and writes existing + delta. Delta may be negative. Composes additively with other primitives and built-in modifiers — multiple add-to-attribute primitives for the same attr simply accumulate.

Examples
+2 HP bonus
{
+  "attr": "HpBonus",
+  "delta": 2
+}

Adds 2 to whatever HpBonus is already there.

Heal 1/turn (inside on-turn-start)
{
+  "attr": "Hp",
+  "delta": 1
+}

Wrapped in on-turn-start, restores 1 HP to this piece at the start of its color's turn.

" +`; + +exports[`ParamField rendering (T14 regression baseline) conditional renders complex-schema JSON fallback 1`] = ` +"

Branches on a condition. If true → runs every primitive in \`then\`; if false and \`else\` is set → runs \`else\`. Condition types: attr-lt (numeric less-than), attr-gt (numeric greater-than), attr-eq (exact match against string/number/boolean/null), always (unconditional then), never (forces else path only).

Examples
Low-HP fortress
{
+  "condition": {
+    "type": "attr-lt",
+    "attr": "Hp",
+    "value": 2
+  },
+  "then": [
+    {
+      "kind": "set-capture-flag",
+      "params": {
+        "flag": 2
+      }
+    }
+  ]
+}

When Hp drops below 2, the piece gains CANNOT_BE_CAPTURED — a last-stand invulnerability.

Unconditional thorns example
{
+  "condition": {
+    "type": "always"
+  },
+  "then": [
+    {
+      "kind": "reflect-damage",
+      "params": {
+        "percentage": 10
+      }
+    }
+  ]
+}

Equivalent to applying reflect-damage unconditionally; useful as a template you can later tighten.

" +`; + +exports[`ParamField rendering (T14 regression baseline) modify-movement-range renders delta 1`] = ` +"

Adds delta to the piece's RangeBonus. Composes additively with the built-in Range Bonus modifier and with other modify-movement-range primitives. Delta is clamped to integer range [-7, 7]. Rook/bishop/queen sliding is extended/reduced by this amount; knight/king ranges are treated by their own pipeline.

Examples
+1 range buff
{
+  "delta": 1
+}

A rook's horizontal slide reaches one square further than its baseline.

-2 range debuff
{
+  "delta": -2
+}

Cuts 2 squares from the piece's reach (useful for 'slowed' tokens).

" +`; + +exports[`ParamField rendering (T14 regression baseline) multiply-attribute renders attr + factor fields 1`] = ` +"

Reads the existing numeric value of attr and writes existing * factor. No-op if the attribute is unset — it does NOT treat absent as 1. Use after seed-attribute or add-to-attribute when you need a baseline to scale.

Examples
Double HP
{
+  "attr": "Hp",
+  "factor": 2
+}

If the piece already has 4 HP, becomes 8 HP.

Halve range bonus
{
+  "attr": "RangeBonus",
+  "factor": 0.5
+}

If RangeBonus is already 4, becomes 2 (rounded per attr consumer). Silently skipped if RangeBonus is unset.

" +`; + +exports[`ParamField rendering (T14 regression baseline) on-capture renders primitives-array fallback 1`] = ` +"

Wraps nested primitives that fire when this piece captures another. Typical uses: 'vampire' lifesteal (heal on capture), stacking buffs, or power-up triggers. Fires only on actual captures, not on quiet moves.

Examples
Vampire lifesteal
{
+  "primitives": [
+    {
+      "kind": "add-to-attribute",
+      "params": {
+        "attr": "Hp",
+        "delta": 1
+      }
+    }
+  ]
+}

Every time this piece captures an enemy, it gains 1 HP. Stacks over a long game.

" +`; + +exports[`ParamField rendering (T14 regression baseline) on-damaged renders primitives-array fallback 1`] = ` +"

Wraps nested primitives that fire whenever this piece takes damage. Useful for reactive behaviours: auto-thorns, emergency buffs, or conditional transformations when HP crosses a threshold (combine with \`conditional\`).

Examples
Thorns on hit
{
+  "primitives": [
+    {
+      "kind": "reflect-damage",
+      "params": {
+        "percentage": 25
+      }
+    }
+  ]
+}

When this piece takes damage, reflects 25% back to the attacker for that hit.

" +`; + +exports[`ParamField rendering (T14 regression baseline) on-turn-start renders primitives-array fallback 1`] = ` +"

Wraps a list of nested primitives that fire at the start of this piece's color's turn. Use for recurring buffs/healing/debuffs tied to turn cadence. The editor's Parameter Inspector accepts the nested \`primitives\` array as JSON; copy snippets from the simpler primitives into that array.

Examples
Regenerate 1 HP/turn
{
+  "primitives": [
+    {
+      "kind": "add-to-attribute",
+      "params": {
+        "attr": "Hp",
+        "delta": 1
+      }
+    }
+  ]
+}

At the start of every turn, this piece regains 1 HP (until capped by its damage pipeline).

" +`; + +exports[`ParamField rendering (T14 regression baseline) reflect-damage renders percentage 1`] = ` +"

Reflects a percentage of incoming damage back to the attacker. Integer percent, 0-100. Multiple reflect primitives on the same piece do NOT stack — the most recent value wins. Great inside on-damaged if you want a one-time thorns reaction instead of a permanent aura.

Examples
Half-reflective armour
{
+  "percentage": 50
+}

50% of incoming damage is dealt back to the attacker.

Total thorns
{
+  "percentage": 100
+}

Full reflection — the attacker takes whatever they dealt.

" +`; + +exports[`ParamField rendering (T14 regression baseline) seed-attribute renders attr + value fields 1`] = ` +"

Writes { attr, value } directly onto the piece, overwriting any existing value. Use to introduce new attributes (like a custom ShieldCharges counter) or to force a baseline (e.g. set HP to an exact number regardless of inheritance). Pair with add-to-attribute / multiply-attribute to build up a final value.

Examples
Force exact HP
{
+  "attr": "Hp",
+  "value": 5
+}

Piece always starts with 5 HP regardless of baseline.

Declare shield charges
{
+  "attr": "ShieldCharges",
+  "value": 3
+}

Creates a 3-charge counter. Combine with absorb-damage-with-attribute to make each charge soak one damage point.

" +`; + +exports[`ParamField rendering (T14 regression baseline) set-capture-flag renders flag enum 1`] = ` +"

Turns on one capture-flag bit. Flags combine (OR) so stacking multiple primitives is fine. Supported: 1 = CAN_CAPTURE_OWN (piece may capture its own color), 2 = CANNOT_BE_CAPTURED (untargetable by enemies), 4 = EN_PASSANT (piece participates in en-passant capture resolution).

Examples
Untouchable piece
{
+  "flag": 2
+}

Sets CANNOT_BE_CAPTURED — no enemy move can target this piece.

Friendly-fire rook
{
+  "flag": 1
+}

Sets CAN_CAPTURE_OWN — the piece may capture its own color's pieces.

" +`;