Commit Graph
100 Commits
Author SHA1 Message Date
Keith Solomon 840e86b9af Merge feature/deeper-combat into develop
CI / php (push) Failing after 1m19s
Implements Plan 5: Deeper Combat.

Engine-side changes only:
- To-hit roll: 40 + (attack*3) - (defense+terrain) - flanking, floor 5, ceiling 95
- Crit: roll >= 95 (always hits, damage doubled before variance)
- Damage variance: random_int(85,115)/100 multiplier
- Action log: '(rolled X / needed Y) for Z damage' with miss/crit/concealed suffixes
- Forest concealment: independent 15% miss chance after to-hit
- Flanking: +15 per orthogonal ally, cap +30
- Water traversal: cost 3, ends turn via wadedThisTurn flag

Final review: 9 minor findings, all parked-as-is or already-resolved.
Per-task review verdicts: 6/6 approved.

256/256 tests pass (was 237/237 pre-Plan-5).
No new PHPCS warnings (baseline: 91-92).
2026-07-27 08:00:01 -05:00
Keith Solomon 8bec9baab6 feat(combat): wade visual + smoke test budget 2026-07-27 00:10:53 -05:00
Keith Solomon 09f2bda924 feat(combat): water traversal costs 3 and ends the turn 2026-07-26 23:38:13 -05:00
Keith Solomon fb06da3f28 feat(combat): forest concealment roll, only on Forest terrain 2026-07-26 23:12:16 -05:00
Keith Solomon 241195bbc5 feat(combat): flanking bonus caps at +30, diagonal allies do not count 2026-07-26 22:51:13 -05:00
Keith Solomon 4cc457c231 feat(combat): to-hit roll, ±15% damage variance, crits, and new log format 2026-07-26 21:41:53 -05:00
Keith Solomon 9eac4981f4 docs: add Plan 5 implementation plan (deeper combat)
CI / php (push) Failing after 1m10s
2026-07-26 18:39:54 -05:00
Keith Solomon f72072f3ef docs: add Plan 5 design spec (deeper combat)
CI / php (push) Failing after 1m14s
2026-07-26 18:25:58 -05:00
Keith Solomon 44199ec0a8 chore: address Plan 4 deferred-minors (stale-cookie cleanup pass)
CI / php (push) Failing after 1m11s
- Rename $secret to $csrfSecret in public/index.php to make
  intent clear at every call site; the value is the CSRF/turn-token
  HMAC key, not just 'the secret'.
- Drop the stale 'Configure the router with the eight routes' comment
  in public/index.php; the router now has sixteen routes.
- Drop the duplicate .bf-toast rule in public/assets/styles.css; the
  second declaration (the new yellow box) is the active one, the
  first (legacy green pill) is dead code from the editor era.
- Add a trailing newline to src/Views/home.php.
- Tighten the matchId regex in two tests from {16,} to {16} since
  bin2hex(random_bytes(8)) always produces exactly 16 hex chars; the
  spec's {16,} stays in production TurnToken.

No behavior changes; addresses the deferred-minors parked during
per-task review.
2026-07-26 16:38:05 -05:00
Keith Solomon 3185bc3602 fix: detect stale CSRF cookie pair and auto-rotate
CI / php (push) Failing after 1m17s
When a browser session predates a server restart with a fresh
secret.key, the old __csrf HMAC cookie is no longer verifiable
under the new secret. Every action POST returns 403 until the user
manually clears cookies, because the existing-pair check on the
front controller was 'is the cookie present?' rather than 'does
the cookie verify under the current secret?'.

The new check at the top of the front controller computes the
expected HMAC of the raw-token cookie under the current secret and
constant-time-compares it to the __csrf cookie. If they don't match,
the controller re-mints a fresh pair on the response, replacing the
browser's stale cookies with a working set. The next click of 'Play
bundled' then succeeds without user intervention.

Test coverage in tests/Integration/StaleCookieRotationTest.php:
  - testStaleCookiesAreReplacedOnNextRequest
  - testValidCookiesAreNotReIssuedOnNextRequest
  - testMissingTokenCookieIsFilledInOnNextRequest
2026-07-26 16:07:51 -05:00
Keith Solomon 2db141efc6 fix: route the real secret to the action handlers (was reading empty $_SERVER['__csrf_secret'])
public/index.php had a line that overwrote the local $secret with
$_SERVER['__csrf_secret'] ?? '' just before constructing the four
match-action handler closures. The synthetic key is only written
to the request's $server array (line 105), not to the global
$_SERVER superglobal, so the read always returned ''. The handlers
were constructed with an empty secret, TurnToken::verify then
HMAC'd the supplied token against '' which never matched the token
that PostStartMatch had minted with the real (32-byte) secret, and
every action returned 409 {error: turn}.

Fix: delete the line that re-reads $secret; the closures now use the
outer $secret (read from var/secret.key or BATTLEFORGE_SECRET at
script start).
2026-07-26 11:44:51 -05:00
Keith Solomon d80ceae128 fix: declare grid-template-columns so the battle grid lays out as a grid, not a single column
CSS Grid without an explicit grid-template-columns defaults to a
single-column layout, so the 64-144 tiles stacked vertically and
the user saw a long column instead of an 8x8 (or 10x8 or 12x12)
battlefield. Set the column count via a CSS variable on the grid
root, written by match.js after the match snapshot loads (CSS attr()
for non-content properties is not yet shipped in Safari or Firefox).
2026-07-26 11:21:49 -05:00
Keith Solomon 2ea8cfd83d fix: CSRF flow ships the raw token, not the HMAC cookie 2026-07-26 11:01:36 -05:00
Keith Solomon 73254cbf80 Merge feature/battle-interface into develop
Implements Plan 4: battle interface, bundled scenarios, smoke test, release hardening.

- Domain: TurnToken HMAC + ScenarioSerializer objective round-trip
- Web layer: PostStartMatch mints matchId+turnToken; four per-verb action handlers (move/attack/ability/end-turn) on MatchActionSupport
- Bundled scenarios: skirmish, hold-the-line, last-stand + manifest
- Match view: server-rendered shell + match.js renderer with hot-seat action modes
- E2E smoke test gated by BATTLEFORGE_E2E=1 + ci-e2e.yml workflow (PR-to-develop + nightly)

Final review: 1 Critical (permanently disabled action buttons) addressed in d53a84c; re-review clean.

234 PHPUnit tests pass (+47 new since Plan 1 baseline); PHPStan clean; PHPCS delta 0; ESLint clean; Prettier clean.
2026-07-26 10:28:36 -05:00
Keith Solomon 0058a99939 docs: sync plan with shipped corrections (hold-the-line, smoke-test matchId) 2026-07-26 10:26:42 -05:00
Keith Solomon d53a84ce85 fix: enable action-mode buttons in match view (final-review Critical) 2026-07-26 03:58:03 -05:00
Keith Solomon 48e7fa9a25 ci: add end-to-end smoke test workflow 2026-07-26 03:43:57 -05:00
Keith Solomon 4c2586908f fix: use server-minted matchId in smoke test actions 2026-07-26 03:32:54 -05:00
Keith Solomon d118920739 test: add end-to-end smoke test for the battle interface 2026-07-26 03:25:57 -05:00
Keith Solomon 06f9c9e37c fix: add .bf-unit--active CSS rule 2026-07-26 02:05:29 -05:00
Keith Solomon 22e38cfebd feat: add match.js renderer and battle UI styles 2026-07-26 01:56:17 -05:00
Keith Solomon da4982e55d feat: register bundled-scenario and match-action routes 2026-07-26 01:19:49 -05:00
Keith Solomon df8d4023ba feat: add four per-verb match action handlers 2026-07-26 01:05:14 -05:00
Keith Solomon 47dc95fa56 feat: add MatchActionSupport for shared CSRF/turn/state verification 2026-07-26 00:36:49 -05:00
Keith Solomon b444f98aa0 feat: add match view server-rendered shell 2026-07-26 00:13:40 -05:00
Keith Solomon 51f57d309a feat: render the bundled-scenarios list on the home page 2026-07-26 00:01:00 -05:00
Keith Solomon b8451112d3 feat: add GetBundledScenario handler with fixture validation 2026-07-25 23:47:09 -05:00
Keith Solomon 1f9e76afef feat: add GetBundledScenarios handler with manifest-driven list 2026-07-25 23:37:27 -05:00
Keith Solomon c2f9e0a970 fix: update hold-the-line summary to match corrected fixture 2026-07-25 23:19:56 -05:00
Keith Solomon 01029b43b7 feat: add three bundled scenario fixtures and manifest 2026-07-25 23:15:57 -05:00
Keith Solomon 9314fc5870 feat: mint matchId and turnToken in PostStartMatch 2026-07-25 22:52:34 -05:00
Keith Solomon 3d8c1cfdb2 feat: add TurnToken HMAC for match-action anti-cheat 2026-07-25 22:39:29 -05:00
Keith Solomon a0dcff2c2a fix: round-trip match objectives through ScenarioSerializer 2026-07-25 22:21:25 -05:00
Keith Solomon a6d93dd1d3 docs: add Plan 4 implementation plan (battle interface, bundled scenarios, smoke test) 2026-07-25 21:56:19 -05:00
Keith Solomon e8522a510e docs: clarify matchId lifecycle, body shape, and bundled manifest in Plan 4 spec 2026-07-25 21:36:52 -05:00
Keith Solomon fbb6960be2 docs: add Plan 4 design spec (battle interface, bundled scenarios, smoke test) 2026-07-25 21:35:43 -05:00
Keith Solomon ced4363d20 Merge feature/frontend-js into develop
CI / php (push) Failing after 1m12s
Plan 3c: JavaScript frontend (storage.js, grid-editor.js, toolchain).

Adds the Node toolchain (ESLint airbnb-base + Prettier) with a CI step,
public/js/storage.js (bfStorage module for localStorage pre-fill and the home
page's recent-scenarios list), public/js/grid-editor.js (battlefield grid
interactions and JSON fetch save), public/assets/archetypes.json (the four
ArchetypeTemplate definitions), four placeholder PNGs at
public/assets/placeholders/, and updated <script> tags in home.php and
team-editor.php and a data-scenario-id attribute + script tag in
battlefield-editor.php. Also adds *.js and *.json to .gitattributes to
enforce LF line endings on those file types.

196/196 tests pass; PHPStan level 6 clean; ESLint 0 errors; Prettier clean.
Two fix commits were made during the per-task review loop: brief's URL
parsing slice index in storage.js was off-by-one (slice(-2,-1) returned
'edit' instead of the id), and brief's buildScenarioJson emitted a nested
objective shape that didn't match the backend's flat objectiveId/X/Y
fields. Both were corrected.
2026-07-12 21:36:54 -05:00
Keith Solomon 02bf9aa390 feat: add battlefield grid editor and CI lint step 2026-07-12 20:47:58 -05:00
Keith Solomon facbb1a311 feat: add localStorage helpers and wire team editor pre-fill 2026-07-12 20:10:04 -05:00
Keith Solomon 8eefd749be chore: add Node toolchain, archetypes.json, and placeholder images 2026-07-12 19:46:29 -05:00
Keith Solomon 78565cfae9 docs: add Plan 3c implementation plan (JavaScript frontend) 2026-07-12 19:24:12 -05:00
Keith Solomon 7acda302e8 docs: add Plan 3c design spec (JavaScript frontend) 2026-07-12 19:15:01 -05:00
Keith Solomon eef8724626 Merge feature/editor-handlers-and-views into develop
Plan 3b: editor handlers, views, and front controller.

Adds the 6 view templates (layout, home, team-editor, battlefield-editor,
match-stub deleted, upload-result deleted), the 5 remaining HTTP handlers
(GetTeamEditor, PostTeamEditor, GetBattlefieldEditor, PostBattlefieldEditor,
PostImageUpload, PostStartMatch), the real front controller in
public/index.php, and the FullFlowTest integration test. The final
whole-branch review found 3 Critical issues (broken upload-asset URL contract,
upload token source mismatch, and an XSS in the PostTeamEditor success page)
plus 2 Important issues (dead home.php/match-stub.php/upload-result.php);
all were fixed in 4 follow-up commits before this merge.

196/196 tests pass; PHPStan level 6 clean; PHPCS 0 errors on new files.
The web app is fully functional from the browser: php -S 0.0.0.0:8000 -t public
serves the home page, team editor, battlefield editor, image upload endpoint,
and asset-serving endpoint end-to-end.
2026-07-07 10:11:27 -05:00
Keith Solomon b4495f24af chore: remove unused match-stub and upload-result view templates
Neither src/Views/match-stub.php nor src/Views/upload-result.php is
referenced by any handler or route in Plan 3b:

- match-stub is the Plan 4 battle-interface placeholder; Plan 3b does
  not register a /match route.
- upload-result is the iframe-style post-upload response; Plan 3b
  returns JSON from POST /assets/upload and Plan 3c will wire the
  client-side upload form.

Both files were over-eager scaffolding from Task 3 (6a0b147). Drop
them in a follow-up commit rather than amend the shared Task 3 commit.
2026-07-06 23:24:27 -05:00
Keith Solomon ba8fd00411 refactor: route GetHomePage through the home.php template
The handler had a stale inline heredoc that used a '{{ csrf }}' string
substitution to inject the token. The home.php template created in Task 2
is the canonical version (uses render_layout + Escape::attr). Switching
to the template removes the duplicated markup and the ad-hoc str_replace
escape path.
2026-07-06 23:23:32 -05:00
Keith Solomon d15dfc6835 fix: escape scenario id when serialising saved page
PostTeamEditor interpolated the scenario id into a <script> block as
'localStorage.setItem("scenario:<id>", ...)', so an id containing
'</script><script>...</script>' would break out of the JS-string context
and execute in the browser. Use json_encode with JSON_HEX_TAG / HEX_AMP /
HEX_APOS / HEX_QUOT so the id is always a safe JS string literal.

Adds a PostTeamEditorTest case that submits '</script><script>alert(1)</script>'
and asserts the literal '<script>alert(1)</script>' substring is absent
from the response. Updates the existing happy-path and FullFlowTest
assertions to match the new 'scenario:"demo"' (quoted) form.
2026-07-06 23:22:42 -05:00
Keith Solomon 61abb93e59 fix: align upload-asset URL contract end-to-end
- ImageUploadService::store() now returns /assets/uploads/<token>/<file>
  so the URL the upload handler emits matches the new GET route.
- public/index.php registers a dedicated GET /assets/uploads/{userToken}/{filename}
  route; the legacy /assets/{kind}/{filename} is kept as a fallback.
- GetAssets reads the upload token from $request->server['__uploads_token']
  (the front controller threads it that way) instead of the cookies bag.
- Adds GetAssets unit tests for the upload-token happy path and a
  token-mismatch 404, plus a FullFlowTest step that uploads and re-fetches
  the asset through the front controller.
2026-07-06 23:21:33 -05:00
Keith Solomon 5d9af7a4fd test: cover full create-and-save flow end-to-end
Add FullFlowTest, a single end-to-end test that exercises the home page, the team editor POST, the battlefield editor POST, and the start-match POST through the front controller (no php -S).

Wiring fixes in public/index.php (called out in the test brief):

- Honor $_SERVER['__csrf_secret'] when set so the test can supply its own secret; fall back to BATTLEFORGE_SECRET / var/secret.key otherwise.
- Read the raw body from $_SERVER['__raw_body'] when set; fall back to php://input otherwise. PHP CLI's php://input is empty, so an explicit hook is needed for JSON bodies.
- Return the response status from the front controller so the test can assert the HTTP status it set via http_response_code().

Test fixes:

- Drop the static keyword on the runFrontController closure; static forbids $this access and the closure must capture $this to record lastStatus.
- Drop the redundant '?? ''' on the ob_get_clean() result; (string) ob_get_clean() is always a string.
- Set $_SERVER['__raw_body'] alongside $this->rawBody for the JSON requests so the front controller can read the body.
2026-07-06 22:59:18 -05:00
Keith Solomon d387caca86 feat: wire the front controller and harden userToken path 2026-07-06 22:36:34 -05:00
Keith Solomon 2dc7bc1130 feat: add image upload and start-match handlers 2026-07-06 22:24:07 -05:00
Keith Solomon 7cb670b512 feat: add battlefield editor GET and POST handlers 2026-07-06 22:05:47 -05:00
Keith Solomon 12b1547fac feat: add team editor GET and POST handlers 2026-07-06 21:54:59 -05:00
Keith Solomon 6a0b147e4e feat: add battlefield, match stub, and upload result templates 2026-07-06 21:41:18 -05:00
Keith Solomon 7e6240733b feat: add home and team editor templates 2026-07-06 21:30:17 -05:00
Keith Solomon c2233c1639 feat: add shared layout template 2026-07-06 21:21:17 -05:00
Keith Solomon 58b3bc8ece docs: add Plan 3b implementation plan (handlers, views, front controller) 2026-07-06 21:10:59 -05:00
Keith Solomon db12577e78 docs: add Plan 3b design spec (handlers, views, front controller) 2026-07-06 21:00:20 -05:00
Keith Solomon 87a7e10c06 Merge feature/persistence-backend into develop
CI / php (push) Failing after 1m19s
Plan 3a (10 of 17 written tasks): web toolchain, output escaping, CSRF,
Request/Response value objects, Router, JSON serializer, image validator,
image upload service, scenario draft, and the first two HTTP handlers
(home page and asset serving). The remaining 7 tasks (5 more handlers,
6 view templates, the front controller, full-flow integration test, and
CI verification) are scope notes in the plan; the next plan will pick
them up.

177/177 tests pass; PHPStan level 6 clean; PHPCS clean on the new files
(pre-existing line-length warnings remain in Plan 1+2 files). All
deviations from the brief are recorded in the per-task reports under
.superpowers/sdd/.
2026-07-06 19:33:57 -05:00
Keith Solomon 3e4184f08d feat: add home page and asset-serving handlers 2026-07-06 19:02:06 -05:00
Keith Solomon 0f4a220e70 feat: add ScenarioDraft form-field adapter 2026-07-06 18:49:33 -05:00
Keith Solomon c24964ff96 feat: add image upload service 2026-07-06 18:36:34 -05:00
Keith Solomon 6399e2d86c feat: add image content validator 2026-07-06 18:32:50 -05:00
Keith Solomon 288c42e176 feat: add JSON serializer for scenarios and match state 2026-07-06 18:20:01 -05:00
Keith Solomon 8b83df60cd feat: add Http router 2026-07-06 17:58:56 -05:00
Keith Solomon 3263975d2a feat: add Request and Response value objects 2026-07-06 17:52:29 -05:00
Keith Solomon 9ce0c2ac20 feat: add CSRF token store and verifier 2026-07-06 17:44:41 -05:00
Keith Solomon a05baeb4f6 chore: track tests/Integration directory with placeholder
The phpunit.xml split into unit + integration testsuites requires the
integration directory to exist on disk, even when empty. The .gitkeep
keeps the directory present in fresh checkouts and on CI; the first real
integration test in a later task will replace the placeholder.
2026-07-06 17:24:41 -05:00
Keith Solomon eed678e688 chore: add web toolchain and infrastructure for Plan 3 2026-07-06 17:22:46 -05:00
Keith Solomon 17782aab33 docs: complete Plan 3a scope notes (handlers/views/front controller stubbed for Plan 3b) 2026-07-06 16:01:22 -05:00
Keith Solomon 8df51fc1ed docs: in-progress Plan 3a (PHP backend) - 10 of ~19 tasks 2026-07-06 15:58:55 -05:00
Keith Solomon 18520fee22 docs: add Plan 3 design spec (persistence, editors, uploads) 2026-07-06 15:36:21 -05:00
Keith Solomon 1d69e89faf chore: add editor settings and initial project notes
CI / php (push) Failing after 1m43s
2026-07-06 14:34:43 -05:00
Keith Solomon 02addec8df docs: include Plan 2 implementation plan in repo 2026-07-06 14:33:41 -05:00
Keith Solomon 86d82c353d Merge feature/curated-content-and-objectives into develop
CI / php (push) Failing after 1m37s
Plan 2: Curated Content, Abilities, Objectives, and Scenario Validation.

Adds the curated content library (archetypes, abilities, terrain behaviors),
per-round objective control, both victory conditions, and full scenario
validation. 9 production files, 7 test files, 130/130 tests, PHPStan level 6
clean, PHPCS clean. See docs/superpowers/plans/2026-07-05-curated-content-
abilities-and-objectives.md for the implementation plan.
2026-07-05 20:38:44 -05:00
Keith Solomon 39b15c35da refactor: tighten useAbility and Scenario archetype lookup
Final-review-driven changes (Tasks 6/M1, I1 from the ledger):

- Drop the redundant abilities-list check in CombatEngine::useAbility.
  By construction (Scenario::__construct enforces the allowlist), any
  ability the unit 'knows' is also archetype-allowed; the second check
  alone is sufficient and removes a dead code path.
- Update testUseAbilityRejectsAnAbilityTheArchetypeForbids to assert the
  'Unit archetype forbids that ability.' message it actually exercises.
- Compute $template once in Scenario::__construct's per-unit loop and
  drop the redundant catalog lookup on the next line.
2026-07-05 20:33:03 -05:00
Keith Solomon 12f5c0bd3a test: cover scenario to match flow with ability and objective 2026-07-05 20:23:28 -05:00
Keith Solomon 6fffaa2306 feat: validate scenario deployment zones and objectives 2026-07-05 20:11:35 -05:00
Keith Solomon 84fb175c0c feat: resolve objective control and objective victory 2026-07-05 20:06:27 -05:00
Keith Solomon 088e85c9b4 feat: model scenarios and start match snapshots 2026-07-05 19:27:22 -05:00
Keith Solomon 06632ff9b3 feat: model objectives, deployment zones, and victory conditions 2026-07-05 19:11:24 -05:00
Keith Solomon e70b60696c feat: track archetype, abilities, and buff on unit state 2026-07-05 19:05:03 -05:00
Keith Solomon 88149ab1cf feat: catalog curated abilities and definitions 2026-07-05 18:47:20 -05:00
Keith Solomon 0ac8c06c5e feat: catalog curated unit archetypes and templates 2026-07-05 18:41:53 -05:00
Keith Solomon 78fc802cdc fix: enforce LF for PHP files 2026-07-04 18:07:00 -05:00
Keith Solomon de0bc9c43f ci: pin workflow action revisions 2026-07-04 16:15:54 -05:00
Keith Solomon 07e58c84fe ci: enforce PHP quality checks 2026-07-04 16:11:14 -05:00
Keith Solomon fef22d7b56 fix: guard invalid turn transitions 2026-07-04 16:07:36 -05:00
Keith Solomon acfbbf4a91 feat: add alternating team turns 2026-07-04 16:02:49 -05:00
Keith Solomon 7588b304e1 refactor: reuse position distance for attacks 2026-07-04 15:58:29 -05:00
Keith Solomon 3c6359243a feat: resolve attacks and elimination victory 2026-07-04 15:53:57 -05:00
Keith Solomon 3eba823e52 fix: enforce executable movement state 2026-07-04 15:47:29 -05:00
Keith Solomon 1550e2920b feat: validate and resolve unit movement 2026-07-04 15:37:05 -05:00
Keith Solomon 5297b41208 fix: enforce immutable match state invariants 2026-07-04 15:33:05 -05:00
Keith Solomon 94560da5b4 test: verify original match remains unchanged 2026-07-04 15:25:06 -05:00
Keith Solomon 0c5e824942 feat: model immutable combat state 2026-07-04 15:23:00 -05:00
Keith Solomon 4421c5ae32 test: strengthen battlefield boundary coverage 2026-07-04 15:19:06 -05:00
Keith Solomon b789f1b61f fix: validate battlefield reachability inputs 2026-07-04 15:15:08 -05:00
Keith Solomon 189be5bc64 feat: model battlefield terrain and movement costs 2026-07-04 15:08:40 -05:00
Keith Solomon 7b82adbd71 chore: identify Composer package as project 2026-07-04 15:05:11 -05:00
Keith Solomon a97ecbc6d3 chore: bootstrap PHP quality toolchain 2026-07-04 15:02:06 -05:00