@@ -65,6 +65,7 @@ export class Emulator extends EventEmitter {
6565 break ;
6666 case 'frameCompleted' :
6767 // benchmarkRunCount++;
68+ this . advanceAutoType ( ) ;
6869 if ( 'audioBufferLeft' in e . data ) {
6970 this . audioHandler . frameCompleted ( e . data . audioBufferLeft , e . data . audioBufferRight ) ;
7071 }
@@ -86,12 +87,24 @@ export class Emulator extends EventEmitter {
8687 break ;
8788 case 'fileOpened' :
8889 if ( e . data . mediaType == 'tape' && this . autoLoadTapes ) {
89- const TAPE_LOADERS_BY_MACHINE = {
90- '48' : { 'default' : '../tapeloaders/tape_48.szx' , 'usr0' : '../tapeloaders/tape_48.szx' } ,
91- '128' : { 'default' : '../tapeloaders/tape_128.szx' , 'usr0' : '../tapeloaders/tape_128_usr0.szx' } ,
92- '5' : { 'default' : '../tapeloaders/tape_pentagon.szx' , 'usr0' : '../tapeloaders/tape_pentagon_usr0.szx' } ,
93- } ;
94- this . openUrl ( new URL ( TAPE_LOADERS_BY_MACHINE [ this . machineType ] [ this . tapeAutoLoadMode ] , scriptUrl ) ) ;
90+ if ( this . machineType == 48 ) {
91+ /* 48K boots to BASIC, so cold-boot and type LOAD ""
92+ ourselves. The tapeloader snapshots park the machine
93+ in a captured state that breaks some games' loaders
94+ (e.g. report 5 "Out of screen"); a cold boot matches a
95+ real load. */
96+ this . reset ( ) ;
97+ this . beginAutoTypeLoad ( ) ;
98+ } else {
99+ /* 128K/Pentagon boot to a menu where a typed LOAD ""
100+ wouldn't apply, so drive them via the per-machine
101+ tapeloader snapshot. */
102+ const TAPE_LOADERS_BY_MACHINE = {
103+ '128' : { 'default' : '../tapeloaders/tape_128.szx' , 'usr0' : '../tapeloaders/tape_128_usr0.szx' } ,
104+ '5' : { 'default' : '../tapeloaders/tape_pentagon.szx' , 'usr0' : '../tapeloaders/tape_pentagon_usr0.szx' } ,
105+ } ;
106+ this . openUrl ( new URL ( TAPE_LOADERS_BY_MACHINE [ this . machineType ] [ this . tapeAutoLoadMode ] , scriptUrl ) ) ;
107+ }
95108 if ( ! this . tapeTrapsEnabled ) {
96109 this . playTape ( ) ;
97110 }
@@ -214,6 +227,48 @@ export class Emulator extends EventEmitter {
214227 this . worker . postMessage ( { message : 'reset' } ) ;
215228 }
216229
230+ /* Cold-boot autoload for 48K: type LOAD "" + ENTER by injecting the matrix
231+ keypresses. Driven off completed emulator frames (not wall-clock time), so
232+ the keystrokes only advance while the CPU is actually executing - they wait
233+ for the machine to start and the ROM to boot rather than guessing. Tune the
234+ frame counts if the keystrokes land before the ROM is ready or get merged. */
235+ beginAutoTypeLoad ( ) {
236+ const BOOT_FRAMES = 120 ; // ~2.4s of execution for the ROM to reach the input loop
237+ const HOLD_FRAMES = 5 ; // frames each key/chord is held down
238+ const GAP_FRAMES = 5 ; // frames between keystrokes so the ROM debounces
239+
240+ const SYMBOL_SHIFT = { row : 7 , mask : 0x02 } ;
241+ const P = { row : 5 , mask : 0x01 } ;
242+ const J = { row : 6 , mask : 0x08 } ;
243+ const ENTER = { row : 6 , mask : 0x01 } ;
244+
245+ /* LOAD "" : J = the LOAD keyword, " = symbol-shift + P (x2), then ENTER */
246+ const chords = [ [ J ] , [ SYMBOL_SHIFT , P ] , [ SYMBOL_SHIFT , P ] , [ ENTER ] ] ;
247+
248+ const actions = [ ] ;
249+ let frame = BOOT_FRAMES ;
250+ for ( const chord of chords ) {
251+ actions . push ( { frame, message : 'keyDown' , keys : chord } ) ;
252+ frame += HOLD_FRAMES ;
253+ actions . push ( { frame, message : 'keyUp' , keys : chord } ) ;
254+ frame += GAP_FRAMES ;
255+ }
256+ this . autoTypeActions = actions ;
257+ this . autoTypeFrame = 0 ;
258+ }
259+
260+ advanceAutoType ( ) {
261+ if ( ! this . autoTypeActions ) return ;
262+ this . autoTypeFrame ++ ;
263+ while ( this . autoTypeActions . length && this . autoTypeActions [ 0 ] . frame <= this . autoTypeFrame ) {
264+ const action = this . autoTypeActions . shift ( ) ;
265+ for ( const k of action . keys ) {
266+ this . worker . postMessage ( { message : action . message , row : k . row , mask : k . mask } ) ;
267+ }
268+ }
269+ if ( ! this . autoTypeActions . length ) this . autoTypeActions = null ;
270+ }
271+
217272 loadSnapshot ( snapshot ) {
218273 const fileID = this . nextFileOpenID ++ ;
219274 this . worker . postMessage ( {
0 commit comments