fix: make ?cpuMultiplier= run the CPU faster than the peripherals again - #725
Merged
Conversation
The parameter was parsed after the emulation config literal captured it, so Cpu6502 always saw a multiplier of 1 and the polltime path that divides video and peripheral cycles back down never ran. The whole machine sped up, sound pitch and screen refresh included, instead of just the CPU. Resolve the multiplier from the query string where it is declared, so there is no window in which the config can capture a stale value. Paste timing no longer scales by the multiplier: the paste task lives on the processor's scheduler, which is polled with peripheral cycles, so its delays were already in real time. The headless harness plumbs cpuMultiplier through and scales its key hold, which is counted in CPU cycles. Fixes #716
Contributor
There was a problem hiding this comment.
Pull request overview
Restores the intended ?cpuMultiplier= behavior where the CPU runs faster/slower relative to peripherals (video/sound/VIA timing stays real-time), fixing a regression where cpuMultiplier never reached Cpu6502 via emulationConfig.
Changes:
- Resolve
cpuMultiplierdirectly fromparsedQuerybefore constructingemulationConfig, and remove the now-incorrect comment insrc/main.js. - Adjust paste/autotype timing to remain real-time under a live CPU multiplier, and plumb
cpuMultiplierthrough headless/test wiring (MachineSession/fake6502). - Add/adjust unit tests to pin the CPU-vs-peripheral scaling behavior and update existing keyboard tests accordingly.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/unit/test-keyboard.js | Updates paste timing expectations to be based on peripheral cycles (real-time), not CPU multiplier. |
| tests/unit/test-6502.js | Adds new unit coverage for peripheral/video scaling under cpuMultiplier. |
| tests/test-machine.js | Scales key-hold timing in TestMachine.type() by cpuMultiplier to keep holds constant in real time. |
| src/main.js | Fixes config wiring so config.cpuMultiplier reflects the parsed query value. |
| src/machine-session.js | Exposes cpuMultiplier as a per-session option and forwards it into TestMachine. |
| src/keyboard.js | Removes multiplier scaling from paste delays (scheduler already runs at peripheral rate). |
| src/fake6502.js | Passes cpuMultiplier into the CPU config for headless/test instances. |
| README.md | Clarifies that cpuMultiplier is relative to peripherals, not whole-machine speed. |
Comments suppressed due to low confidence (1)
tests/test-machine.js:419
- As above for the BBC path, Atom holdCycles can become non-integer for fractional cpuMultiplier values. Ensure the cycle count is integral before using it for runFor()/cycle comparisons.
const holdCycles = 80000 * this.processor.cpuMultiplier; // Atom at 1 MHz needs longer hold than BBC at 2 MHz
Also coerce the scaled key hold to an integer: cpuMultiplier is a float, and the hold feeds runFor() and so execute()'s target cycle count. Addresses review feedback on #725.
#720 added hasTeletextAdaptor plumbing to the same two harness call sites this branch adds cpuMultiplier to. Both are additive; kept both options.
This was referenced Jul 26, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
?cpuMultiplier=is meant to run the CPU faster relative to the peripherals, leaving video, sound and VIA timing at real-world rates. That takes two halves working together:main.jsfeedscpuMultiplier * cpuSpeedcycles per wall-clock second intoexecute().Cpu6502.buildPolltime()dividesvideoCyclesandperipheralCyclesback down bythis.cpuMultiplier, so peripherals still see real time.Half 2 was dead.
emulationConfigcaptured thecpuMultiplierlocal by value before the query string was parsed into it, soconfig.cpuMultiplierwas always 1. Only half 1 applied, and the whole machine sped up, sound pitch and screen refresh included.Archaeology
2538773(2015-10-07) introduced the feature. The parse sat above theemulationConfigliteral, so it worked.4b0b301("Modernise the code - let and const", Modernise the code - let and const #373, 2022-09-01) hoisted the literal from ~line 560 to ~line 152, above the parse, as part of thevar-to-constconversion. Nothing else in that commit touched the feature. That is the regression.076750c(refactor: move per-session settings off the shared Model singletons #714) later added a comment describing the broken state as deliberate. Removed here.README.md's long-standing caveat that "disc loads become unreliable with a too-slow CPU" only makes sense under the 2015 semantics, where the FDC runs at real time while the CPU is slowed.Changes
cpuMultiplierfromparsedQuerywhere it is declared, so the config cannot capture a stale value, and drop the now-false comment.keyboard.js: paste timing no longer multiplies bycpuMultiplier. The paste task lives on the processor's scheduler, which is polled with peripheral cycles, so its delays were already in real time; the multiplier would have stretched paste by the multiplier now that it is live. This path had only ever run at 1.cpuMultiplierthroughfake6502/MachineSession, alongsidetube, and scaleTestMachine.type()'s key hold, which is counted in CPU cycles rather than scheduler ticks.tests/unit/test-6502.jspins the ratio behaviourally: at multiplier 2, 1000 CPU cycles advance the scheduler epoch and video by 500; at 1 the unscaled path passes every cycle straight through.Sanity-checked headless at multiplier 2: the machine boots,
PRINT 6*7gives 42, and BASIC'sTIMEadvances by the same amount for twice as many CPU cycles.Behaviour change
This changes emulation behaviour for anyone currently passing
?cpuMultiplier=, restoring the 2015 semantics: the CPU speeds up relative to the peripherals rather than the whole machine speeding up.Fixes #716
🤖 Generated with Claude Code