@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 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
- # Temporary Holding Version
1
+ # @fugloy/emu
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
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
  }