@rohal12/spindle 0.51.4 → 0.52.1

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.
Files changed (69) hide show
  1. package/dist/pkg/format.js +1 -1
  2. package/dist/pkg/headless.js +4512 -1702
  3. package/dist/pkg/macro-registry.json +7 -7
  4. package/dist/pkg/story-variables.js +1636 -177
  5. package/package.json +5 -2
  6. package/src/action-registry.ts +70 -18
  7. package/src/automation/runner.ts +2 -1
  8. package/src/class-registry.ts +214 -103
  9. package/src/components/Passage.tsx +2 -2
  10. package/src/components/PassageDialog.tsx +2 -5
  11. package/src/components/StoryInterface.tsx +2 -4
  12. package/src/components/macros/Button.tsx +5 -31
  13. package/src/components/macros/Checkbox.tsx +7 -4
  14. package/src/components/macros/Computed.tsx +28 -14
  15. package/src/components/macros/For.tsx +29 -3
  16. package/src/components/macros/If.tsx +8 -0
  17. package/src/components/macros/Include.tsx +7 -6
  18. package/src/components/macros/MacroError.tsx +2 -1
  19. package/src/components/macros/MacroLink.tsx +12 -45
  20. package/src/components/macros/Meter.tsx +11 -3
  21. package/src/components/macros/Nobr.tsx +1 -0
  22. package/src/components/macros/PassageDisplay.tsx +3 -0
  23. package/src/components/macros/Print.tsx +4 -0
  24. package/src/components/macros/Radiobutton.tsx +5 -2
  25. package/src/components/macros/SaveManager.tsx +25 -8
  26. package/src/components/macros/Set.tsx +5 -4
  27. package/src/components/macros/Span.tsx +1 -0
  28. package/src/components/macros/StoryTitle.tsx +1 -0
  29. package/src/components/macros/Switch.tsx +13 -0
  30. package/src/components/macros/Unset.tsx +31 -10
  31. package/src/components/macros/VarDisplay.tsx +21 -4
  32. package/src/components/macros/Widget.tsx +20 -1
  33. package/src/components/macros/WidgetInvocation.tsx +17 -75
  34. package/src/components/macros/arg-utils.ts +107 -1
  35. package/src/components/macros/detached-body.tsx +68 -0
  36. package/src/components/macros/option-utils.ts +3 -2
  37. package/src/define-macro.ts +32 -5
  38. package/src/execute-mutation.ts +271 -68
  39. package/src/expression.ts +91 -59
  40. package/src/hooks/use-action.ts +24 -3
  41. package/src/hooks/use-interpolate.ts +36 -5
  42. package/src/index.tsx +12 -2
  43. package/src/interpolation.ts +394 -96
  44. package/src/js-lexer.ts +1231 -97
  45. package/src/markup/ast.ts +7 -2
  46. package/src/markup/code-attributes.ts +64 -0
  47. package/src/markup/markdown.ts +188 -9
  48. package/src/markup/render.tsx +430 -49
  49. package/src/markup/tokenizer.ts +578 -110
  50. package/src/prng.ts +41 -8
  51. package/src/registry.ts +35 -0
  52. package/src/saves/save-manager.ts +346 -160
  53. package/src/saves/storage.ts +16 -7
  54. package/src/saves/types.ts +2 -1
  55. package/src/settings.ts +12 -9
  56. package/src/store.ts +640 -186
  57. package/src/story-api.ts +38 -73
  58. package/src/story-init.ts +1 -1
  59. package/src/story-variables.ts +98 -102
  60. package/src/triggers.ts +10 -12
  61. package/src/utils/counts.ts +45 -0
  62. package/src/utils/error-message.ts +12 -0
  63. package/src/utils/live-locals.ts +10 -3
  64. package/src/utils/namespace.ts +71 -0
  65. package/src/utils/object-path.ts +99 -14
  66. package/src/utils/stable-key.ts +14 -9
  67. package/src/widgets/widget-registry.ts +9 -0
  68. package/types/index.d.ts +43 -7
  69. package/types/tooling.d.ts +1 -0
package/src/prng.ts CHANGED
@@ -9,11 +9,13 @@
9
9
  // Core algorithm
10
10
  // ---------------------------------------------------------------------------
11
11
 
12
+ const MULBERRY_STEP = 0x6d2b79f5;
13
+
12
14
  /** Mulberry32: fast, high-quality 32-bit PRNG. */
13
15
  function mulberry32(seed: number): () => number {
14
16
  let t = seed | 0;
15
17
  return () => {
16
- t = (t + 0x6d2b79f5) | 0;
18
+ t = (t + MULBERRY_STEP) | 0;
17
19
  let r = Math.imul(t ^ (t >>> 15), t | 1);
18
20
  r ^= r + Math.imul(r ^ (r >>> 7), r | 61);
19
21
  return ((r ^ (r >>> 14)) >>> 0) / 0x100000000;
@@ -38,6 +40,8 @@ let enabled = false;
38
40
  let currentSeed = '';
39
41
  let currentPull = 0;
40
42
  let generator: (() => number) | null = null;
43
+ /** Incremented whenever the generator is replaced (init, restore, reset). */
44
+ let generation = 0;
41
45
 
42
46
  // ---------------------------------------------------------------------------
43
47
  // Public API
@@ -69,6 +73,7 @@ export function initPRNG(seed?: string, useEntropy = true): void {
69
73
  currentPull = 0;
70
74
  generator = mulberry32(hashSeed(resolvedSeed));
71
75
  enabled = true;
76
+ generation++;
72
77
  }
73
78
 
74
79
  /**
@@ -76,26 +81,54 @@ export function initPRNG(seed?: string, useEntropy = true): void {
76
81
  * Recreates the generator from the seed and fast-forwards to the saved pull count.
77
82
  */
78
83
  export function restorePRNG(seed: string, pull: number): void {
84
+ generation++;
85
+ moveTo(seed, pull);
86
+ }
87
+
88
+ /** Recreate the generator at `pull` pulls from `seed`. */
89
+ function moveTo(seed: string, pull: number): void {
79
90
  currentSeed = seed;
80
- currentPull = 0;
81
- generator = mulberry32(hashSeed(seed));
91
+ // Fast-forward in constant time: each pull adds MULBERRY_STEP to the
92
+ // 32-bit state, so `pull` pulls add pull × MULBERRY_STEP (mod 2^32). A
93
+ // loop would hang on the large counts an imported save may hold.
94
+ const steps = Math.imul(pull % 0x100000000, MULBERRY_STEP);
95
+ generator = mulberry32((hashSeed(seed) + steps) | 0);
82
96
  enabled = true;
83
-
84
- // Fast-forward
85
- for (let i = 0; i < pull; i++) {
86
- generator();
87
- }
88
97
  currentPull = pull;
89
98
  }
90
99
 
91
100
  /** Disable the PRNG (used on restart before StoryInit re-enables it). */
92
101
  export function resetPRNG(): void {
102
+ generation++;
93
103
  enabled = false;
94
104
  currentSeed = '';
95
105
  currentPull = 0;
96
106
  generator = null;
97
107
  }
98
108
 
109
+ /**
110
+ * Run `fn` without its draws advancing the sequence: the draws it makes are
111
+ * taken back afterwards (also when it throws), so the next draws return the
112
+ * same values again. Save and load event handlers run like this, so that
113
+ * saving or loading never shifts the story's random sequence (a load replays
114
+ * the saved passage from its entry state, without the save's handlers). A
115
+ * generator `fn` replaced (a restart or load it started) is kept.
116
+ */
117
+ export function withoutDraws<T>(fn: () => T): T {
118
+ const start = { generation, seed: currentSeed, pull: currentPull };
119
+ try {
120
+ return fn();
121
+ } finally {
122
+ if (
123
+ enabled &&
124
+ generation === start.generation &&
125
+ currentPull !== start.pull
126
+ ) {
127
+ moveTo(start.seed, start.pull);
128
+ }
129
+ }
130
+ }
131
+
99
132
  /** Returns the current PRNG state for snapshotting, or null if disabled. */
100
133
  export function snapshotPRNG(): PRNGSnapshot | null {
101
134
  if (!enabled) return null;
package/src/registry.ts CHANGED
@@ -22,6 +22,41 @@ export function getMacro(name: string): ComponentType<MacroProps> | undefined {
22
22
  return registry.get(name.toLowerCase());
23
23
  }
24
24
 
25
+ /**
26
+ * What a macro's text form gets: see MacroDefinition.text. Evaluation runs
27
+ * in the scope of the attribute (or label) being resolved.
28
+ */
29
+ export interface MacroTextContext {
30
+ /** Evaluate an expression in the current scope. */
31
+ evaluate: (expr: string) => unknown;
32
+ /**
33
+ * The text of AST nodes (a body or branch) in the current scope, with
34
+ * `locals` (keys without `@`) added on top of the current locals.
35
+ */
36
+ renderText: (nodes: ASTNode[], locals?: Record<string, unknown>) => string;
37
+ }
38
+
39
+ /** A macro's text form: the string it stands for in text-only markup. */
40
+ export type MacroTextRenderer = (
41
+ props: MacroProps,
42
+ ctx: MacroTextContext,
43
+ ) => string;
44
+
45
+ const textRegistry = new Map<string, MacroTextRenderer>();
46
+
47
+ /** Register (or, with undefined, remove) the text form of a macro. */
48
+ export function registerMacroText(
49
+ name: string,
50
+ text: MacroTextRenderer | undefined,
51
+ ): void {
52
+ if (text) textRegistry.set(name.toLowerCase(), text);
53
+ else textRegistry.delete(name.toLowerCase());
54
+ }
55
+
56
+ export function getMacroText(name: string): MacroTextRenderer | undefined {
57
+ return textRegistry.get(name.toLowerCase());
58
+ }
59
+
25
60
  const subMacros = new Set<string>();
26
61
 
27
62
  export function registerSubMacro(name: string): void {