DOS turns the drive off at the end of each access. At the start of the next
it reads the data latch eight times, comparing each read with one 14 cycles
later, to tell whether the disk still turns; if it does not seem to, it waits
for the motor to come up to speed, close to a second. With the Disk II card
and a DSK, PO or NIB diskette that wait was paid on every access, and a DOS 3.3
boot took 36 million cycles where the real machine takes five.
Two things made the disk look still:
- The motor stopped as soon as the drive was turned off. The 556 timer of
the controller keeps it turning for about a second, as the sequencer card
already does, and the writable diskette does not move while stopped.
- The diskette gives a nibble on every read, and the 48 0xff sync nibbles of
the gap after a sector, where an access ends, read the same sixteen times
in a row. The latch of a turning disk is never the same nibble twice: it
holds the next one being shifted in, with bit 7 clear. Every other read now
gives that, which programs reading nibbles skip.
DOS 3.3 reaches its banner in 5.4 million cycles, and the System Master loads
Integer BASIC in 4.7 million where it took 59 million, as with the WOZ image
on the sequencer card. Tests pin the boot time and check that a diskette
INIT formats starts the machine.
The mouse the frontends share is little more than a scale, so a2ebiten can have
one now. Ebitengine has no mouse events, the pointer is polled when the frame is
updated, and its position is taken on the picture instead of on the window, so
it lands on the same place whatever the size of the window is. The models with a
mouse, like desktop, are of some use there now.
A pointer outside the window stays on the edge instead of wrapping around, which
Ebitengine reports as soon as the pointer leaves the picture.
NewMixer is now NewFrontMixer, so that it does not look like the NewMixer of the
audio package. The methods that the machine calls are marked as the
implementations of izapple2.MouseProvider and izapple2.JoysticksProvider, and
the note about SDL_WarpMouseInWindow is gone: it came from the SDL2 mouse and
means nothing in a package that knows nothing about SDL.
The frontends were copies of each other. The help was the same text three
times, the chain that picks the screen to show was the same branches three
times, and the SDL2 and SDL3 keyboards, joysticks and mice were the same files
twice with the constants of each toolkit.
frontend/shared holds them now: the view with what is on screen and the help,
the keyboard with the codes of the Apple II keyboard and the commands of the
emulator, the paddles with the four axis and the three buttons, the mouse, and
the mixer with every sound source of the machine already attached. The
frontends keep what belongs to their toolkit: the translation of their keys to
shared.Key, the clipboard, the instance ids of the SDL3 joysticks and the file
system Ebitengine hands the dropped files in. The drop targets take the drag
over the window too, so a frontend only tells which drive the pointer is on.
One implementation aligns what the frontends did differently, following a2sdl
where they disagreed:
- Ctrl with a letter was broken on a2ebiten and a2wasm. The key was already
zero based and 97 was subtracted from it again, so Ctrl-A sent 160 and not 1.
- The libretro core gets Ctrl-Left, the Delete of the Base64A.
- a2sdl3 marks the drive under the pointer when the areas are shown with F8.
- a2sdl finds the apple keys by scancode, so they work on the layouts where the
alt keys produce another symbol, and releases them when the window loses the
focus. Both come from a2sdl3.
a2ebiten keeps Ctrl-F5 for the readout of the speed it draws on the corner of
the screen, the only key that does something else anywhere.
The mouse leaves the pointer where it was on a window without size instead of
dividing by zero, which SDL3 can report when asking for the size fails.
What needs no machine is tested: the codes of the keyboard, the paddles, the
mouse and the drive the drop targets mark.
A file dropped on the window went to drive 1 or drive 2 depending on the
half it landed on. There was no way to know beforehand which drive was
which or what each one had inserted, and no sign afterwards of where the
file went. The machines with two Disk II cards have four drives and only
the first two could be reached.
F8 now shows a screen with one area per removable media drive, each with
the name of its drive and the image it has inserted, or EMPTY, and the
same screen appears for a moment after a drop with the drive that got the
file marked. It is a text screen of 80 columns rendered the same way the
help is, so SnapshotMessageGenerator takes the number of columns now.
The drives register with a label and tell the media they have, so the
frontends ask the machine with GetRemovableMediaDrives instead of assuming
two. The name of the media is written by the emulation goroutine and read
by the frontends, hence the atomic access.
The new frontend/shared package holds what the frontends have in common
and the emulator library has no place for: the composition of the screen
and the mapping from a position to a drive.
a2sdl reads the position of the pointer on the desktop when the file is
dropped. The mouse motion events do not arrive while another application
drags a file, so the last known position was stale and the file could land
on the wrong drive.
a2sdl3 shows the areas while the file is being dragged over the window,
which is something only SDL3 reports.
a2ebiten had no drag and drop at all, although the help it shows said
otherwise. Ebitengine hides the paths of the files dropped, but it opens
the real files and the handle tells the path back, so the images are
loaded like on every other frontend, compressed files included.
The README listed only the SDL2 and the console frontends, and the build
instructions for all of them lived in the README. Now there is a Frontends
section with the eight of them, each linking to a page with how to build it,
how to use it and what it can not do.
The libretro pages take the frontend_ prefix as well. The feature list moves
to doc/features.md, leaving a summary in the README.
The default configuration was wrong in the two places it was written: it
listed a parallel card in slot 1 and a mouse in slot 4, which are empty and a
Mockingboard, and flags -disk35 and -hd that no longer exist. Both lists now
come from -showConfig.
SDL3 resolves the keycode through the current keymap, while SDL2 took it
from the scancode for the non printable keys. On the layouts where the
left alt key is Meta_L instead of Alt_L the keycode is not K_LALT and the
open apple key does nothing.
The open and solid apple keys are a place on the keyboard and not a
symbol, so look at the scancode.
Forget the keys pressed when the window loses the focus. The key up of a
key held while alt-tabbing away goes to whoever took the focus, leaving
the apple key pressed forever.
Reported in https://github.com/ivanizag/izapple2/issues/44
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Calling what works "the 80 column card" had it backwards: the 80 column card is
the Videx, which is one of the videos this core does not render. What it renders
is the 80 column mode of the Apple //e, which is a mode of the machine and not a
card, and lands in the same 560x192 as the rest.
Said in the three places a reader could have taken it the other way, including
the notes of the info file, which is what the frontend shows.
RetroArch turns its game focus on by itself for a core that has a keyboard,
which is what stops its hotkeys from eating the keys meant for the Apple II.
It looks for the callback before loading anything, so the callback has to be
given in retro_set_environment.
Doing that alone was what broke it: the frontend then holds a pointer into Go
and calls it from its own threads while the core is still loading, and the Go
runtime does not survive being entered like that, dying with a morestack on g0.
The callback is given early now, but it is a C function that drops the keys
until shim_set_keyboard_ready opens it, which happens when the machine is built
and is closed again when it goes. Nothing reaches Go before there is something
to type into, and there was nothing to type into anyway.
Verified by firing a hundred key events at the core before any content is
loaded: they are dropped, the process lives, and typing works as before once
the machine is up.
Registering it in retro_set_environment was meant to let RetroArch see that
this is a machine with a keyboard and turn its game focus on by itself. It also
lets the frontend call into Go from its own threads while the core is still
being loaded, and the Go runtime does not survive that: it dies with
runtime: morestack on g0, stack [...], sp=...
running on a stack it has no record of, and the frontend hangs on its main
thread inside the runtime.
It goes back to retro_load_game, where there is a machine to receive the keys.
The game focus of RetroArch has to be set to On rather than Detect, which the
documentation now says.
The frontend asks a core its name and the extensions it takes on its main
thread, on the way to loading it. Every function exported from Go begins by
waiting for the Go runtime to finish starting, and that finishes on the main
thread, so answering from Go there deadlocks the frontend for good: RetroArch
sits in _cgo_wait_runtime_init_done inside libretro_get_system_info and never
comes back, with every sample of the process in the same place.
retro_api_version and retro_get_system_info are in C now, in shim.c. They are
constants, there was never anything to ask Go for.
RetroArch turns its game focus on by itself for the cores that have a keyboard,
which is what stops its hotkeys from eating the keys meant for the Apple II. It
looks for the keyboard callback, and the core was registering it when the
content was loaded, too late to be seen. It goes in retro_set_environment now.
doc/libretro_macos.md takes the Mac from nothing to a running core. It has the
two things that are not guessable:
The RetroArch of Homebrew, the 'retroarch' cask, is an Intel build. Under
Rosetta the Go runtime of the core cannot allocate and dies with a SIGSEGV in
mheap.allocNeedsZero, taking the frontend with it. A core built for both
architectures does not help, the frontend has to run natively, so the cask to
install is retroarch-metal, which is universal.
Game focus comes bound to Scroll Lock, which no Mac keyboard has, so it can be
turned on and never off again. Rebinding it comes first now, and the way out of
that corner is written down.
The RetroArch that Homebrew installs is an Intel build, so on an Apple Silicon
Mac it runs under Rosetta and will not load a native arm64 core, failing with
nothing that says why. 'make universal' builds one dylib with both
architectures inside, which loads in either.
doc/libretro.md gets the steps to go from nothing to a running core on a Mac,
with the checks worth doing on the way, and says to use Game Focus so that the
hotkeys of RetroArch stop eating keys meant for the Apple II.
Runs izapple2 inside RetroArch and the other libretro frontends. It is built
as a C shared library with cgo, so it needs a C toolchain, and it is not part
of the releases.
The frontend owns the frame timing, calling retro_run once per frame, so the
core advances the machine with RunCycles and hands over one video frame and
the matching audio samples on the thread it was called on, with no
goroutines. A frame is 17030 cycles. While a drive asks for fast mode it runs
sixteen frames worth, or loading a disk takes half a minute.
Kept small on purpose: no super hi-res, no Videx video, no RGB card and no
savestates. What is left is generated as 560x192, so the framebuffer is a
fixed 564x192 and the geometry never changes. The cards that produce the
other modes are taken out of the slots.
Diskettes are swapped from the menu through the extended disk control
interface, with the list coming from an M3U playlist, and the saves go to the
save directory of the frontend as overlays.
Go cannot call the function pointers the frontend hands over, so shim.c keeps
them and reaches them through plain C functions, and the callbacks the core
provides go the other way through it. Four entry points take a const pointer,
which cgo cannot generate, so those are wrapped there as well.
The default machine is the new gaming model, an enhanced //e with a
Mockingboard, no mouse and no VidHD, which also serves the command line.
See doc/libretro.md to use it, frontend/a2libretro/README.md for how it works.
The screen modes mixed two independent things. Green, plain and NTSC read
as three points on one scale, but plain and NTSC are the same colour
rendering and differ in whether the gaps between the scan lines are drawn,
while green is a phosphor that happened to always get them.
They are now the four combinations of the two: ScreenModeGreen,
ScreenModeColor, ScreenModeGreenScanlines and ScreenModeColorScanlines,
built from a bit for each property. isColor and hasScanlines read them, so
each place says what it means, and NextScreenMode rotates through the four.
The old modes map onto the new ones exactly, green to green with scan lines,
plain to color, NTSC to color with scan lines, and the frontends are moved
over so nothing they show changes. The scenarios of the tests now cover all
four, and their images are renamed after the modes. The three that already
existed render byte for byte as they did.