Commit Graph
53 Commits
Author SHA1 Message Date
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 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 2ea8cfd83d fix: CSRF flow ships the raw token, not the HMAC cookie 2026-07-26 11:01:36 -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 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 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 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 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 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
Claude 67de6dab75 feat: add output escaping helpers 2026-07-06 17:35:58 -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 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 SolomonandKeith Solomon 6befe7b3e2 feat: support curated abilities and objective victory 2026-07-05 19:49:06 -05:00
Keith Solomon 088e85c9b4 feat: model scenarios and start match snapshots 2026-07-05 19:27:22 -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 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