@world-engines/tmw 0.1.0-alpha.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.
Files changed (120) hide show
  1. package/LICENSE +46 -0
  2. package/dist/activation.d.ts +50 -0
  3. package/dist/activation.js +113 -0
  4. package/dist/audio-presentation-projection.d.ts +23 -0
  5. package/dist/audio-presentation-projection.js +39 -0
  6. package/dist/author-scenario-source-v3.d.ts +34 -0
  7. package/dist/author-scenario-source-v3.js +1 -0
  8. package/dist/authoring-runtime.d.ts +76 -0
  9. package/dist/authoring-runtime.js +282 -0
  10. package/dist/builder-diagnostics.d.ts +3 -0
  11. package/dist/builder-diagnostics.js +8 -0
  12. package/dist/builder-node.d.ts +178 -0
  13. package/dist/builder-node.js +212 -0
  14. package/dist/builder.d.ts +32 -0
  15. package/dist/builder.js +127 -0
  16. package/dist/capability-registry.d.ts +32 -0
  17. package/dist/capability-registry.js +33 -0
  18. package/dist/compile-target-profile.d.ts +16 -0
  19. package/dist/compile-target-profile.js +70 -0
  20. package/dist/compiled-plan.d.ts +44 -0
  21. package/dist/compiled-plan.js +1 -0
  22. package/dist/compiler-compatibility-gate.d.ts +96 -0
  23. package/dist/compiler-compatibility-gate.js +609 -0
  24. package/dist/compiler-release-authority.d.ts +212 -0
  25. package/dist/compiler-release-authority.js +676 -0
  26. package/dist/compiler-stage-cache.d.ts +42 -0
  27. package/dist/compiler-stage-cache.js +100 -0
  28. package/dist/compiler-stage-registry.d.ts +48 -0
  29. package/dist/compiler-stage-registry.js +126 -0
  30. package/dist/compiler.d.ts +7 -0
  31. package/dist/compiler.js +68 -0
  32. package/dist/condition-graph.d.ts +46 -0
  33. package/dist/condition-graph.js +135 -0
  34. package/dist/diagnostics.d.ts +20 -0
  35. package/dist/diagnostics.js +20 -0
  36. package/dist/durable-intent.d.ts +29 -0
  37. package/dist/durable-intent.js +60 -0
  38. package/dist/effect-flow.d.ts +69 -0
  39. package/dist/effect-flow.js +98 -0
  40. package/dist/event-fabric.d.ts +81 -0
  41. package/dist/event-fabric.js +227 -0
  42. package/dist/event-lifecycle.d.ts +59 -0
  43. package/dist/event-lifecycle.js +161 -0
  44. package/dist/evidence.d.ts +37 -0
  45. package/dist/evidence.js +92 -0
  46. package/dist/gameplay-state.d.ts +127 -0
  47. package/dist/gameplay-state.js +214 -0
  48. package/dist/index.d.ts +66 -0
  49. package/dist/index.js +52 -0
  50. package/dist/isolated-replay.d.ts +81 -0
  51. package/dist/isolated-replay.js +316 -0
  52. package/dist/music-candidate-set.d.ts +38 -0
  53. package/dist/music-candidate-set.js +102 -0
  54. package/dist/natural-language-chunking.d.ts +10 -0
  55. package/dist/natural-language-chunking.js +62 -0
  56. package/dist/natural-language-ingestion.d.ts +14 -0
  57. package/dist/natural-language-ingestion.js +128 -0
  58. package/dist/natural-language-structured-decode.d.ts +2 -0
  59. package/dist/natural-language-structured-decode.js +69 -0
  60. package/dist/natural-language-world-types.d.ts +142 -0
  61. package/dist/natural-language-world-types.js +2 -0
  62. package/dist/neutral-source-compiler.d.ts +135 -0
  63. package/dist/neutral-source-compiler.js +608 -0
  64. package/dist/presentation-intent.d.ts +42 -0
  65. package/dist/presentation-intent.js +137 -0
  66. package/dist/recall-query-envelope.d.ts +32 -0
  67. package/dist/recall-query-envelope.js +51 -0
  68. package/dist/rewrite-compensation.d.ts +28 -0
  69. package/dist/rewrite-compensation.js +85 -0
  70. package/dist/runtime-contract.d.ts +56 -0
  71. package/dist/runtime-contract.js +96 -0
  72. package/dist/runtime-domain-capabilities.d.ts +12 -0
  73. package/dist/runtime-domain-capabilities.js +20 -0
  74. package/dist/runtime-sav.d.ts +117 -0
  75. package/dist/runtime-sav.js +356 -0
  76. package/dist/runtime-trace.d.ts +44 -0
  77. package/dist/runtime-trace.js +119 -0
  78. package/dist/runtime.d.ts +238 -0
  79. package/dist/runtime.js +1887 -0
  80. package/dist/scene-author-source.d.ts +13 -0
  81. package/dist/scene-author-source.js +25 -0
  82. package/dist/scene-state.d.ts +275 -0
  83. package/dist/scene-state.js +334 -0
  84. package/dist/scheduler.d.ts +55 -0
  85. package/dist/scheduler.js +235 -0
  86. package/dist/selector-ast.d.ts +24 -0
  87. package/dist/selector-ast.js +18 -0
  88. package/dist/semantic-verdict.d.ts +48 -0
  89. package/dist/semantic-verdict.js +101 -0
  90. package/dist/simulator.d.ts +45 -0
  91. package/dist/simulator.js +58 -0
  92. package/dist/source-canonical.d.ts +4 -0
  93. package/dist/source-canonical.js +51 -0
  94. package/dist/source-crypto.d.ts +20 -0
  95. package/dist/source-crypto.js +17 -0
  96. package/dist/source-revision.d.ts +22 -0
  97. package/dist/source-revision.js +43 -0
  98. package/dist/spatial-impact-receipt.d.ts +38 -0
  99. package/dist/spatial-impact-receipt.js +67 -0
  100. package/dist/temporal-rewrite.d.ts +59 -0
  101. package/dist/temporal-rewrite.js +214 -0
  102. package/dist/time-domains.d.ts +37 -0
  103. package/dist/time-domains.js +142 -0
  104. package/dist/timeline-revision.d.ts +28 -0
  105. package/dist/timeline-revision.js +59 -0
  106. package/dist/trigger-document-v3.d.ts +99 -0
  107. package/dist/trigger-document-v3.js +334 -0
  108. package/dist/turn-commit.d.ts +56 -0
  109. package/dist/turn-commit.js +190 -0
  110. package/dist/validator.d.ts +7 -0
  111. package/dist/validator.js +142 -0
  112. package/dist/value-graph.d.ts +98 -0
  113. package/dist/value-graph.js +318 -0
  114. package/dist/view-event-delivery.d.ts +47 -0
  115. package/dist/view-event-delivery.js +197 -0
  116. package/dist/view-interaction-manifest-v3.d.ts +60 -0
  117. package/dist/view-interaction-manifest-v3.js +198 -0
  118. package/dist/view-payload-schema.d.ts +17 -0
  119. package/dist/view-payload-schema.js +84 -0
  120. package/package.json +62 -0
@@ -0,0 +1,190 @@
1
+ import { TMW_RUNTIME_GENERATION } from "./runtime-contract.js";
2
+ import { canonicalTmwJson, requireTmwIdentifier, requireTmwSafeInteger, requireTmwSha256, tmwSha256Hex, } from "./runtime-trace.js";
3
+ function canonicalIdentity(input) {
4
+ if (input.runtimeGeneration !== 3)
5
+ throw new Error("unsupported_runtime_generation");
6
+ return Object.freeze({
7
+ operationId: requireTmwIdentifier(input.operationId, "turn_commit_operation_id"),
8
+ turnId: requireTmwIdentifier(input.turnId, "turn_commit_turn_id"),
9
+ epoch: requireTmwSafeInteger(input.epoch, "turn_commit_epoch", 1),
10
+ runtimeGeneration: TMW_RUNTIME_GENERATION,
11
+ runtimeContractHash: requireTmwSha256(input.runtimeContractHash, "turn_commit_contract_hash"),
12
+ artifactDigest: requireTmwSha256(input.artifactDigest, "turn_commit_artifact_digest"),
13
+ sourceRevision: requireTmwIdentifier(input.sourceRevision, "turn_commit_source_revision"),
14
+ timelineRevision: requireTmwIdentifier(input.timelineRevision, "turn_commit_timeline_revision"),
15
+ leaseId: requireTmwIdentifier(input.leaseId, "turn_commit_lease_id"),
16
+ });
17
+ }
18
+ function canonicalStageDigests(input) {
19
+ const result = Object.create(null);
20
+ for (const [stage, digest] of [...input.entries()].sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)) {
21
+ result[requireTmwIdentifier(stage, "turn_commit_stage")] = requireTmwSha256(digest, "turn_commit_stage_digest");
22
+ }
23
+ return Object.freeze(result);
24
+ }
25
+ async function preActivationTerminalDigest(receipt) {
26
+ // Break the terminal <-> activation receipt hash cycle. The terminal body
27
+ // digest is fixed before activation; the final receipt then records the
28
+ // activation receipt digest and is compared in full on durable readback.
29
+ const { activationReceiptDigest: _activationReceiptDigest, ...preActivationBody } = receipt;
30
+ return await tmwSha256Hex(canonicalTmwJson(preActivationBody));
31
+ }
32
+ function sameIdentity(left, right) {
33
+ return left.operationId === right.operationId
34
+ && left.turnId === right.turnId
35
+ && left.epoch === right.epoch
36
+ && left.runtimeGeneration === right.runtimeGeneration
37
+ && left.runtimeContractHash === right.runtimeContractHash
38
+ && left.artifactDigest === right.artifactDigest
39
+ && left.sourceRevision === right.sourceRevision
40
+ && left.timelineRevision === right.timelineRevision
41
+ && left.leaseId === right.leaseId;
42
+ }
43
+ export class DurableTurnCommitOperation {
44
+ #identity;
45
+ #operationDigest;
46
+ #store;
47
+ #stages = new Map();
48
+ #terminal;
49
+ constructor(identity, operationDigest, store, terminal) {
50
+ this.#identity = identity;
51
+ this.#operationDigest = operationDigest;
52
+ this.#store = store;
53
+ this.#terminal = terminal;
54
+ if (terminal !== null) {
55
+ for (const [stage, digest] of Object.entries(terminal.stageDigests)) {
56
+ this.#stages.set(stage, digest);
57
+ }
58
+ }
59
+ }
60
+ static async open(input) {
61
+ const identity = canonicalIdentity(input.identity);
62
+ const operationDigest = await tmwSha256Hex(canonicalTmwJson({
63
+ identity,
64
+ operation: input.operation,
65
+ }));
66
+ const terminal = await input.store.read(identity.operationId);
67
+ if (terminal !== null) {
68
+ if (!sameIdentity(identity, terminal) || terminal.operationDigest !== operationDigest) {
69
+ throw new Error("tmw_turn_commit_operation_identity_conflict");
70
+ }
71
+ }
72
+ return new DurableTurnCommitOperation(identity, operationDigest, input.store, terminal);
73
+ }
74
+ recordStage(stageInput, digestInput) {
75
+ if (this.#terminal !== null)
76
+ throw new Error("tmw_turn_commit_terminal_sealed");
77
+ const stage = requireTmwIdentifier(stageInput, "turn_commit_stage");
78
+ const digest = requireTmwSha256(digestInput, "turn_commit_stage_digest");
79
+ const previous = this.#stages.get(stage);
80
+ if (previous !== undefined && previous !== digest) {
81
+ throw new Error("tmw_turn_commit_stage_digest_conflict");
82
+ }
83
+ this.#stages.set(stage, digest);
84
+ }
85
+ async commit(input) {
86
+ return await this.#finalize({
87
+ terminal: "committed",
88
+ reasonCode: null,
89
+ budgetReceiptDigest: input.budgetReceiptDigest,
90
+ activationReceiptDigest: input.activationReceiptDigest,
91
+ });
92
+ }
93
+ async fail(reasonCode, budgetReceiptDigest) {
94
+ return await this.#finalize({
95
+ terminal: "failed",
96
+ reasonCode,
97
+ budgetReceiptDigest,
98
+ activationReceiptDigest: null,
99
+ });
100
+ }
101
+ async abort(reasonCode, budgetReceiptDigest) {
102
+ return await this.#finalize({
103
+ terminal: "aborted",
104
+ reasonCode,
105
+ budgetReceiptDigest,
106
+ activationReceiptDigest: null,
107
+ });
108
+ }
109
+ async dispose(reasonCode, budgetReceiptDigest) {
110
+ return await this.#finalize({
111
+ terminal: "disposed",
112
+ reasonCode,
113
+ budgetReceiptDigest,
114
+ activationReceiptDigest: null,
115
+ });
116
+ }
117
+ snapshot() {
118
+ return Object.freeze({
119
+ identity: this.#identity,
120
+ operationDigest: this.#operationDigest,
121
+ stageDigests: canonicalStageDigests(this.#stages),
122
+ terminal: this.#terminal,
123
+ });
124
+ }
125
+ async previewCommittedTerminalDigest(budgetReceiptDigestInput) {
126
+ const budgetReceiptDigest = requireTmwSha256(budgetReceiptDigestInput, "turn_commit_budget_receipt_digest");
127
+ if (this.#terminal !== null) {
128
+ if (this.#terminal.terminal !== "committed"
129
+ || this.#terminal.budgetReceiptDigest !== budgetReceiptDigest) {
130
+ throw new Error("tmw_turn_commit_terminal_rewrite_forbidden");
131
+ }
132
+ return this.#terminal.preActivationTerminalDigest;
133
+ }
134
+ return await preActivationTerminalDigest(Object.freeze({
135
+ schemaVersion: 3,
136
+ ...this.#identity,
137
+ terminal: "committed",
138
+ reasonCode: null,
139
+ operationDigest: this.#operationDigest,
140
+ budgetReceiptDigest,
141
+ stageDigests: canonicalStageDigests(this.#stages),
142
+ activationReceiptDigest: null,
143
+ }));
144
+ }
145
+ async #finalize(input) {
146
+ const reasonCode = input.reasonCode === null
147
+ ? null
148
+ : requireTmwIdentifier(input.reasonCode, "turn_commit_reason_code");
149
+ const budgetReceiptDigest = requireTmwSha256(input.budgetReceiptDigest, "turn_commit_budget_receipt_digest");
150
+ const activationReceiptDigest = input.activationReceiptDigest === null
151
+ ? null
152
+ : requireTmwSha256(input.activationReceiptDigest, "turn_commit_activation_receipt_digest");
153
+ if (input.terminal === "committed" && activationReceiptDigest === null) {
154
+ throw new Error("tmw_turn_commit_activation_receipt_required");
155
+ }
156
+ if (this.#terminal !== null) {
157
+ if (this.#terminal.terminal !== input.terminal
158
+ || this.#terminal.reasonCode !== reasonCode
159
+ || this.#terminal.budgetReceiptDigest !== budgetReceiptDigest
160
+ || this.#terminal.activationReceiptDigest !== activationReceiptDigest) {
161
+ throw new Error("tmw_turn_commit_terminal_rewrite_forbidden");
162
+ }
163
+ return this.#terminal;
164
+ }
165
+ const body = Object.freeze({
166
+ schemaVersion: 3,
167
+ ...this.#identity,
168
+ terminal: input.terminal,
169
+ reasonCode,
170
+ operationDigest: this.#operationDigest,
171
+ budgetReceiptDigest,
172
+ stageDigests: canonicalStageDigests(this.#stages),
173
+ activationReceiptDigest,
174
+ });
175
+ const candidate = Object.freeze({
176
+ ...body,
177
+ preActivationTerminalDigest: await preActivationTerminalDigest(body),
178
+ });
179
+ const outcome = await this.#store.putIfAbsent(this.#identity.operationId, candidate);
180
+ const stored = outcome.receipt;
181
+ if (!sameIdentity(this.#identity, stored) || stored.operationDigest !== this.#operationDigest) {
182
+ throw new Error("tmw_turn_commit_terminal_store_identity_conflict");
183
+ }
184
+ if (canonicalTmwJson(stored) !== canonicalTmwJson(candidate)) {
185
+ throw new Error("tmw_turn_commit_terminal_rewrite_forbidden");
186
+ }
187
+ this.#terminal = stored;
188
+ return stored;
189
+ }
190
+ }
@@ -0,0 +1,7 @@
1
+ import { type CanonicalTmwCapabilityRegistryV3 } from "./capability-registry.js";
2
+ import { type TmwResult } from "./diagnostics.js";
3
+ import { type TriggerDocumentV3 } from "./trigger-document-v3.js";
4
+ export interface ValidateTmw3Options {
5
+ readonly expected_runtime_contract_hash?: string;
6
+ }
7
+ export declare function validateTmw3(value: unknown, registry: CanonicalTmwCapabilityRegistryV3, options?: ValidateTmw3Options): Promise<TmwResult<TriggerDocumentV3>>;
@@ -0,0 +1,142 @@
1
+ import { capabilityRegistryIndex, isCanonicalTmwCapabilityRegistryV3, } from "./capability-registry.js";
2
+ import { orderTmwDiagnostics, tmwDiagnostic, tmwFailure, tmwSuccess } from "./diagnostics.js";
3
+ import { runtimeContractHashV3 } from "./runtime-contract.js";
4
+ import { allTriggerNodesV3, parseTriggerDocumentV3 } from "./trigger-document-v3.js";
5
+ import { tmwSceneOperationFromEffect } from "./scene-state.js";
6
+ import { isTmwEventLifecycleCapability } from "./event-lifecycle.js";
7
+ import { validateTmwValueGraphV1, validateTmwBoundEffectArgumentsV1, tmwEffectValuePortTypeV1 } from "./value-graph.js";
8
+ export async function validateTmw3(value, registry, options = {}) {
9
+ const parsed = parseTriggerDocumentV3(value);
10
+ if (!parsed.ok)
11
+ return parsed;
12
+ const document = parsed.value;
13
+ const diagnostics = [...parsed.diagnostics];
14
+ if (!isCanonicalTmwCapabilityRegistryV3(registry))
15
+ return tmwFailure([
16
+ tmwDiagnostic("capability_registry_untrusted", "$registry", "canonical registry provider required"),
17
+ ]);
18
+ const expectedHash = options.expected_runtime_contract_hash ?? await runtimeContractHashV3();
19
+ if (document.runtime_contract_hash !== expectedHash)
20
+ diagnostics.push(tmwDiagnostic("runtime_contract_hash_mismatch", "$/runtime_contract_hash", "document runtime contract hash does not match Runtime3"));
21
+ if (document.registry_version !== registry.registry_version)
22
+ diagnostics.push(tmwDiagnostic("capability_registry_version_mismatch", "$/registry_version", "document registry version does not match supplied registry"));
23
+ const capabilityIds = new Set();
24
+ for (const [index, capability] of registry.capabilities.entries()) {
25
+ const path = `$registry/capabilities/${index}`;
26
+ if (capabilityIds.has(capability.capability_id))
27
+ diagnostics.push(tmwDiagnostic("duplicate_capability_id", `${path}/capability_id`, "capability id must be unique", [capability.capability_id]));
28
+ capabilityIds.add(capability.capability_id);
29
+ if (!Number.isSafeInteger(capability.max_invocations_per_turn) || capability.max_invocations_per_turn < 1)
30
+ diagnostics.push(tmwDiagnostic("capability_budget_invalid", `${path}/max_invocations_per_turn`, "capability budget must be a positive safe integer", [capability.capability_id]));
31
+ }
32
+ const nodes = allTriggerNodesV3(document);
33
+ const nodeIds = new Set();
34
+ for (const node of nodes) {
35
+ if (nodeIds.has(node.stable_id))
36
+ diagnostics.push(tmwDiagnostic("duplicate_stable_id", "$/nodes", "stable_id must be unique across all node kinds", [node.stable_id]));
37
+ nodeIds.add(node.stable_id);
38
+ }
39
+ const registryIndex = capabilityRegistryIndex(registry);
40
+ const usesValueInput = (expression) => expression.op === "value_input" || (expression.op === "not" ? usesValueInput(expression.value) : (expression.op === "all" || expression.op === "any") && expression.values.some(usesValueInput));
41
+ const consumers = nodes.map((node) => {
42
+ const inputs = {};
43
+ if (node.kind === "condition" && usesValueInput(node.expression))
44
+ inputs.value = "boolean";
45
+ if (node.kind === "effect")
46
+ for (const edge of document.value_graph?.edges.filter((e) => e.to_node === node.stable_id) ?? []) {
47
+ const capability = registryIndex.get(node.capability_id);
48
+ const portType = node.capability_id === "variable.set" && edge.to_port === "value" ? edge.value_type : capability && tmwEffectValuePortTypeV1(node.capability_id, edge.to_port, node.arguments, capability.argument_schema);
49
+ if (portType)
50
+ inputs[edge.to_port] = portType;
51
+ if (Object.hasOwn(node.arguments, edge.to_port))
52
+ diagnostics.push(tmwDiagnostic("value_input_inline_conflict", `$/effects/${node.stable_id}/arguments/${edge.to_port}`, "bound value input cannot also have an inline argument"));
53
+ }
54
+ return { stable_id: node.stable_id, inputs };
55
+ });
56
+ try {
57
+ validateTmwValueGraphV1(document.value_graph ?? { schema_version: 1, nodes: [], edges: [] }, consumers);
58
+ }
59
+ catch (error) {
60
+ diagnostics.push(tmwDiagnostic("value_graph_invalid", "$/value_graph", error instanceof Error ? error.message : "invalid value graph"));
61
+ }
62
+ const effectIdempotencyKeys = new Set();
63
+ const effectCountsByCapability = new Map();
64
+ for (const [index, effect] of document.effects.entries()) {
65
+ const capability = registryIndex.get(effect.capability_id);
66
+ if (capability === undefined)
67
+ diagnostics.push(tmwDiagnostic("unknown_capability", `$/effects/${index}/capability_id`, "effect capability is not registered", [effect.stable_id, effect.capability_id]));
68
+ else if (capability.selector_required && effect.selector === null)
69
+ diagnostics.push(tmwDiagnostic("capability_selector_required", `$/effects/${index}/selector`, "registered capability requires a selector", [effect.stable_id, effect.capability_id]));
70
+ const boundPorts = document.value_graph?.edges.filter((edge) => edge.to_node === effect.stable_id).map((edge) => edge.to_port) ?? [];
71
+ if (capability && (boundPorts.length > 0 || isTmwEventLifecycleCapability(effect.capability_id))) {
72
+ try {
73
+ validateTmwBoundEffectArgumentsV1(effect.arguments, boundPorts, capability.argument_schema);
74
+ }
75
+ catch (error) {
76
+ diagnostics.push(tmwDiagnostic("value_effect_arguments_invalid", `$/effects/${index}/arguments`, error instanceof Error ? error.message : "invalid arguments"));
77
+ }
78
+ }
79
+ // 只有未被 value graph 绑定的 eventNodeId 才是编译时可判定的 literal。
80
+ // 绑定值在运行时求值后仍由 event-lifecycle 检查,因此不能把数据边误判成常量引用。
81
+ const eventNodeId = effect.arguments.eventNodeId;
82
+ if (isTmwEventLifecycleCapability(effect.capability_id)
83
+ && !boundPorts.includes("eventNodeId")
84
+ && typeof eventNodeId === "string"
85
+ && !document.events.some((event) => event.stable_id === eventNodeId))
86
+ diagnostics.push(tmwDiagnostic("event_lifecycle_target_missing", `$/effects/${index}/arguments/eventNodeId`, "event lifecycle effect must reference an event definition in this document", [effect.stable_id, effect.capability_id, eventNodeId]));
87
+ if (effect.capability_id.startsWith("scene.") && !document.value_graph?.edges.some((edge) => edge.to_node === effect.stable_id)) {
88
+ try {
89
+ tmwSceneOperationFromEffect(effect.capability_id, effect.arguments);
90
+ }
91
+ catch {
92
+ diagnostics.push(tmwDiagnostic("scene_effect_arguments_invalid", `$/effects/${index}/arguments`, "scene effect arguments do not satisfy the canonical Scene operation", [effect.stable_id, effect.capability_id]));
93
+ }
94
+ }
95
+ if (effectIdempotencyKeys.has(effect.idempotency_key))
96
+ diagnostics.push(tmwDiagnostic("duplicate_effect_idempotency_key", `$/effects/${index}/idempotency_key`, "effect idempotency_key must be unique", [effect.stable_id, effect.idempotency_key]));
97
+ effectIdempotencyKeys.add(effect.idempotency_key);
98
+ const invocationCount = (effectCountsByCapability.get(effect.capability_id) ?? 0) + 1;
99
+ effectCountsByCapability.set(effect.capability_id, invocationCount);
100
+ if (capability !== undefined && invocationCount > capability.max_invocations_per_turn)
101
+ diagnostics.push(tmwDiagnostic("capability_budget_exceeded", `$/effects/${index}/capability_id`, "document effect count exceeds capability budget", [effect.stable_id, effect.capability_id]));
102
+ if (capability?.policy === "presentation_only" && effect.execution_policy === "ensure_until")
103
+ diagnostics.push(tmwDiagnostic("effect_execution_policy_invalid", `$/effects/${index}/execution_policy`, "presentation-only effects cannot use ensure_until", [effect.stable_id]));
104
+ }
105
+ const adjacency = new Map();
106
+ for (const id of nodeIds)
107
+ adjacency.set(id, []);
108
+ const edgeIds = new Set();
109
+ for (const [index, edge] of document.edges.entries()) {
110
+ const path = `$/edges/${index}`;
111
+ if (!nodeIds.has(edge.from_stable_id))
112
+ diagnostics.push(tmwDiagnostic("dangling_edge_source", `${path}/from_stable_id`, "edge source does not exist", [edge.from_stable_id]));
113
+ if (!nodeIds.has(edge.to_stable_id))
114
+ diagnostics.push(tmwDiagnostic("dangling_edge_target", `${path}/to_stable_id`, "edge target does not exist", [edge.to_stable_id]));
115
+ const identity = `${edge.from_stable_id}\0${edge.to_stable_id}`;
116
+ if (edgeIds.has(identity))
117
+ diagnostics.push(tmwDiagnostic("duplicate_edge", path, "edge must be unique", [edge.from_stable_id, edge.to_stable_id]));
118
+ edgeIds.add(identity);
119
+ adjacency.get(edge.from_stable_id)?.push(edge.to_stable_id);
120
+ }
121
+ const visiting = new Set();
122
+ const visited = new Set();
123
+ const visit = (id) => {
124
+ if (visiting.has(id)) {
125
+ diagnostics.push(tmwDiagnostic("trigger_cycle", "$/edges", "trigger graph must be acyclic", [id]));
126
+ return;
127
+ }
128
+ if (visited.has(id))
129
+ return;
130
+ visiting.add(id);
131
+ for (const next of [...(adjacency.get(id) ?? [])].sort())
132
+ visit(next);
133
+ visiting.delete(id);
134
+ visited.add(id);
135
+ };
136
+ for (const id of [...nodeIds].sort())
137
+ visit(id);
138
+ const ordered = orderTmwDiagnostics(diagnostics);
139
+ return ordered.some((diagnostic) => diagnostic.severity === "error")
140
+ ? tmwFailure(ordered)
141
+ : tmwSuccess(document, ordered);
142
+ }
@@ -0,0 +1,98 @@
1
+ import type { TmwJsonValue } from "./runtime-contract.js";
2
+ import type { TmwSceneStateV1 } from "./scene-state.js";
3
+ export declare const TMW_VALUE_GRAPH_SCHEMA_VERSION: 1;
4
+ export declare const TMW_VALUE_TYPES: readonly ["number", "string", "boolean", "int32", "world_time", "real_timestamp", "duration", "spatial_ref", "string_list", "object_ref"];
5
+ export type TmwValueTypeV1 = typeof TMW_VALUE_TYPES[number];
6
+ /** 可连线的统一玩法对象身份;不允许把 node / edge 的同名 id 混用。 */
7
+ export interface TmwObjectRefV1 {
8
+ readonly kind: "node" | "edge";
9
+ readonly id: string;
10
+ }
11
+ export type TmwSceneValueFieldV1 = "world_time" | "location" | "scene_id" | "present_entity_ids";
12
+ export type TmwValueComparisonV1 = "eq" | "neq" | "gt" | "gte" | "lt" | "lte";
13
+ export type TmwValueNodeV1 = Readonly<{
14
+ kind: "value";
15
+ stable_id: string;
16
+ }> & (Readonly<{
17
+ op: "constant";
18
+ value_type: TmwValueTypeV1;
19
+ value: TmwJsonValue;
20
+ }> | Readonly<{
21
+ op: "variable_read";
22
+ variable_id: string;
23
+ value_type: TmwValueTypeV1;
24
+ }> | Readonly<{
25
+ op: "scene_read";
26
+ field: TmwSceneValueFieldV1;
27
+ }> | Readonly<{
28
+ op: "state_read";
29
+ target: "node" | "edge";
30
+ entity_id: string;
31
+ field: "unlocked" | "frozen";
32
+ }> | Readonly<{
33
+ op: "compare";
34
+ value_type: TmwValueTypeV1;
35
+ comparison: TmwValueComparisonV1;
36
+ }> | Readonly<{
37
+ op: "bitwise";
38
+ operator: "and" | "or" | "xor" | "not" | "shl" | "shr" | "ushr";
39
+ }>);
40
+ export interface TmwValueDataEdgeV1 {
41
+ readonly from_node: string;
42
+ readonly from_port: "value";
43
+ readonly to_node: string;
44
+ readonly to_port: string;
45
+ readonly value_type: TmwValueTypeV1;
46
+ }
47
+ export interface TmwValueGraphV1 {
48
+ readonly schema_version: 1;
49
+ readonly nodes: readonly TmwValueNodeV1[];
50
+ readonly edges: readonly TmwValueDataEdgeV1[];
51
+ }
52
+ export interface TmwTypedValueV1 {
53
+ readonly value_type: TmwValueTypeV1;
54
+ readonly value: TmwJsonValue;
55
+ }
56
+ export interface TmwValueContextV1 {
57
+ readonly scene?: TmwSceneStateV1 | null;
58
+ readonly variables?: Readonly<Record<string, TmwTypedValueV1>>;
59
+ readonly entity_states?: Readonly<Record<string, Readonly<{
60
+ kind: "available" | "unavailable" | "frozen";
61
+ frozen_stack: readonly Readonly<{
62
+ prev_kind?: "available" | "unavailable";
63
+ }>[];
64
+ }>>>;
65
+ }
66
+ export interface TmwValueConsumerV1 {
67
+ readonly stable_id: string;
68
+ readonly inputs: Readonly<Record<string, TmwValueTypeV1>>;
69
+ }
70
+ /** 变量身份包含 scope,避免执行局部值遮蔽存档变量。 */
71
+ export declare function tmwValueContextFromSavV1(sav: Readonly<{
72
+ scene: TmwSceneStateV1 | null;
73
+ gameplayState?: Readonly<{
74
+ variables: readonly Readonly<{
75
+ name: string;
76
+ scope: string;
77
+ valueType: string;
78
+ value: TmwJsonValue;
79
+ }>[];
80
+ entity_state_stacks?: TmwValueContextV1["entity_states"];
81
+ }>;
82
+ }>): TmwValueContextV1;
83
+ export declare function resolveTmwEffectValueArgumentsV1(graph: TmwValueGraphV1 | undefined, targetId: string, args: Readonly<Record<string, TmwJsonValue>>, context: TmwValueContextV1, capabilityId?: string): Readonly<Record<string, TmwJsonValue>>;
84
+ export declare function tmwEffectValuePortTypeV1(capabilityId: string, port: string, args: Readonly<Record<string, TmwJsonValue>>, schema: TmwJsonValue): TmwValueTypeV1 | undefined;
85
+ /** 绑定输入占据声明参数槽;其余 inline 参数仍须通过原 capability schema。 */
86
+ export declare function validateTmwBoundEffectArgumentsV1(args: Readonly<Record<string, TmwJsonValue>>, boundPorts: readonly string[], schema: TmwJsonValue): void;
87
+ export declare function isTmwTypedValueV1(valueType: TmwValueTypeV1, value: unknown): value is TmwJsonValue;
88
+ export declare function isTmwObjectRefV1(value: unknown): value is TmwObjectRefV1 & TmwJsonValue;
89
+ export declare function tmwValueNodePortsV1(node: TmwValueNodeV1): Readonly<{
90
+ inputs: Readonly<Record<string, TmwValueTypeV1>>;
91
+ outputs: Readonly<Record<string, TmwValueTypeV1>>;
92
+ }>;
93
+ export declare function parseTmwValueGraphV1(value: unknown): TmwValueGraphV1;
94
+ export declare function validateTmwValueGraphV1(graph: TmwValueGraphV1, consumers?: readonly TmwValueConsumerV1[]): void;
95
+ export declare function readTmwSceneValueV1(scene: TmwSceneStateV1 | null | undefined, field: TmwSceneValueFieldV1): TmwTypedValueV1 | undefined;
96
+ export declare function compareTmwValuesV1(left: TmwJsonValue, right: TmwJsonValue, comparison: TmwValueComparisonV1 | "contains"): boolean | undefined;
97
+ export declare function evaluateTmwValueGraphV1(graph: TmwValueGraphV1, context?: TmwValueContextV1, roots?: readonly string[]): ReadonlyMap<string, TmwTypedValueV1 | undefined>;
98
+ export declare function resolveTmwValueGraphInputsV1(graph: TmwValueGraphV1 | undefined, targetId: string, context?: TmwValueContextV1): Readonly<Record<string, TmwTypedValueV1 | undefined>>;