@fugloy/emu 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/NOTICE.md +27 -0
- package/README.md +119 -2
- package/package.json +30 -3
- package/src/cpu/z80cpu.js +3343 -0
- package/src/debug/debugger.js +777 -0
- package/src/debug/instr.js +90 -0
- package/src/debug/label-file.js +423 -0
- package/src/debug/state.js +214 -0
- package/src/devices/cassette-tape.js +254 -0
- package/src/devices/cassette.js +259 -0
- package/src/devices/disk-image.js +578 -0
- package/src/devices/fdc-wd1793.js +466 -0
- package/src/devices/key-queue.js +261 -0
- package/src/devices/keyboard.js +176 -0
- package/src/devices/printer.js +44 -0
- package/src/devices/sound.js +53 -0
- package/src/devices/video.js +66 -0
- package/src/index.js +22 -0
- package/src/loaders/bas.js +107 -0
- package/src/loaders/cas-format.js +213 -0
- package/src/loaders/cmd-format.js +88 -0
- package/src/loaders/handoff.js +29 -0
- package/src/loaders/load.js +135 -0
- package/src/machine/io.js +290 -0
- package/src/machine/machine.js +295 -0
- package/src/machine/memory.js +207 -0
- package/src/machine/rom-hashes.js +14 -0
- package/src/machine/sha256.js +74 -0
- package/src/machine/trs80-system.js +232 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Christopher Schweda
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/NOTICE.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Notice
|
|
2
|
+
|
|
3
|
+
The files under `src/`, all but `src/index.js`, come from [cschweda/trs80-emulator](https://github.com/cschweda/trs80-emulator) at commit 11612849c7e4dbbe76a45b38116486223fb05a9a (v1.6.0), moved into this layout in emu phase 0, under this license:
|
|
4
|
+
|
|
5
|
+
```text
|
|
6
|
+
MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2024 TRS-80 Model III Emulator Contributors
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
```
|
package/README.md
CHANGED
|
@@ -1,3 +1,120 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @fugloy/emu
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Headless, deterministic TRS-80 emulator core (Model III; Model I and 4 to come) in plain
|
|
4
|
+
JavaScript, for Node and the browser alike. Emu phase 0 brought in the core of
|
|
5
|
+
[cschweda/trs80-emulator](https://github.com/cschweda/trs80-emulator) (v1.6.0):
|
|
6
|
+
the Z80 CPU (`src/cpu/`), the Model III memory map, I/O ports, and system
|
|
7
|
+
(`src/machine/`), the keyboard matrix, cassette, WD1793 disk controller, disk
|
|
8
|
+
images, and printer (`src/devices/`), the `.CAS` and `.CMD` formats
|
|
9
|
+
(`src/loaders/`), and save states (`src/debug/`). Model III only for now.
|
|
10
|
+
Phases 0–4 of the spec's eight are done: the Z80 core passes ZEXDOC and
|
|
11
|
+
ZEXALL, the `Machine` below runs real programs, `m.debug` writes the label
|
|
12
|
+
and coverage file, and phase 4, the MCP server
|
|
13
|
+
[`@fugloy/mcp`](https://github.com/cschweda/fugloy/blob/main/packages/mcp/README.md),
|
|
14
|
+
drives it for an AI assistant. Version 0.2.0, its first npm release pending
|
|
15
|
+
(once published, `npm i @fugloy/emu`; until then, use it from a clone; Node 22 or later, or any browser bundler: it has no
|
|
16
|
+
dependencies).
|
|
17
|
+
|
|
18
|
+
The renderer, the WebAudio driver, the turbo throttle, the web UI, and every
|
|
19
|
+
ROM, disk, and program file stayed upstream: rendering and sound belong to the
|
|
20
|
+
workbench, and the caller supplies ROM bytes. Provenance and license:
|
|
21
|
+
[`NOTICE.md`](https://github.com/cschweda/fugloy/blob/main/packages/emu/NOTICE.md).
|
|
22
|
+
|
|
23
|
+
The core never writes to the console. Pass a logger to see what it reports,
|
|
24
|
+
such as an unimplemented opcode or a tape load:
|
|
25
|
+
`new TRS80System({ romData, log: (level, text) => console[level === 'info' ? 'log' : level](text) })`.
|
|
26
|
+
Without one it stays silent, unlike upstream, which printed warnings;
|
|
27
|
+
`system.cpu.unimplementedOpcodes` still records every opcode it hit and does
|
|
28
|
+
not implement.
|
|
29
|
+
|
|
30
|
+
## Using the machine
|
|
31
|
+
|
|
32
|
+
```js
|
|
33
|
+
import { readFileSync } from 'node:fs'
|
|
34
|
+
import { Machine, CPU_CLOCK_HZ } from '@fugloy/emu'
|
|
35
|
+
|
|
36
|
+
const m = new Machine({ roms: { level2: readFileSync('model3.rom') } }) // Model III, 48K; you supply the ROM
|
|
37
|
+
const shows = (text) => (mm) => mm.video.text().join('\n').includes(text)
|
|
38
|
+
const second = CPU_CLOCK_HZ // T-states per emulated second
|
|
39
|
+
|
|
40
|
+
m.run({ until: shows('Cass?'), cycles: 5 * second }) // boot; stops at the prompt, or after 5 s
|
|
41
|
+
m.keyboard.type('\n') // ENTER: 1500 baud
|
|
42
|
+
m.run({ until: shows('Memory Size?'), cycles: 5 * second })
|
|
43
|
+
m.keyboard.type('\n') // ENTER at Memory Size?
|
|
44
|
+
m.run({ until: shows('READY'), cycles: 5 * second })
|
|
45
|
+
|
|
46
|
+
m.load.cmd(readFileSync('seadrg01.cmd')) // PC at the program's entry
|
|
47
|
+
m.run({ cycles: 5 * second })
|
|
48
|
+
console.log(m.video.text().join('\n')) // the screen, 16 lines
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`step`, `run`, the registers, memory, the keyboard (matrix typing and a ROM
|
|
52
|
+
keyboard hook), sound events, and the three loaders are described in spec §3.
|
|
53
|
+
|
|
54
|
+
## Debugging
|
|
55
|
+
|
|
56
|
+
`m.debug` stops on breakpoints and watches, logs memory hits, records which
|
|
57
|
+
bytes ran, were read, written, jumped to, and called, keeps a bounded trace,
|
|
58
|
+
takes snapshots, and writes the label and coverage file that `@fugloy/disasm`
|
|
59
|
+
reads (`fugloy seadrg01.cmd --labels seadrg01.labels.json`). Continuing the
|
|
60
|
+
example above, just after `m.load.cmd`:
|
|
61
|
+
|
|
62
|
+
```js
|
|
63
|
+
import { writeFileSync } from 'node:fs'
|
|
64
|
+
|
|
65
|
+
m.debug.break(0x497f, { once: true }) // Sea Dragon's stub copies the game down, then jumps here
|
|
66
|
+
m.run({ cycles: second }) // → { reason: 'break', id: 1, cycles }
|
|
67
|
+
m.debug.coverage.enable() // from here on: exec, operand, read, write, jump, call
|
|
68
|
+
m.debug.trace.enable({ depth: 10_000 }) // the last 10,000 instructions
|
|
69
|
+
const top = m.debug.watch(0x3c00, 64, { write: true }) // log every write to the screen's top line
|
|
70
|
+
m.run({ cycles: 5 * second })
|
|
71
|
+
m.debug.hits(top) // [{ id: 2, kind: 'write', cycle, pc, addr, value, prev }, …]
|
|
72
|
+
m.debug.callers(0x6212) // [{ from: 18974, kind: 'call', count: 80 }, …]
|
|
73
|
+
m.debug.label(0x6212, 'TONE') // a user label: the listing names the routine TONE
|
|
74
|
+
writeFileSync('seadrg01.labels.json', JSON.stringify(m.debug.labelFile({ name: 'seadrg01.cmd' }), null, 2))
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Coverage can start at the load: the label file's `write` field marks every
|
|
78
|
+
byte a self-relocating program's copy writes, and every loaded byte that
|
|
79
|
+
differs from what the load placed, so the disassembler neither mistakes moved
|
|
80
|
+
code for data nor applies the moved code's coverage to the file's bytes (a
|
|
81
|
+
relocate hint says where it runs).
|
|
82
|
+
Everything on `m.debug` is described in spec §3 and §7, the file in §8 and
|
|
83
|
+
[`docs/label-format.md`](https://github.com/cschweda/fugloy/blob/main/docs/label-format.md).
|
|
84
|
+
`examples/sea-dragon/seadrg01.labels.json` is the file of the scripted session
|
|
85
|
+
in `test/integration/sea-dragon-session.js`; `node
|
|
86
|
+
packages/emu/scripts/sea-dragon-labels.mjs` rewrites it (it needs the ROM).
|
|
87
|
+
|
|
88
|
+
Debugging off costs nothing measurable, and the debugger keeps the §4 target.
|
|
89
|
+
On Sea Dragon, 20 emulated seconds of its title screen
|
|
90
|
+
(`node packages/emu/scripts/benchmark.mjs off|debug`, each mode in its own
|
|
91
|
+
process, on the laptop that built phase 3):
|
|
92
|
+
|
|
93
|
+
| Mode | Speed |
|
|
94
|
+
|---|---|
|
|
95
|
+
| Debugging off | about 42× real time (41.5–43.3× over three runs; phase 2 measured about 43×) |
|
|
96
|
+
| Coverage and a 10,000-deep trace | about 30× real time (29.5–30.7×) |
|
|
97
|
+
|
|
98
|
+
`test/integration/benchmark.test.js` asserts at least 20× for both.
|
|
99
|
+
|
|
100
|
+
- Spec: [`docs/fugloy-emu-spec.md`](https://github.com/cschweda/fugloy/blob/main/docs/fugloy-emu-spec.md) (phases 0–7 in §11)
|
|
101
|
+
- Label and coverage file shared with `mcp` and `disasm`: [`docs/label-format.md`](https://github.com/cschweda/fugloy/blob/main/docs/label-format.md)
|
|
102
|
+
- ROM images are never bundled or committed; `roms/` here is gitignored and
|
|
103
|
+
paths are supplied by the caller.
|
|
104
|
+
|
|
105
|
+
## Testing the Z80 core
|
|
106
|
+
|
|
107
|
+
ZEXDOC and ZEXALL, Frank Cringle's Z80 instruction exercisers, are GPL-2.0, so
|
|
108
|
+
they are never committed. `pnpm zex:install` downloads them into the
|
|
109
|
+
gitignored `tools/zex/`, pinned to one commit of
|
|
110
|
+
[agn453/ZEXALL](https://github.com/agn453/ZEXALL) and checked by SHA-256. Then:
|
|
111
|
+
|
|
112
|
+
```sh
|
|
113
|
+
FUGLOY_ZEX=1 pnpm vitest run packages/emu/test/exerciser # about 5 minutes, one test per group
|
|
114
|
+
FUGLOY_ZEX=1 pnpm vitest run packages/emu/test/exerciser -t "daa,cpl" # one group
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
They run on `test/cpm.js`, a CP/M stand-in: the program at `0100H`, BDOS
|
|
118
|
+
functions 2 and 9 at `0005H`, and a jump to `0000H` to end.
|
|
119
|
+
`test/unit/cpu-timing.test.js` checks every opcode's T-states against
|
|
120
|
+
`@fugloy/z80`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fugloy/emu",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Headless, deterministic TRS-80 emulator core, Model III today (see docs/fugloy-emu-spec.md)",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"src",
|
|
12
|
+
"NOTICE.md"
|
|
13
|
+
],
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=22"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@fugloy/disasm": "1.2.0",
|
|
19
|
+
"@fugloy/z80": "1.2.0"
|
|
20
|
+
},
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/cschweda/fugloy.git",
|
|
24
|
+
"directory": "packages/emu"
|
|
25
|
+
},
|
|
26
|
+
"homepage": "https://github.com/cschweda/fugloy#readme",
|
|
27
|
+
"bugs": {
|
|
28
|
+
"url": "https://github.com/cschweda/fugloy/issues"
|
|
29
|
+
},
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public"
|
|
32
|
+
}
|
|
6
33
|
}
|