@datamoc/mw_games 0.5.4 → 0.7.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 (303) hide show
  1. package/README.md +58 -3
  2. package/dist/actors/Appearances.d.ts.map +1 -1
  3. package/dist/actors/Appearances.js.map +1 -1
  4. package/dist/actors/Aura.d.ts.map +1 -1
  5. package/dist/actors/Aura.js.map +1 -1
  6. package/dist/actors/EntityTemplate.d.ts.map +1 -1
  7. package/dist/actors/EntityTemplate.js.map +1 -1
  8. package/dist/actors/Inventory.d.ts.map +1 -1
  9. package/dist/actors/Inventory.js +6 -1
  10. package/dist/actors/Inventory.js.map +1 -1
  11. package/dist/actors/Support.d.ts.map +1 -1
  12. package/dist/actors/Support.js.map +1 -1
  13. package/dist/actors/index.d.ts +1 -1
  14. package/dist/actors/index.d.ts.map +1 -1
  15. package/dist/actors/index.js.map +1 -1
  16. package/dist/actors/skillCheck.d.ts.map +1 -1
  17. package/dist/actors/skillCheck.js.map +1 -1
  18. package/dist/assets/Streaming.d.ts.map +1 -1
  19. package/dist/assets/Streaming.js.map +1 -1
  20. package/dist/assets/binary.d.ts.map +1 -1
  21. package/dist/assets/binary.js.map +1 -1
  22. package/dist/assets/loader.d.ts +15 -4
  23. package/dist/assets/loader.d.ts.map +1 -1
  24. package/dist/assets/loader.js +8 -23
  25. package/dist/assets/loader.js.map +1 -1
  26. package/dist/audio/Midi.d.ts.map +1 -1
  27. package/dist/audio/Midi.js +17 -3
  28. package/dist/audio/Midi.js.map +1 -1
  29. package/dist/audio/Music.d.ts.map +1 -1
  30. package/dist/audio/Music.js +8 -1
  31. package/dist/audio/Music.js.map +1 -1
  32. package/dist/audio/Synth.d.ts.map +1 -1
  33. package/dist/audio/Synth.js +1 -1
  34. package/dist/audio/Synth.js.map +1 -1
  35. package/dist/battle/BattleAI.js.map +1 -1
  36. package/dist/board/Army.d.ts.map +1 -1
  37. package/dist/board/Army.js.map +1 -1
  38. package/dist/board/Classics.d.ts.map +1 -1
  39. package/dist/board/Classics.js +310 -133
  40. package/dist/board/Classics.js.map +1 -1
  41. package/dist/board/FogOfWar.d.ts.map +1 -1
  42. package/dist/board/FogOfWar.js +12 -4
  43. package/dist/board/FogOfWar.js.map +1 -1
  44. package/dist/board/HexSkirmish.d.ts.map +1 -1
  45. package/dist/board/HexSkirmish.js +12 -4
  46. package/dist/board/HexSkirmish.js.map +1 -1
  47. package/dist/board/Tactics.d.ts.map +1 -1
  48. package/dist/board/Tactics.js +71 -26
  49. package/dist/board/Tactics.js.map +1 -1
  50. package/dist/board/chess.d.ts.map +1 -1
  51. package/dist/board/chess.js +8 -4
  52. package/dist/board/chess.js.map +1 -1
  53. package/dist/board/index.d.ts +2 -2
  54. package/dist/board/index.d.ts.map +1 -1
  55. package/dist/board/index.js +1 -1
  56. package/dist/board/index.js.map +1 -1
  57. package/dist/{roguelike → core}/Blob.d.ts +22 -11
  58. package/dist/core/Blob.d.ts.map +1 -0
  59. package/dist/{roguelike → core}/Blob.js +51 -28
  60. package/dist/core/Blob.js.map +1 -0
  61. package/dist/core/Csv.d.ts.map +1 -1
  62. package/dist/core/Csv.js +8 -2
  63. package/dist/core/Csv.js.map +1 -1
  64. package/dist/core/Input.d.ts.map +1 -1
  65. package/dist/core/Input.js +17 -4
  66. package/dist/core/Input.js.map +1 -1
  67. package/dist/core/Loading.d.ts.map +1 -1
  68. package/dist/core/Loading.js.map +1 -1
  69. package/dist/core/Motion.d.ts +27 -0
  70. package/dist/core/Motion.d.ts.map +1 -0
  71. package/dist/core/Motion.js +102 -0
  72. package/dist/core/Motion.js.map +1 -0
  73. package/dist/core/Multiplayer.d.ts.map +1 -1
  74. package/dist/core/Multiplayer.js.map +1 -1
  75. package/dist/core/PlayerInput.d.ts.map +1 -1
  76. package/dist/core/PlayerInput.js.map +1 -1
  77. package/dist/core/Replay.d.ts.map +1 -1
  78. package/dist/core/Replay.js.map +1 -1
  79. package/dist/core/Sanitize.d.ts.map +1 -1
  80. package/dist/core/Sanitize.js.map +1 -1
  81. package/dist/core/Save.d.ts.map +1 -1
  82. package/dist/core/Save.js.map +1 -1
  83. package/dist/core/Tween.d.ts +22 -2
  84. package/dist/core/Tween.d.ts.map +1 -1
  85. package/dist/core/Tween.js +30 -7
  86. package/dist/core/Tween.js.map +1 -1
  87. package/dist/core/index.d.ts +4 -0
  88. package/dist/core/index.d.ts.map +1 -1
  89. package/dist/core/index.js +2 -0
  90. package/dist/core/index.js.map +1 -1
  91. package/dist/i18n/Content.js +1 -2
  92. package/dist/i18n/Content.js.map +1 -1
  93. package/dist/i18n/EditSession.d.ts.map +1 -1
  94. package/dist/i18n/EditSession.js.map +1 -1
  95. package/dist/i18n/Fluent.d.ts.map +1 -1
  96. package/dist/i18n/Fluent.js +5 -1
  97. package/dist/i18n/Fluent.js.map +1 -1
  98. package/dist/i18n/FormatSpec.d.ts.map +1 -1
  99. package/dist/i18n/FormatSpec.js +3 -1
  100. package/dist/i18n/FormatSpec.js.map +1 -1
  101. package/dist/i18n/Validate.d.ts.map +1 -1
  102. package/dist/i18n/Validate.js +10 -2
  103. package/dist/i18n/Validate.js.map +1 -1
  104. package/dist/i18n/index.d.ts +1 -1
  105. package/dist/i18n/index.d.ts.map +1 -1
  106. package/dist/i18n/index.js +27 -4
  107. package/dist/i18n/index.js.map +1 -1
  108. package/dist/index.d.ts +1 -0
  109. package/dist/index.d.ts.map +1 -1
  110. package/dist/index.js +1 -0
  111. package/dist/index.js.map +1 -1
  112. package/dist/mw_games.global.js +97 -94
  113. package/dist/mwl/actors.d.ts +16 -0
  114. package/dist/mwl/actors.d.ts.map +1 -0
  115. package/dist/mwl/actors.js +25 -0
  116. package/dist/mwl/actors.js.map +1 -0
  117. package/dist/mwl/catalog.d.ts +13 -0
  118. package/dist/mwl/catalog.d.ts.map +1 -0
  119. package/dist/mwl/catalog.js +52 -0
  120. package/dist/mwl/catalog.js.map +1 -0
  121. package/dist/mwl/compiler.d.ts +45 -0
  122. package/dist/mwl/compiler.d.ts.map +1 -0
  123. package/dist/mwl/compiler.js +69 -0
  124. package/dist/mwl/compiler.js.map +1 -0
  125. package/dist/mwl/content.d.ts +54 -0
  126. package/dist/mwl/content.d.ts.map +1 -0
  127. package/dist/mwl/content.js +97 -0
  128. package/dist/mwl/content.js.map +1 -0
  129. package/dist/mwl/expression.d.ts +21 -0
  130. package/dist/mwl/expression.d.ts.map +1 -0
  131. package/dist/mwl/expression.js +100 -0
  132. package/dist/mwl/expression.js.map +1 -0
  133. package/dist/mwl/grammar.d.ts +39 -0
  134. package/dist/mwl/grammar.d.ts.map +1 -0
  135. package/dist/mwl/grammar.js +204 -0
  136. package/dist/mwl/grammar.js.map +1 -0
  137. package/dist/mwl/hooks.d.ts +81 -0
  138. package/dist/mwl/hooks.d.ts.map +1 -0
  139. package/dist/mwl/hooks.js +78 -0
  140. package/dist/mwl/hooks.js.map +1 -0
  141. package/dist/mwl/index.d.ts +21 -0
  142. package/dist/mwl/index.d.ts.map +1 -0
  143. package/dist/mwl/index.js +11 -0
  144. package/dist/mwl/index.js.map +1 -0
  145. package/dist/mwl/persistence.d.ts +15 -0
  146. package/dist/mwl/persistence.d.ts.map +1 -0
  147. package/dist/mwl/persistence.js +33 -0
  148. package/dist/mwl/persistence.js.map +1 -0
  149. package/dist/mwl/runtime.d.ts +169 -0
  150. package/dist/mwl/runtime.d.ts.map +1 -0
  151. package/dist/mwl/runtime.js +519 -0
  152. package/dist/mwl/runtime.js.map +1 -0
  153. package/dist/mwl/schema.d.ts +20 -0
  154. package/dist/mwl/schema.d.ts.map +1 -0
  155. package/dist/mwl/schema.js +351 -0
  156. package/dist/mwl/schema.js.map +1 -0
  157. package/dist/roguelike/ContentRoll.d.ts.map +1 -1
  158. package/dist/roguelike/ContentRoll.js.map +1 -1
  159. package/dist/roguelike/Doors.d.ts.map +1 -1
  160. package/dist/roguelike/Doors.js.map +1 -1
  161. package/dist/roguelike/DungeonParity.d.ts.map +1 -1
  162. package/dist/roguelike/DungeonParity.js +13 -4
  163. package/dist/roguelike/DungeonParity.js.map +1 -1
  164. package/dist/roguelike/Features.d.ts.map +1 -1
  165. package/dist/roguelike/Features.js.map +1 -1
  166. package/dist/roguelike/FieldOfView.d.ts.map +1 -1
  167. package/dist/roguelike/FieldOfView.js.map +1 -1
  168. package/dist/roguelike/Level.d.ts.map +1 -1
  169. package/dist/roguelike/Level.js.map +1 -1
  170. package/dist/roguelike/Scheduler.d.ts.map +1 -1
  171. package/dist/roguelike/Scheduler.js +10 -2
  172. package/dist/roguelike/Scheduler.js.map +1 -1
  173. package/dist/roguelike/Targeting.d.ts.map +1 -1
  174. package/dist/roguelike/Targeting.js.map +1 -1
  175. package/dist/roguelike/index.d.ts +0 -1
  176. package/dist/roguelike/index.d.ts.map +1 -1
  177. package/dist/roguelike/index.js +0 -1
  178. package/dist/roguelike/index.js.map +1 -1
  179. package/dist/rpg/Collision.d.ts.map +1 -1
  180. package/dist/rpg/Collision.js.map +1 -1
  181. package/dist/rpg/EventRunner.d.ts.map +1 -1
  182. package/dist/rpg/EventRunner.js.map +1 -1
  183. package/dist/rpg/Marshal.d.ts.map +1 -1
  184. package/dist/rpg/Marshal.js +2 -2
  185. package/dist/rpg/Marshal.js.map +1 -1
  186. package/dist/rpg/Quest.js.map +1 -1
  187. package/dist/rpg/index.d.ts +1 -1
  188. package/dist/rpg/index.d.ts.map +1 -1
  189. package/dist/rpg/index.js.map +1 -1
  190. package/dist/simulation/Scenario.d.ts.map +1 -1
  191. package/dist/simulation/Scenario.js.map +1 -1
  192. package/dist/simulation/Turns.d.ts.map +1 -1
  193. package/dist/simulation/Turns.js.map +1 -1
  194. package/dist/simulation/index.d.ts +1 -1
  195. package/dist/simulation/index.d.ts.map +1 -1
  196. package/dist/simulation/index.js.map +1 -1
  197. package/dist/three-d/Character3D.d.ts.map +1 -1
  198. package/dist/three-d/Character3D.js +3 -1
  199. package/dist/three-d/Character3D.js.map +1 -1
  200. package/dist/three-d/Collision3D.d.ts.map +1 -1
  201. package/dist/three-d/Collision3D.js.map +1 -1
  202. package/dist/three-d/Heightmap.d.ts.map +1 -1
  203. package/dist/three-d/Heightmap.js.map +1 -1
  204. package/dist/three-d/Models.d.ts.map +1 -1
  205. package/dist/three-d/Models.js.map +1 -1
  206. package/dist/three-d/TileGrid3D.d.ts.map +1 -1
  207. package/dist/three-d/TileGrid3D.js +5 -4
  208. package/dist/three-d/TileGrid3D.js.map +1 -1
  209. package/dist/three-d/Vox.d.ts.map +1 -1
  210. package/dist/three-d/Vox.js +6 -3
  211. package/dist/three-d/Vox.js.map +1 -1
  212. package/dist/two-d/render/AnimatedSprite.d.ts +6 -0
  213. package/dist/two-d/render/AnimatedSprite.d.ts.map +1 -1
  214. package/dist/two-d/render/AnimatedSprite.js +6 -0
  215. package/dist/two-d/render/AnimatedSprite.js.map +1 -1
  216. package/dist/two-d/render/Camera.d.ts.map +1 -1
  217. package/dist/two-d/render/Camera.js +13 -5
  218. package/dist/two-d/render/Camera.js.map +1 -1
  219. package/dist/two-d/render/Capabilities.d.ts.map +1 -1
  220. package/dist/two-d/render/Capabilities.js +48 -8
  221. package/dist/two-d/render/Capabilities.js.map +1 -1
  222. package/dist/two-d/render/ColorBlindness.d.ts.map +1 -1
  223. package/dist/two-d/render/ColorBlindness.js +3 -18
  224. package/dist/two-d/render/ColorBlindness.js.map +1 -1
  225. package/dist/two-d/render/ColorTransformBatcher.d.ts.map +1 -1
  226. package/dist/two-d/render/ColorTransformBatcher.js +1 -1
  227. package/dist/two-d/render/ColorTransformBatcher.js.map +1 -1
  228. package/dist/two-d/render/Particles.d.ts.map +1 -1
  229. package/dist/two-d/render/Particles.js +32 -6
  230. package/dist/two-d/render/Particles.js.map +1 -1
  231. package/dist/two-d/render/ScreenEffects.d.ts.map +1 -1
  232. package/dist/two-d/render/ScreenEffects.js +11 -0
  233. package/dist/two-d/render/ScreenEffects.js.map +1 -1
  234. package/dist/two-d/render/StatusVisuals.d.ts.map +1 -1
  235. package/dist/two-d/render/StatusVisuals.js +3 -1
  236. package/dist/two-d/render/StatusVisuals.js.map +1 -1
  237. package/dist/two-d/render/TileMap.d.ts.map +1 -1
  238. package/dist/two-d/render/TileMap.js +20 -16
  239. package/dist/two-d/render/TileMap.js.map +1 -1
  240. package/dist/two-d/render/TiledMap.d.ts.map +1 -1
  241. package/dist/two-d/render/TiledMap.js.map +1 -1
  242. package/dist/two-d/render/index.d.ts +3 -3
  243. package/dist/two-d/render/index.d.ts.map +1 -1
  244. package/dist/two-d/render/index.js +1 -1
  245. package/dist/two-d/render/index.js.map +1 -1
  246. package/dist/two-d/stage/DialogueStage.d.ts.map +1 -1
  247. package/dist/two-d/stage/DialogueStage.js +8 -4
  248. package/dist/two-d/stage/DialogueStage.js.map +1 -1
  249. package/dist/two-d/stage/script.d.ts.map +1 -1
  250. package/dist/two-d/stage/script.js.map +1 -1
  251. package/dist/two-d/stage/twee.d.ts.map +1 -1
  252. package/dist/two-d/stage/twee.js.map +1 -1
  253. package/dist/two-d/ui/Button.d.ts.map +1 -1
  254. package/dist/two-d/ui/Button.js +5 -2
  255. package/dist/two-d/ui/Button.js.map +1 -1
  256. package/dist/two-d/ui/FloatingText.d.ts.map +1 -1
  257. package/dist/two-d/ui/FloatingText.js +4 -1
  258. package/dist/two-d/ui/FloatingText.js.map +1 -1
  259. package/dist/two-d/ui/IconGrid.d.ts.map +1 -1
  260. package/dist/two-d/ui/IconGrid.js.map +1 -1
  261. package/dist/two-d/ui/ListView.d.ts.map +1 -1
  262. package/dist/two-d/ui/ListView.js.map +1 -1
  263. package/dist/two-d/ui/LoadingScreen.d.ts.map +1 -1
  264. package/dist/two-d/ui/LoadingScreen.js +6 -2
  265. package/dist/two-d/ui/LoadingScreen.js.map +1 -1
  266. package/dist/two-d/ui/MessageBox.d.ts +6 -0
  267. package/dist/two-d/ui/MessageBox.d.ts.map +1 -1
  268. package/dist/two-d/ui/MessageBox.js +11 -1
  269. package/dist/two-d/ui/MessageBox.js.map +1 -1
  270. package/dist/two-d/ui/NinePatch.d.ts.map +1 -1
  271. package/dist/two-d/ui/NinePatch.js.map +1 -1
  272. package/dist/two-d/ui/RebindScreen.d.ts.map +1 -1
  273. package/dist/two-d/ui/RebindScreen.js.map +1 -1
  274. package/dist/two-d/ui/Window.d.ts.map +1 -1
  275. package/dist/two-d/ui/Window.js +3 -0
  276. package/dist/two-d/ui/Window.js.map +1 -1
  277. package/dist/two-d/ui/a11y.d.ts +43 -0
  278. package/dist/two-d/ui/a11y.d.ts.map +1 -0
  279. package/dist/two-d/ui/a11y.js +79 -0
  280. package/dist/two-d/ui/a11y.js.map +1 -0
  281. package/dist/two-d/ui/contrast.d.ts +30 -0
  282. package/dist/two-d/ui/contrast.d.ts.map +1 -0
  283. package/dist/two-d/ui/contrast.js +42 -0
  284. package/dist/two-d/ui/contrast.js.map +1 -0
  285. package/dist/two-d/ui/index.d.ts +3 -0
  286. package/dist/two-d/ui/index.d.ts.map +1 -1
  287. package/dist/two-d/ui/index.js +2 -0
  288. package/dist/two-d/ui/index.js.map +1 -1
  289. package/dist/two-d/ui/markdown.d.ts.map +1 -1
  290. package/dist/two-d/ui/markdown.js +3 -1
  291. package/dist/two-d/ui/markdown.js.map +1 -1
  292. package/dist/two-d/ui/themedText.js +1 -1
  293. package/dist/two-d/ui/themedText.js.map +1 -1
  294. package/dist/version.d.ts +1 -1
  295. package/dist/version.js +1 -1
  296. package/dist/world/Environment.d.ts.map +1 -1
  297. package/dist/world/Environment.js +17 -6
  298. package/dist/world/Environment.js.map +1 -1
  299. package/package.json +264 -239
  300. package/tools/compress-dist.mjs +15 -1
  301. package/tools/mwl.mjs +153 -0
  302. package/dist/roguelike/Blob.d.ts.map +0 -1
  303. package/dist/roguelike/Blob.js.map +0 -1
@@ -0,0 +1,169 @@
1
+ import type { MwlCompiledGame } from './compiler.ts';
2
+ import type { AiHook, CommandHook, GeneratorHook, MigrationHook, ModifierHook, PredicateHook } from './hooks.ts';
3
+ import { type MwlPersistenceOptions } from './persistence.ts';
4
+ export interface MwlMapStart {
5
+ readonly x: number;
6
+ readonly y: number;
7
+ }
8
+ export interface MwlMap {
9
+ readonly id: string;
10
+ readonly width: number;
11
+ readonly height: number;
12
+ /** row-major terrain codes, keep markers stripped */
13
+ readonly codes: readonly string[];
14
+ /** side number -> keep/start positions */
15
+ readonly starts: Readonly<Record<number, readonly MwlMapStart[]>>;
16
+ }
17
+ export interface MwlWorld {
18
+ readonly variables: Record<string, string | number | boolean>;
19
+ readonly units: Record<string, {
20
+ hp: number;
21
+ x: number;
22
+ y: number;
23
+ alive: boolean;
24
+ type?: string;
25
+ side?: number;
26
+ moves?: number;
27
+ }>;
28
+ readonly sides: Record<string, {
29
+ gold: number;
30
+ income: number;
31
+ leader?: string;
32
+ controller?: string;
33
+ recruit?: string;
34
+ }>;
35
+ readonly maps: Record<string, {
36
+ terrain: string;
37
+ file?: string;
38
+ }>;
39
+ gold: Record<string, number>;
40
+ turn: number;
41
+ status: 'playing' | 'won' | 'lost';
42
+ /** the primary map, when the content declares one */
43
+ map?: MwlMap | null;
44
+ /** the current schedule entry, by id */
45
+ timeOfDay?: string;
46
+ scheduleIndex?: number;
47
+ }
48
+ /** The hook implementations a game provides to the runtime. */
49
+ export interface MwlHookRegistry {
50
+ readonly predicate?: Readonly<Record<string, PredicateHook>>;
51
+ readonly modifier?: Readonly<Record<string, ModifierHook>>;
52
+ readonly generator?: Readonly<Record<string, GeneratorHook>>;
53
+ readonly command?: Readonly<Record<string, CommandHook>>;
54
+ readonly ai?: Readonly<Record<string, AiHook>>;
55
+ readonly migration?: Readonly<Record<string, MigrationHook>>;
56
+ }
57
+ export interface MwlRuntimeOptions {
58
+ readonly world?: MwlWorld;
59
+ readonly onMessage?: (text: string) => void;
60
+ /** resolve a `[map] file=` reference to its text */
61
+ readonly resolveMap?: (file: string) => string;
62
+ readonly hooks?: MwlHookRegistry;
63
+ /** schema version and migrations used by save/restore; defaults to version 1 */
64
+ readonly persistence?: MwlPersistenceOptions;
65
+ }
66
+ export type MwlCommand = {
67
+ readonly name: 'set_variable';
68
+ readonly target: string;
69
+ readonly value: string | number | boolean;
70
+ } | {
71
+ readonly name: 'modify_gold';
72
+ readonly target: string;
73
+ readonly amount: number;
74
+ } | {
75
+ readonly name: 'move';
76
+ readonly target: string;
77
+ readonly x: number;
78
+ readonly y: number;
79
+ } | {
80
+ readonly name: 'spawn';
81
+ readonly target: string;
82
+ readonly x: number;
83
+ readonly y: number;
84
+ readonly hp: number;
85
+ } | {
86
+ readonly name: 'kill';
87
+ readonly target: string;
88
+ } | {
89
+ readonly name: 'attack';
90
+ readonly target: string;
91
+ readonly amount: number;
92
+ } | {
93
+ readonly name: 'end_turn';
94
+ } | {
95
+ readonly name: 'win';
96
+ } | {
97
+ readonly name: 'lose';
98
+ };
99
+ export declare function createWorld(): MwlWorld;
100
+ /**
101
+ * Parse map text: one row per line, comma-separated codes. A token may be
102
+ * `<side> <code>` to mark that side's keep/start, which is how a leader knows
103
+ * where to appear.
104
+ */
105
+ export declare function parseTerrain(text: string): {
106
+ width: number;
107
+ height: number;
108
+ codes: string[];
109
+ starts: Record<number, {
110
+ x: number;
111
+ y: number;
112
+ }[]>;
113
+ };
114
+ /**
115
+ * Runtime for compiled MWL content. It never parses source text during play.
116
+ *
117
+ * The framework runtime owns what is game-agnostic: maps, sides, units, turn
118
+ * order, the schedule, objectives, events, and save/load. Game rules that need
119
+ * real data (pathfinding costs, combat formulas) stay in the engine adapter;
120
+ * this runtime's `move` is a bounds and occupancy check, and `attack` applies
121
+ * the amount the content asks for.
122
+ */
123
+ export declare class MwlRuntime {
124
+ readonly game: MwlCompiledGame;
125
+ readonly world: MwlWorld;
126
+ private readonly onMessage?;
127
+ private readonly resolveMap?;
128
+ private readonly hooks?;
129
+ private readonly persistence;
130
+ private readonly unitTypes;
131
+ private schedule;
132
+ constructor(game: MwlCompiledGame, options?: MwlRuntimeOptions);
133
+ run(trigger: string): void;
134
+ /**
135
+ * Evaluate `[objectives]` now and return the resulting status. A game whose
136
+ * own engine resolved the actions (real movement, combat) calls this after
137
+ * writing the results into `world`; `run` already calls it itself.
138
+ */
139
+ evaluate(): MwlWorld['status'];
140
+ save(): string;
141
+ snapshot(): string;
142
+ restore(snapshot: string): void;
143
+ private loadUnitTypes;
144
+ private loadSchedule;
145
+ private loadInitialContent;
146
+ private buildMap;
147
+ private loadInitialUnits;
148
+ private loadLeaders;
149
+ private unitAt;
150
+ private executeNode;
151
+ private runHook;
152
+ private applyMove;
153
+ private spawnUnit;
154
+ private killUnit;
155
+ private addGold;
156
+ private endTurn;
157
+ private advanceSchedule;
158
+ private resetMoves;
159
+ private filterMatches;
160
+ private conditionMatches;
161
+ private conditionMet;
162
+ private checkObjectives;
163
+ private worldView;
164
+ private emit;
165
+ private attack;
166
+ private nodes;
167
+ }
168
+ export declare function execute(world: MwlWorld, command: MwlCommand): void;
169
+ //# sourceMappingURL=runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/mwl/runtime.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,519 @@
1
+ import { decodeSave, encodeSave } from './persistence.js';
2
+ export function createWorld() {
3
+ return {
4
+ variables: {},
5
+ units: {},
6
+ sides: {},
7
+ maps: {},
8
+ gold: {},
9
+ turn: 1,
10
+ status: 'playing',
11
+ map: null,
12
+ timeOfDay: '',
13
+ scheduleIndex: 0,
14
+ };
15
+ }
16
+ /**
17
+ * Parse map text: one row per line, comma-separated codes. A token may be
18
+ * `<side> <code>` to mark that side's keep/start, which is how a leader knows
19
+ * where to appear.
20
+ */
21
+ export function parseTerrain(text) {
22
+ const rows = text
23
+ .split(/\r?\n/)
24
+ .filter((line) => line.trim() !== '')
25
+ .map((line) => line.split(',').map((token) => token.trim()));
26
+ const height = rows.length;
27
+ const width = rows.length ? Math.max(...rows.map((row) => row.length)) : 0;
28
+ const codes = [];
29
+ const starts = {};
30
+ for (let y = 0; y < height; y++) {
31
+ for (let x = 0; x < width; x++) {
32
+ const token = rows[y]?.[x] ?? '_off^_usr';
33
+ const keep = /^([0-9]+)\s+(.+)$/.exec(token);
34
+ if (keep) {
35
+ codes.push(keep[2]);
36
+ (starts[Number(keep[1])] ??= []).push({ x, y });
37
+ }
38
+ else
39
+ codes.push(token);
40
+ }
41
+ }
42
+ return { width, height, codes, starts };
43
+ }
44
+ /**
45
+ * Runtime for compiled MWL content. It never parses source text during play.
46
+ *
47
+ * The framework runtime owns what is game-agnostic: maps, sides, units, turn
48
+ * order, the schedule, objectives, events, and save/load. Game rules that need
49
+ * real data (pathfinding costs, combat formulas) stay in the engine adapter;
50
+ * this runtime's `move` is a bounds and occupancy check, and `attack` applies
51
+ * the amount the content asks for.
52
+ */
53
+ export class MwlRuntime {
54
+ constructor(game, options = {}) {
55
+ this.unitTypes = new Map();
56
+ this.schedule = [];
57
+ this.game = game;
58
+ this.world = options.world ?? createWorld();
59
+ this.onMessage = options.onMessage;
60
+ this.resolveMap = options.resolveMap;
61
+ this.hooks = options.hooks;
62
+ this.persistence = options.persistence ?? { version: 1 };
63
+ this.loadUnitTypes();
64
+ this.loadSchedule();
65
+ this.loadInitialContent();
66
+ this.loadInitialUnits();
67
+ this.loadLeaders();
68
+ }
69
+ run(trigger) {
70
+ for (const event of this.nodes('event')) {
71
+ if ((event.attributes.on ?? event.attributes.trigger) !== trigger)
72
+ continue;
73
+ const condition = event.children.find((child) => child.tag === 'condition');
74
+ if (condition && this.world.variables[condition.attributes.variable] !== condition.attributes.equals)
75
+ continue;
76
+ const filter = event.children.find((child) => child.tag === 'filter');
77
+ if (filter && !this.filterMatches(filter))
78
+ continue;
79
+ for (const command of event.children.filter((child) => child.tag !== 'condition' &&
80
+ child.tag !== 'filter' &&
81
+ child.tag !== 'dialogue' &&
82
+ child.tag !== 'say'))
83
+ this.executeNode(command);
84
+ }
85
+ this.checkObjectives();
86
+ }
87
+ /**
88
+ * Evaluate `[objectives]` now and return the resulting status. A game whose
89
+ * own engine resolved the actions (real movement, combat) calls this after
90
+ * writing the results into `world`; `run` already calls it itself.
91
+ */
92
+ evaluate() {
93
+ this.checkObjectives();
94
+ return this.world.status;
95
+ }
96
+ save() {
97
+ return encodeSave(this.world, this.persistence);
98
+ }
99
+ snapshot() {
100
+ return JSON.stringify(this.world);
101
+ }
102
+ restore(snapshot) {
103
+ const restored = decodeSave(snapshot, this.persistence);
104
+ Object.assign(this.world.variables, restored.variables);
105
+ Object.assign(this.world.units, restored.units);
106
+ Object.assign(this.world.sides, restored.sides ?? {});
107
+ Object.assign(this.world.maps, restored.maps ?? {});
108
+ Object.assign(this.world.gold, restored.gold ?? {});
109
+ this.world.turn = restored.turn;
110
+ this.world.status = restored.status;
111
+ this.world.map = restored.map ?? null;
112
+ this.world.timeOfDay = restored.timeOfDay ?? this.schedule[0] ?? '';
113
+ this.world.scheduleIndex = restored.scheduleIndex ?? 0;
114
+ }
115
+ loadUnitTypes() {
116
+ for (const node of this.nodes('unit_type')) {
117
+ const id = node.attributes.id;
118
+ if (id)
119
+ this.unitTypes.set(id, {
120
+ hitpoints: integer(node, 'hitpoints', 1),
121
+ movement: integer(node, 'movement', 0),
122
+ });
123
+ }
124
+ }
125
+ loadSchedule() {
126
+ this.schedule = this.nodes('schedule').flatMap((schedule) => schedule.children
127
+ .filter((child) => child.tag === 'time')
128
+ .map((child) => child.attributes.id)
129
+ .filter((id) => Boolean(id)));
130
+ this.world.scheduleIndex = 0;
131
+ this.world.timeOfDay = this.schedule[0] ?? '';
132
+ }
133
+ loadInitialContent() {
134
+ for (const side of this.nodes('side')) {
135
+ const entry = {
136
+ gold: integer(side, 'gold', 0),
137
+ income: integer(side, 'income', 0),
138
+ };
139
+ if (side.attributes.leader !== undefined)
140
+ entry.leader = side.attributes.leader;
141
+ if (side.attributes.controller !== undefined)
142
+ entry.controller = side.attributes.controller;
143
+ if (side.attributes.recruit !== undefined)
144
+ entry.recruit = side.attributes.recruit;
145
+ this.world.sides[required(side, 'id')] = entry;
146
+ }
147
+ for (const map of this.nodes('map')) {
148
+ const id = map.attributes.id ?? map.attributes.name ?? 'map';
149
+ this.world.maps[id] = { terrain: map.attributes.terrain ?? '', file: map.attributes.file };
150
+ if (!this.world.map)
151
+ this.world.map = this.buildMap(id, map);
152
+ }
153
+ }
154
+ buildMap(id, node) {
155
+ let text = node.attributes.terrain;
156
+ if (!text && node.attributes.file && this.resolveMap)
157
+ text = this.resolveMap(node.attributes.file);
158
+ if (!text)
159
+ return null;
160
+ const parsed = parseTerrain(text);
161
+ for (const start of node.children) {
162
+ if (start.tag !== 'start')
163
+ continue;
164
+ const side = optionalInteger(start, 'side');
165
+ const x = optionalInteger(start, 'x');
166
+ const y = optionalInteger(start, 'y');
167
+ if (side !== undefined && x !== undefined && y !== undefined)
168
+ (parsed.starts[side] ??= []).push({ x, y });
169
+ }
170
+ return { id, ...parsed };
171
+ }
172
+ loadInitialUnits() {
173
+ for (const unit of this.nodes('unit')) {
174
+ const id = required(unit, 'id');
175
+ const entry = {
176
+ hp: integer(unit, 'hp', 1),
177
+ x: integer(unit, 'x', 0),
178
+ y: integer(unit, 'y', 0),
179
+ alive: true,
180
+ };
181
+ if (unit.attributes.type !== undefined) {
182
+ entry.type = unit.attributes.type;
183
+ const stats = this.unitTypes.get(unit.attributes.type);
184
+ if (stats)
185
+ entry.moves = stats.movement;
186
+ }
187
+ if (unit.attributes.side !== undefined)
188
+ entry.side = integer(unit, 'side', 0);
189
+ this.world.units[id] = entry;
190
+ }
191
+ }
192
+ loadLeaders() {
193
+ for (const side of this.nodes('side')) {
194
+ const leader = side.attributes.leader;
195
+ if (!leader)
196
+ continue;
197
+ const sideNumber = optionalInteger(side, 'id');
198
+ if (sideNumber === undefined)
199
+ continue;
200
+ const starts = this.world.map?.starts[sideNumber];
201
+ if (!starts?.length)
202
+ continue;
203
+ const stats = this.unitTypes.get(leader);
204
+ for (const start of starts) {
205
+ if (this.unitAt(start.x, start.y))
206
+ continue;
207
+ const id = `${leader} ${sideNumber} (${start.x},${start.y})`;
208
+ this.world.units[id] = {
209
+ hp: stats?.hitpoints ?? 1,
210
+ x: start.x,
211
+ y: start.y,
212
+ alive: true,
213
+ type: leader,
214
+ side: sideNumber,
215
+ moves: stats?.movement ?? 0,
216
+ };
217
+ }
218
+ }
219
+ }
220
+ unitAt(x, y) {
221
+ return Object.values(this.world.units).find((unit) => unit.alive && unit.x === x && unit.y === y);
222
+ }
223
+ executeNode(node) {
224
+ const name = node.tag === 'command' ? node.attributes.name : node.tag;
225
+ if (!name)
226
+ throw new Error('MWL command is missing name');
227
+ switch (name) {
228
+ case 'message':
229
+ this.onMessage?.(node.attributes.text ?? node.attributes.value ?? '');
230
+ break;
231
+ case 'spawn':
232
+ this.spawnUnit(node.attributes.type ?? '', node.attributes.side === undefined ? undefined : integer(node, 'side', 0), integer(node, 'x', 0), integer(node, 'y', 0), node.attributes.id ?? node.attributes.target, node.attributes.hp === undefined ? undefined : integer(node, 'hp', 1));
233
+ break;
234
+ case 'move':
235
+ this.applyMove(node.attributes.unit ?? required(node, 'target'), integer(node, 'x', 0), integer(node, 'y', 0));
236
+ break;
237
+ case 'kill':
238
+ this.killUnit(node.attributes.unit ?? required(node, 'target'));
239
+ break;
240
+ case 'attack':
241
+ this.attack(node.attributes.defender ?? node.attributes.target ?? required(node, 'target'), integer(node, 'amount', 0));
242
+ break;
243
+ case 'modify_gold':
244
+ this.addGold(node.attributes.side ?? required(node, 'target'), integer(node, 'delta', integer(node, 'amount', 0)));
245
+ break;
246
+ case 'gold':
247
+ this.addGold(required(node, 'side'), integer(node, 'delta', integer(node, 'amount', 0)));
248
+ break;
249
+ case 'set_variable':
250
+ this.world.variables[node.tag === 'command' ? required(node, 'target') : required(node, 'name')] =
251
+ node.attributes.value ?? '';
252
+ break;
253
+ case 'end_turn':
254
+ this.endTurn();
255
+ break;
256
+ case 'win':
257
+ this.world.status = 'won';
258
+ break;
259
+ case 'lose':
260
+ this.world.status = 'lost';
261
+ break;
262
+ case 'if':
263
+ if (!this.conditionMatches(node))
264
+ break;
265
+ for (const child of node.children)
266
+ this.executeNode(child);
267
+ break;
268
+ case 'else':
269
+ for (const child of node.children)
270
+ this.executeNode(child);
271
+ break;
272
+ case 'hook':
273
+ this.runHook(node);
274
+ break;
275
+ default:
276
+ throw new Error(`unknown MWL command: ${name}`);
277
+ }
278
+ }
279
+ runHook(node) {
280
+ const reference = node.attributes.name ?? '';
281
+ const hook = this.hooks?.command?.[reference];
282
+ if (!hook)
283
+ throw new Error(`MWL hook ${reference} is not implemented`);
284
+ const { name: _name, ...context } = node.attributes;
285
+ hook(this.worldView(), this.emit(), context);
286
+ }
287
+ applyMove(id, x, y) {
288
+ const unit = this.world.units[id];
289
+ if (!unit || !unit.alive)
290
+ throw new Error(`MWL unit is not alive: ${id}`);
291
+ const map = this.world.map ?? null;
292
+ if (map) {
293
+ if (x < 0 || y < 0 || x >= map.width || y >= map.height)
294
+ throw new Error(`MWL move out of bounds: ${x},${y}`);
295
+ const occupant = this.unitAt(x, y);
296
+ if (occupant && occupant !== unit)
297
+ throw new Error(`MWL move onto an occupied cell: ${x},${y}`);
298
+ }
299
+ if (typeof unit.moves === 'number') {
300
+ if (unit.moves <= 0)
301
+ throw new Error(`MWL unit has no moves left: ${id}`);
302
+ unit.moves -= 1;
303
+ }
304
+ unit.x = x;
305
+ unit.y = y;
306
+ }
307
+ spawnUnit(type, side, x, y, id, hp) {
308
+ const stats = type ? this.unitTypes.get(type) : undefined;
309
+ const key = id ?? `${type || 'unit'}#${side ?? 0}@${x},${y}`;
310
+ this.world.units[key] = {
311
+ hp: hp ?? stats?.hitpoints ?? 1,
312
+ x,
313
+ y,
314
+ alive: true,
315
+ ...(type ? { type } : {}),
316
+ ...(side === undefined ? {} : { side }),
317
+ ...(stats ? { moves: stats.movement } : {}),
318
+ };
319
+ }
320
+ killUnit(id) {
321
+ const unit = this.world.units[id];
322
+ if (!unit || !unit.alive)
323
+ throw new Error(`MWL unit is not alive: ${id}`);
324
+ unit.alive = false;
325
+ }
326
+ addGold(side, delta) {
327
+ this.world.gold[side] = (this.world.gold[side] ?? 0) + delta;
328
+ }
329
+ endTurn() {
330
+ this.world.turn += 1;
331
+ this.advanceSchedule();
332
+ this.resetMoves();
333
+ this.run('turn');
334
+ }
335
+ advanceSchedule() {
336
+ if (!this.schedule.length)
337
+ return;
338
+ const index = ((this.world.scheduleIndex ?? 0) + 1) % this.schedule.length;
339
+ this.world.scheduleIndex = index;
340
+ this.world.timeOfDay = this.schedule[index];
341
+ }
342
+ resetMoves() {
343
+ for (const unit of Object.values(this.world.units)) {
344
+ if (!unit.alive)
345
+ continue;
346
+ const stats = unit.type ? this.unitTypes.get(unit.type) : undefined;
347
+ if (stats)
348
+ unit.moves = stats.movement;
349
+ }
350
+ }
351
+ filterMatches(node) {
352
+ const candidates = Object.values(this.world.units).filter((unit) => unit.alive);
353
+ return candidates.some((unit) => (node.attributes.side === undefined || unit.side === Number(node.attributes.side)) &&
354
+ (node.attributes.type === undefined || unit.type === node.attributes.type) &&
355
+ (node.attributes.x === undefined || unit.x === Number(node.attributes.x)) &&
356
+ (node.attributes.y === undefined || unit.y === Number(node.attributes.y)));
357
+ }
358
+ conditionMatches(node) {
359
+ const condition = node.children.find((child) => child.tag === 'condition');
360
+ return !condition || this.world.variables[condition.attributes.variable] === condition.attributes.equals;
361
+ }
362
+ conditionMet(node) {
363
+ const condition = node.attributes.condition ?? '';
364
+ const side = optionalInteger(node, 'side');
365
+ switch (condition) {
366
+ case 'units_dead': {
367
+ const target = optionalInteger(node, 'side_filter') ?? side;
368
+ if (target === undefined)
369
+ return false;
370
+ return !Object.values(this.world.units).some((unit) => unit.alive && unit.side === target);
371
+ }
372
+ case 'turns_elapsed': {
373
+ const turns = optionalInteger(node, 'turns');
374
+ return turns !== undefined && this.world.turn >= turns;
375
+ }
376
+ case 'gold_at_least': {
377
+ const gold = optionalInteger(node, 'gold');
378
+ return (gold !== undefined &&
379
+ node.attributes.side !== undefined &&
380
+ (this.world.gold[node.attributes.side] ?? 0) >= gold);
381
+ }
382
+ case 'unit_at': {
383
+ const x = optionalInteger(node, 'x');
384
+ const y = optionalInteger(node, 'y');
385
+ return Object.values(this.world.units).some((unit) => unit.alive &&
386
+ (node.attributes.type === undefined || unit.type === node.attributes.type) &&
387
+ unit.x === x &&
388
+ unit.y === y);
389
+ }
390
+ case 'hook': {
391
+ const reference = node.attributes.hook ?? '';
392
+ const hook = this.hooks?.predicate?.[reference];
393
+ if (!hook)
394
+ throw new Error(`MWL hook ${reference} is not implemented`);
395
+ // The predicate receives the objective's other attributes as its
396
+ // context, so content can parameterize an engine predicate.
397
+ const { hook: _hook, condition: _condition, ...context } = node.attributes;
398
+ return Boolean(hook(this.worldView(), context));
399
+ }
400
+ default:
401
+ return false;
402
+ }
403
+ }
404
+ checkObjectives() {
405
+ if (this.world.status !== 'playing')
406
+ return;
407
+ if (this.nodes('victory').some((node) => this.conditionMet(node))) {
408
+ this.world.status = 'won';
409
+ return;
410
+ }
411
+ if (this.nodes('defeat').some((node) => this.conditionMet(node)))
412
+ this.world.status = 'lost';
413
+ }
414
+ worldView() {
415
+ return {
416
+ variables: this.world.variables,
417
+ units: this.world.units,
418
+ sides: this.world.sides,
419
+ turn: this.world.turn,
420
+ };
421
+ }
422
+ emit() {
423
+ return {
424
+ move: (unit, x, y) => this.applyMove(unit, x, y),
425
+ attack: (_attacker, _defender, _weapon) => {
426
+ throw new Error('MWL hook attack is not available in the framework runtime; the engine adapter owns combat');
427
+ },
428
+ spawn: (type, side, x, y) => this.spawnUnit(type, side, x, y),
429
+ kill: (unit) => this.killUnit(unit),
430
+ gold: (side, delta) => this.addGold(String(side), delta),
431
+ setVariable: (name, value) => {
432
+ this.world.variables[name] = value;
433
+ },
434
+ message: (_speaker, text) => this.onMessage?.(text),
435
+ endTurn: () => this.endTurn(),
436
+ win: (_side) => {
437
+ this.world.status = 'won';
438
+ },
439
+ lose: (_side) => {
440
+ this.world.status = 'lost';
441
+ },
442
+ };
443
+ }
444
+ attack(target, amount) {
445
+ const unit = this.world.units[target];
446
+ if (!unit || !unit.alive)
447
+ throw new Error(`MWL unit is not alive: ${target}`);
448
+ unit.hp = Math.max(0, unit.hp - amount);
449
+ if (unit.hp === 0)
450
+ unit.alive = false;
451
+ }
452
+ nodes(tag) {
453
+ const result = [];
454
+ const visit = (node) => {
455
+ if (node.tag === tag)
456
+ result.push(node);
457
+ node.children.forEach(visit);
458
+ };
459
+ this.game.roots.forEach(visit);
460
+ return result;
461
+ }
462
+ }
463
+ export function execute(world, command) {
464
+ if (world.status !== 'playing' && command.name !== 'win' && command.name !== 'lose')
465
+ return;
466
+ switch (command.name) {
467
+ case 'set_variable':
468
+ world.variables[command.target] = command.value;
469
+ break;
470
+ case 'modify_gold':
471
+ world.gold[command.target] = (world.gold[command.target] ?? 0) + command.amount;
472
+ break;
473
+ case 'move': {
474
+ const unit = requireUnit(world, command.target);
475
+ unit.x = command.x;
476
+ unit.y = command.y;
477
+ break;
478
+ }
479
+ case 'spawn':
480
+ world.units[command.target] = { hp: command.hp, x: command.x, y: command.y, alive: true };
481
+ break;
482
+ case 'kill':
483
+ requireUnit(world, command.target).alive = false;
484
+ break;
485
+ case 'end_turn':
486
+ world.turn++;
487
+ break;
488
+ case 'win':
489
+ world.status = 'won';
490
+ break;
491
+ case 'lose':
492
+ world.status = 'lost';
493
+ break;
494
+ }
495
+ }
496
+ function requireUnit(world, id) {
497
+ const unit = world.units[id];
498
+ if (!unit || !unit.alive)
499
+ throw new Error(`MWL unit is not alive: ${id}`);
500
+ return unit;
501
+ }
502
+ function required(node, attribute) {
503
+ const value = node.attributes[attribute];
504
+ if (!value)
505
+ throw new Error(`MWL ${node.tag} is missing ${attribute}`);
506
+ return value;
507
+ }
508
+ function integer(node, attribute, fallback) {
509
+ const value = node.attributes[attribute];
510
+ return value === undefined ? fallback : Number.parseInt(value, 10);
511
+ }
512
+ function optionalInteger(node, attribute) {
513
+ const value = node.attributes[attribute];
514
+ if (value === undefined || value === '')
515
+ return undefined;
516
+ const parsed = Number.parseInt(value, 10);
517
+ return Number.isFinite(parsed) ? parsed : undefined;
518
+ }
519
+ //# sourceMappingURL=runtime.js.map