From 0058a99939ad2fba40d53ab35f42c0f4c26337d9 Mon Sep 17 00:00:00 2001 From: Keith Solomon Date: Sun, 26 Jul 2026 10:26:42 -0500 Subject: [PATCH] docs: sync plan with shipped corrections (hold-the-line, smoke-test matchId) --- .../plans/2026-07-25-battle-interface.md | 39 ++++++++++++------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/docs/superpowers/plans/2026-07-25-battle-interface.md b/docs/superpowers/plans/2026-07-25-battle-interface.md index ba57878..e194fee 100644 --- a/docs/superpowers/plans/2026-07-25-battle-interface.md +++ b/docs/superpowers/plans/2026-07-25-battle-interface.md @@ -646,6 +646,8 @@ Create `public/assets/scenarios/skirmish.json`: Create `public/assets/scenarios/hold-the-line.json`: +> **NOTE:** The original terrain (a 12-tile forest pocket containing the objective at `(4,4)`) made the objective unreachable — alpha defenders (speed 1, inside forest) cannot move out, and bravo strikers (speed 3) cannot bridge from column 9 to column 4 through forest. The validator's reachability rule is correct. The corrected fixture below places the objective in the open at `(4,4)`, gives alpha defenders speed 2 so they can step out of their cover onto the objective, gives bravo-4 the scout archetype at speed 5 placed on `(9,4)` so it can reach `(4,4)` across open ground in 5 steps, and limits forest to a small cluster around the alpha deployment zone that doesn't block bravo's path. The tactically-intended property — alpha fights from forest cover, bravo has to push across open ground to reach the objective — is preserved. + ```json { "id": "hold-the-line", @@ -654,23 +656,22 @@ Create `public/assets/scenarios/hold-the-line.json`: "width": 10, "height": 8, "terrain": { - "3:2": "forest", "3:3": "forest", "3:4": "forest", "3:5": "forest", - "4:2": "forest", "4:3": "forest", "4:4": "forest", "4:5": "forest", - "5:2": "forest", "5:3": "forest", "5:4": "forest", "5:5": "forest" + "2:2": "forest", "2:3": "forest", "2:4": "forest", "2:5": "forest", + "3:2": "forest", "3:3": "forest" } }, "units": [ - { "id": "alpha-1", "teamId": "alpha", "position": { "x": 4, "y": 3 }, "maxHealth": 14, "health": 14, "attack": 3, "defense": 5, "speed": 1, "archetype": "defender", "abilities": [] }, - { "id": "alpha-2", "teamId": "alpha", "position": { "x": 3, "y": 4 }, "maxHealth": 14, "health": 14, "attack": 3, "defense": 5, "speed": 1, "archetype": "defender", "abilities": [] }, - { "id": "alpha-3", "teamId": "alpha", "position": { "x": 5, "y": 4 }, "maxHealth": 14, "health": 14, "attack": 3, "defense": 5, "speed": 1, "archetype": "defender", "abilities": [] }, + { "id": "alpha-1", "teamId": "alpha", "position": { "x": 2, "y": 3 }, "maxHealth": 14, "health": 14, "attack": 3, "defense": 5, "speed": 2, "archetype": "defender", "abilities": [] }, + { "id": "alpha-2", "teamId": "alpha", "position": { "x": 2, "y": 4 }, "maxHealth": 14, "health": 14, "attack": 3, "defense": 5, "speed": 2, "archetype": "defender", "abilities": [] }, + { "id": "alpha-3", "teamId": "alpha", "position": { "x": 2, "y": 5 }, "maxHealth": 14, "health": 14, "attack": 3, "defense": 5, "speed": 2, "archetype": "defender", "abilities": [] }, { "id": "bravo-1", "teamId": "bravo", "position": { "x": 9, "y": 1 }, "maxHealth": 8, "health": 8, "attack": 5, "defense": 2, "speed": 3, "archetype": "striker", "abilities": ["area_damage"] }, { "id": "bravo-2", "teamId": "bravo", "position": { "x": 9, "y": 3 }, "maxHealth": 8, "health": 8, "attack": 5, "defense": 2, "speed": 3, "archetype": "striker", "abilities": ["area_damage"] }, - { "id": "bravo-3", "teamId": "bravo", "position": { "x": 9, "y": 5 }, "maxHealth": 7, "health": 7, "attack": 2, "defense": 2, "speed": 3, "archetype": "support", "abilities": ["heal"] }, - { "id": "bravo-4", "teamId": "bravo", "position": { "x": 9, "y": 7 }, "maxHealth": 6, "health": 6, "attack": 3, "defense": 1, "speed": 5, "archetype": "scout", "abilities": [] } + { "id": "bravo-3", "teamId": "bravo", "position": { "x": 9, "y": 7 }, "maxHealth": 7, "health": 7, "attack": 2, "defense": 2, "speed": 3, "archetype": "support", "abilities": ["heal"] }, + { "id": "bravo-4", "teamId": "bravo", "position": { "x": 9, "y": 4 }, "maxHealth": 6, "health": 6, "attack": 3, "defense": 1, "speed": 5, "archetype": "scout", "abilities": [] } ], "deploymentZones": { - "alpha": { "teamId": "alpha", "positions": [{ "x": 4, "y": 3 }, { "x": 3, "y": 4 }, { "x": 5, "y": 4 }] }, - "bravo": { "teamId": "bravo", "positions": [{ "x": 9, "y": 1 }, { "x": 9, "y": 3 }, { "x": 9, "y": 5 }, { "x": 9, "y": 7 }] } + "alpha": { "teamId": "alpha", "positions": [{ "x": 2, "y": 3 }, { "x": 2, "y": 4 }, { "x": 2, "y": 5 }] }, + "bravo": { "teamId": "bravo", "positions": [{ "x": 9, "y": 1 }, { "x": 9, "y": 3 }, { "x": 9, "y": 7 }, { "x": 9, "y": 4 }] } }, "objectives": { "objective-1": { "id": "objective-1", "position": { "x": 4, "y": 4 } } @@ -679,7 +680,6 @@ Create `public/assets/scenarios/hold-the-line.json`: "holdRoundsRequired": 3 } ``` - - [ ] **Step 4: Create `last-stand.json`** Create `public/assets/scenarios/last-stand.json`: @@ -850,7 +850,7 @@ final class GetBundledScenariosTest extends TestCase private function writeFixture(string $name, string $body): void { - file_put_contents($this->tmpDir . '/' . $name, $body); + file_put_contents($this->tmpDir . '/' . $name . '.json', $body); } } ``` @@ -2327,10 +2327,10 @@ final class PostMatchActionTest extends TestCase new Battlefield(8, 8), [ new UnitState('alpha-1', 'alpha', new Position(0, 0), 10, 10, 3, 3, 2, 2, false, Archetype::Defender), - new UnitState('alpha-2', 'alpha', new Position(1, 0), 10, 10, 3, 3, 2, 2, false, Archetype::Defender), - new UnitState('alpha-3', 'alpha', new Position(2, 0), 10, 10, 3, 3, 2, 2, false, Archetype::Support, ['heal']), + new UnitState('alpha-2', 'alpha', new Position(0, 1), 10, 10, 3, 3, 2, 2, false, Archetype::Defender), + new UnitState('alpha-3', 'alpha', new Position(1, 1), 10, 10, 3, 3, 2, 2, false, Archetype::Support, ['heal']), new UnitState('bravo-1', 'bravo', new Position(7, 7), 10, 10, 3, 3, 2, 2, false, Archetype::Striker), - new UnitState('bravo-2', 'bravo', new Position(6, 7), 10, 10, 3, 3, 2, 2, false, Archetype::Striker), + new UnitState('bravo-2', 'bravo', new Position(6, 6), 10, 10, 3, 3, 2, 2, false, Archetype::Striker), new UnitState('bravo-3', 'bravo', new Position(5, 7), 10, 10, 3, 3, 2, 2, false, Archetype::Striker), ], 'alpha', @@ -3024,6 +3024,15 @@ use PHPUnit\Framework\TestCase; * End-to-end smoke test. Boots a real `php -S` dev server and walks the * Skirmish bundled scenario to a winner. Gated by BATTLEFORGE_E2E=1 so the * per-commit CI does not pay its cost. + * + * NOTE: The original brief hard-coded a MATCH_ID constant (`0123456789abcdef`) + * and used it in every action body. But `PostStartMatch` mints a random matchId + * server-side via `bin2hex(random_bytes(8))`, so the action body must echo + * back the response's matchId. The fix below captures `$matchId` from the + * start-match response and threads it through `pickAction`'s second + * argument. The `MATCH_ID` constant is removed; the `pickAction` method + * signature gains `string $matchId`; the `assertSame(self::MATCH_ID, ...)` + * check is replaced with a regex match. */ final class PostMatchActionSmokeTest extends TestCase {