@datamoc/mw_games 0.9.0-alpha → 0.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/CHANGELOG.md +63 -0
- package/README.md +4 -0
- package/dist/core/ActionJournal.d.ts +2 -0
- package/dist/core/ActionJournal.d.ts.map +1 -1
- package/dist/core/ActionJournal.js +6 -0
- package/dist/core/ActionJournal.js.map +1 -1
- package/dist/core/Multiplayer.d.ts +21 -4
- package/dist/core/Multiplayer.d.ts.map +1 -1
- package/dist/core/Multiplayer.js +31 -4
- package/dist/core/Multiplayer.js.map +1 -1
- package/dist/core/State.d.ts +38 -0
- package/dist/core/State.d.ts.map +1 -1
- package/dist/core/State.js +76 -0
- package/dist/core/State.js.map +1 -1
- package/dist/core/index.d.ts +3 -3
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/mw_games.global.js +113 -113
- package/dist/mwl/catalog.d.ts +7 -0
- package/dist/mwl/catalog.d.ts.map +1 -1
- package/dist/mwl/catalog.js +0 -0
- package/dist/mwl/catalog.js.map +1 -1
- package/dist/mwl/compiler.d.ts +3 -0
- package/dist/mwl/compiler.d.ts.map +1 -1
- package/dist/mwl/compiler.js +10 -3
- package/dist/mwl/compiler.js.map +1 -1
- package/dist/mwl/grammar.d.ts +8 -1
- package/dist/mwl/grammar.d.ts.map +1 -1
- package/dist/mwl/grammar.js +207 -39
- package/dist/mwl/grammar.js.map +1 -1
- package/dist/mwl/hooks.d.ts +35 -2
- package/dist/mwl/hooks.d.ts.map +1 -1
- package/dist/mwl/hooks.js +8 -6
- package/dist/mwl/hooks.js.map +1 -1
- package/dist/mwl/index.d.ts +6 -6
- package/dist/mwl/index.d.ts.map +1 -1
- package/dist/mwl/index.js +3 -3
- package/dist/mwl/index.js.map +1 -1
- package/dist/mwl/persistence.d.ts +18 -1
- package/dist/mwl/persistence.d.ts.map +1 -1
- package/dist/mwl/persistence.js +59 -3
- package/dist/mwl/persistence.js.map +1 -1
- package/dist/mwl/readers.d.ts +37 -0
- package/dist/mwl/readers.d.ts.map +1 -1
- package/dist/mwl/readers.js +69 -0
- package/dist/mwl/readers.js.map +1 -1
- package/dist/mwl/runtime.d.ts +45 -1
- package/dist/mwl/runtime.d.ts.map +1 -1
- package/dist/mwl/runtime.js +72 -8
- package/dist/mwl/runtime.js.map +1 -1
- package/dist/mwl/schema.d.ts +31 -0
- package/dist/mwl/schema.d.ts.map +1 -1
- package/dist/mwl/schema.js +57 -2
- package/dist/mwl/schema.js.map +1 -1
- package/dist/simulation/Runtime.d.ts +66 -1
- package/dist/simulation/Runtime.d.ts.map +1 -1
- package/dist/simulation/Runtime.js +131 -3
- package/dist/simulation/Runtime.js.map +1 -1
- package/dist/simulation/index.d.ts +2 -2
- package/dist/simulation/index.d.ts.map +1 -1
- package/dist/simulation/index.js +1 -1
- package/dist/simulation/index.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +1 -1
- package/tools/mwl.mjs +85 -4
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Generator } from '../core/Random.ts';
|
|
2
|
+
import { ActionJournal, type ActionJournalEntry } from '../core/ActionJournal.ts';
|
|
3
|
+
import { type UndoHistoryOptions } from '../core/UndoHistory.ts';
|
|
2
4
|
import { Scheduler, type Actor, type SchedulerSnapshot } from '../roguelike/Scheduler.ts';
|
|
3
5
|
import type { SimulationStatus } from './Scenario.ts';
|
|
4
6
|
/** What a rule receives besides state and command: the two sources of non-determinism a
|
|
@@ -23,7 +25,56 @@ export interface SimulationSnapshot<State> {
|
|
|
23
25
|
state: State;
|
|
24
26
|
scheduler: SchedulerSnapshot;
|
|
25
27
|
random: readonly [number, number, number, number];
|
|
28
|
+
/** Commands already committed, retained for replay, undo checkpoints, and diagnostics. */
|
|
29
|
+
readonly journal?: readonly ActionJournalEntry<unknown, unknown>[];
|
|
26
30
|
}
|
|
31
|
+
export interface SimulationReplayMismatch<Command, Event> {
|
|
32
|
+
readonly index: number;
|
|
33
|
+
readonly action: Command;
|
|
34
|
+
readonly expectedEvents: readonly Event[];
|
|
35
|
+
readonly actualEvents: readonly Event[];
|
|
36
|
+
readonly reason: 'sequence' | 'events' | 'execution' | 'final-state';
|
|
37
|
+
readonly error?: string;
|
|
38
|
+
}
|
|
39
|
+
export interface SimulationReplayResult<State, Command, Event> {
|
|
40
|
+
readonly valid: boolean;
|
|
41
|
+
readonly checked: number;
|
|
42
|
+
readonly state: State;
|
|
43
|
+
readonly journal: readonly ActionJournalEntry<Command, Event>[];
|
|
44
|
+
readonly mismatch?: SimulationReplayMismatch<Command, Event>;
|
|
45
|
+
}
|
|
46
|
+
export interface SimulationRuntimeHistoryOptions<A extends Actor = Actor> extends UndoHistoryOptions {
|
|
47
|
+
/** Resolves scheduler actor ids when an undo or redo restores a checkpoint. */
|
|
48
|
+
readonly actorOf: (id: string) => A;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Replays a journal from an initial snapshot and compares deterministic event batches. The
|
|
52
|
+
* initial snapshot is treated as a checkpoint, so any journal it carries is ignored. An optional
|
|
53
|
+
* final-state check catches rules that changed state without emitting an event.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```ts
|
|
57
|
+
* import { validateSimulationReplay, type SimulationRuntimeRule, type SimulationSnapshot } from '@datamoc/mw_games/simulation';
|
|
58
|
+
* import type { ActionJournalEntry } from '@datamoc/mw_games/core';
|
|
59
|
+
* import type { Actor } from '@datamoc/mw_games/roguelike';
|
|
60
|
+
*
|
|
61
|
+
* declare const initialSnapshot: SimulationSnapshot<unknown>;
|
|
62
|
+
* declare const recordedJournal: readonly ActionJournalEntry<unknown, unknown>[];
|
|
63
|
+
* declare const rule: SimulationRuntimeRule<unknown, unknown, unknown, Actor>;
|
|
64
|
+
* declare const actorOf: (id: string) => Actor;
|
|
65
|
+
* declare const actorId: (actor: Actor) => string;
|
|
66
|
+
* const result = validateSimulationReplay(initialSnapshot, recordedJournal, {
|
|
67
|
+
* rule, actorOf, actorId,
|
|
68
|
+
* });
|
|
69
|
+
* if (!result.valid) console.error('replay diverged at', result.mismatch?.index);
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
export declare function validateSimulationReplay<State, Command, Event, A extends Actor>(initial: SimulationSnapshot<State>, entries: readonly ActionJournalEntry<Command, Event>[], options: {
|
|
73
|
+
rule: SimulationRuntimeRule<State, Command, Event, A>;
|
|
74
|
+
actorOf: (id: string) => A;
|
|
75
|
+
actorId: (actor: A) => string;
|
|
76
|
+
expectedFinalState?: State;
|
|
77
|
+
}): SimulationReplayResult<State, Command, Event>;
|
|
27
78
|
/**
|
|
28
79
|
* A small facade over one state + one scheduler + one RNG, for the interactive half of a
|
|
29
80
|
* turn-based simulation: the command a player just issued, dispatched through the game's own
|
|
@@ -68,21 +119,33 @@ export interface SimulationSnapshot<State> {
|
|
|
68
119
|
*/
|
|
69
120
|
export declare class SimulationRuntime<State, Command, Event, A extends Actor> {
|
|
70
121
|
private _state;
|
|
71
|
-
|
|
122
|
+
private _scheduler;
|
|
72
123
|
readonly random: Generator;
|
|
124
|
+
readonly journal: ActionJournal<Command, Event>;
|
|
73
125
|
private readonly rule;
|
|
74
126
|
private readonly actorId;
|
|
127
|
+
private readonly history;
|
|
128
|
+
private readonly actorOf;
|
|
75
129
|
constructor(options: {
|
|
76
130
|
state: State;
|
|
77
131
|
scheduler: Scheduler<A>;
|
|
78
132
|
random: Generator;
|
|
79
133
|
rule: SimulationRuntimeRule<State, Command, Event, A>;
|
|
80
134
|
actorId: (actor: A) => string;
|
|
135
|
+
journal?: ActionJournal<Command, Event>;
|
|
136
|
+
history?: SimulationRuntimeHistoryOptions<A>;
|
|
81
137
|
});
|
|
138
|
+
get scheduler(): Scheduler<A>;
|
|
82
139
|
get state(): State;
|
|
83
140
|
/** Runs one command through the rule, commits the resulting state, and charges its cost
|
|
84
141
|
* to the scheduler's current actor when one is given. */
|
|
85
142
|
dispatch(command: Command): SimulationOutcome<State, Event>;
|
|
143
|
+
get canUndo(): boolean;
|
|
144
|
+
get canRedo(): boolean;
|
|
145
|
+
/** Restores the previous committed simulation checkpoint, or null when undo is disabled. */
|
|
146
|
+
undo(): SimulationSnapshot<State> | null;
|
|
147
|
+
/** Restores the next checkpoint after an undo, or null when no redo is available. */
|
|
148
|
+
redo(): SimulationSnapshot<State> | null;
|
|
86
149
|
snapshot(version?: number): SimulationSnapshot<State>;
|
|
87
150
|
/**
|
|
88
151
|
* Rebuilds a runtime from a snapshot taken by `snapshot()`. The caller supplies
|
|
@@ -94,5 +157,7 @@ export declare class SimulationRuntime<State, Command, Event, A extends Actor> {
|
|
|
94
157
|
actorOf: (id: string) => A;
|
|
95
158
|
actorId: (actor: A) => string;
|
|
96
159
|
}): SimulationRuntime<State, Command, Event, A>;
|
|
160
|
+
private historySnapshot;
|
|
161
|
+
private restoreCheckpoint;
|
|
97
162
|
}
|
|
98
163
|
//# sourceMappingURL=Runtime.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Runtime.d.ts","sourceRoot":"","sources":["../../src/simulation/Runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,KAAK,KAAK,EAAE,KAAK,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC1F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEtD;sEACsE;AACtE,MAAM,WAAW,iBAAiB,CAAC,CAAC,SAAS,KAAK;IACjD,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,iBAAiB,CAAC,KAAK,EAAE,KAAK;IAC9C,KAAK,EAAE,KAAK,CAAC;IACb,MAAM,EAAE,SAAS,KAAK,EAAE,CAAC;IACzB,MAAM,EAAE,gBAAgB,CAAC;IACzB;6FACyF;IACzF,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB;AAED,MAAM,MAAM,qBAAqB,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,SAAS,KAAK,IAAI,CAC3E,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,iBAAiB,CAAC,CAAC,CAAC,KACzB,iBAAiB,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;AAErC;uDACuD;AACvD,MAAM,WAAW,kBAAkB,CAAC,KAAK;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,KAAK,CAAC;IACb,SAAS,EAAE,iBAAiB,CAAC;IAC7B,MAAM,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"Runtime.d.ts","sourceRoot":"","sources":["../../src/simulation/Runtime.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,83 @@
|
|
|
1
1
|
import { Generator } from '../core/Random.js';
|
|
2
|
+
import { ActionJournal } from '../core/ActionJournal.js';
|
|
3
|
+
import { stateChecksum } from '../core/SyncGuard.js';
|
|
4
|
+
import { UndoHistory } from '../core/UndoHistory.js';
|
|
2
5
|
import { Scheduler } from '../roguelike/Scheduler.js';
|
|
6
|
+
/**
|
|
7
|
+
* Replays a journal from an initial snapshot and compares deterministic event batches. The
|
|
8
|
+
* initial snapshot is treated as a checkpoint, so any journal it carries is ignored. An optional
|
|
9
|
+
* final-state check catches rules that changed state without emitting an event.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* import { validateSimulationReplay, type SimulationRuntimeRule, type SimulationSnapshot } from '@datamoc/mw_games/simulation';
|
|
14
|
+
* import type { ActionJournalEntry } from '@datamoc/mw_games/core';
|
|
15
|
+
* import type { Actor } from '@datamoc/mw_games/roguelike';
|
|
16
|
+
*
|
|
17
|
+
* declare const initialSnapshot: SimulationSnapshot<unknown>;
|
|
18
|
+
* declare const recordedJournal: readonly ActionJournalEntry<unknown, unknown>[];
|
|
19
|
+
* declare const rule: SimulationRuntimeRule<unknown, unknown, unknown, Actor>;
|
|
20
|
+
* declare const actorOf: (id: string) => Actor;
|
|
21
|
+
* declare const actorId: (actor: Actor) => string;
|
|
22
|
+
* const result = validateSimulationReplay(initialSnapshot, recordedJournal, {
|
|
23
|
+
* rule, actorOf, actorId,
|
|
24
|
+
* });
|
|
25
|
+
* if (!result.valid) console.error('replay diverged at', result.mismatch?.index);
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export function validateSimulationReplay(initial, entries, options) {
|
|
29
|
+
const runtime = SimulationRuntime.restore({ ...initial, journal: [] }, options);
|
|
30
|
+
const failed = (value) => ({
|
|
31
|
+
valid: false,
|
|
32
|
+
checked: value.index,
|
|
33
|
+
state: runtime.state,
|
|
34
|
+
journal: runtime.journal.toJSON(),
|
|
35
|
+
mismatch: value,
|
|
36
|
+
});
|
|
37
|
+
for (let index = 0; index < entries.length; index++) {
|
|
38
|
+
const entry = entries[index];
|
|
39
|
+
if (entry.sequence !== index)
|
|
40
|
+
return failed({
|
|
41
|
+
index,
|
|
42
|
+
action: entry.action,
|
|
43
|
+
expectedEvents: entry.events,
|
|
44
|
+
actualEvents: [],
|
|
45
|
+
reason: 'sequence',
|
|
46
|
+
});
|
|
47
|
+
let outcome;
|
|
48
|
+
try {
|
|
49
|
+
outcome = runtime.dispatch(entry.action);
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
return failed({
|
|
53
|
+
index,
|
|
54
|
+
action: entry.action,
|
|
55
|
+
expectedEvents: entry.events,
|
|
56
|
+
actualEvents: [],
|
|
57
|
+
reason: 'execution',
|
|
58
|
+
error: error instanceof Error ? error.message : String(error),
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
if (stateChecksum(outcome.events) !== stateChecksum(entry.events))
|
|
62
|
+
return failed({
|
|
63
|
+
index,
|
|
64
|
+
action: entry.action,
|
|
65
|
+
expectedEvents: entry.events,
|
|
66
|
+
actualEvents: outcome.events,
|
|
67
|
+
reason: 'events',
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
if (options.expectedFinalState !== undefined &&
|
|
71
|
+
stateChecksum(runtime.state) !== stateChecksum(options.expectedFinalState))
|
|
72
|
+
return failed({
|
|
73
|
+
index: entries.length,
|
|
74
|
+
action: entries.at(-1)?.action,
|
|
75
|
+
expectedEvents: [],
|
|
76
|
+
actualEvents: [],
|
|
77
|
+
reason: 'final-state',
|
|
78
|
+
});
|
|
79
|
+
return { valid: true, checked: entries.length, state: runtime.state, journal: runtime.journal.toJSON() };
|
|
80
|
+
}
|
|
3
81
|
/**
|
|
4
82
|
* A small facade over one state + one scheduler + one RNG, for the interactive half of a
|
|
5
83
|
* turn-based simulation: the command a player just issued, dispatched through the game's own
|
|
@@ -45,10 +123,18 @@ import { Scheduler } from '../roguelike/Scheduler.js';
|
|
|
45
123
|
export class SimulationRuntime {
|
|
46
124
|
constructor(options) {
|
|
47
125
|
this._state = options.state;
|
|
48
|
-
this.
|
|
126
|
+
this._scheduler = options.scheduler;
|
|
49
127
|
this.random = options.random;
|
|
50
128
|
this.rule = options.rule;
|
|
51
129
|
this.actorId = options.actorId;
|
|
130
|
+
this.journal = options.journal ?? new ActionJournal();
|
|
131
|
+
this.actorOf = options.history?.actorOf ?? null;
|
|
132
|
+
this.history = options.history ? new UndoHistory(options.history) : null;
|
|
133
|
+
if (this.history)
|
|
134
|
+
this.history.push(this.historySnapshot());
|
|
135
|
+
}
|
|
136
|
+
get scheduler() {
|
|
137
|
+
return this._scheduler;
|
|
52
138
|
}
|
|
53
139
|
get state() {
|
|
54
140
|
return this._state;
|
|
@@ -56,18 +142,48 @@ export class SimulationRuntime {
|
|
|
56
142
|
/** Runs one command through the rule, commits the resulting state, and charges its cost
|
|
57
143
|
* to the scheduler's current actor when one is given. */
|
|
58
144
|
dispatch(command) {
|
|
59
|
-
const outcome = this.rule(this._state, command, { random: this.random, scheduler: this.
|
|
145
|
+
const outcome = this.rule(this._state, command, { random: this.random, scheduler: this._scheduler });
|
|
60
146
|
this._state = outcome.state;
|
|
61
147
|
if (outcome.cost != null)
|
|
62
|
-
this.
|
|
148
|
+
this._scheduler.spend(outcome.cost);
|
|
149
|
+
this.journal.append(command, outcome.events);
|
|
150
|
+
if (this.history)
|
|
151
|
+
this.history.push(this.historySnapshot());
|
|
63
152
|
return outcome;
|
|
64
153
|
}
|
|
154
|
+
get canUndo() {
|
|
155
|
+
return this.history?.canUndo ?? false;
|
|
156
|
+
}
|
|
157
|
+
get canRedo() {
|
|
158
|
+
return this.history?.canRedo ?? false;
|
|
159
|
+
}
|
|
160
|
+
/** Restores the previous committed simulation checkpoint, or null when undo is disabled. */
|
|
161
|
+
undo() {
|
|
162
|
+
if (!this.history || !this.actorOf)
|
|
163
|
+
return null;
|
|
164
|
+
const snapshot = this.history.undo();
|
|
165
|
+
if (!snapshot)
|
|
166
|
+
return null;
|
|
167
|
+
this.restoreCheckpoint(snapshot);
|
|
168
|
+
return this.snapshot();
|
|
169
|
+
}
|
|
170
|
+
/** Restores the next checkpoint after an undo, or null when no redo is available. */
|
|
171
|
+
redo() {
|
|
172
|
+
if (!this.history || !this.actorOf)
|
|
173
|
+
return null;
|
|
174
|
+
const snapshot = this.history.redo();
|
|
175
|
+
if (!snapshot)
|
|
176
|
+
return null;
|
|
177
|
+
this.restoreCheckpoint(snapshot);
|
|
178
|
+
return this.snapshot();
|
|
179
|
+
}
|
|
65
180
|
snapshot(version = 1) {
|
|
66
181
|
return {
|
|
67
182
|
version,
|
|
68
183
|
state: this._state,
|
|
69
184
|
scheduler: this.scheduler.toJSON(this.actorId),
|
|
70
185
|
random: this.random.getState(),
|
|
186
|
+
journal: this.journal.toJSON(),
|
|
71
187
|
};
|
|
72
188
|
}
|
|
73
189
|
/**
|
|
@@ -83,9 +199,21 @@ export class SimulationRuntime {
|
|
|
83
199
|
state: snapshot.state,
|
|
84
200
|
scheduler,
|
|
85
201
|
random,
|
|
202
|
+
journal: ActionJournal.fromJSON((snapshot.journal ?? [])),
|
|
86
203
|
rule: options.rule,
|
|
87
204
|
actorId: options.actorId,
|
|
88
205
|
});
|
|
89
206
|
}
|
|
207
|
+
historySnapshot() {
|
|
208
|
+
return structuredClone(this.snapshot());
|
|
209
|
+
}
|
|
210
|
+
restoreCheckpoint(snapshot) {
|
|
211
|
+
if (!this.actorOf)
|
|
212
|
+
throw new Error('simulation history needs actorOf to restore a checkpoint');
|
|
213
|
+
this._state = structuredClone(snapshot.state);
|
|
214
|
+
this._scheduler = Scheduler.restore(snapshot.scheduler, this.actorOf);
|
|
215
|
+
this.random.setState(snapshot.random);
|
|
216
|
+
this.journal.replace((snapshot.journal ?? []));
|
|
217
|
+
}
|
|
90
218
|
}
|
|
91
219
|
//# sourceMappingURL=Runtime.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Runtime.js","sourceRoot":"","sources":["../../src/simulation/Runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAsC,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"Runtime.js","sourceRoot":"","sources":["../../src/simulation/Runtime.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,8 +6,8 @@ export { Campaign } from './Campaign.ts';
|
|
|
6
6
|
export type { CampaignOutcome, CampaignLevel, CampaignLevelResult, CampaignSnapshot } from './Campaign.ts';
|
|
7
7
|
export { runHeadlessScenario } from './Harness.ts';
|
|
8
8
|
export type { HeadlessScenario, HeadlessScenarioResult } from './Harness.ts';
|
|
9
|
-
export { SimulationRuntime } from './Runtime.ts';
|
|
10
|
-
export type { SimulationContext, SimulationOutcome, SimulationRuntimeRule, SimulationSnapshot } from './Runtime.ts';
|
|
9
|
+
export { SimulationRuntime, validateSimulationReplay } from './Runtime.ts';
|
|
10
|
+
export type { SimulationContext, SimulationOutcome, SimulationReplayMismatch, SimulationReplayResult, SimulationRuntimeHistoryOptions, SimulationRuntimeRule, SimulationSnapshot, } from './Runtime.ts';
|
|
11
11
|
export { EventPresentation } from './EventPresentation.ts';
|
|
12
12
|
export type { EventPresentationOptions } from './EventPresentation.ts';
|
|
13
13
|
export { CampaignSave } from './CampaignSave.ts';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/simulation/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,YAAY,EAAE,cAAc,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxE,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,YAAY,EAAE,gBAAgB,EAAE,cAAc,EAAE,cAAc,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAChH,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAC3G,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,YAAY,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAC7E,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/simulation/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,YAAY,EAAE,cAAc,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxE,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,YAAY,EAAE,gBAAgB,EAAE,cAAc,EAAE,cAAc,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAChH,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAC3G,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,YAAY,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAC7E,OAAO,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAC3E,YAAY,EACX,iBAAiB,EACjB,iBAAiB,EACjB,wBAAwB,EACxB,sBAAsB,EACtB,+BAA+B,EAC/B,qBAAqB,EACrB,kBAAkB,GAClB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,YAAY,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAC;AACvE,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE9E;;;;;GAKG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACtD,YAAY,EAAE,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC"}
|
package/dist/simulation/index.js
CHANGED
|
@@ -2,7 +2,7 @@ export { advanceToInput } from './Turns.js';
|
|
|
2
2
|
export { runScenario } from './Scenario.js';
|
|
3
3
|
export { Campaign } from './Campaign.js';
|
|
4
4
|
export { runHeadlessScenario } from './Harness.js';
|
|
5
|
-
export { SimulationRuntime } from './Runtime.js';
|
|
5
|
+
export { SimulationRuntime, validateSimulationReplay } from './Runtime.js';
|
|
6
6
|
export { EventPresentation } from './EventPresentation.js';
|
|
7
7
|
export { CampaignSave } from './CampaignSave.js';
|
|
8
8
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/simulation/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE5C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAEnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/simulation/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE5C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAEnD,OAAO,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAU3E,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAE3D,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAGjD;;;;;GAKG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC"}
|
package/dist/version.d.ts
CHANGED
|
@@ -9,5 +9,5 @@
|
|
|
9
9
|
* this from `package.json` automatically, chosen because this project's own build already
|
|
10
10
|
* has no step before `tsc`/`vite` that this would need to run ahead of.
|
|
11
11
|
*/
|
|
12
|
-
export declare const version = "0.9.0
|
|
12
|
+
export declare const version = "0.9.0";
|
|
13
13
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/version.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,eAAO,MAAM,OAAO,
|
|
1
|
+
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,eAAO,MAAM,OAAO,UAAU,CAAC"}
|
package/dist/version.js
CHANGED
|
@@ -9,5 +9,5 @@
|
|
|
9
9
|
* this from `package.json` automatically, chosen because this project's own build already
|
|
10
10
|
* has no step before `tsc`/`vite` that this would need to run ahead of.
|
|
11
11
|
*/
|
|
12
|
-
export const version = '0.9.0
|
|
12
|
+
export const version = '0.9.0';
|
|
13
13
|
//# sourceMappingURL=version.js.map
|
package/dist/version.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@datamoc/mw_games",
|
|
3
|
-
"version": "0.9.0
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "A local-file game framework: renderer-free game logic, drawn through PixiJS in 2D or Babylon.js in 3D, with mobile and desktop packaging.",
|
|
5
5
|
"license": "MPL-2.0",
|
|
6
6
|
"repository": {
|
package/tools/mwl.mjs
CHANGED
|
@@ -19,7 +19,8 @@ import {
|
|
|
19
19
|
} from '../dist/mwl/index.js';
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
* Command line for MWL: validate, compile, extract-i18n, assets,
|
|
22
|
+
* Command line for MWL: validate, compile, format, migrate, extract-i18n, assets, report,
|
|
23
|
+
* diagnostics, ci, hooks, build.
|
|
23
24
|
*
|
|
24
25
|
* Reading files happens here, not in the published module, so the library
|
|
25
26
|
* itself stays free of node builtins. `hooks` needs esbuild and says so
|
|
@@ -40,7 +41,19 @@ import {
|
|
|
40
41
|
*/
|
|
41
42
|
|
|
42
43
|
const args = process.argv.slice(2);
|
|
43
|
-
const commands = [
|
|
44
|
+
const commands = [
|
|
45
|
+
'validate',
|
|
46
|
+
'compile',
|
|
47
|
+
'format',
|
|
48
|
+
'migrate',
|
|
49
|
+
'extract-i18n',
|
|
50
|
+
'assets',
|
|
51
|
+
'report',
|
|
52
|
+
'diagnostics',
|
|
53
|
+
'ci',
|
|
54
|
+
'hooks',
|
|
55
|
+
'build',
|
|
56
|
+
];
|
|
44
57
|
const [command, input] = args;
|
|
45
58
|
const outputFlag = args.indexOf('-o');
|
|
46
59
|
const output = outputFlag === -1 ? undefined : args[outputFlag + 1];
|
|
@@ -48,17 +61,44 @@ const manifestFlag = args.indexOf('--manifest');
|
|
|
48
61
|
const manifestPath = manifestFlag === -1 ? undefined : args[manifestFlag + 1];
|
|
49
62
|
const assetRootFlag = args.indexOf('--asset-root');
|
|
50
63
|
const assetRoot = assetRootFlag === -1 ? undefined : args[assetRootFlag + 1];
|
|
64
|
+
const targetFlag = args.indexOf('--to');
|
|
65
|
+
const target = targetFlag === -1 ? undefined : args[targetFlag + 1];
|
|
51
66
|
|
|
52
67
|
if (!command || !input || !commands.includes(command)) {
|
|
53
68
|
console.error(
|
|
54
|
-
'Usage: mwl <validate|compile|extract-i18n|assets|report|hooks|build> input.mwl [-o output] [--manifest hooks.json] [--asset-root directory]',
|
|
69
|
+
'Usage: mwl <validate|compile|format|migrate|extract-i18n|assets|report|diagnostics|ci|hooks|build> input.mwl [-o output] [--to 1.0] [--manifest hooks.json] [--asset-root directory]',
|
|
55
70
|
);
|
|
56
71
|
process.exitCode = 2;
|
|
57
72
|
} else {
|
|
58
73
|
const files = fs.statSync(input).isDirectory()
|
|
59
74
|
? collectSources(input)
|
|
60
75
|
: [{ file: sourceLabel(input), source: fs.readFileSync(input, 'utf8') }];
|
|
61
|
-
if (command === '
|
|
76
|
+
if (command === 'format' || command === 'migrate') {
|
|
77
|
+
if (command === 'migrate' && target !== '1.0') {
|
|
78
|
+
console.error('mwl migrate currently supports only --to 1.0');
|
|
79
|
+
process.exitCode = 2;
|
|
80
|
+
} else {
|
|
81
|
+
const nodes = files.flatMap((file) => parse(preprocess(file.source, { file: file.file }), file.file));
|
|
82
|
+
const text = `${JSON.stringify(
|
|
83
|
+
nodes.map((node) => formatNode(node, command === 'migrate' ? target : undefined)),
|
|
84
|
+
null,
|
|
85
|
+
'\t',
|
|
86
|
+
)}\n`;
|
|
87
|
+
if (output) {
|
|
88
|
+
fs.mkdirSync(path.dirname(path.resolve(output)), { recursive: true });
|
|
89
|
+
fs.writeFileSync(output, text);
|
|
90
|
+
} else process.stdout.write(text);
|
|
91
|
+
}
|
|
92
|
+
} else if (command === 'diagnostics' || command === 'ci') {
|
|
93
|
+
const diagnostics = diagnose(files);
|
|
94
|
+
const report = command === 'ci' ? { valid: diagnostics.length === 0, diagnostics } : diagnostics;
|
|
95
|
+
const text = `${JSON.stringify(report, null, '\t')}\n`;
|
|
96
|
+
if (output) {
|
|
97
|
+
fs.mkdirSync(path.dirname(path.resolve(output)), { recursive: true });
|
|
98
|
+
fs.writeFileSync(output, text);
|
|
99
|
+
} else process.stdout.write(text);
|
|
100
|
+
if (diagnostics.length) process.exitCode = 1;
|
|
101
|
+
} else if (command === 'validate') {
|
|
62
102
|
const diagnostics = files.flatMap((file) =>
|
|
63
103
|
validate(parse(preprocess(file.source, { file: file.file }), file.file)),
|
|
64
104
|
);
|
|
@@ -123,6 +163,47 @@ function sourceLabel(file) {
|
|
|
123
163
|
return (relative || path.basename(path.resolve(file))).split(path.sep).join('/');
|
|
124
164
|
}
|
|
125
165
|
|
|
166
|
+
/** Parse and validate every input without throwing, for machine-readable CI diagnostics. */
|
|
167
|
+
function diagnose(files) {
|
|
168
|
+
const diagnostics = [];
|
|
169
|
+
for (const file of files) {
|
|
170
|
+
try {
|
|
171
|
+
diagnostics.push(...validate(parse(preprocess(file.source, { file: file.file }), file.file)));
|
|
172
|
+
} catch (error) {
|
|
173
|
+
if (error?.diagnostic) diagnostics.push(error.diagnostic);
|
|
174
|
+
else
|
|
175
|
+
diagnostics.push({
|
|
176
|
+
code: 'MWL_CLI',
|
|
177
|
+
message: error instanceof Error ? error.message : String(error),
|
|
178
|
+
location: { file: file.file, line: 1, column: 1 },
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return diagnostics.sort(
|
|
183
|
+
(a, b) =>
|
|
184
|
+
a.location.file.localeCompare(b.location.file) ||
|
|
185
|
+
a.location.line - b.location.line ||
|
|
186
|
+
a.location.column - b.location.column ||
|
|
187
|
+
a.code.localeCompare(b.code),
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Emit a stable, source-independent MWL document after preprocessing. */
|
|
192
|
+
function formatNode(node, target) {
|
|
193
|
+
const attributes = Object.fromEntries(
|
|
194
|
+
Object.entries(node.attributes).map(([name, value]) => [
|
|
195
|
+
name,
|
|
196
|
+
node.gettext?.includes(name) ? { $gettext: value } : value,
|
|
197
|
+
]),
|
|
198
|
+
);
|
|
199
|
+
if (target === '1.0' && node.tag === 'game') attributes.schema = '1.0';
|
|
200
|
+
return {
|
|
201
|
+
tag: node.tag,
|
|
202
|
+
...attributes,
|
|
203
|
+
...(node.children.length ? { children: node.children.map((child) => formatNode(child, target)) } : {}),
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
126
207
|
/** One deterministic build step for the three files every MWL game consumes. */
|
|
127
208
|
function build(files) {
|
|
128
209
|
const game = compileSources(files);
|