Commit Graph
100 Commits
Author SHA1 Message Date
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