aureate 0.0.0-stage → 1.0.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/README.md +47 -2
- package/bin/aureate.cjs +661 -0
- package/package.json +24 -4
package/README.md
CHANGED
|
@@ -1,3 +1,48 @@
|
|
|
1
|
-
#
|
|
1
|
+
# AUREATE
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
An offline terminal puzzle game.
|
|
4
|
+
|
|
5
|
+
You inherit an ORISON-4 terminal and one local note from its former owner. Somewhere inside the machine is an unfinished operation named AUREATE.
|
|
6
|
+
|
|
7
|
+
## Play
|
|
8
|
+
|
|
9
|
+
Run without permanent installation:
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
npx aureate
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Or install the command globally:
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
npm install --global aureate
|
|
19
|
+
aureate
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Start a new session:
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
aureate --new
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Run without loading or writing a save:
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
aureate --new --no-save
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Disable animation and terminal control sequences:
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
aureate --no-animation
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Use a specific save file:
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
aureate --save ./save.json
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Input is case-insensitive. `SAVE` and `QUIT` are process controls. The machine has no help or hint system.
|
|
47
|
+
|
|
48
|
+
AUREATE does not access the network during play and has no runtime package dependencies.
|
package/bin/aureate.cjs
ADDED
|
@@ -0,0 +1,661 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
+
for (let key of __getOwnPropNames(from))
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
12
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
13
|
+
}
|
|
14
|
+
return to;
|
|
15
|
+
};
|
|
16
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
17
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
18
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
19
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
20
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
21
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
22
|
+
mod
|
|
23
|
+
));
|
|
24
|
+
|
|
25
|
+
// src/cli/adapter.js
|
|
26
|
+
var import_node_fs = __toESM(require("node:fs"), 1);
|
|
27
|
+
var import_node_os = __toESM(require("node:os"), 1);
|
|
28
|
+
var import_node_path = __toESM(require("node:path"), 1);
|
|
29
|
+
var import_node_readline = __toESM(require("node:readline"), 1);
|
|
30
|
+
|
|
31
|
+
// src/core/game.js
|
|
32
|
+
var ROOMS = {
|
|
33
|
+
desk: ["WELCOME", "BRASS-PLATE"],
|
|
34
|
+
drum: ["ADDRESS-TRACE", "FOLIO-A", "FOLIO-B", "CONTEXT-LEDGER"],
|
|
35
|
+
lens: ["APERTURE", "SERVICE-SLIP", "GLASS-A", "GLASS-B"],
|
|
36
|
+
patch: ["FLOW-TRACE", "CHANNEL-AMBER", "DRY-CHANNEL"],
|
|
37
|
+
bench: ["AUREATE-PLATE", "CROSSPOINT", "AMBER-JACK", "LEGACY-JACK", "SCRIPT-STORE"]
|
|
38
|
+
};
|
|
39
|
+
var TEXT = {
|
|
40
|
+
"desk/welcome": `ELIAS VALE / LAST LOCAL ENTRY
|
|
41
|
+
|
|
42
|
+
To whoever received this machine:
|
|
43
|
+
|
|
44
|
+
I left AUREATE unfinished under /BENCH/AUREATE-PLATE. It opens the note I
|
|
45
|
+
could not leave on this card. No single page carries the route, but ORISON
|
|
46
|
+
still does.
|
|
47
|
+
|
|
48
|
+
Survey what remains before you move anything. The brass battery was new when
|
|
49
|
+
I closed the case.
|
|
50
|
+
|
|
51
|
+
-- E.V.`,
|
|
52
|
+
"desk/brass-plate": `BELLWETHER LOGIC TERMINAL / ORISON-4 / 1978
|
|
53
|
+
LOCAL STORE 4096 W
|
|
54
|
+
LINE INTERFACE: NOT FITTED
|
|
55
|
+
|
|
56
|
+
OWNER RESIDUE 14-9-92
|
|
57
|
+
|
|
58
|
+
DESK> SURVEY
|
|
59
|
+
DESK> TRAVERSE /DRUM
|
|
60
|
+
DRUM> SURVEY`,
|
|
61
|
+
"desk/mark": `DESK MARK
|
|
62
|
+
CALLER != DESK
|
|
63
|
+
ADDRESS /DESK/MARK`,
|
|
64
|
+
"drum/address-trace": `ADDRESS TRACE 6C
|
|
65
|
+
|
|
66
|
+
DRUM> UNSEAL FOLIO-A
|
|
67
|
+
SOURCE DRUM/FOLIO-A
|
|
68
|
+
|
|
69
|
+
DRUM> UNSEAL /DESK/MARK
|
|
70
|
+
SOURCE DESK/MARK
|
|
71
|
+
CALLER DRUM`,
|
|
72
|
+
"drum/folio-a": `SERVICE MARGIN / 12-2-91
|
|
73
|
+
|
|
74
|
+
FINAL OPERATION: AUR---
|
|
75
|
+
REMAINDER: GLASS-A + GLASS-B
|
|
76
|
+
ANCHOR: /LENS`,
|
|
77
|
+
"drum/folio-b": `DOUBLE EXPOSURE
|
|
78
|
+
|
|
79
|
+
//// 0 1 0 //// AMB //// 1 1 0 ////
|
|
80
|
+
VISIBLE != CARRIER
|
|
81
|
+
OPTICAL PLANE: PRESENT`,
|
|
82
|
+
"drum/context-ledger": `LOCAL CONTEXT LEDGER
|
|
83
|
+
|
|
84
|
+
/DESK
|
|
85
|
+
/DRUM
|
|
86
|
+
/LENS
|
|
87
|
+
/PATCH CONTINUITY 0
|
|
88
|
+
/BENCH
|
|
89
|
+
|
|
90
|
+
EXTERNAL CONTEXT: NONE`,
|
|
91
|
+
"lens/aperture": `APERTURE / FIXED OPTICAL CONSOLE
|
|
92
|
+
ANCHOR /LENS
|
|
93
|
+
STATE COLD`,
|
|
94
|
+
"lens/service-slip": `APERTURE SERVICE RESIDUE
|
|
95
|
+
|
|
96
|
+
LENS> INVOKE APERTURE
|
|
97
|
+
APERTURE> FOCUS GLASS-A
|
|
98
|
+
APERTURE> SUPERPOSE GLASS-A :: GLASS-B
|
|
99
|
+
APERTURE> WITHDRAW`,
|
|
100
|
+
"lens/glass-a": `GLASS-A
|
|
101
|
+
VISIBLE CLEAR
|
|
102
|
+
LATENT YES`,
|
|
103
|
+
"lens/glass-b": `GLASS-B
|
|
104
|
+
VISIBLE SMOKED
|
|
105
|
+
LATENT YES`,
|
|
106
|
+
"patch/flow-trace": `LOCAL FLOW TRACE
|
|
107
|
+
|
|
108
|
+
DRAW <SOURCE> | APERTURE{FOCUS} | DELIVER <SINK>
|
|
109
|
+
|
|
110
|
+
CALIBRATION
|
|
111
|
+
SOURCE /PATCH/DRY-CHANNEL
|
|
112
|
+
FILTER APERTURE{FOCUS}
|
|
113
|
+
SINK /PATCH/CHANNEL-AMBER`,
|
|
114
|
+
"patch/channel-amber": `CHANNEL-AMBER
|
|
115
|
+
LOCAL CONTINUITY 1
|
|
116
|
+
EXTERNAL CONTINUITY 0`,
|
|
117
|
+
"patch/dry-channel": `DRY-CHANNEL
|
|
118
|
+
INPUT 0
|
|
119
|
+
LATENT 0
|
|
120
|
+
WIRE NONE`
|
|
121
|
+
};
|
|
122
|
+
var LATENT = {
|
|
123
|
+
"desk/welcome": `OWNER MARK
|
|
124
|
+
ORIGIN /DESK/WELCOME
|
|
125
|
+
VOICE FIRST`,
|
|
126
|
+
"drum/folio-b": `CARRIER AMBER
|
|
127
|
+
SOURCE /DRUM/FOLIO-B
|
|
128
|
+
SINK /BENCH/AMBER-JACK
|
|
129
|
+
PLANE LATENT`,
|
|
130
|
+
"lens/glass-a": "AURE",
|
|
131
|
+
"lens/glass-b": "ATE"
|
|
132
|
+
};
|
|
133
|
+
var TESTAMENT = `TESTAMENT / ELIAS VALE / LOCAL COPY
|
|
134
|
+
|
|
135
|
+
ORISON began at Bellwether Works in 1978. It counted parts, opened test bays,
|
|
136
|
+
and printed faults on a ribbon that stained every hand in the room.
|
|
137
|
+
|
|
138
|
+
When Bellwether closed, ORISON was scrap. I took it home in the passenger seat
|
|
139
|
+
of my van. Its casing was too heavy for one man, even then. Over the years I
|
|
140
|
+
replaced the drum with a small local store, rebuilt the optical reader, and
|
|
141
|
+
wired the dead test channels back through the desk. It has never been on a
|
|
142
|
+
network. Everything you saw was here when you opened the case.
|
|
143
|
+
|
|
144
|
+
AUREATE was the last thing I wrote. Paper goes damp in this house. Magnetic
|
|
145
|
+
store forgets. I trusted neither with the whole account, so I left the account
|
|
146
|
+
inside the route by which it is reached.
|
|
147
|
+
|
|
148
|
+
If these words are on the glass, the battery held and the old relays moved one
|
|
149
|
+
more time. That is enough.
|
|
150
|
+
|
|
151
|
+
Keep the machine if you have room. If not, save the brass plate. My hands wore
|
|
152
|
+
the corner smooth.
|
|
153
|
+
|
|
154
|
+
-- Elias Vale
|
|
155
|
+
|
|
156
|
+
[ AUREATE ]`;
|
|
157
|
+
var ENDING_STATUS = `LOCAL STORE HELD
|
|
158
|
+
OPTICAL COLD
|
|
159
|
+
AUREATE COMPLETE
|
|
160
|
+
ORISON-4 AT REST`;
|
|
161
|
+
function objectKey(target) {
|
|
162
|
+
return `${target.room}/${target.object}`;
|
|
163
|
+
}
|
|
164
|
+
var Game = class {
|
|
165
|
+
constructor(data = {}) {
|
|
166
|
+
const state = data.version === 2 ? data : {};
|
|
167
|
+
this.room = ROOMS[state.room] ? state.room : "desk";
|
|
168
|
+
this.mode = "shell";
|
|
169
|
+
this.flags = new Set(state.flags || []);
|
|
170
|
+
this.scripts = { ...state.scripts || {} };
|
|
171
|
+
this.visited = new Set((state.visited || ["desk"]).filter((room) => ROOMS[room]));
|
|
172
|
+
if (!this.visited.size) this.visited.add("desk");
|
|
173
|
+
this.finished = Boolean(state.finished);
|
|
174
|
+
}
|
|
175
|
+
toJSON() {
|
|
176
|
+
return {
|
|
177
|
+
version: 2,
|
|
178
|
+
room: this.room,
|
|
179
|
+
flags: [...this.flags].sort(),
|
|
180
|
+
scripts: this.scripts,
|
|
181
|
+
visited: [...this.visited].sort(),
|
|
182
|
+
finished: this.finished
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
opening() {
|
|
186
|
+
return `ORISON-4 / 1978
|
|
187
|
+
LOCAL STORE OK
|
|
188
|
+
OPTICAL COLD
|
|
189
|
+
LINE --
|
|
190
|
+
|
|
191
|
+
AUTO> UNSEAL /DESK/WELCOME
|
|
192
|
+
|
|
193
|
+
${TEXT["desk/welcome"]}`;
|
|
194
|
+
}
|
|
195
|
+
get prompt() {
|
|
196
|
+
return this.mode === "aperture" ? "APERTURE> " : `${this.room.toUpperCase()}> `;
|
|
197
|
+
}
|
|
198
|
+
process(input, options = {}) {
|
|
199
|
+
const line = input.trim();
|
|
200
|
+
if (!line) return { text: "", quit: false };
|
|
201
|
+
const upper = line.toUpperCase();
|
|
202
|
+
if (upper === "QUIT") return { text: "LOCAL STORE HELD", quit: true };
|
|
203
|
+
if (upper === "SAVE") return { text: "SAVE REQUESTED", quit: false };
|
|
204
|
+
if (upper === "HELP" || upper === "HINT" || upper === "RECALL") {
|
|
205
|
+
return { text: "NO SUCH LOCAL FILE", quit: false };
|
|
206
|
+
}
|
|
207
|
+
if (this.finished) return { text: TESTAMENT, quit: false };
|
|
208
|
+
if (this.mode === "aperture") return { text: this.aperture(line), quit: false };
|
|
209
|
+
if (line.includes("|") && !upper.startsWith("INSCRIBE ")) {
|
|
210
|
+
return { text: this.pipeline(line, options), quit: false };
|
|
211
|
+
}
|
|
212
|
+
const firstSpace = line.search(/\s/);
|
|
213
|
+
const command = (firstSpace < 0 ? line : line.slice(0, firstSpace)).toUpperCase();
|
|
214
|
+
const argument = firstSpace < 0 ? "" : line.slice(firstSpace).trim();
|
|
215
|
+
let text;
|
|
216
|
+
switch (command) {
|
|
217
|
+
case "SURVEY":
|
|
218
|
+
text = this.survey(argument);
|
|
219
|
+
break;
|
|
220
|
+
case "UNSEAL":
|
|
221
|
+
text = this.unseal(argument);
|
|
222
|
+
break;
|
|
223
|
+
case "TRAVERSE":
|
|
224
|
+
text = this.traverse(argument);
|
|
225
|
+
break;
|
|
226
|
+
case "INVOKE":
|
|
227
|
+
text = this.invoke(argument);
|
|
228
|
+
break;
|
|
229
|
+
case "DRAW": {
|
|
230
|
+
const drawn = this.draw(argument);
|
|
231
|
+
text = drawn.error || `STREAM ${this.pathOf(drawn.stream.source)}
|
|
232
|
+
PLANE VISIBLE
|
|
233
|
+
SINK --`;
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
case "DELIVER":
|
|
237
|
+
text = "DELIVER: INPUT 0";
|
|
238
|
+
break;
|
|
239
|
+
case "INSCRIBE":
|
|
240
|
+
text = this.inscribe(line);
|
|
241
|
+
break;
|
|
242
|
+
case "PERFORM":
|
|
243
|
+
text = this.perform(argument);
|
|
244
|
+
break;
|
|
245
|
+
case "AUREATE":
|
|
246
|
+
text = this.aureate(line);
|
|
247
|
+
break;
|
|
248
|
+
default:
|
|
249
|
+
text = `? ${line.split(/\s+/)[0]}`;
|
|
250
|
+
}
|
|
251
|
+
return { text, quit: false };
|
|
252
|
+
}
|
|
253
|
+
accessibleRooms() {
|
|
254
|
+
const result = ["desk", "drum", "bench"];
|
|
255
|
+
if (this.visited.has("drum") || this.visited.has("lens")) result.push("lens");
|
|
256
|
+
if (this.flags.has("patch_open") || this.visited.has("patch")) result.push("patch");
|
|
257
|
+
return result;
|
|
258
|
+
}
|
|
259
|
+
survey(argument = "") {
|
|
260
|
+
let room = this.room;
|
|
261
|
+
if (argument) {
|
|
262
|
+
room = argument.replace(/^\/+|\/+$/g, "").toLowerCase();
|
|
263
|
+
if (!ROOMS[room] || !this.accessibleRooms().includes(room) && room !== this.room) return `? /${room.toUpperCase()}`;
|
|
264
|
+
}
|
|
265
|
+
const lines = [`/${room.toUpperCase()}`];
|
|
266
|
+
for (const object of ROOMS[room]) {
|
|
267
|
+
let mark = "";
|
|
268
|
+
if (room === "bench") {
|
|
269
|
+
const flag = { CROSSPOINT: "crosspoint", "AMBER-JACK": "amber", "LEGACY-JACK": "legacy" }[object];
|
|
270
|
+
if (flag) mark = this.flags.has(flag) ? " *" : " .";
|
|
271
|
+
}
|
|
272
|
+
lines.push(`${object}${mark}`);
|
|
273
|
+
}
|
|
274
|
+
lines.push("::");
|
|
275
|
+
for (const available of this.accessibleRooms()) lines.push(`/${available.toUpperCase()}`);
|
|
276
|
+
if (room === "drum" && !this.flags.has("patch_open")) lines.push("/PATCH !");
|
|
277
|
+
return lines.join("\n");
|
|
278
|
+
}
|
|
279
|
+
resolve(token) {
|
|
280
|
+
token = token.trim().replaceAll("\\", "/");
|
|
281
|
+
if (!token) return { error: "? ADDRESS" };
|
|
282
|
+
let room;
|
|
283
|
+
let object;
|
|
284
|
+
if (token.startsWith("/")) {
|
|
285
|
+
const parts = token.split("/").filter(Boolean);
|
|
286
|
+
if (parts.length !== 2) return { error: "? ADDRESS" };
|
|
287
|
+
room = parts[0].toLowerCase();
|
|
288
|
+
object = parts[1].toLowerCase().replaceAll("_", "-");
|
|
289
|
+
} else {
|
|
290
|
+
room = this.room;
|
|
291
|
+
object = token.toLowerCase().replaceAll("_", "-");
|
|
292
|
+
}
|
|
293
|
+
const key = `${room}/${object}`;
|
|
294
|
+
const liveBench = room === "bench" && ["aureate-plate", "crosspoint", "amber-jack", "legacy-jack", "script-store"].includes(object);
|
|
295
|
+
if (!(key in TEXT) && !liveBench) return { error: `? ${token}` };
|
|
296
|
+
return { target: { room, object } };
|
|
297
|
+
}
|
|
298
|
+
pathOf(target) {
|
|
299
|
+
return `/${target.room.toUpperCase()}/${target.object.toUpperCase()}`;
|
|
300
|
+
}
|
|
301
|
+
unseal(token) {
|
|
302
|
+
const found = this.resolve(token);
|
|
303
|
+
if (found.error) return found.error;
|
|
304
|
+
const target = found.target;
|
|
305
|
+
const key = objectKey(target);
|
|
306
|
+
if (key === "desk/mark" && this.room === "bench") {
|
|
307
|
+
this.flags.add("crosspoint");
|
|
308
|
+
return `${TEXT[key]}
|
|
309
|
+
|
|
310
|
+
CROSSPOINT *
|
|
311
|
+
IMPRINT
|
|
312
|
+
INVOKE APERTURE
|
|
313
|
+
ANCHOR /LENS`;
|
|
314
|
+
}
|
|
315
|
+
if (target.room === "bench") return this.benchObject(target.object);
|
|
316
|
+
return TEXT[key];
|
|
317
|
+
}
|
|
318
|
+
benchObject(object) {
|
|
319
|
+
if (object === "aureate-plate") {
|
|
320
|
+
if (this.flags.has("legacy")) {
|
|
321
|
+
return `AUREATE-PLATE
|
|
322
|
+
|
|
323
|
+
AUREATE [LEGACY] :: @/DESK/WELCOME >> TESTAMENT`;
|
|
324
|
+
}
|
|
325
|
+
return `AUREATE-PLATE
|
|
326
|
+
|
|
327
|
+
??????? [??????] :: @????????????? >> TESTAMENT`;
|
|
328
|
+
}
|
|
329
|
+
if (object === "crosspoint") {
|
|
330
|
+
if (this.flags.has("crosspoint")) return "CROSSPOINT *\nCALLER /BENCH\nSOURCE /DESK/MARK";
|
|
331
|
+
return `CROSSPOINT .
|
|
332
|
+
CALLER /BENCH
|
|
333
|
+
SOURCE /DESK/MARK
|
|
334
|
+
TRAVERSE 0`;
|
|
335
|
+
}
|
|
336
|
+
if (object === "amber-jack") {
|
|
337
|
+
if (this.flags.has("amber")) return "AMBER-JACK *\nCARRIER AMBER\nPLANE LATENT";
|
|
338
|
+
return `AMBER-JACK .
|
|
339
|
+
SOURCE /DRUM/FOLIO-B
|
|
340
|
+
PLANE LATENT
|
|
341
|
+
CARRIER AMBER`;
|
|
342
|
+
}
|
|
343
|
+
if (object === "legacy-jack") {
|
|
344
|
+
if (this.flags.has("legacy")) return "LEGACY-JACK *\nNAME LEGACY\nORIGIN /DESK/WELCOME";
|
|
345
|
+
return `LEGACY-JACK .
|
|
346
|
+
NAME LEGACY
|
|
347
|
+
SOURCE /DESK/WELCOME
|
|
348
|
+
PLANE LATENT
|
|
349
|
+
SINK /BENCH/LEGACY-JACK`;
|
|
350
|
+
}
|
|
351
|
+
if (object === "script-store") {
|
|
352
|
+
if (!this.flags.has("amber")) return "SCRIPT-STORE\nSTATE COLD";
|
|
353
|
+
return `SCRIPT-STORE
|
|
354
|
+
STATE HELD
|
|
355
|
+
|
|
356
|
+
INSCRIBE <NAME> := <LINE>
|
|
357
|
+
PERFORM <NAME>`;
|
|
358
|
+
}
|
|
359
|
+
return "?";
|
|
360
|
+
}
|
|
361
|
+
traverse(token) {
|
|
362
|
+
if (!token) return "? CONTEXT";
|
|
363
|
+
const room = token.replace(/^\/+|\/+$/g, "").toLowerCase();
|
|
364
|
+
if (!ROOMS[room]) return `? /${room.toUpperCase()}`;
|
|
365
|
+
this.room = room;
|
|
366
|
+
this.mode = "shell";
|
|
367
|
+
this.visited.add(room);
|
|
368
|
+
return this.survey();
|
|
369
|
+
}
|
|
370
|
+
invoke(token) {
|
|
371
|
+
if (token.trim().toLowerCase() !== "aperture") return `? ${token || "TOOL"}`;
|
|
372
|
+
if (this.room !== "lens") return "APERTURE ANCHOR /LENS";
|
|
373
|
+
this.mode = "aperture";
|
|
374
|
+
return `APERTURE
|
|
375
|
+
ANCHOR /LENS
|
|
376
|
+
|
|
377
|
+
> FOCUS GLASS-A
|
|
378
|
+
> SUPERPOSE GLASS-A :: GLASS-B
|
|
379
|
+
> WITHDRAW`;
|
|
380
|
+
}
|
|
381
|
+
aperture(line) {
|
|
382
|
+
const upper = line.toUpperCase();
|
|
383
|
+
if (upper === "WITHDRAW") {
|
|
384
|
+
this.mode = "shell";
|
|
385
|
+
return "/LENS";
|
|
386
|
+
}
|
|
387
|
+
if (upper.startsWith("FOCUS ")) {
|
|
388
|
+
const found = this.resolve(line.slice(line.search(/\s/) + 1));
|
|
389
|
+
if (found.error) return found.error;
|
|
390
|
+
const key = objectKey(found.target);
|
|
391
|
+
if (!(key in LATENT)) return `${this.pathOf(found.target)}
|
|
392
|
+
LATENT 0`;
|
|
393
|
+
return `${this.pathOf(found.target)}
|
|
394
|
+
LATENT 1
|
|
395
|
+
|
|
396
|
+
${LATENT[key]}`;
|
|
397
|
+
}
|
|
398
|
+
if (upper.startsWith("SUPERPOSE ")) {
|
|
399
|
+
const parts = line.slice(line.search(/\s/) + 1).split("::");
|
|
400
|
+
if (parts.length !== 2) return "SUPERPOSE ? :: ?";
|
|
401
|
+
const left = this.resolve(parts[0].trim());
|
|
402
|
+
const right = this.resolve(parts[1].trim());
|
|
403
|
+
if (left.error) return left.error;
|
|
404
|
+
if (right.error) return right.error;
|
|
405
|
+
const pair = /* @__PURE__ */ new Set([objectKey(left.target), objectKey(right.target)]);
|
|
406
|
+
if (pair.size === 2 && pair.has("lens/glass-a") && pair.has("lens/glass-b")) {
|
|
407
|
+
this.flags.add("patch_open");
|
|
408
|
+
return `AURE + ATE
|
|
409
|
+
AUREATE
|
|
410
|
+
|
|
411
|
+
/PATCH CONTINUITY 1`;
|
|
412
|
+
}
|
|
413
|
+
return "REGISTRATION 0";
|
|
414
|
+
}
|
|
415
|
+
return `? ${line.split(/\s+/)[0]}`;
|
|
416
|
+
}
|
|
417
|
+
draw(token) {
|
|
418
|
+
const found = this.resolve(token);
|
|
419
|
+
if (found.error) return { error: found.error };
|
|
420
|
+
const target = found.target;
|
|
421
|
+
const text = target.room === "bench" ? this.benchObject(target.object) : TEXT[objectKey(target)];
|
|
422
|
+
return { stream: { source: target, plane: "visible", text } };
|
|
423
|
+
}
|
|
424
|
+
pipeline(line, options = {}) {
|
|
425
|
+
const parts = line.split("|").map((part) => part.trim());
|
|
426
|
+
if (parts.some((part) => !part) || !/^DRAW\s+/i.test(parts[0])) return "FLOW 0";
|
|
427
|
+
const drawn = this.draw(parts[0].replace(/^DRAW\s+/i, ""));
|
|
428
|
+
if (drawn.error) return drawn.error;
|
|
429
|
+
let stream = drawn.stream;
|
|
430
|
+
const result = [`DRAW ${this.pathOf(stream.source)}`];
|
|
431
|
+
let delivered = false;
|
|
432
|
+
for (const part of parts.slice(1)) {
|
|
433
|
+
if (/^APERTURE\{FOCUS\}$/i.test(part)) {
|
|
434
|
+
const key = objectKey(stream.source);
|
|
435
|
+
if (!(key in LATENT)) return "APERTURE{FOCUS} LATENT 0";
|
|
436
|
+
stream = { source: stream.source, plane: "latent", text: LATENT[key] };
|
|
437
|
+
result.push("APERTURE{FOCUS} LATENT 1");
|
|
438
|
+
} else if (/^DELIVER\s+/i.test(part)) {
|
|
439
|
+
if (delivered) return "SINK HELD";
|
|
440
|
+
const found = this.resolve(part.replace(/^DELIVER\s+/i, ""));
|
|
441
|
+
if (found.error) return found.error;
|
|
442
|
+
result.push(this.deliver(stream, found.target, options));
|
|
443
|
+
delivered = true;
|
|
444
|
+
} else {
|
|
445
|
+
return `? ${part}`;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
if (!delivered) result.push("", stream.text);
|
|
449
|
+
return result.join("\n");
|
|
450
|
+
}
|
|
451
|
+
deliver(stream, target, options = {}) {
|
|
452
|
+
const destination = objectKey(target);
|
|
453
|
+
if (destination === "bench/amber-jack") {
|
|
454
|
+
if (objectKey(stream.source) !== "drum/folio-b" || stream.plane !== "latent") return "AMBER-JACK REJECT";
|
|
455
|
+
this.flags.add("amber");
|
|
456
|
+
return `DELIVER /BENCH/AMBER-JACK
|
|
457
|
+
AMBER-JACK *
|
|
458
|
+
|
|
459
|
+
SCRIPT-STORE HELD
|
|
460
|
+
INSCRIBE <NAME> := <LINE>
|
|
461
|
+
PERFORM <NAME>`;
|
|
462
|
+
}
|
|
463
|
+
if (destination === "bench/legacy-jack") {
|
|
464
|
+
if (objectKey(stream.source) !== "desk/welcome" || stream.plane !== "latent") return "LEGACY-JACK REJECT";
|
|
465
|
+
if (!options.scripted || String(options.scriptName).toUpperCase() !== "LEGACY") return "LEGACY-JACK NAME ?";
|
|
466
|
+
this.flags.add("legacy");
|
|
467
|
+
return `DELIVER /BENCH/LEGACY-JACK
|
|
468
|
+
LEGACY-JACK *
|
|
469
|
+
ORIGIN /DESK/WELCOME
|
|
470
|
+
AUREATE-PLATE HELD`;
|
|
471
|
+
}
|
|
472
|
+
return `${this.pathOf(target)} SINK 0`;
|
|
473
|
+
}
|
|
474
|
+
inscribe(line) {
|
|
475
|
+
if (this.room !== "bench") return "SCRIPT-STORE ANCHOR /BENCH";
|
|
476
|
+
const match = line.match(/^INSCRIBE\s+([A-Z0-9_-]+)\s*:=\s*(.+)$/is);
|
|
477
|
+
if (!match) return "INSCRIBE ? := ?";
|
|
478
|
+
const name = match[1].toUpperCase();
|
|
479
|
+
const body = match[2].trim();
|
|
480
|
+
const head = body.split(/\s+/, 1)[0].toUpperCase();
|
|
481
|
+
if (["INSCRIBE", "PERFORM", "AUREATE"].includes(head) || body.length > 300) return "SCRIPT-STORE REJECT";
|
|
482
|
+
this.scripts[name] = body;
|
|
483
|
+
return `${name} := ${body}`;
|
|
484
|
+
}
|
|
485
|
+
perform(token) {
|
|
486
|
+
if (this.room !== "bench") return "SCRIPT-STORE ANCHOR /BENCH";
|
|
487
|
+
const name = token.trim().toUpperCase();
|
|
488
|
+
if (!(name in this.scripts)) return `${name || "?"} : --`;
|
|
489
|
+
const output = this.process(this.scripts[name], { scripted: true, scriptName: name });
|
|
490
|
+
return `${name}
|
|
491
|
+
${output.text}`;
|
|
492
|
+
}
|
|
493
|
+
aureate(line) {
|
|
494
|
+
const match = line.match(/^AUREATE\s+\[([A-Z0-9_-]+)\]\s*::\s*@([^\s]+)\s*>>\s*TESTAMENT$/i);
|
|
495
|
+
if (!match) return "AUREATE [??????] :: @????????????? >> TESTAMENT";
|
|
496
|
+
if (this.room !== "bench") return "AUREATE CONTEXT ?";
|
|
497
|
+
const script = match[1].toUpperCase();
|
|
498
|
+
const origin = this.resolve(match[2]);
|
|
499
|
+
if (script !== "LEGACY") return "LEGACY --";
|
|
500
|
+
if (origin.error || objectKey(origin.target) !== "desk/welcome") return "ORIGIN ?";
|
|
501
|
+
this.finished = true;
|
|
502
|
+
this.flags.add("aureate");
|
|
503
|
+
return TESTAMENT;
|
|
504
|
+
}
|
|
505
|
+
};
|
|
506
|
+
|
|
507
|
+
// src/cli/adapter.js
|
|
508
|
+
var wait = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
509
|
+
async function writeSlow(text, milliseconds = 12) {
|
|
510
|
+
for (const character of text) {
|
|
511
|
+
process.stdout.write(character);
|
|
512
|
+
if (character !== " " && character !== "\n") await wait(milliseconds);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
async function writeLines(text, milliseconds = 24) {
|
|
516
|
+
const lines = text.split("\n");
|
|
517
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
518
|
+
process.stdout.write(lines[index]);
|
|
519
|
+
if (index < lines.length - 1) process.stdout.write("\n");
|
|
520
|
+
await wait(milliseconds);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
function clearTerminal() {
|
|
524
|
+
process.stdout.write("\x1B[2J\x1B[3J\x1B[H");
|
|
525
|
+
}
|
|
526
|
+
async function boot(game, restored, animated) {
|
|
527
|
+
if (!animated) {
|
|
528
|
+
if (!restored) console.log(game.opening());
|
|
529
|
+
else if (game.finished) console.log("ORISON-4 / 1978\nLOCAL STORE HELD");
|
|
530
|
+
else console.log(`ORISON-4 / 1978
|
|
531
|
+
LOCAL STORE HELD
|
|
532
|
+
|
|
533
|
+
${game.survey()}`);
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
clearTerminal();
|
|
537
|
+
process.stdout.write("\x1B[?25l");
|
|
538
|
+
await writeSlow("ORISON-4 / 1978", 24);
|
|
539
|
+
process.stdout.write("\n\nLOCAL STORE 0000 W");
|
|
540
|
+
for (const amount of ["1024", "2048", "3072", "4096"]) {
|
|
541
|
+
await wait(110);
|
|
542
|
+
process.stdout.write(`\rLOCAL STORE ${amount} W`);
|
|
543
|
+
}
|
|
544
|
+
await wait(140);
|
|
545
|
+
process.stdout.write(" OK\nOPTICAL COLD\nLINE --\n");
|
|
546
|
+
await wait(260);
|
|
547
|
+
if (restored) {
|
|
548
|
+
process.stdout.write("\nSTORE> HELD");
|
|
549
|
+
if (!game.finished) {
|
|
550
|
+
process.stdout.write("\n\n");
|
|
551
|
+
await writeLines(game.survey(), 16);
|
|
552
|
+
}
|
|
553
|
+
} else {
|
|
554
|
+
const opening = game.opening().split("\n");
|
|
555
|
+
const autoIndex = opening.findIndex((line) => line.startsWith("AUTO>"));
|
|
556
|
+
process.stdout.write("\n");
|
|
557
|
+
await writeSlow(opening[autoIndex], 18);
|
|
558
|
+
process.stdout.write("\n\n");
|
|
559
|
+
await writeLines(opening.slice(autoIndex + 2).join("\n"), 22);
|
|
560
|
+
}
|
|
561
|
+
process.stdout.write("\x1B[?25h");
|
|
562
|
+
}
|
|
563
|
+
async function settleTerminal(animated) {
|
|
564
|
+
if (animated) {
|
|
565
|
+
await wait(420);
|
|
566
|
+
process.stdout.write("\n\n\x1B[?25l");
|
|
567
|
+
await writeLines(ENDING_STATUS, 180);
|
|
568
|
+
process.stdout.write("\n\x1B[?25h");
|
|
569
|
+
} else {
|
|
570
|
+
console.log(`
|
|
571
|
+
${ENDING_STATUS}`);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
function defaultSavePath() {
|
|
575
|
+
if (process.platform === "win32") {
|
|
576
|
+
const root2 = process.env.APPDATA || import_node_path.default.join(import_node_os.default.homedir(), "AppData", "Roaming");
|
|
577
|
+
return import_node_path.default.join(root2, "AUREATE", "save.json");
|
|
578
|
+
}
|
|
579
|
+
const root = process.env.XDG_DATA_HOME || import_node_path.default.join(import_node_os.default.homedir(), ".local", "share");
|
|
580
|
+
return import_node_path.default.join(root, "aureate", "save.json");
|
|
581
|
+
}
|
|
582
|
+
function loadGame(savePath) {
|
|
583
|
+
try {
|
|
584
|
+
return new Game(JSON.parse(import_node_fs.default.readFileSync(savePath, "utf8")));
|
|
585
|
+
} catch {
|
|
586
|
+
return null;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
function saveGame(game, savePath) {
|
|
590
|
+
import_node_fs.default.mkdirSync(import_node_path.default.dirname(savePath), { recursive: true });
|
|
591
|
+
import_node_fs.default.writeFileSync(savePath, `${JSON.stringify(game.toJSON(), null, 2)}
|
|
592
|
+
`, "utf8");
|
|
593
|
+
}
|
|
594
|
+
function parseArgs(args) {
|
|
595
|
+
const options = { fresh: false, noSave: false, noAnimation: false, savePath: defaultSavePath() };
|
|
596
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
597
|
+
if (args[index] === "--new") options.fresh = true;
|
|
598
|
+
else if (args[index] === "--no-save") options.noSave = true;
|
|
599
|
+
else if (args[index] === "--no-animation") options.noAnimation = true;
|
|
600
|
+
else if (args[index] === "--save" && args[index + 1]) options.savePath = import_node_path.default.resolve(args[++index]);
|
|
601
|
+
else if (args[index] === "--help") {
|
|
602
|
+
console.log("AUREATE [--new] [--no-save] [--no-animation] [--save FILE]");
|
|
603
|
+
return null;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
return options;
|
|
607
|
+
}
|
|
608
|
+
async function run(args = process.argv.slice(2)) {
|
|
609
|
+
const options = parseArgs(args);
|
|
610
|
+
if (!options) return;
|
|
611
|
+
let game = null;
|
|
612
|
+
if (!options.fresh && !options.noSave && import_node_fs.default.existsSync(options.savePath)) game = loadGame(options.savePath);
|
|
613
|
+
const restored = Boolean(game);
|
|
614
|
+
if (!game) game = new Game();
|
|
615
|
+
const animated = Boolean(process.stdin.isTTY && process.stdout.isTTY && !options.noAnimation);
|
|
616
|
+
if (animated) process.once("exit", () => process.stdout.write("\x1B[?25h"));
|
|
617
|
+
await boot(game, restored, animated);
|
|
618
|
+
if (game.finished) {
|
|
619
|
+
await settleTerminal(animated);
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
const terminal = import_node_readline.default.createInterface({
|
|
623
|
+
input: process.stdin,
|
|
624
|
+
output: process.stdout,
|
|
625
|
+
terminal: Boolean(process.stdin.isTTY)
|
|
626
|
+
});
|
|
627
|
+
terminal.setPrompt(`
|
|
628
|
+
${game.prompt}`);
|
|
629
|
+
terminal.prompt();
|
|
630
|
+
for await (const line of terminal) {
|
|
631
|
+
const output = game.process(line);
|
|
632
|
+
if (output.text === "SAVE REQUESTED") {
|
|
633
|
+
if (!options.noSave) saveGame(game, options.savePath);
|
|
634
|
+
console.log(options.noSave ? "LOCAL STORE INHIBITED" : "LOCAL STORE HELD");
|
|
635
|
+
} else {
|
|
636
|
+
if (output.text === TESTAMENT && animated) {
|
|
637
|
+
process.stdout.write("\n");
|
|
638
|
+
await writeLines(output.text, 30);
|
|
639
|
+
process.stdout.write("\n");
|
|
640
|
+
} else if (output.text) {
|
|
641
|
+
console.log(output.text);
|
|
642
|
+
}
|
|
643
|
+
if (!options.noSave) saveGame(game, options.savePath);
|
|
644
|
+
if (game.finished) {
|
|
645
|
+
await settleTerminal(animated);
|
|
646
|
+
break;
|
|
647
|
+
}
|
|
648
|
+
if (output.quit) break;
|
|
649
|
+
}
|
|
650
|
+
terminal.setPrompt(`
|
|
651
|
+
${game.prompt}`);
|
|
652
|
+
terminal.prompt();
|
|
653
|
+
}
|
|
654
|
+
terminal.close();
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// src/cli/main.js
|
|
658
|
+
run().catch((error) => {
|
|
659
|
+
console.error(error);
|
|
660
|
+
process.exitCode = 1;
|
|
661
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aureate",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "An offline terminal puzzle game about discovering a missing command.",
|
|
5
|
+
"bin": {
|
|
6
|
+
"aureate": "bin/aureate.cjs"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"bin",
|
|
10
|
+
"README.md"
|
|
11
|
+
],
|
|
12
|
+
"engines": {
|
|
13
|
+
"node": ">=20"
|
|
14
|
+
},
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"game",
|
|
20
|
+
"terminal",
|
|
21
|
+
"cli",
|
|
22
|
+
"puzzle",
|
|
23
|
+
"interactive-fiction"
|
|
24
|
+
],
|
|
25
|
+
"license": "UNLICENSED"
|
|
26
|
+
}
|