feat(website): a save scrubber page, longshot_saves.py scrub in the browser #747

Merged
HumanBean merged 2 commits from HumanBean/Longshot:feat/website-save-scrubber into main 2026-11-15 17:29:36 +00:00
Owner

Type: feat · Risk: low · Tested on: Linux, Chromium (headless) · Needs: Firefox and Safari, a real save

Why

Making a save for the repository (assets/saves) means running python -I scripts/longshot_saves.py scrub from a
copy of the source. People without Python, or without a checkout, had no way to make one. This adds a page to the
website, Development → Scrub a save (/saves/scrub/), where you drop a save in and get the scrubbed slot back as
a zip, the same files the script makes. It all runs in the browser: nothing is uploaded.

What changed

The scrub is ported to TypeScript byte for byte (the caption.sav layout, the date keys, the grey JPEG writer and the
JPEG reader that checks it), with a page that takes a slot folder, a whole save folder or the two files, by drag and
drop or file pickers, lists the slots it finds and zips the one you pick. A test runs the Python script and the port
on the same made-up saves and requires identical output.

  • website, logic: website/src/lib/save-scrub.ts: captionLayout, captionDates, greyJpeg, jpegBlocks /
    jpegIsMidGrey and scrubSlot (the script's scrub: same refusals, same change lines); findSlots groups the
    dropped files by folder (account.sav, option.sav and anything else are named and left out); zipStored writes
    the zip, entries dated 2026-01-01 00:00 like the script's modification times.
  • website, page: website/src/lib/save-scrub-page.ts (the <save-scrubber> element: drop, folder walk capped at
    2000 entries, file size capped at the script's 4 MB, text from files shown with textContent only),
    website/src/components/SaveScrubber.astro, website/src/content/docs/saves/scrub.mdx, and the sidebar entry in
    website/astro.config.ts. The zip holds <name>/README.md (the script's template), <name>/slot_00/progress.sav
    and <name>/slot_00/caption.sav, ready to unzip into assets/saves/.
  • The page repeats the rule from the issue templates: scrubbed or not, saves are not attached to issues; repo saves
    follow assets/saves/README.md and still pass check-repo-saves.
  • docs: docs/saves-script.md points to the page from "Making one".
  • agents: AGENTS.md (its own commit) says a change that shows on screen carries screenshots in the description,
    taken by the agent (a website page from a local build, dark, light and phone width, with made-up data), and how
    to attach them with the forge sign-in.
  • test: website/tests/save-scrub.test.ts: the port against longshot_saves.py (scrubbed slot, grey JPEGs at three
    sizes/levels, refusals for a repeated or missing date key, bad magic, short file, bad scene name), slot finding, and
    the zip read back by Python's zipfile.

Measurements

Nothing to measure: a website page with its tests.

Flags and settings

None

Testing

Tested:

  • node --test tests/save-scrub.test.ts in website/: 6/6 pass, including byte-for-byte equality with
    longshot_saves.py scrub on the Python tests' made-up saves.
  • astro build builds the page (17 pages). In headless Chromium (agent-browser) on the built site: chose a made-up
    save folder with slot_01, slot_04, account.sav and option.sav through Choose a folder: both slots listed
    with scene and date, the two other files named as left out; scrubbed slot_04, downloaded the zip, and its
    progress.sav and caption.sav are identical to the script's scrub of the same slot. Choose files with a
    slot's two files works too.
  • node --test tests/*.test.ts: 81/82; the one failure (HDR Native condition accepts current prose but remains required, tests/graphics-settings.test.ts) fails the same on main.
  • npm run check's site check stops on three progress-feed items ("game branding remains in public copy", issues
    603, 615 and 715): today's issue titles from Forgejo, the same on main, not this page.
  • python -I -B tests/run_all.py (before the AGENTS.md commit; tests/test_switches_removed.py, which reads AGENTS.md, passes after it): 509 passed, 14 failed, 70 skipped; the same 14 fail on main @ a00c485c (test_make_game_cache, test_lift_cleanup_error, test_bake_portability, test_crash_dialog, test_cpm_discovery, test_env_on, test_linux_preflight, test_appimage_player_build, test_options_budget_once, test_pipeline_tour, test_mods_size_table, test_readback_late, test_vk_pgo, test_walk_key); none reads the files changed here.

Screenshots (After: this PR @ 7778a235; the page is new, main has none), the built site in headless Chromium,
with a made-up save folder (slot_01, slot_04, account.sav, option.sav):

The page, dark:

The page, dark

slot_04 scrubbed, dark:

slot_04 scrubbed, dark

The same, light:

slot_04 scrubbed, light

The same at phone width (390 px):

slot_04 scrubbed, phone width

Not tested: drag and drop with a real mouse (only the file pickers were driven; the drop path walks the same
entries), Firefox, Safari, and a real save from the game (the made-up saves are the ones the script's own tests use).

Must be tested:

  • Firefox or Safari, by hand: open the preview's /saves/scrub/, drag a save folder from the file manager onto the
    drop box, scrub a slot, expecting the slots listed and a zip whose files python -I scripts/longshot_saves.py check-repo-saves passes once unzipped into assets/saves/ with its README filled in.

Risks

What could break: only the new page; nothing else on the site changes except a sidebar entry under Development.

Turning it off: revert the commit.

Compatibility: Nothing changes.

Checklist

Everyone

  • One topic.
  • The title is a Conventional Commit (type(scope): description) of the status line's type.
  • Why gives the reason plainly and links the issues that fit (the tracker searched).
  • Measurements has a verdict, or one line saying why there is nothing to measure.
  • Testing names what was tested, what wasn't, and what must still be tested, where and how.
  • Rebased on current main.
  • python -I -B tests/run_all.py passes (any failure that also fails on main is named).
  • No game files, dumps, build output or generated files.
  • third_party/ untouched (a new folder with a PROVENANCE.md instead).
  • The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled.

When it applies

  • AI assistance recorded with Assisted-by: / Co-Authored-By: trailers.
  • Screenshots of the same spot, captioned like the Measurements columns, for a change that shows on screen.
  • Docs updated.
**Type:** feat · **Risk:** low · **Tested on:** Linux, Chromium (headless) · **Needs:** Firefox and Safari, a real save ## Why Making a save for the repository (`assets/saves`) means running `python -I scripts/longshot_saves.py scrub` from a copy of the source. People without Python, or without a checkout, had no way to make one. This adds a page to the website, **Development → Scrub a save** (`/saves/scrub/`), where you drop a save in and get the scrubbed slot back as a zip, the same files the script makes. It all runs in the browser: nothing is uploaded. ## What changed The scrub is ported to TypeScript byte for byte (the `caption.sav` layout, the date keys, the grey JPEG writer and the JPEG reader that checks it), with a page that takes a slot folder, a whole save folder or the two files, by drag and drop or file pickers, lists the slots it finds and zips the one you pick. A test runs the Python script and the port on the same made-up saves and requires identical output. - website, logic: `website/src/lib/save-scrub.ts`: `captionLayout`, `captionDates`, `greyJpeg`, `jpegBlocks` / `jpegIsMidGrey` and `scrubSlot` (the script's `scrub`: same refusals, same change lines); `findSlots` groups the dropped files by folder (`account.sav`, `option.sav` and anything else are named and left out); `zipStored` writes the zip, entries dated 2026-01-01 00:00 like the script's modification times. - website, page: `website/src/lib/save-scrub-page.ts` (the `<save-scrubber>` element: drop, folder walk capped at 2000 entries, file size capped at the script's 4 MB, text from files shown with `textContent` only), `website/src/components/SaveScrubber.astro`, `website/src/content/docs/saves/scrub.mdx`, and the sidebar entry in `website/astro.config.ts`. The zip holds `<name>/README.md` (the script's template), `<name>/slot_00/progress.sav` and `<name>/slot_00/caption.sav`, ready to unzip into `assets/saves/`. - The page repeats the rule from the issue templates: scrubbed or not, saves are not attached to issues; repo saves follow `assets/saves/README.md` and still pass `check-repo-saves`. - docs: `docs/saves-script.md` points to the page from "Making one". - agents: `AGENTS.md` (its own commit) says a change that shows on screen carries screenshots in the description, taken by the agent (a website page from a local build, dark, light and phone width, with made-up data), and how to attach them with the forge sign-in. - test: `website/tests/save-scrub.test.ts`: the port against `longshot_saves.py` (scrubbed slot, grey JPEGs at three sizes/levels, refusals for a repeated or missing date key, bad magic, short file, bad scene name), slot finding, and the zip read back by Python's `zipfile`. ## Measurements Nothing to measure: a website page with its tests. ### Flags and settings None ## Testing **Tested:** - `node --test tests/save-scrub.test.ts` in `website/`: 6/6 pass, including byte-for-byte equality with `longshot_saves.py scrub` on the Python tests' made-up saves. - `astro build` builds the page (17 pages). In headless Chromium (agent-browser) on the built site: chose a made-up save folder with `slot_01`, `slot_04`, `account.sav` and `option.sav` through **Choose a folder**: both slots listed with scene and date, the two other files named as left out; scrubbed `slot_04`, downloaded the zip, and its `progress.sav` and `caption.sav` are identical to the script's scrub of the same slot. **Choose files** with a slot's two files works too. - `node --test tests/*.test.ts`: 81/82; the one failure (`HDR Native condition accepts current prose but remains required`, `tests/graphics-settings.test.ts`) fails the same on `main`. - `npm run check`'s site check stops on three progress-feed items ("game branding remains in public copy", issues 603, 615 and 715): today's issue titles from Forgejo, the same on `main`, not this page. - `python -I -B tests/run_all.py` (before the AGENTS.md commit; `tests/test_switches_removed.py`, which reads AGENTS.md, passes after it): 509 passed, 14 failed, 70 skipped; the same 14 fail on `main` @ a00c485c (`test_make_game_cache`, `test_lift_cleanup_error`, `test_bake_portability`, `test_crash_dialog`, `test_cpm_discovery`, `test_env_on`, `test_linux_preflight`, `test_appimage_player_build`, `test_options_budget_once`, `test_pipeline_tour`, `test_mods_size_table`, `test_readback_late`, `test_vk_pgo`, `test_walk_key`); none reads the files changed here. **Screenshots** (After: this PR @ 7778a235; the page is new, `main` has none), the built site in headless Chromium, with a made-up save folder (`slot_01`, `slot_04`, `account.sav`, `option.sav`): The page, dark: ![The page, dark](https://git.magicbean.dev/attachments/5883fd6a-b7fc-4beb-9b0d-1b04581d1f90) `slot_04` scrubbed, dark: ![slot_04 scrubbed, dark](https://git.magicbean.dev/attachments/bb778009-5d44-4493-bbe9-f600c205cd23) The same, light: ![slot_04 scrubbed, light](https://git.magicbean.dev/attachments/1a198c8b-01f6-421e-970e-cd2e0e122811) The same at phone width (390 px): ![slot_04 scrubbed, phone width](https://git.magicbean.dev/attachments/3ce1e582-c3ae-49d4-9bed-c801d8a109f6) **Not tested:** drag and drop with a real mouse (only the file pickers were driven; the drop path walks the same entries), Firefox, Safari, and a real save from the game (the made-up saves are the ones the script's own tests use). **Must be tested:** - Firefox or Safari, by hand: open the preview's `/saves/scrub/`, drag a save folder from the file manager onto the drop box, scrub a slot, expecting the slots listed and a zip whose files `python -I scripts/longshot_saves.py check-repo-saves` passes once unzipped into `assets/saves/` with its README filled in. ## Risks **What could break:** only the new page; nothing else on the site changes except a sidebar entry under Development. **Turning it off:** revert the commit. **Compatibility:** Nothing changes. ## Checklist ### Everyone - [x] One topic. - [x] The title is a Conventional Commit (`type(scope): description`) of the status line's type. - [x] Why gives the reason plainly and links the issues that fit (the tracker searched). - [x] Measurements has a verdict, or one line saying why there is nothing to measure. - [x] Testing names what was tested, what wasn't, and what must still be tested, where and how. - [x] Rebased on current `main`. - [x] `python -I -B tests/run_all.py` passes (any failure that also fails on `main` is named). - [x] No game files, dumps, build output or generated files. - [x] `third_party/` untouched (a new folder with a `PROVENANCE.md` instead). - [x] The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled. ### When it applies - [x] AI assistance recorded with `Assisted-by:` / `Co-Authored-By:` trailers. - [x] Screenshots of the same spot, captioned like the Measurements columns, for a change that shows on screen. - [x] Docs updated.
feat(website): a save scrubber page, longshot_saves.py scrub in the browser
Some checks failed
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been skipped
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been skipped
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been skipped
Third-party parts / The same as upstream (pull_request) Has been skipped
Website / Website on Cloudflare (pull_request) Has been skipped
CI / Conventional Commit title (pull_request) Successful in 4s
CI / Workflow lint (actionlint) (pull_request) Successful in 7s
Guide screenshots / Guide images and local UI source review (pull_request) Failing after 6s
Third-party parts / As their notes say (pull_request) Successful in 9s
PR description / Description follows the template (pull_request_target) Has been cancelled
Website / Website on Cloudflare (pull_request_target) Failing after 5s
846a784253
/saves/scrub/: drop a slot folder, a save folder or a slot's two files and
get the scrubbed slot back as a zip (README.md, slot_00/progress.sav,
slot_00/caption.sav), the same bytes scripts/longshot_saves.py scrub
writes. Nothing is uploaded: it all runs in the browser.

src/lib/save-scrub.ts ports the caption.sav layout, the date keys, the
grey JPEG writer and the JPEG reader; tests/save-scrub.test.ts runs the
Python script and the port on the same made-up saves and requires
identical output.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
HumanBean force-pushed feat/website-save-scrubber from 846a784253
Some checks failed
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been skipped
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been skipped
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been skipped
Third-party parts / The same as upstream (pull_request) Has been skipped
Website / Website on Cloudflare (pull_request) Has been skipped
CI / Conventional Commit title (pull_request) Successful in 4s
CI / Workflow lint (actionlint) (pull_request) Successful in 7s
Guide screenshots / Guide images and local UI source review (pull_request) Failing after 6s
Third-party parts / As their notes say (pull_request) Successful in 9s
PR description / Description follows the template (pull_request_target) Has been cancelled
Website / Website on Cloudflare (pull_request_target) Failing after 5s
to 7778a235cf
Some checks failed
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been skipped
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been skipped
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been skipped
Third-party parts / The same as upstream (pull_request) Has been skipped
Website / Website on Cloudflare (pull_request) Has been skipped
CI / Conventional Commit title (pull_request) Successful in 13s
CI / Workflow lint (actionlint) (pull_request) Successful in 20s
Guide screenshots / Guide images and local UI source review (pull_request) Failing after 19s
Third-party parts / As their notes say (pull_request) Successful in 28s
PR description / Description follows the template (pull_request_target) Successful in 15s
Website / Website on Cloudflare (pull_request_target) Has been skipped
2026-11-15 09:50:37 +00:00
Compare
Author
Owner

Added screenshots of the page (dark, light, phone width) to the description, and a second commit: AGENTS.md now asks agents to put screenshots in the description of any PR that changes what's on screen, with how to attach them.

Added screenshots of the page (dark, light, phone width) to the description, and a second commit: AGENTS.md now asks agents to put screenshots in the description of any PR that changes what's on screen, with how to attach them.
HumanBean force-pushed feat/website-save-scrubber from 7778a235cf
Some checks failed
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been skipped
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been skipped
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been skipped
Third-party parts / The same as upstream (pull_request) Has been skipped
Website / Website on Cloudflare (pull_request) Has been skipped
CI / Conventional Commit title (pull_request) Successful in 13s
CI / Workflow lint (actionlint) (pull_request) Successful in 20s
Guide screenshots / Guide images and local UI source review (pull_request) Failing after 19s
Third-party parts / As their notes say (pull_request) Successful in 28s
PR description / Description follows the template (pull_request_target) Successful in 15s
Website / Website on Cloudflare (pull_request_target) Has been skipped
to eba1f5d99b
Some checks failed
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been skipped
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been skipped
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been skipped
Third-party parts / The same as upstream (pull_request) Has been skipped
Website / Website on Cloudflare (pull_request) Has been skipped
PR description / Description follows the template (pull_request_target) Successful in 5s
CI / Conventional Commit title (pull_request) Successful in 4s
CI / Workflow lint (actionlint) (pull_request) Successful in 8s
Guide screenshots / Guide images and local UI source review (pull_request) Failing after 9s
Third-party parts / As their notes say (pull_request) Successful in 12s
2026-11-15 17:02:40 +00:00
Compare
HumanBean force-pushed feat/website-save-scrubber from eba1f5d99b
Some checks failed
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been skipped
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been skipped
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been skipped
Third-party parts / The same as upstream (pull_request) Has been skipped
Website / Website on Cloudflare (pull_request) Has been skipped
PR description / Description follows the template (pull_request_target) Successful in 5s
CI / Conventional Commit title (pull_request) Successful in 4s
CI / Workflow lint (actionlint) (pull_request) Successful in 8s
Guide screenshots / Guide images and local UI source review (pull_request) Failing after 9s
Third-party parts / As their notes say (pull_request) Successful in 12s
to b5f04ef33a
Some checks failed
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been skipped
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been skipped
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been skipped
Third-party parts / The same as upstream (pull_request) Has been skipped
Website / Website on Cloudflare (pull_request) Has been skipped
CI / Conventional Commit title (pull_request) Successful in 12s
CI / Workflow lint (actionlint) (pull_request) Successful in 18s
Guide screenshots / Guide images and local UI source review (pull_request) Failing after 19s
Third-party parts / As their notes say (pull_request) Successful in 33s
PR description / Description follows the template (pull_request_target) Successful in 16s
2026-11-15 17:25:01 +00:00
Compare
HumanBean deleted branch feat/website-save-scrubber 2026-11-15 17:29:37 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
MagicBeanDev/Longshot!747
No description provided.