Add AGENTS.md and move the verification tools into the repo
The checks that were being run by hand lived in /tmp and would have been lost, and the colour-contrast pass was ad-hoc enough that it missed --dim2 entirely. - tools/verify.js: headless smoke test. Boots the page against a stubbed DOM/canvas and checks all 9 tabs render, all 10 maps draw with no non-finite geometry and no squashed labels, dog-tag sub-checkboxes cover every tag in every list, no photo appears in two steps, every theme has a CSS block, and no personal data shipped. - tools/theme-contrast.py: WCAG check across all themes, now also covering --on-acc on --acc. Running the stricter check found real problems: --dim2 (small captions) was 2.3-3.0:1 in four themes and --acc-d was 2.7-2.8:1 in two. Lightened those. Worst theme is now 3.2:1, all on-accent pairs are 4.8:1 or better. - AGENTS.md documents the architecture, build stages, assertions, data provenance, deployment and the traps already hit, so a future session does not rediscover them the hard way.
This commit is contained in:
parent
a63606b5c8
commit
fbb5f02f49
6 changed files with 530 additions and 17 deletions
167
AGENTS.md
Normal file
167
AGENTS.md
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
# AGENTS.md
|
||||
|
||||
Orientation for anyone (human or agent) picking this repo up cold. Read this before changing anything.
|
||||
|
||||
## What this is
|
||||
|
||||
A single-page companion for 100%-ing **Metal Gear Solid 2: Sons of Liberty — Master Collection Version** (Steam appid `2131640`). It contains a full walkthrough, all 51 achievements, all 394 dog tags, rebuilt guard-position maps, location screenshots, and live Steam achievement sync.
|
||||
|
||||
- **Live:** <https://rain.pages.melonbread.xyz/la-li-lu-le-lo/>
|
||||
- **Repo:** <https://git.melonbread.xyz/rain/la-li-lu-le-lo> (branch `pages`, which is the default branch)
|
||||
- **Owner:** `rain` / `rain@melonbread.xyz`
|
||||
|
||||
## Golden rules
|
||||
|
||||
1. **Never edit `index.html`.** It is generated. Your change will be silently overwritten on the next build. Edit `build/` and run `./deploy.sh`.
|
||||
2. **Run `node tools/verify.js` before deploying.** It catches most of the ways this project breaks.
|
||||
3. **Run `python3 tools/theme-contrast.py` after touching any colour or theme.**
|
||||
4. The build has assertions. If one fires, **fix the data, do not delete the assertion.**
|
||||
|
||||
## Layout
|
||||
|
||||
```
|
||||
index.html generated - do not edit
|
||||
steam-sync.py generated copy of build/steam-sync.py
|
||||
steam-proxy-worker.js generated copy of build/steam-proxy-worker.js
|
||||
deploy.sh build + commit + push
|
||||
tools/verify.js headless smoke test (stubbed DOM, no browser)
|
||||
tools/theme-contrast.py WCAG contrast check across all themes
|
||||
build/ ALL the sources
|
||||
build.py the build script
|
||||
template.html the page: CSS + JS + markup, with __PLACEHOLDER__ slots
|
||||
achievements_src.py hand-written how-to text for all 51 achievements
|
||||
roadmap_src.py hand-written walkthrough steps + Big Boss content
|
||||
*.json extracted data (see Provenance)
|
||||
```
|
||||
|
||||
`build.py` writes to `OUT = os.path.dirname(HERE)`, i.e. the repo root, and copies the two helper files up from `build/`. So the helpers are edited in `build/` and published to the root.
|
||||
|
||||
## How the page works
|
||||
|
||||
One self-contained HTML file. No framework, no build step at runtime, no network needed except for hot-linked photos.
|
||||
|
||||
`build.py` injects five JSON blobs into `template.html`:
|
||||
|
||||
| Blob | What |
|
||||
|---|---|
|
||||
| `ACHIEVEMENTS` | 51 entries: name, desc, official Steam icon, category, missable flag, guide HTML, photos |
|
||||
| `ROADMAP` | 7 phases / 48 steps, each with body HTML, `gets`, `areas`, `photos` |
|
||||
| `DOGTAGS` | 10 lists (chapter × difficulty), 394 tags with name, area, conditional note |
|
||||
| `MAPS` | 10 spreadsheet sheets: palette, RLE fill runs, text cells, merges, col widths, row heights |
|
||||
| `BIGBOSS` | criteria, heal points, time checkpoints, boss strategies |
|
||||
|
||||
All interactive state lives in **one `localStorage` key**, `mgs2mc-guide-v1`:
|
||||
|
||||
```js
|
||||
S = { ach:{id:true}, tags:{'listId:index':true}, road:{stepId:true},
|
||||
items:{'stepId:slug':true}, hideDone, tab, roadDiff,
|
||||
steam:{profile,id64,persona,unlocked:{api:true},lastSync} }
|
||||
```
|
||||
|
||||
There is a **separate** key `mgs2-theme` for the colour theme, so it is not affected by Export/Import.
|
||||
|
||||
## Build pipeline
|
||||
|
||||
Order matters — later stages depend on earlier ones:
|
||||
|
||||
1. **achievements** — merge `achievements_src.py` with `steam_official.json` (names/descriptions/icons from Steam's own XML).
|
||||
2. **screenshots** — assemble per-achievement photo lists from the two guides, drop perceptual duplicates, pin multi-location photos to steps, attach `st.photos`.
|
||||
3. **colour coding** — classify every `<b>`/`<i>` in the roadmap as tag / item / achievement / missable / boss / area.
|
||||
4. **step sub-checklists** — extract `gets` from the colourised HTML, map tag areas (`TAGMAP`), attach `st.areas`.
|
||||
5. **dog tags**, then **inject** everything into `template.html`.
|
||||
|
||||
### Assertions (do not remove)
|
||||
|
||||
- every dog-tag area is claimed by exactly one step (`TAGMAP`) — nothing silently unmapped
|
||||
- every `PHOTO_STEP` key matches at least one real photo — catches typos in the 40-char hashes
|
||||
- every photo of a split achievement is pinned to a step
|
||||
- no achievement whose photos span several steps is left unpinned
|
||||
|
||||
## Verifying changes
|
||||
|
||||
**`node tools/verify.js`** loads `index.html`, runs its script against a stubbed DOM/canvas and checks: boot does not throw, all 9 tabs render, all 10 maps draw without non-finite geometry, no label is squashed, dog-tag sub-checkboxes cover every tag in every list, no photo appears twice, every theme has a CSS block, and no personal data shipped.
|
||||
|
||||
**`python3 tools/theme-contrast.py`** checks every foreground against `--panel` plus `--on-acc` against `--acc`, across all themes. Fails under 3.0:1.
|
||||
|
||||
**There is no browser tooling available.** `browser_screenshot` crashes the harness GUI process (SIGSEGV in the Electron/sharp path) — do not call it. The Node stub approach above is the substitute; it verifies logic, not layout, so **layout changes cannot be verified here** and should be stated as unverified.
|
||||
|
||||
Quick manual checks worth knowing:
|
||||
|
||||
```bash
|
||||
grep -c 'class="lb"' index.html # photos present in the build
|
||||
node -e "..." # see tools/verify.js for the stub pattern
|
||||
```
|
||||
|
||||
## Data provenance
|
||||
|
||||
All third-party content is credited in the page's Sources tab and in README.md.
|
||||
|
||||
| Data | Source | Notes |
|
||||
|---|---|---|
|
||||
| Achievement list, icons, apinames | Steam community XML | `steam_official.json` — 51 entries, `ach_002_001`…`ach_002_051` |
|
||||
| Achievement how-to text | Steam guide by Cole ヴ Viper + Dayngl's Guides | hand-written into `achievements_src.py` |
|
||||
| 394 dog tags | Video Chums checklist | `dogtags_vc.json` (names + areas) |
|
||||
| Conditional-spawn notes | u/Spikeyroxas spreadsheet | `dogtag_notes.json` (81 notes) |
|
||||
| Area maps | same spreadsheet | `maps.json` — the sheets are cell-grid art, not images |
|
||||
| Location photos | Steam guide + Dayngl's Guides | hot-linked, never downloaded into the repo |
|
||||
| Achievement removal check | TrueSteamAchievements | confirmed 51, incl. `Hold Up-aholic` which the Steam guide skips |
|
||||
|
||||
**Refreshing a dataset** means re-extracting into `build/*.json` and re-running the build. The extraction was originally done with ad-hoc Python against the source pages; if a source changes shape, re-derive rather than patching the JSON by hand.
|
||||
|
||||
## Traps already hit (don't repeat)
|
||||
|
||||
- **Canvas `fillText` with `maxWidth` condenses text, it does not truncate.** Narrow clip widths turn labels into an unreadable smear. `drawMap` measures each label and steps the font down (to 7px), then lets it overflow rather than squashing. `tools/verify.js` fails if any label comes out condensed.
|
||||
- **Excel does not clip overflowing text at a coloured cell.** Only a cell with actual content stops it. Clipping map labels at fill cells made them tiny; clip at text cells only.
|
||||
- **`data-sub` must hold the handler key, not the CSS class.** It was set to the class, so missable achievements emitted `data-sub="ach miss"` and their tick boxes did nothing — and missables are the whole point.
|
||||
- **Per-achievement photo caps break per-step distribution.** Capping at 4 dropped exactly the photos that needed spreading across steps. Deduplicate globally, distribute per step, cap per step.
|
||||
- **Normalize both sides of a name lookup.** An items list written with spaces (`"stun grenades"`) was compared against space-stripped text, so no multi-word item ever matched — 4 spans instead of 31.
|
||||
- **`pkill -f "steam-sync"` kills the invoking shell**, because that shell's own command line contains the pattern. Use `pkill -f "[s]team-sync"` **in a call that does not also contain the literal string**, or split kill and start into separate calls.
|
||||
- **A bad slice edit duplicated half of `build.py`.** The tell was the build printing every stage twice — check build output length when editing that file.
|
||||
- **Code that uses a computed value must run after it is computed.** The photo-to-step assignment needed `gets`, which is built later in the script.
|
||||
|
||||
## Steam sync
|
||||
|
||||
Steam's community XML sends no CORS headers, so a browser cannot read it directly. Two ways around it:
|
||||
|
||||
**Local helper (what is deployed uses).** `steam-sync.py` serves the folder and proxies `/api/steam?profile=…`. A hosted HTTPS page calls it at `http://127.0.0.1:8765`, so **the helper must be running on the viewer's machine**:
|
||||
|
||||
```bash
|
||||
nohup python3 ~/Desktop/la-li-lu-le-lo/steam-sync.py > /tmp/steam-sync.log 2>&1 &
|
||||
```
|
||||
|
||||
It answers `Access-Control-Allow-Private-Network: true` on the preflight — Chrome blocks a public page from reaching localhost without it.
|
||||
|
||||
**Cloudflare Worker.** `steam-proxy-worker.js` does the same job with no local process. Deploy it and set `STEAM_PROXY` near the top of the page script. `ALLOW_ORIGIN` is pinned to the Pages origin.
|
||||
|
||||
`syncEndpoint()` resolves: localhost → same-origin `/api/steam`; `STEAM_PROXY` set → that; otherwise → the local helper.
|
||||
|
||||
Parsing notes: match achievements by **apiname** (`ach_002_0xx`), never by display name. The XML has **no `<steamID>` persona field** — fall back to `customURL`, then to what the user typed.
|
||||
|
||||
## Themes
|
||||
|
||||
Ten dark themes, switched from the header and applied by a small script in `<head>` before first paint (no flash). Everything is themed through CSS custom properties on `:root` and `html[data-theme="…"]`. **Any new colour must go through a variable**, otherwise it will be wrong in nine themes. Tints use `color-mix()` so they adapt automatically.
|
||||
|
||||
Two deliberate choices: the **area maps keep a white background** (they reproduce a spreadsheet; inverting them would make the colour legend lie), and there are **no light themes** (removed on request).
|
||||
|
||||
## Deployment
|
||||
|
||||
```bash
|
||||
./deploy.sh "what changed"
|
||||
```
|
||||
|
||||
Builds, commits, pushes to `pages`. Pages redeploys in ~15s. Verify with:
|
||||
|
||||
```bash
|
||||
curl -s -L https://rain.pages.melonbread.xyz/la-li-lu-le-lo/ | sha256sum
|
||||
sha256sum index.html
|
||||
```
|
||||
|
||||
The two must match. A stale deploy is the most common confusing failure — check the hash before debugging anything else.
|
||||
|
||||
## Conventions
|
||||
|
||||
- British-ish plain English in user-facing copy; no marketing voice.
|
||||
- Comments explain **why**, not what, and are only added where the reasoning is non-obvious.
|
||||
- Content lives in the `*_src.py` files as HTML strings; structural/page changes live in `template.html`.
|
||||
- Keep the page dependency-free. Every external thing is a hot-linked image; nothing else loads off-origin.
|
||||
- Do not commit personal data. `tools/verify.js` fails if `DEFAULT_PROFILE` is non-empty.
|
||||
Loading…
Add table
Add a link
Reference in a new issue