Skip to content

browser: recover cleanly from a rejected boot ROM#193

Merged
LinuxJedi merged 2 commits into
mainfrom
fix/try-boot-state
Jul 15, 2026
Merged

browser: recover cleanly from a rejected boot ROM#193
LinuxJedi merged 2 commits into
mainfrom
fix/try-boot-state

Conversation

@LinuxJedi

Copy link
Copy Markdown
Owner

The bug

A field report from /try: with a bad ROM image (a TOSEC [b] Kickstart 2.04 dump, 528509 bytes - a byte-exact LF-to-CRLF ASCII-transfer casualty, the clean image contains exactly 4221 LFs), the page wedged in ways that looked order-dependent:

  • Bad ROM first, then a good one: "boot failed: ROM size is 528509 bytes..." forever. boot() assigned emu before load_rom could throw, so a failed boot left a half-initialized, never-started machine behind. The ROM pickers then took the if (emu) live-swap branch, which never updated the bootRom stash - so every Boot retry re-fed the rejected image, whatever was picked since.
  • The same stale-stash pattern existed in the drag-and-drop path (Drag and drop disk images (desktop and browser) #191).

(The reporter's third symptom - a black-instead-of-blue Kickstart 1.3 insert-disk screen after a live swap from 2.04 - was chased into the core and ruled out: reload_rom + power_on_reset renders pixel-identical to a fresh boot, both at current main and at the deployed wasm's commit, for 512K and mirrored 256K images alike. Their 1.3 image is suspect; verification pending.)

The fix

JS-only change in crates/copperline-web/www/try.js:

  • boot() fits the ROM into a fresh WebEmu before anything else and assigns emu only once the whole boot has succeeded, so a rejected image aborts with the page still in its pre-boot state and the pickers keep working.
  • The picker and drag-drop paths share a new fitRom() helper: on a successful live swap it also updates the bootRom stash, so a later reboot fits the ROM chosen last; a rejected image throws before the stash is touched.
  • The frame loop's error path re-arms the boot button, and boot() closes the previous audio stack, so a machine that hit an emulator error can be rebooted cleanly instead of wedging behind a disabled button.

Testing

Verified end-to-end in Chrome against the deployed wasm bundle (the fix uses no new WebEmu API), with a bad ROM generated the same way the reporter's was corrupted:

  • bad ROM, Boot -> "boot failed: ROM size is 528509 bytes..."; good ROM then goes down the pre-boot branch ("will boot ...") and Boot starts the machine - previously this errored forever
  • bad ROM picked while running -> "ROM load failed: ...", machine keeps running on its old ROM (unchanged core behaviour: reload_rom validates before mutating)
  • live swap 2.04 -> 256K 1.3 -> hand screen renders with the correct colours

Rust untouched, so cargo fmt/clippy/test are unaffected by this diff.

boot() assigned emu before load_rom could throw, so a bad ROM image
left a half-initialized machine behind: the ROM pickers then took the
live-swap branch, which never updated the bootRom stash, and every
Boot retry re-fed the rejected image. Once a boot had failed there was
no way back short of reloading the page, even after picking a good
ROM.

boot() now fits the ROM into a fresh WebEmu before anything else and
assigns emu only after the whole boot has succeeded, so a rejected
image leaves the page in its pre-boot state. The picker and drag-drop
paths share a new fitRom() helper that updates the stash on a
successful live swap too, so a later reboot fits the ROM chosen last;
a rejected image throws before the stash is touched. The frame loop's
error path re-arms the boot button and boot() closes the previous
audio stack, so a machine that hit an emulator error can be rebooted
cleanly.
Copilot AI review requested due to automatic review settings July 15, 2026 08:50

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a browser-side boot flow bug where a rejected Kickstart ROM could leave the page in a half-booted state, causing subsequent ROM selections/retries to keep reusing a stale rejected image. It restructures the ROM-fit and boot logic in try.js to make boot atomic and to keep the “boot ROM” stash consistent across live swaps and reboots.

Changes:

  • Add a shared fitRom() helper to unify ROM picker and drag-drop behavior, and update the bootRom stash on successful live swaps.
  • Make boot() construct/load a fresh WebEmu first and assign emu only after the overall boot succeeds.
  • Re-arm the boot UI after main-loop emulator errors and close/rebuild the audio stack on reboot.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 247 to +251
running = false;
setLoadStatus(`emulator error: ${e.message ?? e}`);
overlay.style.display = '';
// Re-arm the boot button: a fresh boot replaces the wedged machine.
refreshBootButton();
Review follow-up: tick()'s error path re-armed the boot button but
left emu set, so the pickers still took the live-swap branch into the
crashed instance (which may have panicked) instead of stashing for
the fresh boot. Null it there, and guard the two deferred touch
timers (long-press and tap-release) that can fire after the machine
is gone.
@LinuxJedi
LinuxJedi merged commit f208a6a into main Jul 15, 2026
6 checks passed
@LinuxJedi
LinuxJedi deleted the fix/try-boot-state branch July 15, 2026 09:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants