miaoda-game-devkit 0.7.3 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -93,6 +93,31 @@ Phaser 4 模板对应使用 `miaoda-phaser-game-lint`。命令类型不通过依
93
93
  模板即使预装 Phaser 3 及相关插件,也不会解析 Phaser 或运行 Phaser 4 检查。旧的
94
94
  `miaoda-game-lint` 不再发布,避免在同时包含 React 与 Phaser 依赖的项目中产生歧义。
95
95
 
96
+ ## 可编辑的游戏机制源码
97
+
98
+ Devkit 发布统一的 `miaoda` 命令。裸 `miaoda-game-*` 包名默认从公开源码索引解析为
99
+ 当前稳定版本,Devkit 直接下载并校验该包及其传递机制依赖的源码归档;消费项目自己的 pnpm
100
+ 10 或 11 只负责最终 workspace 联动和普通第三方依赖安装。生产 TypeScript 源码写入
101
+ `src/game-mechanics/<包名>/`。`.miaoda/mechanics-source.json` 只保存版本、索引地址与安装摘要,
102
+ 不保存第二份源码。
103
+
104
+ ```bash
105
+ pnpm exec miaoda mechanics --help
106
+ pnpm exec miaoda mechanics add miaoda-game-beam-core
107
+ pnpm exec miaoda mechanics add miaoda-game-beam-core@1.2.3 \
108
+ --source-index=https://public.example.com/game-mechanics/stable.json
109
+ pnpm exec miaoda mechanics status
110
+ ```
111
+
112
+ 默认索引是 `https://resource-static.bj.bcebos.com/miaoda-game/stable.json`。只有调试、测试或
113
+ 私有镜像场景才需要通过 `--source-index` 或 `MIAODA_MECHANICS_INDEX_URL` 覆盖。
114
+
115
+ 重复添加同版本不会覆盖源码;`status` 报告 `clean`、`modified` 或 `missing`。更新到不同版本时,
116
+ 若本地源码已修改,命令会在写入前失败。包内的单元测试、Vitest 配置、构建输出和 `node_modules`
117
+ 不会复制进游戏项目,避免它们进入游戏自身的 TypeScript 编译范围。索引中的相对 TGZ URL 以
118
+ `stable.json` 为基准解析;每个版本必须提供精确的机制依赖和 SHA-256。机制源码只通过该索引
119
+ 解析,不回退 npm registry,也不接受绕过索引的直接 TGZ 或本地包路径。
120
+
96
121
  开发工具包自行维护并精确固定 Vitest 等工具版本,从自身依赖中解析可执行文件,使用方不需要重复声明
97
122
  Biome、Oxlint 或 tsgo。仓库中保留稳定的命令入口,因此 `dist/` 尚未生成时,pnpm
98
123
  也能正确建立命令链接;工作区开发状态下由 Nx 在模板检查前构建开发工具包,发布包则
package/bin/miaoda.js ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+
3
+ void import('../mechanics/materialize-game-mechanics-source.mjs')
4
+ .then(({ main }) => main(process.argv.slice(2)))
5
+ .catch((error) => {
6
+ console.error(error instanceof Error ? error.message : String(error));
7
+ process.exitCode = 1;
8
+ });
@@ -311,9 +311,15 @@ function checkReactProductTestAlignment() {
311
311
  console.error(`[${name}] ${audit.issues[0]}`);
312
312
  return { name, ok: false };
313
313
  }
314
+ function styleLintRoots(target) {
315
+ const sourceRoots = (0, import_node_fs2.readdirSync)((0, import_node_path2.join)(projectRoot, "src"), {
316
+ withFileTypes: true
317
+ }).filter(({ name }) => name !== "game-mechanics").map(({ name }) => (0, import_node_path2.join)("src", name));
318
+ return target === "react" && (0, import_node_fs2.existsSync)((0, import_node_path2.join)(projectRoot, "tests")) ? [...sourceRoots, "tests"] : sourceRoots;
319
+ }
314
320
  async function runAllChecks(target) {
315
321
  const targetChecks = target === "react" ? [checkVitestConfig("react"), checkReactProductTestAlignment()] : [checkPhaser4Dependency(), checkPhaserViteConfig(), checkVitestConfig("phaser")];
316
- const lintRoots = target === "react" && (0, import_node_fs2.existsSync)((0, import_node_path2.join)(projectRoot, "tests")) ? ["src", "tests"] : ["src"];
322
+ const lintRoots = styleLintRoots(target);
317
323
  const [tsgo, biome] = await Promise.all([
318
324
  run("tsgo", "@typescript/native-preview", "tsgo", ["-p", "tsconfig.json"]),
319
325
  run("biome", "@biomejs/biome", "biome", [
@@ -311,9 +311,15 @@ function checkReactProductTestAlignment() {
311
311
  console.error(`[${name}] ${audit.issues[0]}`);
312
312
  return { name, ok: false };
313
313
  }
314
+ function styleLintRoots(target) {
315
+ const sourceRoots = (0, import_node_fs2.readdirSync)((0, import_node_path2.join)(projectRoot, "src"), {
316
+ withFileTypes: true
317
+ }).filter(({ name }) => name !== "game-mechanics").map(({ name }) => (0, import_node_path2.join)("src", name));
318
+ return target === "react" && (0, import_node_fs2.existsSync)((0, import_node_path2.join)(projectRoot, "tests")) ? [...sourceRoots, "tests"] : sourceRoots;
319
+ }
314
320
  async function runAllChecks(target) {
315
321
  const targetChecks = target === "react" ? [checkVitestConfig("react"), checkReactProductTestAlignment()] : [checkPhaser4Dependency(), checkPhaserViteConfig(), checkVitestConfig("phaser")];
316
- const lintRoots = target === "react" && (0, import_node_fs2.existsSync)((0, import_node_path2.join)(projectRoot, "tests")) ? ["src", "tests"] : ["src"];
322
+ const lintRoots = styleLintRoots(target);
317
323
  const [tsgo, biome] = await Promise.all([
318
324
  run("tsgo", "@typescript/native-preview", "tsgo", ["-p", "tsconfig.json"]),
319
325
  run("biome", "@biomejs/biome", "biome", [
@@ -2,22 +2,27 @@
2
2
  "$schema": "./game-mechanics-capabilities.schema.json",
3
3
  "schemaVersion": 1,
4
4
  "requiredPackages": [
5
+ "miaoda-game-action-runtime-core",
5
6
  "miaoda-game-arcade-motion-core",
6
7
  "miaoda-game-adventure-interaction-cocos",
7
8
  "miaoda-game-adventure-interaction-core",
8
9
  "miaoda-game-adventure-interaction-phaser",
9
10
  "miaoda-game-adventure-interaction-react",
10
11
  "miaoda-game-adventure-scenario-bridge-core",
12
+ "miaoda-game-belt-core",
13
+ "miaoda-game-brawler-core",
11
14
  "miaoda-game-command-cocos",
12
15
  "miaoda-game-command-core",
13
16
  "miaoda-game-command-phaser",
14
17
  "miaoda-game-command-react",
18
+ "miaoda-game-combat2d-core",
15
19
  "miaoda-game-contact2d-core",
16
20
  "miaoda-game-controls-phaser",
17
21
  "miaoda-game-expression-core",
18
22
  "miaoda-game-fighter-cocos",
19
23
  "miaoda-game-fighter-core",
20
24
  "miaoda-game-fixed-step-core",
25
+ "miaoda-game-frame-action-core",
21
26
  "miaoda-game-grid-core",
22
27
  "miaoda-game-investigation-core",
23
28
  "miaoda-game-kinematic2d-core",
@@ -120,6 +125,53 @@
120
125
  "miaoda-game-tractor-analysis-core"
121
126
  ],
122
127
  "packages": {
128
+ "miaoda-game-action-runtime-core": {
129
+ "engine": "neutral",
130
+ "domains": ["action", "combat", "orchestration"],
131
+ "owns": [
132
+ "action-request-buffering",
133
+ "action-cancel-policy",
134
+ "action-priority-arbitration",
135
+ "action-cooldowns",
136
+ "action-constraint-derivation"
137
+ ],
138
+ "doesNotOwn": [
139
+ "simulation-clock",
140
+ "movement-integration",
141
+ "hitbox-resolution",
142
+ "damage-rules",
143
+ "animation-playback"
144
+ ],
145
+ "compatibleWith": [
146
+ "miaoda-game-frame-action-core",
147
+ "miaoda-game-fixed-step-core",
148
+ "miaoda-game-combat2d-core"
149
+ ],
150
+ "useInsteadWhen": [
151
+ {
152
+ "package": "miaoda-game-frame-action-core",
153
+ "condition": "Only a deterministic integer-tick timeline is needed, without request buffering, cancellation policy, priorities, cooldowns, or movement constraints."
154
+ }
155
+ ],
156
+ "persistence": "snapshot",
157
+ "guidance": {
158
+ "useFor": [
159
+ "Use ActionRuntime for game-facing action requests that need buffering, explicit cancel windows, priority arbitration, cooldowns, or tag-derived movement constraints."
160
+ ],
161
+ "keepOutside": [
162
+ "Keep movement integration, hit acceptance, damage rules, animation playback, and the simulation clock outside this package."
163
+ ],
164
+ "rules": [
165
+ "Call request(id) at the logical-input boundary and tick() exactly once per fixed simulation tick.",
166
+ "Use FrameActionRunner directly instead when the action only needs a deterministic timeline."
167
+ ]
168
+ },
169
+ "testability": {
170
+ "observation": "snapshot-and-step-result",
171
+ "advance": "tick",
172
+ "methods": { "observe": "snapshot, tick() result", "advance": "tick()" }
173
+ }
174
+ },
123
175
  "miaoda-game-arcade-motion-core": {
124
176
  "engine": "neutral",
125
177
  "domains": ["input", "motion", "racing"],
@@ -134,6 +186,101 @@
134
186
  "methods": { "observe": "snapshot()", "advance": "update(dt, input, curve?)" }
135
187
  }
136
188
  },
189
+ "miaoda-game-belt-core": {
190
+ "engine": "neutral",
191
+ "domains": ["brawler", "motion", "spatial"],
192
+ "owns": [
193
+ "belt-spatial-model",
194
+ "belt-motion-integration",
195
+ "belt-jump-and-launch",
196
+ "belt-lane-queries",
197
+ "belt-screen-projection",
198
+ "belt-depth-sorting"
199
+ ],
200
+ "doesNotOwn": [
201
+ "simulation-clock",
202
+ "hitbox-resolution",
203
+ "attack-state",
204
+ "pathfinding",
205
+ "rendering"
206
+ ],
207
+ "compatibleWith": [
208
+ "miaoda-game-belt-phaser",
209
+ "miaoda-game-brawler-core",
210
+ "miaoda-game-combat2d-core",
211
+ "miaoda-game-fixed-step-core"
212
+ ],
213
+ "useInsteadWhen": [],
214
+ "persistence": "snapshot",
215
+ "guidance": {
216
+ "useFor": [
217
+ "Use BeltBody for the authoritative x/depth/height model, walking, jumping, launch and knockback arcs, lane queries, screen projection, and ground-depth sorting."
218
+ ],
219
+ "keepOutside": [
220
+ "Keep attack state, hit acceptance, AI and pathfinding, rendering, and fixed-tick scheduling outside this package."
221
+ ],
222
+ "rules": [
223
+ "Advance BeltBody.step(dtSeconds) from the single fixed simulation owner and render only from its committed snapshot and projection helpers.",
224
+ "Use belt lane queries as a depth pre-filter and combat2d as the hit-acceptance authority; do not implement a second hit resolver in BeltBody."
225
+ ]
226
+ },
227
+ "testability": {
228
+ "observation": "snapshot-and-step-result",
229
+ "advance": "step",
230
+ "methods": { "observe": "BeltBody.snapshot", "advance": "BeltBody.step(dtSeconds)" }
231
+ }
232
+ },
233
+ "miaoda-game-brawler-core": {
234
+ "engine": "neutral",
235
+ "domains": ["brawler", "combat", "orchestration"],
236
+ "owns": [
237
+ "brawler-combo-windows",
238
+ "brawler-hurt-and-knockdown",
239
+ "brawler-attack-token-pacing",
240
+ "brawler-crowd-role-coordination",
241
+ "brawler-grapple-relationships",
242
+ "brawler-hit-reaction-policy",
243
+ "brawler-juggle-budgets"
244
+ ],
245
+ "doesNotOwn": [
246
+ "simulation-clock",
247
+ "hitbox-resolution",
248
+ "hp-damage-application",
249
+ "movement-integration",
250
+ "pathfinding",
251
+ "rendering"
252
+ ],
253
+ "compatibleWith": [
254
+ "miaoda-game-frame-action-core",
255
+ "miaoda-game-fixed-step-core",
256
+ "miaoda-game-belt-core",
257
+ "miaoda-game-combat2d-core"
258
+ ],
259
+ "useInsteadWhen": [],
260
+ "persistence": "snapshot",
261
+ "guidance": {
262
+ "useFor": [
263
+ "Use this package for brawler combo windows, stagger and knockdown, shared enemy attack pacing, crowd roles and slots, grapples, hit reactions, and bounded juggling."
264
+ ],
265
+ "keepOutside": [
266
+ "Keep raw hitbox acceptance in combat2d, HP mutation in a stats owner, body integration in a movement owner, and rendering in the host."
267
+ ],
268
+ "rules": [
269
+ "Choose HurtState or HitReactionResolver for an actor profile; never apply both to the same hit.",
270
+ "BrawlerCrowdCoordinator attack eligibility is the single slot-level permit; do not combine it with an unrelated second attack-token authority.",
271
+ "GrappleCoordinator is the only holder/victim relationship authority; do not mirror writable isGrabbing or isGrabbed flags on actors.",
272
+ "Advance all selected brawler components from one fixed simulation owner, never from animation completion callbacks."
273
+ ]
274
+ },
275
+ "testability": {
276
+ "observation": "snapshot-and-step-result",
277
+ "advance": "tick",
278
+ "methods": {
279
+ "observe": "component snapshot properties and BrawlerCrowdCoordinator.inspect()",
280
+ "advance": "tick(), update(input), resolve(context), or advance(session, definition) on the selected component"
281
+ }
282
+ }
283
+ },
137
284
  "miaoda-game-adventure-interaction-cocos": {
138
285
  "engine": "cocos",
139
286
  "domains": ["adventure", "narrative"],
@@ -288,6 +435,56 @@
288
435
  ],
289
436
  "persistence": "delegated"
290
437
  },
438
+ "miaoda-game-combat2d-core": {
439
+ "engine": "neutral",
440
+ "domains": ["collision", "combat", "simulation"],
441
+ "owns": [
442
+ "combat-hit-acceptance",
443
+ "combat-team-mask-filtering",
444
+ "combat-attack-hit-deduplication",
445
+ "combat-invincibility-frames",
446
+ "combat-pierce-limits",
447
+ "combat-aabb-circle-overlap"
448
+ ],
449
+ "doesNotOwn": [
450
+ "simulation-clock",
451
+ "attack-timelines",
452
+ "damage-formulas",
453
+ "hit-reaction-policy",
454
+ "movement-integration",
455
+ "rendering"
456
+ ],
457
+ "compatibleWith": [
458
+ "miaoda-game-combat2d-cocos",
459
+ "miaoda-game-combat2d-phaser",
460
+ "miaoda-game-frame-action-core",
461
+ "miaoda-game-fixed-step-core",
462
+ "miaoda-game-belt-core",
463
+ "miaoda-game-brawler-core"
464
+ ],
465
+ "useInsteadWhen": [],
466
+ "persistence": "snapshot",
467
+ "guidance": {
468
+ "useFor": [
469
+ "Use CombatField to turn geometry overlaps or engine contacts into accepted combat hits with team masks, once-per-attack deduplication, invincibility frames, and pierce limits."
470
+ ],
471
+ "keepOutside": [
472
+ "Keep attack timelines, damage formulas, semantic hit reactions, movement integration, rendering, and fixed-tick scheduling outside this package."
473
+ ],
474
+ "rules": [
475
+ "Update registered positions and resolve hits exactly once at the logical gameplay-tick boundary.",
476
+ "Route each accepted HitEvent once to the stats, reaction, movement, status, and presentation owners; do not duplicate combat acceptance in those systems."
477
+ ]
478
+ },
479
+ "testability": {
480
+ "observation": "snapshot-and-result",
481
+ "advance": "update",
482
+ "methods": {
483
+ "observe": "CombatField.snapshot and resolve()/resolveContacts() results",
484
+ "advance": "update(...) then resolve(defaultIframes), or resolveContacts(contacts, defaultIframes)"
485
+ }
486
+ }
487
+ },
291
488
  "miaoda-game-contact2d-core": {
292
489
  "engine": "neutral",
293
490
  "domains": ["collision", "geometry", "physics"],
@@ -359,12 +556,73 @@
359
556
  "compatibleWith": ["miaoda-game-controls-phaser"],
360
557
  "useInsteadWhen": [],
361
558
  "persistence": "none",
559
+ "guidance": {
560
+ "useFor": [
561
+ "Use FixedStepper as the single fixed gameplay-tick owner, LogicalInputBuffer between host-frame sampling and tick consumption, and InputTapeRecorder for deterministic logical-input replay."
562
+ ],
563
+ "keepOutside": [
564
+ "Keep game rules, physics ownership, rendering, wall-clock scheduling, and presentation interpolation outside the fixed-step package."
565
+ ],
566
+ "rules": [
567
+ "Sample hardware input once per host frame, consume logical input inside fixed callbacks, and advance each gameplay system exactly once per logical tick.",
568
+ "Use alpha only for presentation interpolation and never feed interpolated state back into gameplay.",
569
+ "Disable engine or adapter auto-step before manually driving the same simulation to prevent duplicate time authority."
570
+ ]
571
+ },
362
572
  "testability": {
363
573
  "observation": "snapshot-and-step-result",
364
574
  "advance": "step",
365
575
  "methods": { "observe": "snapshot", "advance": "step(count, callback)" }
366
576
  }
367
577
  },
578
+ "miaoda-game-frame-action-core": {
579
+ "engine": "neutral",
580
+ "domains": ["action", "orchestration", "simulation"],
581
+ "owns": [
582
+ "deterministic-action-timelines",
583
+ "action-step-entry-events",
584
+ "action-run-identity",
585
+ "action-timeline-snapshots"
586
+ ],
587
+ "doesNotOwn": [
588
+ "simulation-clock",
589
+ "input-buffering",
590
+ "action-cancel-policy",
591
+ "hitbox-resolution",
592
+ "damage-rules",
593
+ "animation-playback"
594
+ ],
595
+ "compatibleWith": [
596
+ "miaoda-game-action-runtime-core",
597
+ "miaoda-game-fixed-step-core",
598
+ "miaoda-game-combat2d-core",
599
+ "miaoda-game-brawler-core"
600
+ ],
601
+ "useInsteadWhen": [
602
+ {
603
+ "package": "miaoda-game-action-runtime-core",
604
+ "condition": "Game-facing action requests need buffering, explicit cancellation, priority arbitration, cooldowns, or movement constraints in addition to the timeline."
605
+ }
606
+ ],
607
+ "persistence": "snapshot",
608
+ "guidance": {
609
+ "useFor": [
610
+ "Use FrameActionRunner for deterministic integer-tick startup, active, recovery, pose, tag, payload, and entry-event timelines whose gameplay timing must not depend on animation playback."
611
+ ],
612
+ "keepOutside": [
613
+ "Keep input buffering, cancel and cooldown policy, hit acceptance, damage rules, animation playback, and the simulation clock outside this package."
614
+ ],
615
+ "rules": [
616
+ "Call tick() exactly once per fixed simulation tick and use sample.instanceId as the stable action-run identity when connecting timelines to combat or effects.",
617
+ "Use ActionRuntime on top when requests need buffering, cancellation, priorities, cooldowns, or movement constraints; do not rebuild those policies beside FrameActionRunner."
618
+ ]
619
+ },
620
+ "testability": {
621
+ "observation": "snapshot-and-step-result",
622
+ "advance": "tick",
623
+ "methods": { "observe": "snapshot, current, tick() result", "advance": "tick()" }
624
+ }
625
+ },
368
626
  "miaoda-game-grid-core": {
369
627
  "engine": "neutral",
370
628
  "domains": ["grid", "pathfinding", "search"],
@@ -49,6 +49,12 @@
49
49
  "uniqueItems": true,
50
50
  "items": { "$ref": "#/$defs/packageName" }
51
51
  },
52
+ "proseList": {
53
+ "type": "array",
54
+ "minItems": 1,
55
+ "uniqueItems": true,
56
+ "items": { "type": "string", "minLength": 1 }
57
+ },
52
58
  "alternative": {
53
59
  "type": "object",
54
60
  "additionalProperties": false,
@@ -107,6 +113,26 @@
107
113
  "enum": ["none", "snapshot", "envelope", "delegated"],
108
114
  "description": "Persistence responsibility: none, package snapshot, versioned envelope, or delegated to another owner."
109
115
  },
116
+ "guidance": {
117
+ "type": "object",
118
+ "additionalProperties": false,
119
+ "required": ["useFor", "keepOutside", "rules"],
120
+ "description": "Concise natural-language implementation contract copied into consumer AI context for packages whose token boundaries need composition guidance.",
121
+ "properties": {
122
+ "useFor": {
123
+ "$ref": "#/$defs/proseList",
124
+ "description": "Concrete situations and logic for which consumers should use this package."
125
+ },
126
+ "keepOutside": {
127
+ "$ref": "#/$defs/proseList",
128
+ "description": "Adjacent implementation that must stay in the game, engine, or another package."
129
+ },
130
+ "rules": {
131
+ "$ref": "#/$defs/proseList",
132
+ "description": "Composition, ordering, and single-authority rules that prevent plausible integration mistakes."
133
+ }
134
+ }
135
+ },
110
136
  "testability": {
111
137
  "type": "object",
112
138
  "additionalProperties": false,
@@ -126,7 +152,7 @@
126
152
  "description": "Smallest stable readback shape exposed by the package."
127
153
  },
128
154
  "advance": {
129
- "enum": ["none", "command", "update", "step", "scheduler", "manual"],
155
+ "enum": ["none", "command", "update", "tick", "step", "scheduler", "manual"],
130
156
  "description": "How a consumer explicitly advances or samples the package."
131
157
  },
132
158
  "methods": {