@rpgm-tools/neo-angband-mod-sdk 0.10.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.md +43 -0
- package/README.md +72 -0
- package/dist/capabilities.d.ts +116 -0
- package/dist/capabilities.d.ts.map +1 -0
- package/dist/capabilities.js +170 -0
- package/dist/capabilities.js.map +1 -0
- package/dist/compose.d.ts +71 -0
- package/dist/compose.d.ts.map +1 -0
- package/dist/compose.js +118 -0
- package/dist/compose.js.map +1 -0
- package/dist/conflicts.d.ts +78 -0
- package/dist/conflicts.d.ts.map +1 -0
- package/dist/conflicts.js +160 -0
- package/dist/conflicts.js.map +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/loader.d.ts +83 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +314 -0
- package/dist/loader.js.map +1 -0
- package/dist/manifest.d.ts +261 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +264 -0
- package/dist/manifest.js.map +1 -0
- package/dist/patch.d.ts +90 -0
- package/dist/patch.d.ts.map +1 -0
- package/dist/patch.js +195 -0
- package/dist/patch.js.map +1 -0
- package/dist/record-key.d.ts +99 -0
- package/dist/record-key.d.ts.map +1 -0
- package/dist/record-key.js +157 -0
- package/dist/record-key.js.map +1 -0
- package/dist/resolve.d.ts +42 -0
- package/dist/resolve.d.ts.map +1 -0
- package/dist/resolve.js +161 -0
- package/dist/resolve.js.map +1 -0
- package/dist/semver.d.ts +37 -0
- package/dist/semver.d.ts.map +1 -0
- package/dist/semver.js +212 -0
- package/dist/semver.js.map +1 -0
- package/package.json +58 -0
- package/src/capabilities.ts +205 -0
- package/src/compose.ts +186 -0
- package/src/conflicts.ts +242 -0
- package/src/index.ts +73 -0
- package/src/loader.ts +393 -0
- package/src/manifest.ts +523 -0
- package/src/patch.ts +257 -0
- package/src/record-key.ts +180 -0
- package/src/resolve.ts +175 -0
- package/src/semver.ts +231 -0
package/dist/loader.js
ADDED
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content-pack loading: turn a resolved set of packs into the merged per-file
|
|
3
|
+
* record arrays a host binds into the running game.
|
|
4
|
+
*
|
|
5
|
+
* This is the join MOD_INTEGRATION_PLAN.md (Wave 1, W1.1) calls for. Until now
|
|
6
|
+
* the composition engine (resolveLoadOrder + composePacks) had no runtime
|
|
7
|
+
* caller: the game bound a single hard-coded pack directly. This wraps the
|
|
8
|
+
* engine into one entry point so the base game and every mod flow through the
|
|
9
|
+
* same pipeline, and a mod's records / patches / replaces / removes /
|
|
10
|
+
* fieldPatches actually take effect.
|
|
11
|
+
*
|
|
12
|
+
* The host (web / cli) owns the glue: it reads packs off disk or bundle, calls
|
|
13
|
+
* composeContentPacks, assembles its GamePack from the result, and hands that
|
|
14
|
+
* to core bindCore. Core stays mod-sdk-agnostic (it only ever sees a merged
|
|
15
|
+
* pack), so the layering the audit relied on is preserved.
|
|
16
|
+
*
|
|
17
|
+
* TWO MERGE PHASES, AND WHY
|
|
18
|
+
*
|
|
19
|
+
* composePacks needs a unique string `name` on every added record, because that
|
|
20
|
+
* is the identity it builds refs from. Measured over the shipped core pack, 24
|
|
21
|
+
* of the 44 record files satisfy that and 20 do not (14 carry no string `name`
|
|
22
|
+
* at all; 6 carry names that slug to the same ref). So:
|
|
23
|
+
*
|
|
24
|
+
* 1. COMPOSED FILES (24) go through composePacks, which merges records
|
|
25
|
+
* per-record and appends a later pack's additions after an earlier pack's.
|
|
26
|
+
* 2. PASSTHROUGH FILES (20) keep whole-file semantics for `records` - the last
|
|
27
|
+
* provider in load order wins the file - because a mod that ships
|
|
28
|
+
* `constants.json` means "use mine", not "add a second constants record",
|
|
29
|
+
* and the host binds one. Per-record ops are then applied on top, in load
|
|
30
|
+
* order, against the winning array (applyPassthroughOps below), using the
|
|
31
|
+
* per-file identity declared in record-key.ts.
|
|
32
|
+
*
|
|
33
|
+
* NOTHING IS DROPPED IN SILENCE. This is the invariant the whole file exists to
|
|
34
|
+
* hold. Until 2026-07-29 phase 2 did not exist: a `patches` / `replaces` /
|
|
35
|
+
* `fieldPatches` / `removes` entry aimed at any of the 20 was stripped from the
|
|
36
|
+
* contribution before compose ever saw it, so a mod author shipped a valid op
|
|
37
|
+
* and got no effect and no message - the worst failure mode there is, because a
|
|
38
|
+
* feature that appears to work cannot be found by review. Now every per-record
|
|
39
|
+
* op either takes effect or produces a line in `ComposedContent.problems`
|
|
40
|
+
* naming the pack, the file, the op and the ref. Whole-file replacement of a
|
|
41
|
+
* passthrough file is reported the same way, because it silently discards
|
|
42
|
+
* whatever the previous provider (usually core) put there.
|
|
43
|
+
*
|
|
44
|
+
* `problems` is reported rather than thrown because the web host composes at
|
|
45
|
+
* module scope with no try (packages/web/src/pack.ts), so a throw here is a
|
|
46
|
+
* blank page rather than a message. It is the same shape of channel the pack
|
|
47
|
+
* readers already use (`problems: readonly string[]`), so a host concatenates it
|
|
48
|
+
* into the list it already shows.
|
|
49
|
+
*/
|
|
50
|
+
import { slugify } from "./manifest.js";
|
|
51
|
+
import { resolveLoadOrder } from "./resolve.js";
|
|
52
|
+
import { composePacks, mergePatch } from "./compose.js";
|
|
53
|
+
import { applyFieldPatch } from "./patch.js";
|
|
54
|
+
import { keyDescription, keySpecFor, recordKey, RECORD_KEY_SPECS } from "./record-key.js";
|
|
55
|
+
function isNamedRecord(r) {
|
|
56
|
+
return (typeof r === "object" &&
|
|
57
|
+
r !== null &&
|
|
58
|
+
!Array.isArray(r) &&
|
|
59
|
+
typeof r.name === "string" &&
|
|
60
|
+
r.name.length > 0);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* A pack's added records for one file are per-record composable only if they
|
|
64
|
+
* are all name-keyed and their refs (pack:slug(name)) do not collide - exactly
|
|
65
|
+
* the two conditions composePacks would otherwise throw on.
|
|
66
|
+
*/
|
|
67
|
+
function recordsComposable(records) {
|
|
68
|
+
const slugs = new Set();
|
|
69
|
+
for (const r of records) {
|
|
70
|
+
if (!isNamedRecord(r))
|
|
71
|
+
return false;
|
|
72
|
+
const slug = slugify(r["name"]);
|
|
73
|
+
if (slugs.has(slug))
|
|
74
|
+
return false;
|
|
75
|
+
slugs.add(slug);
|
|
76
|
+
}
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
/** Reorder loaded packs into resolved load order (dependencies first). */
|
|
80
|
+
function orderPacks(packs) {
|
|
81
|
+
const ordered = resolveLoadOrder(packs.map((p) => p.manifest));
|
|
82
|
+
const byId = new Map(packs.map((p) => [p.manifest.id, p]));
|
|
83
|
+
return ordered.map((m) => byId.get(m.id));
|
|
84
|
+
}
|
|
85
|
+
/** The pack id a ref's "<owner>:<slug>" prefix names. */
|
|
86
|
+
function ownerOf(ref) {
|
|
87
|
+
const at = ref.indexOf(":");
|
|
88
|
+
return at === -1 ? "" : ref.slice(0, at);
|
|
89
|
+
}
|
|
90
|
+
/** compose.ts mayModify: a pack may only touch its own or a declared dep's records. */
|
|
91
|
+
function mayModify(m, ownerPack) {
|
|
92
|
+
return ownerPack === m.id || (m.dependencies ?? {})[ownerPack] !== undefined;
|
|
93
|
+
}
|
|
94
|
+
/** Every per-record op in one contribution, flattened, in apply order. */
|
|
95
|
+
function perRecordOps(contrib) {
|
|
96
|
+
const out = [];
|
|
97
|
+
for (const ref of Object.keys(contrib.patches ?? {}))
|
|
98
|
+
out.push({ kind: "patch", ref });
|
|
99
|
+
for (const ref of Object.keys(contrib.replaces ?? {}))
|
|
100
|
+
out.push({ kind: "replace", ref });
|
|
101
|
+
for (const ref of Object.keys(contrib.fieldPatches ?? {})) {
|
|
102
|
+
out.push({ kind: "fieldPatch", ref });
|
|
103
|
+
}
|
|
104
|
+
for (const ref of contrib.removes ?? [])
|
|
105
|
+
out.push({ kind: "remove", ref });
|
|
106
|
+
return out;
|
|
107
|
+
}
|
|
108
|
+
const OP_VERB = {
|
|
109
|
+
patch: "patches",
|
|
110
|
+
replace: "replaces",
|
|
111
|
+
fieldPatch: "fieldPatches",
|
|
112
|
+
remove: "removes",
|
|
113
|
+
};
|
|
114
|
+
/**
|
|
115
|
+
* Apply every pack's per-record ops to a PASSTHROUGH file's winning record
|
|
116
|
+
* array, in load order, and report every op that could not be honoured.
|
|
117
|
+
*
|
|
118
|
+
* Returns the (possibly new) record array; the input is never mutated, and when
|
|
119
|
+
* no pack contributes an op the input array is returned unchanged so routing the
|
|
120
|
+
* base game alone through this path stays a no-op by reference.
|
|
121
|
+
*/
|
|
122
|
+
function applyPassthroughOps(file, records, ordered, providerId, problems) {
|
|
123
|
+
const hasOps = ordered.some((p) => p.files[file] !== undefined && perRecordOps(p.files[file]).length > 0);
|
|
124
|
+
if (!hasOps)
|
|
125
|
+
return records;
|
|
126
|
+
/* No declared identity (history: chart/next/roll/phrase are all values a mod
|
|
127
|
+
* would change). Every op is reported and none is applied - the one honest
|
|
128
|
+
* answer, because inventing a key here would mis-merge instead of dropping. */
|
|
129
|
+
if (RECORD_KEY_SPECS[file] === undefined && !recordsKeyedByName(records)) {
|
|
130
|
+
for (const pack of ordered) {
|
|
131
|
+
const contrib = pack.files[file];
|
|
132
|
+
if (!contrib)
|
|
133
|
+
continue;
|
|
134
|
+
for (const { kind, ref } of perRecordOps(contrib)) {
|
|
135
|
+
problems.push(`${pack.manifest.id}: ${file} ${OP_VERB[kind]} "${ref}", but ${file} records have no per-record identity, so only whole-file replacement can change them`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return records;
|
|
139
|
+
}
|
|
140
|
+
const spec = keySpecFor(file);
|
|
141
|
+
const working = records.map((r) => typeof r === "object" && r !== null && !Array.isArray(r) ? r : null);
|
|
142
|
+
/* ref -> EVERY position claiming it. Kept as a list rather than resolved to a
|
|
143
|
+
* winner on insert, so "not found" and "claimed twice" are one lookup with two
|
|
144
|
+
* outcomes: there is nowhere for a first-claim-wins fallback to hide. A ref two
|
|
145
|
+
* records claim is unaddressable and reported; both records stay in the game. */
|
|
146
|
+
const claims = new Map();
|
|
147
|
+
records.forEach((record, i) => {
|
|
148
|
+
const key = recordKey(file, record, spec);
|
|
149
|
+
if (key === null)
|
|
150
|
+
return; // unkeyable record: stays in the game, not addressable
|
|
151
|
+
const ref = `${providerId}:${key}`;
|
|
152
|
+
const at = claims.get(ref);
|
|
153
|
+
if (at)
|
|
154
|
+
at.push(i);
|
|
155
|
+
else
|
|
156
|
+
claims.set(ref, [i]);
|
|
157
|
+
});
|
|
158
|
+
const removed = new Set();
|
|
159
|
+
const reject = (pid, kind, ref, why) => {
|
|
160
|
+
problems.push(`${pid}: ${file} ${OP_VERB[kind]} "${ref}", but ${why}`);
|
|
161
|
+
};
|
|
162
|
+
/** Resolve a ref to a live index, or report why it cannot be touched. */
|
|
163
|
+
const resolve = (pack, kind, ref) => {
|
|
164
|
+
const pid = pack.manifest.id;
|
|
165
|
+
const claimants = claims.get(ref) ?? [];
|
|
166
|
+
if (claimants.length > 1) {
|
|
167
|
+
reject(pid, kind, ref, `${claimants.length} ${file} records share that identity (${keyDescription(file)}), so it cannot be addressed - patch a record with a unique identity instead`);
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
const at = claimants[0];
|
|
171
|
+
if (at === undefined) {
|
|
172
|
+
reject(pid, kind, ref, `no such record exists in ${file} (identity is ${keyDescription(file)})`);
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
if (removed.has(at)) {
|
|
176
|
+
reject(pid, kind, ref, "an earlier pack already removed it");
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
if (!mayModify(pack.manifest, ownerOf(ref))) {
|
|
180
|
+
reject(pid, kind, ref, `${pid} does not declare ${ownerOf(ref)} as a dependency`);
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
return at;
|
|
184
|
+
};
|
|
185
|
+
for (const pack of ordered) {
|
|
186
|
+
const contrib = pack.files[file];
|
|
187
|
+
if (!contrib)
|
|
188
|
+
continue;
|
|
189
|
+
for (const [ref, body] of Object.entries(contrib.patches ?? {})) {
|
|
190
|
+
const at = resolve(pack, "patch", ref);
|
|
191
|
+
if (at === null)
|
|
192
|
+
continue;
|
|
193
|
+
working[at] = mergePatch(working[at], body);
|
|
194
|
+
}
|
|
195
|
+
for (const [ref, body] of Object.entries(contrib.replaces ?? {})) {
|
|
196
|
+
const at = resolve(pack, "replace", ref);
|
|
197
|
+
if (at === null)
|
|
198
|
+
continue;
|
|
199
|
+
working[at] = body;
|
|
200
|
+
}
|
|
201
|
+
for (const [ref, ops] of Object.entries(contrib.fieldPatches ?? {})) {
|
|
202
|
+
const at = resolve(pack, "fieldPatch", ref);
|
|
203
|
+
if (at === null)
|
|
204
|
+
continue;
|
|
205
|
+
working[at] = applyFieldPatch(working[at], ops);
|
|
206
|
+
}
|
|
207
|
+
for (const ref of contrib.removes ?? []) {
|
|
208
|
+
const at = resolve(pack, "remove", ref);
|
|
209
|
+
if (at === null)
|
|
210
|
+
continue;
|
|
211
|
+
removed.add(at);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
const out = [];
|
|
215
|
+
working.forEach((r, i) => {
|
|
216
|
+
if (!removed.has(i))
|
|
217
|
+
out.push(r === null ? records[i] : r);
|
|
218
|
+
});
|
|
219
|
+
return out;
|
|
220
|
+
}
|
|
221
|
+
/** Whether a passthrough file's records are name-keyed after all (mod-only file). */
|
|
222
|
+
function recordsKeyedByName(records) {
|
|
223
|
+
return records.some((r) => isNamedRecord(r));
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Compose a set of loaded packs into merged per-file record arrays. With a
|
|
227
|
+
* single pack (the base game alone) the output is record-identical to the
|
|
228
|
+
* input: every record object is preserved by reference and its order is
|
|
229
|
+
* unchanged, so routing the base game through this path is a no-op.
|
|
230
|
+
*/
|
|
231
|
+
export function composeContentPacks(packs) {
|
|
232
|
+
const ordered = orderPacks(packs);
|
|
233
|
+
const problems = [];
|
|
234
|
+
const fileNames = new Set();
|
|
235
|
+
for (const p of ordered) {
|
|
236
|
+
for (const f of Object.keys(p.files))
|
|
237
|
+
fileNames.add(f);
|
|
238
|
+
}
|
|
239
|
+
// Classify each file: per-record composable, or whole-file passthrough.
|
|
240
|
+
const composable = new Set();
|
|
241
|
+
for (const f of fileNames) {
|
|
242
|
+
let ok = true;
|
|
243
|
+
for (const p of ordered) {
|
|
244
|
+
const contrib = p.files[f];
|
|
245
|
+
if (contrib?.records && !recordsComposable(contrib.records)) {
|
|
246
|
+
ok = false;
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
if (ok)
|
|
251
|
+
composable.add(f);
|
|
252
|
+
}
|
|
253
|
+
const contents = ordered.map((p) => {
|
|
254
|
+
const files = {};
|
|
255
|
+
for (const [f, contrib] of Object.entries(p.files)) {
|
|
256
|
+
if (composable.has(f))
|
|
257
|
+
files[f] = contrib;
|
|
258
|
+
}
|
|
259
|
+
return { manifest: p.manifest, files };
|
|
260
|
+
});
|
|
261
|
+
const game = composePacks(contents);
|
|
262
|
+
const out = {};
|
|
263
|
+
for (const [file, table] of game) {
|
|
264
|
+
out[file] = [...table.values()].map((r) => r.value);
|
|
265
|
+
}
|
|
266
|
+
/* Passthrough files, in two phases (see the header): the last provider in load
|
|
267
|
+
* order wins the whole file, then every pack's per-record ops apply on top. */
|
|
268
|
+
for (const f of fileNames) {
|
|
269
|
+
if (composable.has(f))
|
|
270
|
+
continue;
|
|
271
|
+
let providerId = "";
|
|
272
|
+
for (const p of ordered) {
|
|
273
|
+
const contrib = p.files[f];
|
|
274
|
+
if (!contrib?.records)
|
|
275
|
+
continue;
|
|
276
|
+
if (providerId !== "") {
|
|
277
|
+
/* Whole-file replacement is destructive and used to be invisible: the
|
|
278
|
+
* previous provider's records simply vanished. Say so. */
|
|
279
|
+
problems.push(`${p.manifest.id}: ${f} replaces the whole file, discarding ${out[f].length} record(s) from ${providerId} - ${f} records are not name-keyed, so a whole file is the only thing that can be added to it`);
|
|
280
|
+
}
|
|
281
|
+
out[f] = [...contrib.records];
|
|
282
|
+
providerId = p.manifest.id;
|
|
283
|
+
}
|
|
284
|
+
if (providerId === "") {
|
|
285
|
+
/* SAFETY NET, not a live path. A file is only classified passthrough
|
|
286
|
+
* because some pack's `records` for it are not name-keyed, and such a pack
|
|
287
|
+
* is by definition a provider - so today providerId is never "" here. The
|
|
288
|
+
* equivalent case for a COMPOSABLE file (a mod patches a file nobody
|
|
289
|
+
* supplies records for) is loud already: composePacks throws
|
|
290
|
+
* "patch target ... does not exist". This branch is kept, cordoned and
|
|
291
|
+
* documented rather than deleted, because if the classification ever
|
|
292
|
+
* changes its absence would be a silent drop - the exact failure this file
|
|
293
|
+
* exists to prevent. Covered by loader.test.ts only through the composable
|
|
294
|
+
* counterpart. */
|
|
295
|
+
for (const p of ordered) {
|
|
296
|
+
const contrib = p.files[f];
|
|
297
|
+
if (!contrib)
|
|
298
|
+
continue;
|
|
299
|
+
for (const { kind, ref } of perRecordOps(contrib)) {
|
|
300
|
+
problems.push(`${p.manifest.id}: ${f} ${OP_VERB[kind]} "${ref}", but no pack supplies any ${f} records`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
out[f] = applyPassthroughOps(f, out[f], ordered, providerId, problems);
|
|
306
|
+
}
|
|
307
|
+
return {
|
|
308
|
+
records: out,
|
|
309
|
+
composedFiles: [...composable].sort(),
|
|
310
|
+
passthroughFiles: [...fileNames].filter((f) => !composable.has(f)).sort(),
|
|
311
|
+
problems,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
//# sourceMappingURL=loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.js","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pack manifests: identity, versioning, and dependencies.
|
|
3
|
+
*
|
|
4
|
+
* Every pack - the base game included - carries a manifest. Load order,
|
|
5
|
+
* record composition, and savefile provenance all key off it.
|
|
6
|
+
*/
|
|
7
|
+
/** Pack identifiers are namespaced: "<pack>:<id>", e.g. "core:kobold". */
|
|
8
|
+
export type PackRef = `${string}:${string}`;
|
|
9
|
+
/** The three pack shapes (docs/MODS.md). */
|
|
10
|
+
export type PackShape = "content" | "tiles" | "plugin";
|
|
11
|
+
/** Every shape, for validation and for iterating the facet vocabulary. */
|
|
12
|
+
export declare const PACK_SHAPES: readonly PackShape[];
|
|
13
|
+
/**
|
|
14
|
+
* WHAT A PACK CONTRIBUTES, as a SET rather than one exclusive kind.
|
|
15
|
+
*
|
|
16
|
+
* `shape` was exclusive, and the two halves of the loader gated on opposite
|
|
17
|
+
* values: code loaded only for `shape: "plugin"` (web/src/mod-code.ts) and
|
|
18
|
+
* records composed only for `shape: "content"` (web/src/pack.ts). So the folder
|
|
19
|
+
* layout the plugin documentation promises -
|
|
20
|
+
*
|
|
21
|
+
* my-mod/ manifest.json plugin.js monster.json tiles/orc.png
|
|
22
|
+
*
|
|
23
|
+
* - could never work: declaring "plugin" dropped monster.json from composition,
|
|
24
|
+
* and declaring "content" refused the code. Each half had tests and each half
|
|
25
|
+
* passed; nothing asserted the two together. A mod that adds a monster AND gives
|
|
26
|
+
* it behaviour is the ordinary case, not an exotic one.
|
|
27
|
+
*
|
|
28
|
+
* `facets` is that set. `shape` stays REQUIRED and remains the pack's primary
|
|
29
|
+
* kind - it is what the manager displays and what every existing manifest
|
|
30
|
+
* already carries - and when `facets` is present it must CONTAIN `shape`, so the
|
|
31
|
+
* two fields cannot contradict each other. A hybrid declares:
|
|
32
|
+
*
|
|
33
|
+
* { "shape": "content", "facets": ["content", "plugin"] }
|
|
34
|
+
*
|
|
35
|
+
* The consent property is unchanged and is why `facets` is a declaration rather
|
|
36
|
+
* than something inferred from the folder's contents: shipping plugin.js without
|
|
37
|
+
* naming the `plugin` facet is still a REFUSAL, because running code must be
|
|
38
|
+
* something a mod states rather than something a file listing implies.
|
|
39
|
+
*/
|
|
40
|
+
export declare function packFacets(manifest: Pick<PackManifest, "shape" | "facets">): ReadonlySet<PackShape>;
|
|
41
|
+
/** Whether a pack contributes `facet` (its shape, or any declared facet). */
|
|
42
|
+
export declare function hasFacet(manifest: Pick<PackManifest, "shape" | "facets">, facet: PackShape): boolean;
|
|
43
|
+
/**
|
|
44
|
+
* One player-toggleable "rule" a pack contributes: a flag name the pack owns,
|
|
45
|
+
* plus the human-facing label / description / default the in-app "Fixes &
|
|
46
|
+
* tweaks" menu renders.
|
|
47
|
+
*
|
|
48
|
+
* WHAT THIS USED TO BE, AND WHY IT IS NOT THAT ANY MORE. The first design made
|
|
49
|
+
* this a registry of CORE flags: the corrected behaviour lived in ported core as
|
|
50
|
+
* an off-by-default branch guarded by `if (modRuleEnabled(state, flag))`, the
|
|
51
|
+
* host applied the resolved choices to GameState.modRules, and no mod code ran.
|
|
52
|
+
* That design was deleted on 2026-07-29 because a flag-gated fix is not excluded
|
|
53
|
+
* from core - core shipped the fix body AND the mod's flag name, so deleting the
|
|
54
|
+
* mod folder would not have deleted a line of it. `modRuleEnabled` is GONE
|
|
55
|
+
* (packages/core/src/game/context.ts, where its removal is recorded), and
|
|
56
|
+
* `GameState.modRules` still exists but is OPAQUE to core: core stores it because
|
|
57
|
+
* a save has to record which patches a character was played with, and never
|
|
58
|
+
* branches on it (`context.ts`, the modRules doc comment).
|
|
59
|
+
*
|
|
60
|
+
* WHAT A RULE IS NOW: an input to the MOD's own code. Mods do run code. A mod
|
|
61
|
+
* that changes behaviour ships `hooks.ts` next to its manifest, default-exporting
|
|
62
|
+
* `(flags: Readonly<Record<string, boolean>>) => ModHooks`. The host discovers it
|
|
63
|
+
* (packages/web/src/mod-hooks.ts), calls it once per ENABLED mod in load order
|
|
64
|
+
* with only THAT mod's resolved flags (`choices[flag] ?? rule.default` for the
|
|
65
|
+
* rules its own manifest declares, so one mod cannot read another's toggles), and
|
|
66
|
+
* folds the results into the single ModHooks core holds via `composeModHooks`
|
|
67
|
+
* (packages/core/src/mod/hooks.ts). Each fix body lives in its mod's folder; what
|
|
68
|
+
* core contains is the generic seam, not any mod's name.
|
|
69
|
+
*
|
|
70
|
+
* A disabled mod's patches DO NOT EXIST rather than existing and reading false:
|
|
71
|
+
* its entry point is never called, it contributes no hook, composeModHooks
|
|
72
|
+
* returns undefined, and GameState.modHooks stays absent - so core runs the
|
|
73
|
+
* faithful 4.2.6 path, which is the only path compiled into the branch.
|
|
74
|
+
*
|
|
75
|
+
* A rules-only pack is still a plain `content` pack requesting no capabilities;
|
|
76
|
+
* `rules` remains pure declaration, and this manifest still holds no behaviour.
|
|
77
|
+
*/
|
|
78
|
+
export interface PackRule {
|
|
79
|
+
/**
|
|
80
|
+
* The flag this rule toggles (e.g. "qol.autoDig"). Namespaced by convention to
|
|
81
|
+
* the owning pack, because the pack's own hooks.ts is what reads it; the host
|
|
82
|
+
* also records the resolved value on GameState.modRules as save state.
|
|
83
|
+
*/
|
|
84
|
+
flag: string;
|
|
85
|
+
/** Short menu label (e.g. "Auto-dig"). */
|
|
86
|
+
title: string;
|
|
87
|
+
/** One- or two-line description shown under the toggle in the menu. */
|
|
88
|
+
description: string;
|
|
89
|
+
/** Whether the rule is ON by default when the mod is enabled. */
|
|
90
|
+
default: boolean;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* A capability a scripted plugin requests (MOD_LIFECYCLE section 4). The
|
|
94
|
+
* runtime grants only what a `shape: plugin` pack declares and the user
|
|
95
|
+
* approves; content and tile packs request none. The vocabulary
|
|
96
|
+
* ("command:add", "event:turn-start", "state:*.read", "network:<host>", ...)
|
|
97
|
+
* is enforced by the capability model (P7 phase 5); the manifest only records
|
|
98
|
+
* the request, so any string is accepted here.
|
|
99
|
+
*/
|
|
100
|
+
export type Capability = string;
|
|
101
|
+
/**
|
|
102
|
+
* One graphics mode a `tiles`-facet pack contributes.
|
|
103
|
+
*
|
|
104
|
+
* This was read loosely off the raw JSON for a long time and was NOT in the
|
|
105
|
+
* validated schema, which the moddability measurement recorded as a gap
|
|
106
|
+
* (docs/modding/MOD_REACH.md). The consequence was specific rather than
|
|
107
|
+
* theoretical: a typo in `grafID` or `path` produced no error anywhere - the entry
|
|
108
|
+
* was silently skipped, and a mod author saw a Graphics row that simply never
|
|
109
|
+
* appeared. Declaring it here means the manifest is refused at the edge, with the
|
|
110
|
+
* mod's id and the offending field named.
|
|
111
|
+
*/
|
|
112
|
+
export interface PackTilePack {
|
|
113
|
+
/**
|
|
114
|
+
* The list.txt serial number this mode renders as. A `tilesheet` pack must claim
|
|
115
|
+
* one the core catalog already knows (it borrows that row's cell size, atlas
|
|
116
|
+
* filename and pref file); a `linoleum` pack carries its own metadata and may
|
|
117
|
+
* claim a new id - use >= 100 to stay clear of upstream's numbering.
|
|
118
|
+
*/
|
|
119
|
+
grafID: number;
|
|
120
|
+
/**
|
|
121
|
+
* The pack's directory INSIDE THE MOD FOLDER (`original-tiles`,
|
|
122
|
+
* `tiles/my-set`), or absent for a pack that is the mod folder itself.
|
|
123
|
+
*
|
|
124
|
+
* Mod-relative, not a site path. A mod cannot know where a host serves it from,
|
|
125
|
+
* and two of the three sources serve it from nowhere: a folder the player picked
|
|
126
|
+
* has no URL for its files until their bytes are wrapped in a blob:, and a mod
|
|
127
|
+
* installed from a repository lives in IndexedDB. The host composes this with
|
|
128
|
+
* the mod's own asset resolver.
|
|
129
|
+
*/
|
|
130
|
+
path?: string;
|
|
131
|
+
/**
|
|
132
|
+
* Which renderer draws it: `tilesheet` (or absent) for upstream's own scheme -
|
|
133
|
+
* one atlas PNG addressed by row/column - and `linoleum` for a loose pack, a
|
|
134
|
+
* directory of individually named PNGs. This is the PACK's renderer; the
|
|
135
|
+
* manifest's top-level `engine` is the game version the mod targets.
|
|
136
|
+
*/
|
|
137
|
+
engine?: "tilesheet" | "linoleum";
|
|
138
|
+
/**
|
|
139
|
+
* The Graphics row's label. Required in effect for a mode the core catalog does
|
|
140
|
+
* not have, since there would be nothing to name the row; a pack re-skinning a
|
|
141
|
+
* catalogued mode may omit it and borrow that row's name.
|
|
142
|
+
*/
|
|
143
|
+
menuname?: string;
|
|
144
|
+
}
|
|
145
|
+
export interface PackManifest {
|
|
146
|
+
/**
|
|
147
|
+
* The pack's namespace: lowercase kebab-case, unique among loaded
|
|
148
|
+
* packs. "core" is reserved for the base game.
|
|
149
|
+
*/
|
|
150
|
+
id: string;
|
|
151
|
+
/** Human-readable title. */
|
|
152
|
+
name: string;
|
|
153
|
+
/** Semantic version of the pack itself. */
|
|
154
|
+
version: string;
|
|
155
|
+
/**
|
|
156
|
+
* The pack's primary kind, and what the mod manager displays. When `facets` is
|
|
157
|
+
* absent this is the pack's only facet.
|
|
158
|
+
*/
|
|
159
|
+
shape: PackShape;
|
|
160
|
+
/**
|
|
161
|
+
* Everything this pack contributes, when it contributes more than one kind -
|
|
162
|
+
* a mod shipping both `plugin.js` and record JSON declares
|
|
163
|
+
* `["content", "plugin"]`. Must contain `shape`. See packFacets().
|
|
164
|
+
*/
|
|
165
|
+
facets?: readonly PackShape[];
|
|
166
|
+
/**
|
|
167
|
+
* Engine version range the pack requires (semver range, e.g. ">=0.5.0
|
|
168
|
+
* <0.7.0"). A save refuses to load on an incompatible engine.
|
|
169
|
+
*/
|
|
170
|
+
engine?: string;
|
|
171
|
+
/**
|
|
172
|
+
* Packs this one depends on, by id. A pack may only patch, replace,
|
|
173
|
+
* or remove records owned by packs it declares here. Values are
|
|
174
|
+
* version constraints; "*" accepts any version.
|
|
175
|
+
*/
|
|
176
|
+
dependencies?: Record<string, string>;
|
|
177
|
+
/**
|
|
178
|
+
* Soft dependencies: if the named pack is present it loads first and may
|
|
179
|
+
* be modified, but its absence is not an error (MOD_LIFECYCLE section 2).
|
|
180
|
+
*/
|
|
181
|
+
optionalDependencies?: Record<string, string>;
|
|
182
|
+
/** Load-order hints (MOD_LIFECYCLE section 3): follow / precede these ids. */
|
|
183
|
+
loadAfter?: string[];
|
|
184
|
+
loadBefore?: string[];
|
|
185
|
+
/**
|
|
186
|
+
* The pack's own save-block schema version. The engine hands a mod its
|
|
187
|
+
* old `mod:<id>` bag and asks it to migrate from this number on update.
|
|
188
|
+
*/
|
|
189
|
+
saveSchema?: number;
|
|
190
|
+
/** Capabilities a `shape: plugin` pack requests (see Capability). */
|
|
191
|
+
capabilities?: Capability[];
|
|
192
|
+
/**
|
|
193
|
+
* The mod-plugin ABI version this pack's `plugin.js` was written against, and
|
|
194
|
+
* REQUIRED of any pack that ships one. Separate from `engine` on purpose: the
|
|
195
|
+
* engine version and the ABI a mod's code compiles against diverge immediately
|
|
196
|
+
* - a patch release changes the former and not the latter.
|
|
197
|
+
*
|
|
198
|
+
* Declared here, in the MANIFEST, rather than only inside plugin.js, so the
|
|
199
|
+
* host can refuse an incompatible plugin BEFORE importing it. A version check
|
|
200
|
+
* that lives inside the module can only run after the module's top-level code
|
|
201
|
+
* has already executed, which is the wrong order for player-supplied code.
|
|
202
|
+
*
|
|
203
|
+
* An exact integer, matched exactly, because the ABI is explicitly unstable
|
|
204
|
+
* until 1.0: every change to it bumps this number and every mod must
|
|
205
|
+
* republish. A semver range would imply a compatibility promise that does not
|
|
206
|
+
* exist yet.
|
|
207
|
+
*/
|
|
208
|
+
modApi?: number;
|
|
209
|
+
/**
|
|
210
|
+
* Player-toggleable flags this pack owns (see PackRule). The bundled qol /
|
|
211
|
+
* bug-fixes mods use this to declare their fixes/tweaks for the in-app "Fixes
|
|
212
|
+
* & tweaks" menu; the host resolves (choice ?? default), hands each mod its own
|
|
213
|
+
* slice when it calls that mod's hooks.ts, and records the result on
|
|
214
|
+
* GameState.modRules as save state. Absent for a pack with nothing to toggle.
|
|
215
|
+
*/
|
|
216
|
+
rules?: PackRule[];
|
|
217
|
+
/**
|
|
218
|
+
* Graphics modes this pack contributes (see PackTilePack). Only read for a pack
|
|
219
|
+
* with the `tiles` facet; a content pack that declares them contributes none.
|
|
220
|
+
*/
|
|
221
|
+
tilePacks?: PackTilePack[];
|
|
222
|
+
/**
|
|
223
|
+
* Declares the pack deliberately nondeterministic (a wall-clock event, an
|
|
224
|
+
* external agent, live multiplayer). Trips the save's determinism ratchet
|
|
225
|
+
* once, irreversibly (MOD_LIFECYCLE section 4, decision 4/18).
|
|
226
|
+
*/
|
|
227
|
+
nondeterministic?: boolean;
|
|
228
|
+
/** Declares a gameplay change that permanently makes an enabled save non-scoring. */
|
|
229
|
+
affectsGameplay?: boolean;
|
|
230
|
+
/**
|
|
231
|
+
* What the pack does, in the author's own words, for a human deciding whether
|
|
232
|
+
* to enable it. Prose, not a tagline: the in-app mod manager wraps it to fill
|
|
233
|
+
* the detail pane of the highlighted row, and a marketplace listing would show
|
|
234
|
+
* the same text. Absent is allowed but leaves a player with only the id/shape
|
|
235
|
+
* to go on.
|
|
236
|
+
*/
|
|
237
|
+
description?: string;
|
|
238
|
+
/** Free-form author credit. */
|
|
239
|
+
author?: string;
|
|
240
|
+
/** SPDX license expression for the pack's own content. */
|
|
241
|
+
license?: string;
|
|
242
|
+
/** Source repository URL (installer provenance). */
|
|
243
|
+
repository?: string;
|
|
244
|
+
/** Path to the changelog within the pack. */
|
|
245
|
+
changelog?: string;
|
|
246
|
+
/** Paths to screenshot assets within the pack (marketplace preview). */
|
|
247
|
+
screenshots?: string[];
|
|
248
|
+
}
|
|
249
|
+
export declare class ManifestError extends Error {
|
|
250
|
+
}
|
|
251
|
+
/** Validate a parsed manifest object; throws ManifestError. */
|
|
252
|
+
export declare function validateManifest(value: unknown): PackManifest;
|
|
253
|
+
/**
|
|
254
|
+
* Slug a record name into the id segment of a PackRef: lowercase, runs
|
|
255
|
+
* of non-alphanumerics collapse to single hyphens ("Farmer Maggot" ->
|
|
256
|
+
* "farmer-maggot"). Stable: this is a savefile-visible identity.
|
|
257
|
+
*/
|
|
258
|
+
export declare function slugify(name: string): string;
|
|
259
|
+
/** Build a namespaced record reference. */
|
|
260
|
+
export declare function packRef(packId: string, name: string): PackRef;
|
|
261
|
+
//# sourceMappingURL=manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"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"}
|