diff --git a/docs/superpowers/plans/2026-07-06-persistence-backend.md b/docs/superpowers/plans/2026-07-06-persistence-backend.md
new file mode 100644
index 0000000..8bd812e
--- /dev/null
+++ b/docs/superpowers/plans/2026-07-06-persistence-backend.md
@@ -0,0 +1,2659 @@
+# Persistence and Scenario Editors (Backend) Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Build the plain-PHP backend that lets an anonymous browser visitor author, validate, save, and start a scenario, with secure image uploads and a CSRF-protected, escape-everything web stack.
+
+**Architecture:** A front controller (`public/index.php`) dispatches every request to a small `Http\Router` that routes by HTTP method + path to a `Http\Handlers\*` class. Handlers parse the request through a `Http\Request` value object, do their work through `Application\*` services (which know the `Domain`), and return a `Http\Response` rendered by `Views\*` PHP templates. The browser owns persistence via `localStorage`; the server only ever sees form POSTs and image uploads, never the assembled `Scenario` JSON.
+
+**Tech Stack:** PHP 8.3, PHPUnit 11, PHPStan level 6, PHP_CodeSniffer PSR-12, plain `php -S` for local dev. The frontend toolchain (ESLint airbnb/base + Prettier) and JS files land in Plan 3b.
+
+## Global Constraints
+
+These apply to every task and are copied verbatim from the design spec.
+
+- Anonymous browser persistence: scenarios and matches live in the browser's `localStorage` (`scenario:{id}` and `match:current`); the server never stores game state.
+- No user accounts, no cross-device, no server-side storage of scenarios or matches. The server only keeps per-session CSRF state and uploaded image files.
+- Built-in dev server only: `php -S 0.0.0.0:8000 -t public` (documented in README). No Apache-only assumptions; the `var/uploads/.htaccess` and `var/uploads/index.php` sentinels cover both Apache deployments and the built-in server.
+- Every state-changing form or request uses and verifies a CSRF token before mutation. Tokens are per-session, signed with the app's secret using `hash_hmac('sha256', …)`. Forms use a hidden `_csrf` field; fetch uses an `X-CSRF-Token` header.
+- All rendered output is escaped via `Escape::html`, `Escape::attr`, and `Escape::url` helpers. Code review enforces the pattern; PHPStan has no rule for it.
+- All uploaded images are content-validated by reading the first 12 bytes and checking the magic number against the allowlist (PNG, JPEG, WebP, GIF), then `getimagesizefromstring()` confirms dimensions ≤ 512×512, then size is checked at ≤ 2 MB. The original filename and declared MIME are discarded after validation.
+- `Content-Security-Policy: default-src 'self'; img-src 'self' data:; style-src 'self'` on every page. `X-Content-Type-Options: nosniff` and `Referrer-Policy: same-origin` on every page.
+- Bundled placeholders are committed under `public/assets/placeholders/`; user uploads live in `var/uploads/` (git-ignored).
+- PHP follows the repository PHPCS rules and passes PHPStan at level 6.
+- The small JS surface passes ESLint `airbnb/base` and Prettier.
+- Composer configuration passes `composer validate --strict`.
+- Lint, static analysis, unit and integration tests, the end-to-end smoke test, and Composer validation are required CI checks. The MVP is releasable only when all checks pass.
+- Domain code has no dependency on HTTP, sessions, storage, or browser code (unchanged from Plans 1 and 2).
+
+## File Structure
+
+```text
+src/
+├── Application/
+│ ├── ScenarioSerializer.php JSON ↔ Scenario (used by both editors and the start-match endpoint)
+│ ├── ScenarioDraft.php mutable draft state used to build a Scenario from form input
+│ ├── ImageValidator.php content-sniff + size + dimension checks
+│ └── ImageUploadService.php validates + stores a user-uploaded image; returns a stable URL
+├── Http/
+│ ├── Request.php value object wrapping $_GET, $_POST, $_FILES, $_SERVER
+│ ├── Response.php value object with status, headers, body
+│ ├── Router.php tiny path → handler dispatcher
+│ ├── CsrfToken.php per-session token store + verify helper
+│ ├── Escape.php html(), attr(), url() helpers
+│ └── Handlers/
+│ ├── GetHomePage.php
+│ ├── GetTeamEditor.php
+│ ├── PostTeamEditor.php
+│ ├── GetBattlefieldEditor.php
+│ ├── PostBattlefieldEditor.php
+│ ├── GetAssets.php
+│ ├── PostImageUpload.php
+│ └── PostStartMatch.php
+├── Views/
+│ ├── layout.php
+│ ├── home.php
+│ ├── team-editor.php
+│ ├── battlefield-editor.php
+│ ├── match-stub.php
+│ └── upload-result.php
+└── public/
+ ├── index.php front controller
+ └── assets/
+ └── styles.css
+ ├── storage.js
+ └── grid-editor.js
+
+var/ git-ignored, runtime-writable
+├── phpunit/
+├── phpstan/
+└── uploads/
+ ├── .htaccess Deny from all (Apache)
+ ├── index.php Sentinel that returns 403 (built-in dev server)
+ └── {userToken}/ Per-browser namespace; created on first upload
+
+tests/
+├── Unit/
+│ ├── Application/
+│ │ ├── ScenarioSerializerTest.php
+│ │ ├── ScenarioDraftTest.php
+│ │ ├── ImageValidatorTest.php
+│ │ └── ImageUploadServiceTest.php
+│ └── Http/
+│ ├── CsrfTokenTest.php
+│ ├── EscapeTest.php
+│ ├── RequestTest.php
+│ └── RouterTest.php
+└── Integration/
+ ├── GetHomePageTest.php
+ ├── GetTeamEditorTest.php
+ ├── PostTeamEditorTest.php
+ ├── GetBattlefieldEditorTest.php
+ ├── PostBattlefieldEditorTest.php
+ ├── GetAssetsTest.php
+ ├── PostImageUploadTest.php
+ └── PostStartMatchTest.php
+```
+
+## Delivery Sequence
+
+Plan 3 is the third of four plans. It is split into:
+
+- **Plan 3a (this plan, the PHP backend):** Tasks 1-14. The web app is fully working and `curl`-able; tests cover all backend behavior.
+- **Plan 3b (the frontend, separate plan):** the small JS surface (`public/js/storage.js`, `public/js/grid-editor.js`), the placeholder images, and the ESLint/Prettier toolchain. The editor pages render with HTML-only submit and `localStorage` write today; Plan 3b adds the fetch-based battlefield grid and the JS UX.
+
+This plan covers 1-14 below. Plan 3b is drafted and executed after this one lands.
+
+1. Toolchain and infrastructure (Task 1)
+2. Application layer (Tasks 2-4)
+3. Http layer (Tasks 5-7)
+4. Editor pages and the front controller (Tasks 8-12)
+5. Integration tests and end-to-end plumbing (Task 13)
+6. CI updates (Task 14)
+7. Final whole-branch review
+
+## Execution Preflight
+
+Execute this plan in an isolated worktree on `feature/persistence-backend`, branched from `develop`. The implementation branch will be submitted as a pull request into `develop` only after every completion check is green.
+
+### Task 1: Toolchain and infrastructure
+
+**Files:**
+- Create: `var/uploads/.htaccess`
+- Create: `var/uploads/index.php`
+- Create: `public/index.php`
+- Create: `public/assets/styles.css`
+- Modify: `.gitignore`
+- Modify: `composer.json`
+- Modify: `phpcs.xml`
+- Modify: `phpstan.neon`
+- Modify: `phpunit.xml`
+
+**Interfaces:**
+- Produces: a public document root that the built-in dev server can serve; an expanded PHPCS/PHPStan/PHPUnit scope that covers the new layers; an updated `.gitignore` that excludes `var/uploads/*` but commits `var/uploads/.htaccess` and `var/uploads/index.php`. The Node toolchain (ESLint + Prettier) and JS files land in Plan 3b.
+
+- [ ] **Step 1: Update `.gitignore`**
+
+Append a force-include rule for the upload sentinels so the `var/uploads/.htaccess` and `var/uploads/index.php` files are committed even though the rest of `var/uploads/` is ignored.
+
+Replace `.gitignore` with:
+
+```
+.worktrees/
+/vendor/
+/var/cache/
+/var/phpunit/
+/var/phpstan/
+/var/logs/
+/var/uploads/*
+!/var/uploads/.htaccess
+!/var/uploads/index.php
+/node_modules/
+/public/js/*.map
+.phpunit.result.cache
+.phpunit.cache
+```
+
+- [ ] **Step 2: Create `var/uploads/.htaccess`**
+
+Create `var/uploads/.htaccess`:
+
+```
+Require all denied
+Deny from all
+```
+
+- [ ] **Step 3: Create `var/uploads/index.php`**
+
+Create `var/uploads/index.php`:
+
+```php
+
+
+ BattleForge coding standards.
+
+ src
+ tests
+ public/index.php
+
+
+
+
+
+
+```
+
+- [ ] **Step 6: Update `phpstan.neon` to include the new layers and exclude templates**
+
+Replace `phpstan.neon` with:
+
+```neon
+parameters:
+ level: 6
+ paths:
+ - src
+ - tests
+ - public/index.php
+ excludePaths:
+ - src/Views/*
+ tmpDir: var/phpstan
+```
+
+- [ ] **Step 7: Update `phpunit.xml` to add an integration test suite**
+
+Replace `phpunit.xml` with:
+
+```xml
+
+
+
+
+ tests/Unit
+
+
+ tests/Integration
+
+
+
+
+
+ src
+
+
+
+```
+
+- [ ] **Step 8: Create `public/index.php` (front controller stub)**
+
+The full front controller lands in Task 13. This step creates the file with a placeholder so the document root exists and `php -S` can serve it.
+
+Create `public/index.php`:
+
+```php
+
+ */
+ public static function htmlProvider(): iterable
+ {
+ yield 'plain' => ['hello', 'hello'];
+ yield 'less-than' => ['
+