Before, the `DiskII` object took care of both nibble disks and WOZ
disks at the same time. This made it hard to see which code affected
which disks.
Now, nibble disks are handled by the `NibbleDiskDriver` and WOZ disks
are handled by the `WozDiskDriver`. This separation of code should
lead to easeir testing and, perhaps, faster evolution of disk
handling.
An upcoming change will move the `NibbleDiskDriver` and the
`WozDiskDriver` to separate files.
This passes all tests, compiles, and runs both nibble disks and WOZ
disks.
Before, `getBinary` did not work for 13 sector disks because it
assumed that all tracks had 16 sectors. On top of that, `readSector`
could not decode sectors with 5 and 3 encoding. And finally,
`findSector` couldn't even find sectors with DOS 3.2 address fields.
All of these have been fixed and some tests added to make sure that
they keep working.
* Harmonize drive and disk type hierarchies
Before, the `XXXDrive` and `XXXDisk` type hierarchies were similar,
but not exactly the same. For example, `encoding` and `format` were
missing on some `XXXDisk` types where they existed on the `XXXDrive`
type. This change attempts to bring the hierarchies closer together.
However, the biggest visible consequence is the introduction of the
`FLOPPY_FORMATS` array and its associated `FloppyFormat` type. This
replaces `NIBBLE_FORMATS` in most places. A couple of new type guards
for disk formats and disks have been added as well.
All tests pass, everything compiles with no errors, and both WOZ and
nibble format disks load in the emulator.
* Move disk data to a `disk` field in the drive
Before, disk data was mixed in with state about the drive itself (like
track, motor phase, etc.). This made it hard to know exactly what data
was necessary for different image formats.
Now, the disk data is in a `disk` field whose type depends on the
drive type. This makes responisbility a bit easier.
One oddity, though, is that the `Drive` has metadata _and_ the `Disk`
has metadata. When a disk is in the drive, these should be `===`, but
when there is no disk in the drive, obviously only the drive metadata
is set.
All tests pass, everything compiles, and both WOZ and nibble disks
work in the emulator (both preact and classic).
* Squash the `Drive` type hierarchy
Before, the type of the drive depended on the type of the disk in the
drive. Thus, `NibbleDrive` contained a `NibbleDisk` and a `WozDrive`
contained a `WozDisk`. With the extraction of the disk data to a
single field, this type hierarchy makes no sense. Instead, it
suffices to check the type of the disk.
This change removes the `NibbleDrive` and `WozDrive` types and type
guards, checking the disk type where necessary. This change also
introduces the `NoFloppyDisk` type to represent the lack of a
disk. This allows the drive to have metadata, for one.
All tests pass, everything compiles, and both WOZ and nibble disks
work locally.
* Use more destructuring assignment
Now, more places use constructs like:
```TypeScript
const { metadata, readOnly, track, head, phase, dirty } = drive;
return {
disk: getDiskState(drive.disk),
metadata: {...metadata},
readOnly,
track,
head,
phase,
dirty,
};
```
* Remove the `Disk` object from the `Drive` object
This change splits out the disk objects into a record parallel to the
drive objects. The idea is that the `Drive` structure becomes a
representation of the state of the drive that is separate from the
disk image actually in the drive. This helps in an upcoming
refactoring.
This also changes the default empty disks to be writable. While odd,
the write protect switch should be in the "off" position since there
is no disk pressing on it.
Finally, `insertDisk` now resets the head position to 0 since there is
no way of preserving the head position across disks. (Even in the real
world, the motor-off delay plus spindle spin-down would make it
impossible to know the disk head position with any accuracy.)
* Add `DiskMetada` to the `Disk` interface
Before, metadata about the image, such as name, side, etc. was mixed
in with actual disk image information. This change breaks that
information into a separate structure called `DiskMetadata`.
Currently, the only two fields are `name` and `side`, but the idea is
that more fields could be added as necessary, like a description, a
scan of the disk or label, etc. In a follow-on change, the default
write-protection status will come from the metadata as well.
The current implementation copies the metadata when saving/restoring
state, loading disk images, etc. In the future, the metadata should
passed around until the format is required to change (like saving one
disk image format as another). Likewise, in the future, in may be
desirable to be able to override the disk image metadata with
user-supplied metadata. This could be use, for example, to
temporarily add or remove write-protection from a disk image.
All existing tests pass and the emulator builds with no errors.
* Rename `writeMode` to `q7`
Before, nibble disk emulation used the `writeMode` field to keep track
of whether the drive should be read from or written to, but the WOZ
emulation used `q7` to keep track of the same state.
This change renames `writeMode` to `q7` because it more accurately
reflects the state of the Disk II controller as specified in the
manuals, DOS source, and, especially, _Understanding the Apple //e_ by
Jim Sather.
* Remove the coil state
Before, `q` captured the state of the coils. But it was never read.
This change just deletes it.
* Use the bootstrap and sequencer ROMs with indirection
Before, the contents of the bootstrap ROM and sequencer ROM were set
directly on fields of the controller. These were not saved or
restored with the state in `getState` and `setState`. (It would have
been very space inefficient if they had).
Now, these ROMs are used from constants indexed by the number of
sectors the card supports. This, in turn, means that if the number of
sectors is saved with the state, it can be easily restored.
* Split out the Disk II controller state
This change factors the emulated hardware state into a separate
structure in the Disk II controller. The idea is that this hardware
state will be able to be shared with the WOZ and nibble disk code
instead of sharing _all_ of the controller state (like callbacks and
so forth).
* Factor out disk insertion
Before, several places in the code essentially inserted a new disk
image into the drive, which similar—but not always exactly the
same—code. Now there is an `insertDisk` method that is responsible
for inserting a new `FloppyDisk`.
All tests pass, everything compiles, manually tested nibble disks and
WOZ disks.
Before, the `drives` field was an `array[0..1]` of `Drive`, but all of
the methods took a `DriveNumber`, which was `[1..2]`. This meant that
code everywhere was always subtracting 1 from the drive number.
Now, `drives` is a `Record<DriveNumber, Drive>`, which means tha it
has indexes `1, 2` and there's no need to subtract 1 everywhere.
This change updates the `DiskII` class and its tests.
The motivation for this change is to slowly split the WOZ disk
implementation from the nibble disk implementation. I've tried twice,
but the change has always grown too big and hairy, so I'm starting
very small this time and working my way up.
* Add tests for Applesoft compiler in preparation for refactoring
While refactoring the compiler, I found several small bugs:
* Lower-case letters in strings and REM statements were converted
to upper-case.
* Lines are stored in the order received, not sorted by line number.
* Does not prefer `ATN` to `AT`.
* Does not prefer `TO` to `AT`.
* `DATA` statements don't preserve spaces.
* `DATA` statements don't preserve lowercase.
These will be fixed in the upcoming refactoring.
* Refactor the Applesoft Compiler
Before, the compiler had a few bugs that were not trivial to solve
because the implementation was in one heavily-nested function.
In this refactoring of the compiler, things like tokenization have
been split into separate methods which makes them a bit easier to
understand.
This refactoring also passes all of the tests.
* Set `PRGEND` when compiling to memory
Before, `PRGEND` was not adjusted which made round-tripping from
the Applesoft compiler to the decompiler not work. This change
now updates `PRGEND` with the end-of-program + 2 bytes which seems
to be the most frequent value that I have observed.
* Fix two compiler bugs
In debugging the decompiler, I noticed two bugs in the compiler:
* The first character after a line number was skipped.
* `?` was not accepted as a shortcut for `PRINT`.
This change fixes these two problems and adds tests.
* Ignore spaces more aggressively
It turns out that Applesoft happily accepts 'T H E N' for `THEN`
but the parser did not. This change fixes that and adds tests for
some odd cases.
Interestingly, this means that there are some valid statements
that Applesoft can never parse correctly because it is greedy
and ignores (most) spaces. For example, `NOT RACE` will always
parse as `NOTRACE` even though `NOT RACE` is a valid expression.
* Move tokens into a separate file
Because the token lists are just maps in opposite directions, put
them in the same file. In the future, maybe we can build one
automatically.
* Fix `apple2.ts`
I had neglected to actually update `apple2.ts` to use the new
compiler and decompiler. They now do.
Also, the decompiler can be created from `Memory`. It assumes,
though, that the zero page pointers to the start and end of the
program are correct.
* Address comments
* No more `as const` for tokens.
* Extracted zero page constants to their own file.
Co-authored-by: Will Scullin <scullin@scullin.com>
* Add a basic write test for WOZ images
The new test just tries to change some random nibbles at the beginning
of the image and then verifies that the change has been recorded.
This exposed a bug where `q7` was never set to `true` when write mode
was toggled on.
Also, the assumptions and limitations of `moveHead` are more clearly
documented.
* Address comments
* Improved `moveHead` documentation a bit more.
* Removed redundant variable in `readNibble`.
* Refactored `findSector` and commented out the chatty log line.
All tests pass. No lint warnings.
* Add a test for the dirty callback on writes
This new test just checks that a clean disk becomes dirty after a
write _and_ that the dirty callback is fired.
* Add tests for WOZ disks
The new tests verify the basic read behavior of the state sequencer on
well-behaved disks, including sync bytes and so on. Write tests are
still to come.
There's also a change to the Woz format to return the info chunk data
as well.
* Add tests for the DiskII card
This change adds basic read tests for nibble-based disks for the
DiskII card and fixes a few minor errors.
These tests are in preparation for refactoring.
* Add write tests
These are some basic tests of writing to nibble disks. In the process,
one minor bug was found, fixed and documented.
* Fix the write tests
I misinterpreted something from Sather and thought that the high bit
had to be set on the data for writing to happen at all. This is not
true. Instead, there is a flux transition every time the high bit is
set as the data is left-shifted out of the data register. The
erroneous test has been removed.
At the same time, I finally understand what `skip` does and documented
that.
* Add tests for saving and restoring Disk II state
These are not exhaustive tests, but they ensure that some basic state
is saved and restored.
* Add `spawn` as a way of calling promise-returning blocks
This change adds `spawn` which takes a no-argument, promise-returning
function, calls it, and returns `void`. This makes it easy to call
async blocks from `useEffect` and other places that don't take async
functions, but also makes such calls explicit.
* Adds interruptability to `spawn`
Now, the task function passed to `spawn` can take an `Interrupted`
argument, which is merely a method that returns `true` if the task
should stop doing work. Likewise, `spawn` returns an `Interrupt`
function that causes the `Interrupted` function to return `true`.
* Change to using `AbortController` and `AbortSignal`
Before, `spawn` used functions to interrupt and determine interruption
state. Now, based on feedback from @whscullin, it uses
`AbortController` and `AbortSignal`.
Tests now show how the controller can be used to abort long-running
tasks and API calls in the `spawn`. The also show how signals can be
chained using `addEventListener`.
* Fix `Apple2.tsx`
Forgot to change it to use `AbortController` and `AbortSignal`.
Co-authored-by: Will Scullin <scullin@scullin.com>
Before, the offset for `FLAGS` in `2mg.ts` was `0x0A`, which is
incorrect according to the spec at:
https://apple2.org.za/gswv/a2zine/Docs/DiskImage_2MG_Info.txt
Now, all of the fields in the 2mg header are described, including
their lengths and any constraints. These constraints are enforced by
`read2MGHeader` and tested by new tests.
This removes the `FileSystemFileHandleLike` interface in preference to
just implementing the correct interface. The advantage of the
`FileSystemFileHandle` interface is that it can be passed to the
worker directly to load the file.
This adds both the recommended TypeScript checks, plus the recommended
TypeScript checks that require type checking. This latter addition
means that eslint essentially has to compile all of the TypeScript in
the project, causing it to be slower. This isn't much of a problem in
VS Code because there's a lot of caching being done, but it's clearly
slower when run on the commandline.
All of the errors are either fixed or suppressed. Some errors are
suppressed because fixing them would be too laborious for the little
value gained.
The eslint config is also slightly refactored to separate the strictly
TypeScript checks from the JavaScript checks.
* Create a FileChooser component using showOpenFilePicker
Before, `FileModal` always used a file input control for selecting
local files. This allowed the emulator to read from the file, but
precluded writing back to the file.
With this change, the `FileModal` delegates to the new `FileChooser`
component. The `FileChooser` will use `showOpenFilePicker` if it is
available and a regular file input if it's not.
Using `showOpenFilePicker` has the advantage of allowing the emulator
to write back to the file (if the user grants permission). While the
emulator does not yet take advantage of this write capability, that
will come.
* Addressed comments
* useState() instead of direct DOM manipulation
* backed out eslint changes in favor of suppressing the warning
A test data set was published at https://github.com/TomHarte/ProcessorTests which contain cycle traces of instructions for various versions of the 6502.
This adds a test harness that reads those data files, and adjusts the CPU6502 behavior to match the behavior of the vanilla and WDC 65C02 test data.
Also converts the existing CPU tests to Typescript, and fixes any inconsistencies that came up from the new behaviors.
Disk side information was being dropped and thus not displayable in the UI. This plumbs the value through various formats and adds some light testing.
Also fixes an issue where URL encoded hashes were not properly interpreted.
* Refactor disk handling to allow disk processing to happen in a worker
* Type cleanup
* Convert format handlers to TypeScript
* Convert CFFA to TypeScript
* Switch modules to `esnext` to allow `webpack` to see import statements
* Pass rom names into Apple2 class
* Move ROMs into `system` and `character` directories to allow webpack bundle appropriate ROMs.
* Wait for ROMs to load before completing initialization.
Stop stringifying opcodes during runtime and only do so upon inspection. Moves all the debugging logic to a common place to allow building an interface.
Like the DOS 3.3 and ProDOS sector order issues, this change fixes the
physical order of the sectors on 13-sector disks when nibblized.
This change also adds tests for the 13-sector format to verify the
sector order.
One of the crazy things is that _Beneath Apple DOS_ failed me in this
instance because it doesn't discuss what happens to the last byte in
"5 and 3" encoding anywhere (AFAICT). I went back to the DOS 3.1
source released by the Computer History Museum here:
https://computerhistory.org/blog/apple-ii-dos-source-code/
The code is in `appdos31.lst` in the `POSTNIB` routine on line 4777.
Like the DOS 3.3 sector order issues in #49, this change fixes the
order of the physical sectors on the ProDOS disk when nibblized.
This change also adds tests similar to the DOS 3.3 tests to verify the
sector order.
Because the DOS 3.3 and ProDOS tests are so similar, the utility
methods have been refactored into their own file.
* Adds an initial test for DOS format (.do) files
* Fix physical sector order when nibblizing DOS 3.3 ordered images
Before, when `.dsk` or `.do` images were nibblized, the resulting
track had the sectors in the wrong physical layout.
Now the nibblized track has the correct physical layout (all sectors
in order) which results in the correct DOS 3.3 layout as well.
There is also a test that verifies the order.
* Add another test for a non-zero sector
The new test checks that the values in physical sector 1 are those for
DOS sector 7.
* Add test for all physical sectors on all tracks
This change also removes a few stray console.log calls in the test.
* Convert js/ram to a class
* Convert js/mmu to Typescript
* Convert js/apple2io to Typescript
* Convert js/canvas to Typescript
* Use new types in js/mmu
* Rename js/symbols.js to js/symbols.ts
* Remove the difference between readPages and writePages
As @whscullin said in PR #38, there's no need to have both readable
and writable pages since all implementations are currently both. This
change combines them into `Page`. Likewise, `PageHandler` now extends
`Page`.
`Apple2IO` now implements `PageHandler`. This caught a bug where `end`
had been renamed `endend` by mistake.
There are a few other formatting changes as well.
* Convert js/apple2 to Typescript
* Convert js/prefs to Typescript
* Convert all of the ROMs in js/roms to Typescript
Now all of the ROMs are classes that extend the ROM class. There is
some rudamentary checking to make sure that the length of the ROM
matches the declared start and end pages. (This caught what looks to
be an error in roms/apple2e, but it's hard for me to tell.)
The typing also caught an error where the character ROM was being
used for the main ROM for the apple2j version.
* Convert js/roms/cards/* to Typescript
* Convert js/formats/format_utils to Typescript
This change also seems to fix a bug with `.po` image files that
weren't being read correctly.