@datamoc/mw_games 0.8.2 → 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.
Files changed (71) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/README.md +11 -4
  3. package/dist/core/ActionJournal.d.ts +2 -0
  4. package/dist/core/ActionJournal.d.ts.map +1 -1
  5. package/dist/core/ActionJournal.js +6 -0
  6. package/dist/core/ActionJournal.js.map +1 -1
  7. package/dist/core/Multiplayer.d.ts +21 -4
  8. package/dist/core/Multiplayer.d.ts.map +1 -1
  9. package/dist/core/Multiplayer.js +31 -4
  10. package/dist/core/Multiplayer.js.map +1 -1
  11. package/dist/core/State.d.ts +38 -0
  12. package/dist/core/State.d.ts.map +1 -1
  13. package/dist/core/State.js +76 -0
  14. package/dist/core/State.js.map +1 -1
  15. package/dist/core/index.d.ts +3 -3
  16. package/dist/core/index.d.ts.map +1 -1
  17. package/dist/core/index.js +1 -1
  18. package/dist/core/index.js.map +1 -1
  19. package/dist/mw_games.global.js +107 -91
  20. package/dist/mwl/catalog.d.ts +7 -0
  21. package/dist/mwl/catalog.d.ts.map +1 -1
  22. package/dist/mwl/catalog.js +0 -0
  23. package/dist/mwl/catalog.js.map +1 -1
  24. package/dist/mwl/compiler.d.ts +10 -7
  25. package/dist/mwl/compiler.d.ts.map +1 -1
  26. package/dist/mwl/compiler.js +17 -10
  27. package/dist/mwl/compiler.js.map +1 -1
  28. package/dist/mwl/content.d.ts +1 -1
  29. package/dist/mwl/content.js +1 -1
  30. package/dist/mwl/grammar.d.ts +25 -8
  31. package/dist/mwl/grammar.d.ts.map +1 -1
  32. package/dist/mwl/grammar.js +408 -105
  33. package/dist/mwl/grammar.js.map +1 -1
  34. package/dist/mwl/hooks.d.ts +35 -2
  35. package/dist/mwl/hooks.d.ts.map +1 -1
  36. package/dist/mwl/hooks.js +8 -6
  37. package/dist/mwl/hooks.js.map +1 -1
  38. package/dist/mwl/index.d.ts +6 -6
  39. package/dist/mwl/index.d.ts.map +1 -1
  40. package/dist/mwl/index.js +3 -3
  41. package/dist/mwl/index.js.map +1 -1
  42. package/dist/mwl/persistence.d.ts +18 -1
  43. package/dist/mwl/persistence.d.ts.map +1 -1
  44. package/dist/mwl/persistence.js +59 -3
  45. package/dist/mwl/persistence.js.map +1 -1
  46. package/dist/mwl/readers.d.ts +39 -2
  47. package/dist/mwl/readers.d.ts.map +1 -1
  48. package/dist/mwl/readers.js +71 -2
  49. package/dist/mwl/readers.js.map +1 -1
  50. package/dist/mwl/report.d.ts +2 -2
  51. package/dist/mwl/report.js +2 -2
  52. package/dist/mwl/runtime.d.ts +46 -2
  53. package/dist/mwl/runtime.d.ts.map +1 -1
  54. package/dist/mwl/runtime.js +73 -9
  55. package/dist/mwl/runtime.js.map +1 -1
  56. package/dist/mwl/schema.d.ts +32 -1
  57. package/dist/mwl/schema.d.ts.map +1 -1
  58. package/dist/mwl/schema.js +58 -3
  59. package/dist/mwl/schema.js.map +1 -1
  60. package/dist/simulation/Runtime.d.ts +66 -1
  61. package/dist/simulation/Runtime.d.ts.map +1 -1
  62. package/dist/simulation/Runtime.js +131 -3
  63. package/dist/simulation/Runtime.js.map +1 -1
  64. package/dist/simulation/index.d.ts +2 -2
  65. package/dist/simulation/index.d.ts.map +1 -1
  66. package/dist/simulation/index.js +1 -1
  67. package/dist/simulation/index.js.map +1 -1
  68. package/dist/version.d.ts +1 -1
  69. package/dist/version.js +1 -1
  70. package/package.json +2 -1
  71. 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
- readonly scheduler: Scheduler<A>;
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":"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"}
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.scheduler = options.scheduler;
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.scheduler });
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.scheduler.spend(outcome.cost);
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;AAkC1F;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,MAAM,OAAO,iBAAiB;IAO7B,YAAY,OAMX;QACA,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;QAC5B,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;QACnC,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAC7B,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;QACzB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAChC,CAAC;IAED,IAAI,KAAK;QACR,OAAO,IAAI,CAAC,MAAM,CAAC;IACpB,CAAC;IAED;6DACyD;IACzD,QAAQ,CAAC,OAAgB;QACxB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;QACpG,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;QAC5B,IAAI,OAAO,CAAC,IAAI,IAAI,IAAI;YAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC7D,OAAO,OAAO,CAAC;IAChB,CAAC;IAED,QAAQ,CAAC,OAAO,GAAG,CAAC;QACnB,OAAO;YACN,OAAO;YACP,KAAK,EAAE,IAAI,CAAC,MAAM;YAClB,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC;YAC9C,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE;SAC9B,CAAC;IACH,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,OAAO,CACb,QAAmC,EACnC,OAIC;QAED,MAAM,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;QAC/B,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QACjC,MAAM,SAAS,GAAG,SAAS,CAAC,OAAO,CAAI,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;QAC5E,OAAO,IAAI,iBAAiB,CAA2B;YACtD,KAAK,EAAE,QAAQ,CAAC,KAAK;YACrB,SAAS;YACT,MAAM;YACN,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,OAAO,EAAE,OAAO,CAAC,OAAO;SACxB,CAAC,CAAC;IACJ,CAAC;CACD"}
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;AACjD,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACpH,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"}
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"}
@@ -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;AAEjD,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"}
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.8.2";
12
+ export declare const version = "0.9.0";
13
13
  //# sourceMappingURL=version.d.ts.map
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.8.2';
12
+ export const version = '0.9.0';
13
13
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datamoc/mw_games",
3
- "version": "0.8.2",
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": {
@@ -255,6 +255,7 @@
255
255
  "roadmap:browser": "node tools/roadmap-progress.mjs --browser"
256
256
  },
257
257
  "dependencies": {
258
+ "json5": "^2.2.3",
258
259
  "rot-js": "^2.2.1"
259
260
  },
260
261
  "peerDependencies": {
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, hooks, build.
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 = ['validate', 'compile', 'extract-i18n', 'assets', 'report', 'hooks', 'build'];
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 === 'validate') {
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);