@frotzinc/zmachine 0.0.0-stage → 0.1.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.
@@ -0,0 +1,246 @@
1
+ /**
2
+ * The interpreter's public types (docs/11-frotzplay-phase-1-interpreter.md §2). Everything in
3
+ * MachineState is plain data, so it can be copied, posted to a worker, and saved.
4
+ */
5
+ /** What run() stopped for. */
6
+ export type StopReason = {
7
+ kind: 'input-line';
8
+ maxLength: number;
9
+ timeout?: Timeout;
10
+ preloaded: string;
11
+ } | {
12
+ kind: 'input-char';
13
+ timeout?: Timeout;
14
+ }
15
+ /** The story is saving: keep `data` (a Quetzal file), then resume({ saved }). */
16
+ | {
17
+ kind: 'save';
18
+ data: Uint8Array;
19
+ }
20
+ /** The story wants a saved game: resume({ restore: bytes }), or { restore: null } to cancel. */
21
+ | {
22
+ kind: 'restore';
23
+ }
24
+ /** The instruction budget is spent; call run() again. */
25
+ | {
26
+ kind: 'budget';
27
+ } | {
28
+ kind: 'quit';
29
+ } | {
30
+ kind: 'fault';
31
+ fault: Fault;
32
+ };
33
+ /** A timed read's interval (in tenths of a second) and its interrupt routine (packed). */
34
+ export interface Timeout {
35
+ tenths: number;
36
+ routine: number;
37
+ }
38
+ export type FaultCode = 'illegal-opcode' | 'bad-address' | 'stack-underflow' | 'stack-overflow' | 'bad-variable' | 'write-to-static' | 'division-by-zero' | 'bad-object' | 'bad-property' | 'bad-story' | 'not-waiting'
39
+ /** A save file that can't be restored here (another story's, or damaged). The game goes on: the restore fails. */
40
+ | 'bad-save';
41
+ /** Something the story did that the Standard doesn't allow, or a story that can't run. Never thrown past the package. */
42
+ export interface Fault {
43
+ code: FaultCode;
44
+ message: string;
45
+ /** The instruction that faulted (0 when there is none). */
46
+ pc: number;
47
+ }
48
+ export interface Frame {
49
+ returnPC: number;
50
+ /** Up to 15, each 16-bit unsigned. */
51
+ locals: number[];
52
+ evalStack: number[];
53
+ /** The variable the result goes to; null when it's thrown away (call_*n) or for the main routine. */
54
+ storeVariable: number | null;
55
+ /** How many arguments the caller gave (@check_arg_count). */
56
+ argCount: number;
57
+ /** A timed-input interrupt routine's frame: its result decides whether the read ends. */
58
+ interrupt?: boolean;
59
+ }
60
+ export interface RandomState {
61
+ /**
62
+ * 'session': draws from the session's own stream (mulberry32 from the seed), where ZVM would use
63
+ * Math.random. 'xorshift': ZVM's xorshift, after the game seeds it with @random -n.
64
+ */
65
+ mode: 'session' | 'xorshift';
66
+ /** mulberry32's state. */
67
+ session: number;
68
+ /** ZVM's xorshift_seed (signed 32-bit). */
69
+ xorshift: number;
70
+ }
71
+ /** @set_text_style's bits: 1 reverse, 2 bold, 4 italic, 8 fixed-pitch. */
72
+ export type TextStyle = number;
73
+ export interface StyledRun {
74
+ text: string;
75
+ style: TextStyle;
76
+ fg: number;
77
+ bg: number;
78
+ font: number;
79
+ }
80
+ export interface Cell {
81
+ ch: string;
82
+ style: TextStyle;
83
+ fg: number;
84
+ bg: number;
85
+ }
86
+ export interface ScreenModel {
87
+ /** In characters: what the header says. */
88
+ width: number;
89
+ height: number;
90
+ lower: {
91
+ /** Everything printed to the lower window since the story started (or the last erase), in styled runs. */
92
+ runs: StyledRun[];
93
+ bufferMode: boolean;
94
+ };
95
+ upper: {
96
+ rows: number;
97
+ grid: Cell[][];
98
+ /** 0-based. */
99
+ cursor: {
100
+ row: number;
101
+ col: number;
102
+ };
103
+ };
104
+ window: 0 | 1;
105
+ style: TextStyle;
106
+ colours: {
107
+ fg: number;
108
+ bg: number;
109
+ };
110
+ font: number;
111
+ /** v3: what the status line says, for the announcer (frotzplay-design §6). */
112
+ statusLine?: StatusLine;
113
+ /** @sound_effect 1 and 2 (beeps) since the host last cleared this. */
114
+ beeps: number[];
115
+ }
116
+ export interface StatusLine {
117
+ location: string;
118
+ score?: number;
119
+ moves?: number;
120
+ hours?: number;
121
+ minutes?: number;
122
+ }
123
+ export interface StreamState {
124
+ /** Stream 1 (the screen), 2 (transcript, the header bit mirrors it) and 4 (the command script). */
125
+ screen: boolean;
126
+ transcript: boolean;
127
+ commands: boolean;
128
+ /** Stream 3: the nested tables (§7.1.2.1.1: up to 16), innermost last. */
129
+ memory: {
130
+ table: number;
131
+ count: number;
132
+ }[];
133
+ }
134
+ /** A read, save or restore the machine is waiting on, so resume() can finish it. */
135
+ export interface PendingInput {
136
+ kind: 'line' | 'char' | 'save' | 'restore';
137
+ /** save/restore: the address of the instruction's store byte (v4+) or branch byte (v3), Quetzal's PC. */
138
+ at?: number;
139
+ /** The read's store variable (aread, read_char), or null (sread). */
140
+ store: number | null;
141
+ textBuffer: number;
142
+ parseBuffer: number;
143
+ timeout?: Timeout;
144
+ }
145
+ export interface InputState {
146
+ pending: PendingInput | null;
147
+ }
148
+ /** One save_undo's state. */
149
+ export interface Snapshot {
150
+ dynamic: Uint8Array;
151
+ frames: Frame[];
152
+ pc: number;
153
+ random: RandomState;
154
+ /** The save_undo's store variable, which receives 2 when this is restored. */
155
+ store: number | null;
156
+ }
157
+ export interface MachineState {
158
+ /** The whole story; dynamic memory is live. Static and high memory are never written (§1.1). */
159
+ memory: Uint8Array;
160
+ pc: number;
161
+ frames: Frame[];
162
+ random: RandomState;
163
+ screen: ScreenModel;
164
+ input: InputState;
165
+ streams: StreamState;
166
+ counters: {
167
+ instructions: number;
168
+ turns: number;
169
+ };
170
+ undo: Snapshot[];
171
+ }
172
+ export interface Hooks {
173
+ write?(address: number, value: number, size: 1 | 2): void;
174
+ call?(routine: number, args: number[], frame: Frame): void;
175
+ return?(value: number, frame: Frame): void;
176
+ branch?(at: number, taken: boolean): void;
177
+ print?(text: string, window: 0 | 1): void;
178
+ input?(kind: 'line' | 'char', value: string | number): void;
179
+ }
180
+ /**
181
+ * What the interpreter writes into the header (§11). 'standard': what frotzplay is (Standard 1.1,
182
+ * timed input, colour). 'zvm': exactly what ZVM writes, for the differential tests.
183
+ */
184
+ export type HeaderProfile = 'standard' | 'zvm';
185
+ export interface MachineOptions {
186
+ seed: number;
187
+ header?: {
188
+ profile?: HeaderProfile;
189
+ screen?: {
190
+ width: number;
191
+ height: number;
192
+ };
193
+ interpreterNumber?: number;
194
+ interpreterVersion?: number;
195
+ /** Colour available (Flags 1 bit 0, v5+). */
196
+ colour?: boolean;
197
+ /** The Standard revision to claim (default 1.1, what this implements). Tests set ZVM's 1.2 to compare save files byte for byte. */
198
+ standard?: [number, number];
199
+ };
200
+ hooks?: Hooks;
201
+ /** save_undo states kept (default 10). */
202
+ undoDepth?: number;
203
+ }
204
+ /** What resume() takes: the line typed, the key pressed, a timed read's interval running out, or how a save or restore went. */
205
+ export type InputValue = {
206
+ line: string;
207
+ } | {
208
+ char: number;
209
+ } | {
210
+ timeout: true;
211
+ }
212
+ /** After a `save` stop: whether the host kept the file. */
213
+ | {
214
+ saved: boolean;
215
+ }
216
+ /** After a `restore` stop: the file, or null when the player cancelled. */
217
+ | {
218
+ restore: Uint8Array | null;
219
+ };
220
+ /**
221
+ * The whole machine as plain data (for autosave, and for posting to and from a worker): restored
222
+ * with Machine.fromSnapshot(story, snapshot, options), it goes on exactly where it was.
223
+ */
224
+ export interface MachineSnapshot {
225
+ version: 1;
226
+ /** Which story: release, serial and checksum, checked on restore. */
227
+ story: {
228
+ release: number;
229
+ serial: string;
230
+ checksum: number;
231
+ };
232
+ dynamic: Uint8Array;
233
+ pc: number;
234
+ frames: Frame[];
235
+ random: RandomState;
236
+ screen: ScreenModel;
237
+ input: InputState;
238
+ streams: StreamState;
239
+ counters: {
240
+ instructions: number;
241
+ turns: number;
242
+ };
243
+ undo: Snapshot[];
244
+ /** A timed read whose interrupt routine is running. */
245
+ interrupted: PendingInput | null;
246
+ }
package/dist/types.js ADDED
@@ -0,0 +1,6 @@
1
+ /**
2
+ * The interpreter's public types (docs/11-frotzplay-phase-1-interpreter.md §2). Everything in
3
+ * MachineState is plain data, so it can be copied, posted to a worker, and saved.
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
package/package.json CHANGED
@@ -1,6 +1,59 @@
1
1
  {
2
2
  "name": "@frotzinc/zmachine",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "A Z-machine interpreter (versions 3, 4, 5 and 8) in TypeScript: decoded instructions run one at a time, no eval, all state plain data, input supplied by the host. Runs in a browser, a Web Worker, or Node.",
5
+ "license": "MIT",
6
+ "author": "Christopher Schweda",
7
+ "type": "module",
8
+ "homepage": "https://github.com/cschweda/frotzlens/tree/main/packages/zmachine",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/cschweda/frotzlens.git",
12
+ "directory": "packages/zmachine"
13
+ },
14
+ "bugs": {
15
+ "url": "https://github.com/cschweda/frotzlens/issues"
16
+ },
17
+ "keywords": [
18
+ "z-machine",
19
+ "interpreter",
20
+ "interactive-fiction",
21
+ "inform",
22
+ "zcode",
23
+ "frotzinc"
24
+ ],
25
+ "sideEffects": false,
26
+ "exports": {
27
+ ".": {
28
+ "types": "./dist/index.d.ts",
29
+ "default": "./dist/index.js"
30
+ },
31
+ "./package.json": "./package.json"
32
+ },
33
+ "bin": {
34
+ "zmachine-run": "./bin/run.mjs"
35
+ },
36
+ "files": [
37
+ "dist",
38
+ "bin",
39
+ "!dist/**/*.test.*",
40
+ "README.md",
41
+ "CHANGELOG.md",
42
+ "LICENSE"
43
+ ],
44
+ "dependencies": {
45
+ "@frotzinc/zcode": "0.4.0"
46
+ },
47
+ "devDependencies": {
48
+ "@types/node": "^22.20.5",
49
+ "typescript": "5.9.3",
50
+ "vitest": "^5.1.0",
51
+ "@frotzinc/zmachine-oracle": "0.0.0",
52
+ "@frotzinc/inform6-wasm": "0.1.0"
53
+ },
54
+ "scripts": {
55
+ "build": "rm -rf dist && tsc -p tsconfig.build.json",
56
+ "test": "vitest run",
57
+ "typecheck": "tsc -p tsconfig.json --noEmit"
58
+ }
6
59
  }