@rpgm-tools/neo-angband-mod-sdk 1.3.0 → 1.9.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/src/manifest.ts CHANGED
@@ -304,11 +304,16 @@ export const DEFAULT_PACK_GROUP = "content";
304
304
  * runtime grants only what a `shape: plugin` pack declares and the user
305
305
  * approves; content and tile packs request none. The vocabulary
306
306
  * ("command:add", "event:turn-start", "state:*.read", "network:<host>", ...)
307
- * is enforced by the capability model (P7 phase 5); the manifest only records
308
- * the request, so any string is accepted here.
307
+ * is enforced by parseCapability / CapabilitySet.fromManifest in
308
+ * capabilities.ts. The manifest records the request as strings; checkMod
309
+ * runs that same function, so an unrecognized capability is a check
310
+ * failure rather than a silent accept.
309
311
  */
310
312
  export type Capability = string;
311
313
 
314
+ /** How code declared by a plugin-facet pack is executed. */
315
+ export type PluginRuntime = "in-process" | "worker";
316
+
312
317
  /**
313
318
  * One graphics mode a `tiles`-facet pack contributes.
314
319
  *
@@ -479,6 +484,16 @@ export interface PackManifest {
479
484
  * exist yet.
480
485
  */
481
486
  modApi?: number;
487
+ /**
488
+ * The execution ABI selected by a code pack. API 1 keeps the historical
489
+ * in-process `plugin.js` entry; API 2 requires the isolated worker runtime.
490
+ */
491
+ runtime?: PluginRuntime;
492
+ /**
493
+ * The module imported by the host-owned API-2 Worker bootstrap. This is a
494
+ * mod-relative file, never a host module name or URL.
495
+ */
496
+ workerEntry?: string;
482
497
  /**
483
498
  * Player-toggleable flags this pack owns (see PackRule). The bundled qol /
484
499
  * bug-fixes mods use this to declare their fixes/tweaks for the in-app "Fixes
@@ -683,6 +698,32 @@ export function validateManifest(value: unknown): PackManifest {
683
698
  );
684
699
  }
685
700
  }
701
+ if (m["runtime"] !== undefined && m["runtime"] !== "in-process" && m["runtime"] !== "worker") {
702
+ throw new ManifestError(`manifest ${id}: runtime must be "in-process" or "worker"`);
703
+ }
704
+ if (m["workerEntry"] !== undefined) {
705
+ const entry = m["workerEntry"];
706
+ if (
707
+ typeof entry !== "string" ||
708
+ entry.length === 0 ||
709
+ entry.startsWith("/") ||
710
+ entry.includes("\\") ||
711
+ entry.split("/").some((part) => part === "" || part === "." || part === "..")
712
+ ) {
713
+ throw new ManifestError(`manifest ${id}: workerEntry must be a non-empty relative file path`);
714
+ }
715
+ }
716
+ if (m["modApi"] === 2) {
717
+ if (m["runtime"] !== "worker") {
718
+ throw new ManifestError(`manifest ${id}: modApi 2 requires runtime "worker"`);
719
+ }
720
+ if (typeof m["workerEntry"] !== "string") {
721
+ throw new ManifestError(`manifest ${id}: modApi 2 requires workerEntry`);
722
+ }
723
+ }
724
+ if (m["runtime"] === "worker" && m["modApi"] !== 2) {
725
+ throw new ManifestError(`manifest ${id}: runtime "worker" requires modApi 2`);
726
+ }
686
727
  if (
687
728
  m["nondeterministic"] !== undefined &&
688
729
  typeof m["nondeterministic"] !== "boolean"
package/src/standards.ts CHANGED
@@ -15,10 +15,13 @@
15
15
  * cannot describe a rule that is not enforced or omit one that is.
16
16
  *
17
17
  * WHAT IT DOES NOT DUPLICATE. Everything validateManifest already decides is
18
- * delegated to validateManifest. This module adds only the rules that need to see
19
- * something the manifest validator cannot: the FILE LIST. That is not a small
20
- * addition - two of the rules below are defects this project actually shipped, both
21
- * invisible to a validator with no files to look at:
18
+ * delegated to validateManifest. Capability grammar is the same: this module
19
+ * calls CapabilitySet.fromManifest rather than restating parseCapability's
20
+ * patterns. What it adds is the rules that need to see something those two
21
+ * cannot: the FILE LIST, and the join between a validated manifest and the
22
+ * capability model (validateManifest cannot import capabilities.ts without a
23
+ * cycle). Two of the file-list rules below are defects this project actually
24
+ * shipped, both invisible to a validator with no files to look at:
22
25
  *
23
26
  * - `modApi` is documented as REQUIRED of any pack shipping plugin.js, and nothing
24
27
  * enforced it, because validateManifest never sees whether plugin.js is there.
@@ -32,8 +35,9 @@
32
35
  * learn to ignore.
33
36
  */
34
37
 
35
- import { compareSemver, satisfies } from "./semver.js";
38
+ import { CapabilitySet } from "./capabilities.js";
36
39
  import { ManifestError, hasFacet, validateManifest, type PackManifest } from "./manifest.js";
40
+ import { compareSemver, satisfies } from "./semver.js";
37
41
 
38
42
  /** The file a mod's code lives in, when it has any. */
39
43
  export const PLUGIN_FILE = "plugin.js";
@@ -299,6 +303,38 @@ export const MOD_REQUIREMENTS: readonly Requirement[] = [
299
303
  }
300
304
  },
301
305
  },
306
+ {
307
+ id: "capabilities-recognized",
308
+ level: "required",
309
+ title: "Request only capabilities the game knows",
310
+ why:
311
+ "A capability string is the consent surface a player reads and the gate the " +
312
+ "runtime actually opens. An unrecognized string, or a capability on a pack " +
313
+ "that cannot execute, is refused when the game loads the plugin - after the " +
314
+ "player has already installed it. This check is CapabilitySet.fromManifest, " +
315
+ "the same function the loader calls, so a typo that used to pass here and " +
316
+ "fail there cannot.",
317
+ check: (mod) => {
318
+ const m = manifestObject(mod);
319
+ if (m === null) return null;
320
+ let manifest: PackManifest;
321
+ try {
322
+ /* validateManifest first, so this rule never re-describes the schema. A
323
+ * malformed capabilities field (not an array of strings) belongs to
324
+ * manifest-fields; this rule only speaks once there is a PackManifest
325
+ * to hand to the capability model. */
326
+ manifest = validateManifest(m);
327
+ } catch {
328
+ return null;
329
+ }
330
+ try {
331
+ CapabilitySet.fromManifest(manifest);
332
+ return null;
333
+ } catch (e) {
334
+ return e instanceof Error ? e.message : String(e);
335
+ }
336
+ },
337
+ },
302
338
  {
303
339
  id: "plugin-declares-modapi",
304
340
  level: "required",