la-li-lu-le-lo/AGENTS.md
rain fbb5f02f49 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.
2026-09-15 17:41:48 -04:00

10 KiB
Raw Blame History

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.

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:

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:

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_001ach_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:

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

./deploy.sh "what changed"

Builds, commits, pushes to pages. Pages redeploys in ~15s. Verify with:

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.