@storylet-studio/runtime 0.6.0 → 0.8.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 +10 -0
- package/dist/index.cjs +710 -64
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +129 -21
- package/dist/index.d.ts +129 -21
- package/dist/index.js +710 -64
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { ScalarValue, ScopeResolver, ExprNode } from '@wildwinter/expr';
|
|
1
|
+
import { ScalarValue, ScopeResolver, ExprNode, EvalContext } from '@wildwinter/expr';
|
|
2
2
|
export { Prng, makePrng, shuffleInPlace } from '@wildwinter/expr';
|
|
3
|
-
import { Bundle, FlowSave, LoadReport, Card, Expression, Deck, Box, Hand, HandTemplate, TagGroup, PropertyDecl, SaveEnvelope, PropertyType, ScalarValue as ScalarValue$1 } from '@storylet-studio/model';
|
|
4
|
-
import { PropertyBag, PropertyRow } from '@wildwinter/scoperegistry';
|
|
3
|
+
import { Bundle, FlowSave, LoadReport, Card, Expression, Deck, Box, Hand, HandTemplate, TagGroup, PropertyDecl, SaveEnvelope, SaveEnvelopeV1, PropertyType, ScalarValue as ScalarValue$1 } from '@storylet-studio/model';
|
|
4
|
+
import { PropertyBag, ScopeRegistry, PropertyRow } from '@wildwinter/scoperegistry';
|
|
5
5
|
export { PropertyRow } from '@wildwinter/scoperegistry';
|
|
6
6
|
|
|
7
7
|
interface EngineOptions {
|
|
@@ -18,12 +18,25 @@ interface EngineOptions {
|
|
|
18
18
|
};
|
|
19
19
|
/**
|
|
20
20
|
* The host's resolver for @world - the values the game owns and the
|
|
21
|
-
* story reads (and, where `set` is offered, writes).
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
21
|
+
* story reads (and, where `set` is offered, writes). Engine-level, shared
|
|
22
|
+
* by all flows, never saved: the game keeps these values. Omit it and a
|
|
23
|
+
* standalone engine self-backs @world from the declared defaults, as a
|
|
24
|
+
* property its registry stores and saves. A game running several engines
|
|
25
|
+
* registers @world in its registry itself instead.
|
|
25
26
|
*/
|
|
26
27
|
world?: ScopeResolver;
|
|
28
|
+
/**
|
|
29
|
+
* The game's registry: ONE per game, holding every engine's properties
|
|
30
|
+
* except those the game keeps itself, saved once. Given one, the engine
|
|
31
|
+
* registers its own scopes in it (@story under `story`, every other bag
|
|
32
|
+
* under a key starting `storylets/`, and @world if `world` is passed),
|
|
33
|
+
* reads every other scope from it, and `saveGame()` leaves the property
|
|
34
|
+
* values to the game. @world is then the game's to register: owned if the
|
|
35
|
+
* registry should store it, foreign if the game keeps it. Omit it and the
|
|
36
|
+
* engine makes its own registry and acts as its own game: it self-backs
|
|
37
|
+
* @world, and `saveGame()` carries the registry's values too.
|
|
38
|
+
*/
|
|
39
|
+
registry?: ScopeRegistry;
|
|
27
40
|
/**
|
|
28
41
|
* Diagnostics hook (opt-in, dev tooling only): fired when `openFlow` REPLACES
|
|
29
42
|
* a flow that still had cards dealt, with the flow id and how many. The
|
|
@@ -133,7 +146,8 @@ type TraceEvent = {
|
|
|
133
146
|
card: string;
|
|
134
147
|
reason: TraceVerdict | "hand-condition" | "vanished";
|
|
135
148
|
}
|
|
136
|
-
/** Card and outcome gameIds
|
|
149
|
+
/** Card and outcome gameIds; `outcome` is "" for a card with no outcomes,
|
|
150
|
+
* played with none. */
|
|
137
151
|
| {
|
|
138
152
|
type: "play";
|
|
139
153
|
card: string;
|
|
@@ -312,9 +326,24 @@ interface Internals {
|
|
|
312
326
|
* what the shared side WOULD hold without building a bag, which is what
|
|
313
327
|
* makes previewLoad pure. */
|
|
314
328
|
sharedDecls: DeclSet;
|
|
315
|
-
/** The shared stores
|
|
329
|
+
/** The shared stores, registered in the registry for the engine's life.
|
|
330
|
+
* Reseeded in place by reset and by a load that carries values. */
|
|
316
331
|
shared: Partition;
|
|
317
|
-
/**
|
|
332
|
+
/** The game's one registry (or the engine's own, when it is standalone). */
|
|
333
|
+
registry: ScopeRegistry;
|
|
334
|
+
/** True when the engine made the registry: `saveGame()` then carries its values. */
|
|
335
|
+
ownsRegistry: boolean;
|
|
336
|
+
/** True when the engine self-backed @world (standalone, no resolver bound). */
|
|
337
|
+
selfWorld: boolean;
|
|
338
|
+
/** Every OTHER scope in the registry, as an eval context sees it (instance
|
|
339
|
+
* keys left out), rebuilt only when the registry's set of scopes moves. */
|
|
340
|
+
registryView: () => {
|
|
341
|
+
scopes: EvalContext["scopes"];
|
|
342
|
+
qualities: EvalContext["qualities"];
|
|
343
|
+
};
|
|
344
|
+
/** @world, read through the registry by name (the scope's own normalisation),
|
|
345
|
+
* so a @world the game registered folded to lower case still answers the
|
|
346
|
+
* names as authored. */
|
|
318
347
|
worldResolver: ScopeResolver;
|
|
319
348
|
/** The @world WRITE seam. `host` says the caller is the GAME's own surface -
|
|
320
349
|
* setProperty, the coverage harness, the CLI's --set - which the shared
|
|
@@ -347,10 +376,19 @@ declare class Engine {
|
|
|
347
376
|
* outlives reset/loadGame (the host's container is the host's). The
|
|
348
377
|
* self-backed resolver is rebuilt instead. */
|
|
349
378
|
private readonly hostWorld?;
|
|
379
|
+
/** The options this engine was built with: hotSwap builds its replacement from them. */
|
|
380
|
+
private readonly creationOptions;
|
|
381
|
+
/** How to register each shared scope again, in registration order: a failed
|
|
382
|
+
* hotSwap puts this engine back exactly as it was. */
|
|
383
|
+
private readonly sharedMounts;
|
|
350
384
|
constructor(bundle: Bundle, opts?: EngineOptions);
|
|
351
|
-
/** Build the shared stores and
|
|
352
|
-
* engine's
|
|
385
|
+
/** Build the shared stores, register them and @world, and set up the @world
|
|
386
|
+
* seam. Once, for the engine's life: reset and loads reseed the bags in
|
|
387
|
+
* place, so the registry never sees them come and go. */
|
|
353
388
|
private initShared;
|
|
389
|
+
/** Every shared bag back to its declared defaults, in place (the registry
|
|
390
|
+
* keeps them registered), the self-backed @world included. */
|
|
391
|
+
private reseedShared;
|
|
354
392
|
/** Quality ladders by scope for the eval channel (design/quality.md):
|
|
355
393
|
* world/story keyed by name; box/deck/value keyed by owner id then name.
|
|
356
394
|
* Built once - a bundle's declarations never change. Ladders are
|
|
@@ -361,6 +399,10 @@ declare class Engine {
|
|
|
361
399
|
* state; shared state is untouched. There is no default flow: "main" is
|
|
362
400
|
* a caller convention, not an engine rule. */
|
|
363
401
|
openFlow(id: string, opts?: OpenFlowOptions): Flow;
|
|
402
|
+
/** openFlow, and loadGame's rebuild. `claim` says the new flow's bags take
|
|
403
|
+
* the values the registry holds for them (a load); a fresh open is a reset
|
|
404
|
+
* of that name, so anything waiting for it is discarded first. */
|
|
405
|
+
private open;
|
|
364
406
|
getFlow(id: string): Flow | undefined;
|
|
365
407
|
/** Every live flow, open order. */
|
|
366
408
|
flows(): Flow[];
|
|
@@ -375,6 +417,11 @@ declare class Engine {
|
|
|
375
417
|
* self-backed @world included; a host-bound @world is the host's and is
|
|
376
418
|
* not touched). */
|
|
377
419
|
reset(): void;
|
|
420
|
+
/** End the run: clear the log, close every flow, forget spent cards. Each
|
|
421
|
+
* flow's bags leave the registry; `keepFlows` names the flows whose values
|
|
422
|
+
* are kept there for the flow that replaces them (a load into the game's
|
|
423
|
+
* registry). */
|
|
424
|
+
private dropRun;
|
|
378
425
|
/** Cards a shared `redraw: "never"` has taken out of the world, by card id.
|
|
379
426
|
* The claim ledger is DERIVED from live boards and so needs no storage;
|
|
380
427
|
* this one is durable, so it rides the save's shared half. */
|
|
@@ -417,6 +464,12 @@ declare class Engine {
|
|
|
417
464
|
* carries no flow. It reaches the run log and the engine tap; there is
|
|
418
465
|
* nowhere else for it to go, and it fires only on a legacy address. */
|
|
419
466
|
private diagnose;
|
|
467
|
+
/** Content that names another engine's scope (`@patter.visits`) runs only
|
|
468
|
+
* where that engine is on this registry: without it every read would answer
|
|
469
|
+
* false and every write fail, so the flow is refused as it opens, before
|
|
470
|
+
* anything changes. By then a game has built all of its engines, whatever
|
|
471
|
+
* order it built them in. The same message on every runtime. */
|
|
472
|
+
private assertExternalScopes;
|
|
420
473
|
/** The shared surface as examiner rows: @world (read through the
|
|
421
474
|
* resolver) then the shared partitions. Per-flow rows live on each Flow. */
|
|
422
475
|
listProperties(): PropertyRow[];
|
|
@@ -426,9 +479,33 @@ declare class Engine {
|
|
|
426
479
|
listBags(): BagMount[];
|
|
427
480
|
/** Every flow's trace, one stream, each event tagged with its flow id. */
|
|
428
481
|
subscribeTrace(handler: EngineTraceHandler): () => void;
|
|
429
|
-
/**
|
|
430
|
-
*
|
|
431
|
-
*
|
|
482
|
+
/**
|
|
483
|
+
* Live bundle refresh: rebuild on an edited bundle with the whole run carried
|
|
484
|
+
* over, and return the replacement with the report its load produced.
|
|
485
|
+
*
|
|
486
|
+
* Standalone, that is a save and a load into a new engine, and this one is left
|
|
487
|
+
* untouched (discard it). With the game's registry the two cannot both hold the
|
|
488
|
+
* same keys, so this engine is spent afterwards (its flows closed, the
|
|
489
|
+
* replacement holding everything on the same registry): it carries its
|
|
490
|
+
* own values into the snapshot, steps out of the registry, and the replacement
|
|
491
|
+
* loads them the way a standalone save loads: so the report covers the
|
|
492
|
+
* properties the edit dropped, defaulted, or retyped, and a dropped property
|
|
493
|
+
* is dropped rather than kept. Values the game loaded that were still waiting
|
|
494
|
+
* for a flow of this engine carry across as they were, and nothing belonging
|
|
495
|
+
* to any other engine is touched. A save for another project is refused before
|
|
496
|
+
* anything moves; if the rebuild fails for any other reason, this engine takes
|
|
497
|
+
* its registrations back and is left exactly as it was.
|
|
498
|
+
*/
|
|
499
|
+
hotSwap(bundle: Bundle, opts?: EngineOptions): {
|
|
500
|
+
engine: Engine;
|
|
501
|
+
report: LoadReport;
|
|
502
|
+
};
|
|
503
|
+
/** The whole engine's NON-property state, one envelope: the spent cards
|
|
504
|
+
* once, then every live flow (board, clocks, cooldowns, PRNG, play log)
|
|
505
|
+
* keyed by its id. The property values are the registry's: a standalone
|
|
506
|
+
* engine (one that made its own registry) carries them here under
|
|
507
|
+
* `registry`, self-backed @world included; a game that passed a registry
|
|
508
|
+
* saves it once itself, beside each engine's envelope. */
|
|
432
509
|
saveGame(): SaveEnvelope;
|
|
433
510
|
/** ONE flow's blob, to park a visit that is walking away: the same shape
|
|
434
511
|
* the envelope carries per flow, and the same shape `openFlow`'s `restore`
|
|
@@ -441,7 +518,7 @@ declare class Engine {
|
|
|
441
518
|
* doing any of it (design/engine-server.md 4.9). Pure: nothing on this
|
|
442
519
|
* engine moves. A project mismatch is refused here exactly as `loadGame`
|
|
443
520
|
* refuses it - it is the one thing neither call will tolerate. */
|
|
444
|
-
previewLoad(envelope: SaveEnvelope): LoadReport;
|
|
521
|
+
previewLoad(envelope: SaveEnvelope | SaveEnvelopeV1): LoadReport;
|
|
445
522
|
/** What `openFlow(id, { restore: saved })` would do to a flow of that name,
|
|
446
523
|
* without doing it: the same report shape, since a visit parked under one
|
|
447
524
|
* build and resumed under the next raises the same questions. Pure. */
|
|
@@ -450,10 +527,18 @@ declare class Engine {
|
|
|
450
527
|
* Handles held from before the load are closed and inert (Patter's
|
|
451
528
|
* rule); take fresh ones from getFlow()/flows().
|
|
452
529
|
*
|
|
530
|
+
* Property values come from the registry. An envelope that carries them
|
|
531
|
+
* (a standalone engine's, or a version 1 envelope) has them walked, cleaned,
|
|
532
|
+
* and moved into the registry here, over fresh defaults. Otherwise the game
|
|
533
|
+
* loads its registry itself, before or after this call: each flow's bags
|
|
534
|
+
* are handed back to the registry with their values, and the restored
|
|
535
|
+
* flows claim them. The report then covers only what this envelope holds;
|
|
536
|
+
* the registry's own load rule applies to the values.
|
|
537
|
+
*
|
|
453
538
|
* Returns the report `previewLoad` would have given for this envelope: the
|
|
454
539
|
* drift tolerance that makes a load forgiving is what hides its cost, so
|
|
455
540
|
* the cost comes back with the load whether or not anybody looked first. */
|
|
456
|
-
loadGame(envelope: SaveEnvelope): LoadReport;
|
|
541
|
+
loadGame(envelope: SaveEnvelope | SaveEnvelopeV1): LoadReport;
|
|
457
542
|
private assertSameProject;
|
|
458
543
|
/** The whole-envelope walk: the report, and the cleaned state the apply
|
|
459
544
|
* half writes. Nothing here touches the engine, which is what lets
|
|
@@ -481,8 +566,12 @@ declare class Flow {
|
|
|
481
566
|
private lastPlayOf;
|
|
482
567
|
private tagPlayCount;
|
|
483
568
|
private lastPlayInTag;
|
|
484
|
-
/** The per-flow property partitions (the not-shared halves)
|
|
569
|
+
/** The per-flow property partitions (the not-shared halves), each bag
|
|
570
|
+
* that declares something registered under this flow's keys. */
|
|
485
571
|
private stores;
|
|
572
|
+
private readonly registered;
|
|
573
|
+
/** This flow's bags that declare something, under their registry keys. */
|
|
574
|
+
private readonly bagKeys;
|
|
486
575
|
private traceHandlers;
|
|
487
576
|
private logEntries;
|
|
488
577
|
private logSeq;
|
|
@@ -500,6 +589,15 @@ declare class Flow {
|
|
|
500
589
|
close(): void;
|
|
501
590
|
/** @internal */
|
|
502
591
|
markClosed(): void;
|
|
592
|
+
/** @internal - take this flow's bags out of the registry; with `keep`, their
|
|
593
|
+
* values wait there for the flow that replaces this one (a load into the
|
|
594
|
+
* game's registry). Idempotent. */
|
|
595
|
+
releaseBags(keep: boolean): void;
|
|
596
|
+
/** @internal - the registry keys this flow holds right now. */
|
|
597
|
+
registeredKeys(): string[];
|
|
598
|
+
/** @internal - register this flow's bags (again): at construction, and when a
|
|
599
|
+
* failed hotSwap hands them back. Each claims what the registry holds for it. */
|
|
600
|
+
mountBags(): void;
|
|
503
601
|
private assertOpen;
|
|
504
602
|
/** A box's current turn (schema 3.4), on THIS flow's clock. */
|
|
505
603
|
turn(boxRef: string): number;
|
|
@@ -660,7 +758,16 @@ declare class Flow {
|
|
|
660
758
|
outcomes(cardId: string, from: string): OutcomeView[];
|
|
661
759
|
/** Apply an outcome (schema 3.7): the card must sit in a hand on the
|
|
662
760
|
* board (you never play a card from inside the deck). Throws before any
|
|
663
|
-
* mutation on a gated-shut outcome or a bad write target.
|
|
761
|
+
* mutation on a gated-shut outcome or a bad write target.
|
|
762
|
+
*
|
|
763
|
+
* A card with NO outcomes is played with none, named as "" (the
|
|
764
|
+
* no-outcome-play brief, 2026-09-14): a masthead, a notice, a codex entry,
|
|
765
|
+
* whose play means "shown". It is everything a play is except the writes:
|
|
766
|
+
* the play log and the history functions count it, the box's turn moves by
|
|
767
|
+
* the usual rule, the redraw rests it and it leaves its hand. "" is the one
|
|
768
|
+
* spelling in all four runtimes, because a Blueprint pin cannot be absent.
|
|
769
|
+
* Only the empty-for-empty case is new: "" on a card that has outcomes is
|
|
770
|
+
* refused, and a named outcome on a card with none is refused as before. */
|
|
664
771
|
play(cardId: string, outcomeGameId: string, from: string, opts?: PlayOptions): void;
|
|
665
772
|
/** One owned property's address, owner segment and all (4.4). */
|
|
666
773
|
private address;
|
|
@@ -699,8 +806,9 @@ declare class Flow {
|
|
|
699
806
|
getProperty(path: string): ScalarValue;
|
|
700
807
|
setProperty(path: string, value: ScalarValue): void;
|
|
701
808
|
private resolvePath;
|
|
702
|
-
/** @internal - this flow's blob inside the engine's envelope
|
|
703
|
-
|
|
809
|
+
/** @internal - this flow's blob: inside the engine's envelope without its
|
|
810
|
+
* properties (the registry has them), or parked whole by saveFlow. */
|
|
811
|
+
snapshot(withProps: boolean): FlowSave;
|
|
704
812
|
/** @internal - restore a freshly opened flow from its blob (loadGame).
|
|
705
813
|
* Orphaned keys (deleted entities) drop; new declarations keep defaults. */
|
|
706
814
|
restore(saved: FlowSave): void;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { ScalarValue, ScopeResolver, ExprNode } from '@wildwinter/expr';
|
|
1
|
+
import { ScalarValue, ScopeResolver, ExprNode, EvalContext } from '@wildwinter/expr';
|
|
2
2
|
export { Prng, makePrng, shuffleInPlace } from '@wildwinter/expr';
|
|
3
|
-
import { Bundle, FlowSave, LoadReport, Card, Expression, Deck, Box, Hand, HandTemplate, TagGroup, PropertyDecl, SaveEnvelope, PropertyType, ScalarValue as ScalarValue$1 } from '@storylet-studio/model';
|
|
4
|
-
import { PropertyBag, PropertyRow } from '@wildwinter/scoperegistry';
|
|
3
|
+
import { Bundle, FlowSave, LoadReport, Card, Expression, Deck, Box, Hand, HandTemplate, TagGroup, PropertyDecl, SaveEnvelope, SaveEnvelopeV1, PropertyType, ScalarValue as ScalarValue$1 } from '@storylet-studio/model';
|
|
4
|
+
import { PropertyBag, ScopeRegistry, PropertyRow } from '@wildwinter/scoperegistry';
|
|
5
5
|
export { PropertyRow } from '@wildwinter/scoperegistry';
|
|
6
6
|
|
|
7
7
|
interface EngineOptions {
|
|
@@ -18,12 +18,25 @@ interface EngineOptions {
|
|
|
18
18
|
};
|
|
19
19
|
/**
|
|
20
20
|
* The host's resolver for @world - the values the game owns and the
|
|
21
|
-
* story reads (and, where `set` is offered, writes).
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
21
|
+
* story reads (and, where `set` is offered, writes). Engine-level, shared
|
|
22
|
+
* by all flows, never saved: the game keeps these values. Omit it and a
|
|
23
|
+
* standalone engine self-backs @world from the declared defaults, as a
|
|
24
|
+
* property its registry stores and saves. A game running several engines
|
|
25
|
+
* registers @world in its registry itself instead.
|
|
25
26
|
*/
|
|
26
27
|
world?: ScopeResolver;
|
|
28
|
+
/**
|
|
29
|
+
* The game's registry: ONE per game, holding every engine's properties
|
|
30
|
+
* except those the game keeps itself, saved once. Given one, the engine
|
|
31
|
+
* registers its own scopes in it (@story under `story`, every other bag
|
|
32
|
+
* under a key starting `storylets/`, and @world if `world` is passed),
|
|
33
|
+
* reads every other scope from it, and `saveGame()` leaves the property
|
|
34
|
+
* values to the game. @world is then the game's to register: owned if the
|
|
35
|
+
* registry should store it, foreign if the game keeps it. Omit it and the
|
|
36
|
+
* engine makes its own registry and acts as its own game: it self-backs
|
|
37
|
+
* @world, and `saveGame()` carries the registry's values too.
|
|
38
|
+
*/
|
|
39
|
+
registry?: ScopeRegistry;
|
|
27
40
|
/**
|
|
28
41
|
* Diagnostics hook (opt-in, dev tooling only): fired when `openFlow` REPLACES
|
|
29
42
|
* a flow that still had cards dealt, with the flow id and how many. The
|
|
@@ -133,7 +146,8 @@ type TraceEvent = {
|
|
|
133
146
|
card: string;
|
|
134
147
|
reason: TraceVerdict | "hand-condition" | "vanished";
|
|
135
148
|
}
|
|
136
|
-
/** Card and outcome gameIds
|
|
149
|
+
/** Card and outcome gameIds; `outcome` is "" for a card with no outcomes,
|
|
150
|
+
* played with none. */
|
|
137
151
|
| {
|
|
138
152
|
type: "play";
|
|
139
153
|
card: string;
|
|
@@ -312,9 +326,24 @@ interface Internals {
|
|
|
312
326
|
* what the shared side WOULD hold without building a bag, which is what
|
|
313
327
|
* makes previewLoad pure. */
|
|
314
328
|
sharedDecls: DeclSet;
|
|
315
|
-
/** The shared stores
|
|
329
|
+
/** The shared stores, registered in the registry for the engine's life.
|
|
330
|
+
* Reseeded in place by reset and by a load that carries values. */
|
|
316
331
|
shared: Partition;
|
|
317
|
-
/**
|
|
332
|
+
/** The game's one registry (or the engine's own, when it is standalone). */
|
|
333
|
+
registry: ScopeRegistry;
|
|
334
|
+
/** True when the engine made the registry: `saveGame()` then carries its values. */
|
|
335
|
+
ownsRegistry: boolean;
|
|
336
|
+
/** True when the engine self-backed @world (standalone, no resolver bound). */
|
|
337
|
+
selfWorld: boolean;
|
|
338
|
+
/** Every OTHER scope in the registry, as an eval context sees it (instance
|
|
339
|
+
* keys left out), rebuilt only when the registry's set of scopes moves. */
|
|
340
|
+
registryView: () => {
|
|
341
|
+
scopes: EvalContext["scopes"];
|
|
342
|
+
qualities: EvalContext["qualities"];
|
|
343
|
+
};
|
|
344
|
+
/** @world, read through the registry by name (the scope's own normalisation),
|
|
345
|
+
* so a @world the game registered folded to lower case still answers the
|
|
346
|
+
* names as authored. */
|
|
318
347
|
worldResolver: ScopeResolver;
|
|
319
348
|
/** The @world WRITE seam. `host` says the caller is the GAME's own surface -
|
|
320
349
|
* setProperty, the coverage harness, the CLI's --set - which the shared
|
|
@@ -347,10 +376,19 @@ declare class Engine {
|
|
|
347
376
|
* outlives reset/loadGame (the host's container is the host's). The
|
|
348
377
|
* self-backed resolver is rebuilt instead. */
|
|
349
378
|
private readonly hostWorld?;
|
|
379
|
+
/** The options this engine was built with: hotSwap builds its replacement from them. */
|
|
380
|
+
private readonly creationOptions;
|
|
381
|
+
/** How to register each shared scope again, in registration order: a failed
|
|
382
|
+
* hotSwap puts this engine back exactly as it was. */
|
|
383
|
+
private readonly sharedMounts;
|
|
350
384
|
constructor(bundle: Bundle, opts?: EngineOptions);
|
|
351
|
-
/** Build the shared stores and
|
|
352
|
-
* engine's
|
|
385
|
+
/** Build the shared stores, register them and @world, and set up the @world
|
|
386
|
+
* seam. Once, for the engine's life: reset and loads reseed the bags in
|
|
387
|
+
* place, so the registry never sees them come and go. */
|
|
353
388
|
private initShared;
|
|
389
|
+
/** Every shared bag back to its declared defaults, in place (the registry
|
|
390
|
+
* keeps them registered), the self-backed @world included. */
|
|
391
|
+
private reseedShared;
|
|
354
392
|
/** Quality ladders by scope for the eval channel (design/quality.md):
|
|
355
393
|
* world/story keyed by name; box/deck/value keyed by owner id then name.
|
|
356
394
|
* Built once - a bundle's declarations never change. Ladders are
|
|
@@ -361,6 +399,10 @@ declare class Engine {
|
|
|
361
399
|
* state; shared state is untouched. There is no default flow: "main" is
|
|
362
400
|
* a caller convention, not an engine rule. */
|
|
363
401
|
openFlow(id: string, opts?: OpenFlowOptions): Flow;
|
|
402
|
+
/** openFlow, and loadGame's rebuild. `claim` says the new flow's bags take
|
|
403
|
+
* the values the registry holds for them (a load); a fresh open is a reset
|
|
404
|
+
* of that name, so anything waiting for it is discarded first. */
|
|
405
|
+
private open;
|
|
364
406
|
getFlow(id: string): Flow | undefined;
|
|
365
407
|
/** Every live flow, open order. */
|
|
366
408
|
flows(): Flow[];
|
|
@@ -375,6 +417,11 @@ declare class Engine {
|
|
|
375
417
|
* self-backed @world included; a host-bound @world is the host's and is
|
|
376
418
|
* not touched). */
|
|
377
419
|
reset(): void;
|
|
420
|
+
/** End the run: clear the log, close every flow, forget spent cards. Each
|
|
421
|
+
* flow's bags leave the registry; `keepFlows` names the flows whose values
|
|
422
|
+
* are kept there for the flow that replaces them (a load into the game's
|
|
423
|
+
* registry). */
|
|
424
|
+
private dropRun;
|
|
378
425
|
/** Cards a shared `redraw: "never"` has taken out of the world, by card id.
|
|
379
426
|
* The claim ledger is DERIVED from live boards and so needs no storage;
|
|
380
427
|
* this one is durable, so it rides the save's shared half. */
|
|
@@ -417,6 +464,12 @@ declare class Engine {
|
|
|
417
464
|
* carries no flow. It reaches the run log and the engine tap; there is
|
|
418
465
|
* nowhere else for it to go, and it fires only on a legacy address. */
|
|
419
466
|
private diagnose;
|
|
467
|
+
/** Content that names another engine's scope (`@patter.visits`) runs only
|
|
468
|
+
* where that engine is on this registry: without it every read would answer
|
|
469
|
+
* false and every write fail, so the flow is refused as it opens, before
|
|
470
|
+
* anything changes. By then a game has built all of its engines, whatever
|
|
471
|
+
* order it built them in. The same message on every runtime. */
|
|
472
|
+
private assertExternalScopes;
|
|
420
473
|
/** The shared surface as examiner rows: @world (read through the
|
|
421
474
|
* resolver) then the shared partitions. Per-flow rows live on each Flow. */
|
|
422
475
|
listProperties(): PropertyRow[];
|
|
@@ -426,9 +479,33 @@ declare class Engine {
|
|
|
426
479
|
listBags(): BagMount[];
|
|
427
480
|
/** Every flow's trace, one stream, each event tagged with its flow id. */
|
|
428
481
|
subscribeTrace(handler: EngineTraceHandler): () => void;
|
|
429
|
-
/**
|
|
430
|
-
*
|
|
431
|
-
*
|
|
482
|
+
/**
|
|
483
|
+
* Live bundle refresh: rebuild on an edited bundle with the whole run carried
|
|
484
|
+
* over, and return the replacement with the report its load produced.
|
|
485
|
+
*
|
|
486
|
+
* Standalone, that is a save and a load into a new engine, and this one is left
|
|
487
|
+
* untouched (discard it). With the game's registry the two cannot both hold the
|
|
488
|
+
* same keys, so this engine is spent afterwards (its flows closed, the
|
|
489
|
+
* replacement holding everything on the same registry): it carries its
|
|
490
|
+
* own values into the snapshot, steps out of the registry, and the replacement
|
|
491
|
+
* loads them the way a standalone save loads: so the report covers the
|
|
492
|
+
* properties the edit dropped, defaulted, or retyped, and a dropped property
|
|
493
|
+
* is dropped rather than kept. Values the game loaded that were still waiting
|
|
494
|
+
* for a flow of this engine carry across as they were, and nothing belonging
|
|
495
|
+
* to any other engine is touched. A save for another project is refused before
|
|
496
|
+
* anything moves; if the rebuild fails for any other reason, this engine takes
|
|
497
|
+
* its registrations back and is left exactly as it was.
|
|
498
|
+
*/
|
|
499
|
+
hotSwap(bundle: Bundle, opts?: EngineOptions): {
|
|
500
|
+
engine: Engine;
|
|
501
|
+
report: LoadReport;
|
|
502
|
+
};
|
|
503
|
+
/** The whole engine's NON-property state, one envelope: the spent cards
|
|
504
|
+
* once, then every live flow (board, clocks, cooldowns, PRNG, play log)
|
|
505
|
+
* keyed by its id. The property values are the registry's: a standalone
|
|
506
|
+
* engine (one that made its own registry) carries them here under
|
|
507
|
+
* `registry`, self-backed @world included; a game that passed a registry
|
|
508
|
+
* saves it once itself, beside each engine's envelope. */
|
|
432
509
|
saveGame(): SaveEnvelope;
|
|
433
510
|
/** ONE flow's blob, to park a visit that is walking away: the same shape
|
|
434
511
|
* the envelope carries per flow, and the same shape `openFlow`'s `restore`
|
|
@@ -441,7 +518,7 @@ declare class Engine {
|
|
|
441
518
|
* doing any of it (design/engine-server.md 4.9). Pure: nothing on this
|
|
442
519
|
* engine moves. A project mismatch is refused here exactly as `loadGame`
|
|
443
520
|
* refuses it - it is the one thing neither call will tolerate. */
|
|
444
|
-
previewLoad(envelope: SaveEnvelope): LoadReport;
|
|
521
|
+
previewLoad(envelope: SaveEnvelope | SaveEnvelopeV1): LoadReport;
|
|
445
522
|
/** What `openFlow(id, { restore: saved })` would do to a flow of that name,
|
|
446
523
|
* without doing it: the same report shape, since a visit parked under one
|
|
447
524
|
* build and resumed under the next raises the same questions. Pure. */
|
|
@@ -450,10 +527,18 @@ declare class Engine {
|
|
|
450
527
|
* Handles held from before the load are closed and inert (Patter's
|
|
451
528
|
* rule); take fresh ones from getFlow()/flows().
|
|
452
529
|
*
|
|
530
|
+
* Property values come from the registry. An envelope that carries them
|
|
531
|
+
* (a standalone engine's, or a version 1 envelope) has them walked, cleaned,
|
|
532
|
+
* and moved into the registry here, over fresh defaults. Otherwise the game
|
|
533
|
+
* loads its registry itself, before or after this call: each flow's bags
|
|
534
|
+
* are handed back to the registry with their values, and the restored
|
|
535
|
+
* flows claim them. The report then covers only what this envelope holds;
|
|
536
|
+
* the registry's own load rule applies to the values.
|
|
537
|
+
*
|
|
453
538
|
* Returns the report `previewLoad` would have given for this envelope: the
|
|
454
539
|
* drift tolerance that makes a load forgiving is what hides its cost, so
|
|
455
540
|
* the cost comes back with the load whether or not anybody looked first. */
|
|
456
|
-
loadGame(envelope: SaveEnvelope): LoadReport;
|
|
541
|
+
loadGame(envelope: SaveEnvelope | SaveEnvelopeV1): LoadReport;
|
|
457
542
|
private assertSameProject;
|
|
458
543
|
/** The whole-envelope walk: the report, and the cleaned state the apply
|
|
459
544
|
* half writes. Nothing here touches the engine, which is what lets
|
|
@@ -481,8 +566,12 @@ declare class Flow {
|
|
|
481
566
|
private lastPlayOf;
|
|
482
567
|
private tagPlayCount;
|
|
483
568
|
private lastPlayInTag;
|
|
484
|
-
/** The per-flow property partitions (the not-shared halves)
|
|
569
|
+
/** The per-flow property partitions (the not-shared halves), each bag
|
|
570
|
+
* that declares something registered under this flow's keys. */
|
|
485
571
|
private stores;
|
|
572
|
+
private readonly registered;
|
|
573
|
+
/** This flow's bags that declare something, under their registry keys. */
|
|
574
|
+
private readonly bagKeys;
|
|
486
575
|
private traceHandlers;
|
|
487
576
|
private logEntries;
|
|
488
577
|
private logSeq;
|
|
@@ -500,6 +589,15 @@ declare class Flow {
|
|
|
500
589
|
close(): void;
|
|
501
590
|
/** @internal */
|
|
502
591
|
markClosed(): void;
|
|
592
|
+
/** @internal - take this flow's bags out of the registry; with `keep`, their
|
|
593
|
+
* values wait there for the flow that replaces this one (a load into the
|
|
594
|
+
* game's registry). Idempotent. */
|
|
595
|
+
releaseBags(keep: boolean): void;
|
|
596
|
+
/** @internal - the registry keys this flow holds right now. */
|
|
597
|
+
registeredKeys(): string[];
|
|
598
|
+
/** @internal - register this flow's bags (again): at construction, and when a
|
|
599
|
+
* failed hotSwap hands them back. Each claims what the registry holds for it. */
|
|
600
|
+
mountBags(): void;
|
|
503
601
|
private assertOpen;
|
|
504
602
|
/** A box's current turn (schema 3.4), on THIS flow's clock. */
|
|
505
603
|
turn(boxRef: string): number;
|
|
@@ -660,7 +758,16 @@ declare class Flow {
|
|
|
660
758
|
outcomes(cardId: string, from: string): OutcomeView[];
|
|
661
759
|
/** Apply an outcome (schema 3.7): the card must sit in a hand on the
|
|
662
760
|
* board (you never play a card from inside the deck). Throws before any
|
|
663
|
-
* mutation on a gated-shut outcome or a bad write target.
|
|
761
|
+
* mutation on a gated-shut outcome or a bad write target.
|
|
762
|
+
*
|
|
763
|
+
* A card with NO outcomes is played with none, named as "" (the
|
|
764
|
+
* no-outcome-play brief, 2026-09-14): a masthead, a notice, a codex entry,
|
|
765
|
+
* whose play means "shown". It is everything a play is except the writes:
|
|
766
|
+
* the play log and the history functions count it, the box's turn moves by
|
|
767
|
+
* the usual rule, the redraw rests it and it leaves its hand. "" is the one
|
|
768
|
+
* spelling in all four runtimes, because a Blueprint pin cannot be absent.
|
|
769
|
+
* Only the empty-for-empty case is new: "" on a card that has outcomes is
|
|
770
|
+
* refused, and a named outcome on a card with none is refused as before. */
|
|
664
771
|
play(cardId: string, outcomeGameId: string, from: string, opts?: PlayOptions): void;
|
|
665
772
|
/** One owned property's address, owner segment and all (4.4). */
|
|
666
773
|
private address;
|
|
@@ -699,8 +806,9 @@ declare class Flow {
|
|
|
699
806
|
getProperty(path: string): ScalarValue;
|
|
700
807
|
setProperty(path: string, value: ScalarValue): void;
|
|
701
808
|
private resolvePath;
|
|
702
|
-
/** @internal - this flow's blob inside the engine's envelope
|
|
703
|
-
|
|
809
|
+
/** @internal - this flow's blob: inside the engine's envelope without its
|
|
810
|
+
* properties (the registry has them), or parked whole by saveFlow. */
|
|
811
|
+
snapshot(withProps: boolean): FlowSave;
|
|
704
812
|
/** @internal - restore a freshly opened flow from its blob (loadGame).
|
|
705
813
|
* Orphaned keys (deleted entities) drop; new declarations keep defaults. */
|
|
706
814
|
restore(saved: FlowSave): void;
|