@khorsheed/dsh-game 0.1.0 → 0.1.1

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.
@@ -197,6 +197,26 @@ const _khorsheed_dsh_game_game_finalizeParams_result$schema = () => (_khorsheed_
197
197
  }), z.object({
198
198
  'ok': z.literal(true).readonly(),
199
199
  })]))
200
+ let _khorsheed_dsh_game_game_liveVersion_parameter_0$schema$value
201
+ const _khorsheed_dsh_game_game_liveVersion_parameter_0$schema = () => (_khorsheed_dsh_game_game_liveVersion_parameter_0$schema$value ??= z.string())
202
+ let _khorsheed_dsh_game_game_liveVersion_result$schema$value
203
+ const _khorsheed_dsh_game_game_liveVersion_result$schema = () => (_khorsheed_dsh_game_game_liveVersion_result$schema$value ??= z.union([z.literal(null), z.object({
204
+ 'projectId': z.string().readonly(),
205
+ 'version': z.number().readonly(),
206
+ 'kind': z.union([z.literal("none"), z.literal("candidate"), z.literal("deployed")]).readonly(),
207
+ 'at': z.union([z.literal(null), z.string()]).readonly(),
208
+ 'by': z.union([z.literal("user"), z.literal("agent"), z.literal("turn")]).readonly().optional(),
209
+ 'show': z.object({
210
+ 'seq': z.number().readonly(),
211
+ 'target': z.object({
212
+ 'view': z.union([z.literal("versions"), z.literal("play"), z.literal("elements"), z.literal("design")]).readonly(),
213
+ 'elementId': z.string().readonly().optional(),
214
+ 'chapterId': z.string().readonly().optional(),
215
+ }).readonly(),
216
+ 'note': z.string().readonly().describe('What to look at, in the agent\'s words (shown with the notice).'),
217
+ 'at': z.string().readonly(),
218
+ }).readonly().optional().describe('The agent\'s latest request to show the user something, if any since the host started.'),
219
+ })]))
200
220
  let _khorsheed_dsh_game_game_logPlay_parameter_0$schema$value
201
221
  const _khorsheed_dsh_game_game_logPlay_parameter_0$schema = () => (_khorsheed_dsh_game_game_logPlay_parameter_0$schema$value ??= z.string())
202
222
  let _khorsheed_dsh_game_game_logPlay_parameter_1$schema$value
@@ -490,6 +510,7 @@ const _khorsheed_dsh_game_game_state_result$schema = () => (_khorsheed_dsh_game_
490
510
  }).readonly().optional(),
491
511
  })]).readonly().describe('The 3D look, if the element has one (3d-three projects).'),
492
512
  'lore': z.string().readonly().describe('The 设定 text (the element\'s `lore` field): who it is, its temper, how it meets the player. \'\' when unwritten.'),
513
+ 'updatedAt': z.number().readonly().optional().describe('When its file last changed (ms), for listing the newest first.'),
493
514
  })).readonly(),
494
515
  'elementErrors': z.array(z.object({
495
516
  'where': z.string().readonly().describe('`smoke`, `data`, or the test file\'s project-relative path.'),
@@ -813,6 +834,8 @@ let _khorsheed_dsh_game_game_finalizeParams_parameter_1_strict$schema$value
813
834
  const _khorsheed_dsh_game_game_finalizeParams_parameter_1_strict$schema = () => (_khorsheed_dsh_game_game_finalizeParams_parameter_1_strict$schema$value ??= z.strictObject({
814
835
  'projectId': z.string(),
815
836
  }))
837
+ let _khorsheed_dsh_game_game_liveVersion_parameter_0_strict$schema$value
838
+ const _khorsheed_dsh_game_game_liveVersion_parameter_0_strict$schema = () => (_khorsheed_dsh_game_game_liveVersion_parameter_0_strict$schema$value ??= z.string())
816
839
  let _khorsheed_dsh_game_game_logPlay_parameter_0_strict$schema$value
817
840
  const _khorsheed_dsh_game_game_logPlay_parameter_0_strict$schema = () => (_khorsheed_dsh_game_game_logPlay_parameter_0_strict$schema$value ??= z.string())
818
841
  let _khorsheed_dsh_game_game_logPlay_parameter_1_strict$schema$value
@@ -961,7 +984,7 @@ export const TYPERT_REMOTE = createTypertRemoteInfo({
961
984
  schema: _khorsheed_dsh_game_game_bind_result$schema(),
962
985
  jsonSchema: {"oneOf":[{"type":"object","properties":{"ok":{"type":"boolean","const":false},"error":{"type":"string","enum":["not-found","denied","io","unbound","invalid","no-version","not-a-project","is-workspace","trash-failed"]}},"required":["ok","error"],"additionalProperties":true},{"type":"object","properties":{"ok":{"type":"boolean","const":true}},"required":["ok"],"additionalProperties":true}]},
963
986
  },
964
- sourceLocation: {"file":"packages/game/src/remote.ts","line":67,"column":3},
987
+ sourceLocation: {"file":"packages/game/src/remote.ts","line":73,"column":3},
965
988
  },
966
989
  'game/bundle': {
967
990
  service: 'gameRemote',
@@ -994,7 +1017,7 @@ export const TYPERT_REMOTE = createTypertRemoteInfo({
994
1017
  schema: _khorsheed_dsh_game_game_bundle_result$schema(),
995
1018
  jsonSchemaError: 'typert tool schema: selected union branches overlap or have no required discriminator',
996
1019
  },
997
- sourceLocation: {"file":"packages/game/src/remote.ts","line":79,"column":3},
1020
+ sourceLocation: {"file":"packages/game/src/remote.ts","line":85,"column":3},
998
1021
  },
999
1022
  'game/changes': {
1000
1023
  service: 'gameRemote',
@@ -1027,7 +1050,7 @@ export const TYPERT_REMOTE = createTypertRemoteInfo({
1027
1050
  schema: _khorsheed_dsh_game_game_changes_result$schema(),
1028
1051
  jsonSchema: {"oneOf":[{"type":"object","properties":{"ok":{"type":"boolean","const":false},"error":{"type":"string","enum":["not-found","denied","io","unbound","invalid","no-version","not-a-project","is-workspace","trash-failed"]}},"required":["ok","error"],"additionalProperties":true},{"type":"object","properties":{"against":{"oneOf":[{"type":"null","const":null},{"type":"number"}],"description":"The version compared against (the waiting candidate, else the live one); null before any."},"hasEntry":{"type":"boolean"},"count":{"type":"number","description":"Build files added, changed or removed (first 30 of \\`count\\`)."},"files":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"kind":{"type":"string","enum":["changed","added","removed"]}},"required":["path","kind"],"additionalProperties":true}},"lastAt":{"oneOf":[{"type":"null","const":null},{"type":"string"}],"description":"The newest change's time."},"busy":{"oneOf":[{"type":"null","const":null},{"type":"string","enum":["turn","recent"]}],"description":"Someone is still writing: a bound session's turn is running, or a file changed in the last 20 s."},"edits":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","description":"Stable key so repeated edits of one thing collapse into one entry (`param:speed`, `look:fog`, `design:core-loop`)."},"text":{"type":"string","description":"Short label for the dock and the model (`参数 移动速度 1.4→1.8`)."}},"required":["key","text"],"additionalProperties":true},"description":"The panel edits among them."},"failed":{"oneOf":[{"type":"null","const":null},{"type":"array","items":{"type":"object","properties":{"where":{"type":"string"},"message":{"type":"string"}},"required":["where","message"],"additionalProperties":true}}],"description":"The last check ran after the newest change and failed: its findings."},"ok":{"type":"boolean","const":true}},"required":["against","hasEntry","count","files","lastAt","busy","edits","failed","ok"],"additionalProperties":true}]},
1029
1052
  },
1030
- sourceLocation: {"file":"packages/game/src/remote.ts","line":91,"column":3},
1053
+ sourceLocation: {"file":"packages/game/src/remote.ts","line":97,"column":3},
1031
1054
  },
1032
1055
  'game/checkShot': {
1033
1056
  service: 'gameRemote',
@@ -1060,7 +1083,7 @@ export const TYPERT_REMOTE = createTypertRemoteInfo({
1060
1083
  schema: _khorsheed_dsh_game_game_checkShot_result$schema(),
1061
1084
  jsonSchema: {"oneOf":[{"type":"object","properties":{"ok":{"type":"boolean","const":false},"error":{"type":"string","enum":["not-found","denied","io","unbound","invalid","no-version","not-a-project","is-workspace","trash-failed"]}},"required":["ok","error"],"additionalProperties":true},{"type":"object","properties":{"ok":{"type":"boolean","const":true},"webp":{"type":"string"}},"required":["ok","webp"],"additionalProperties":true}]},
1062
1085
  },
1063
- sourceLocation: {"file":"packages/game/src/remote.ts","line":198,"column":3},
1086
+ sourceLocation: {"file":"packages/game/src/remote.ts","line":204,"column":3},
1064
1087
  },
1065
1088
  'game/commit': {
1066
1089
  service: 'gameRemote',
@@ -1111,7 +1134,7 @@ export const TYPERT_REMOTE = createTypertRemoteInfo({
1111
1134
  schema: _khorsheed_dsh_game_game_commit_result$schema(),
1112
1135
  jsonSchema: {"oneOf":[{"type":"object","properties":{"ok":{"type":"boolean","const":false},"error":{"type":"string","enum":["not-found","denied","io","unbound","invalid","no-version","not-a-project","is-workspace","trash-failed"]}},"required":["ok","error"],"additionalProperties":true},{"type":"object","properties":{"ok":{"type":"boolean","const":true},"kind":{"type":"string","enum":["candidate","deployed"]},"version":{"type":"number"}},"required":["ok","kind","version"],"additionalProperties":true},{"type":"object","properties":{"ok":{"type":"boolean","const":true},"kind":{"type":"string","const":"failed"},"failures":{"type":"array","items":{"type":"object","properties":{"where":{"type":"string"},"message":{"type":"string"}},"required":["where","message"],"additionalProperties":true}}},"required":["ok","kind","failures"],"additionalProperties":true},{"type":"object","properties":{"ok":{"type":"boolean","const":true},"kind":{"type":"string","enum":["unchanged","no-entry"]}},"required":["ok","kind"],"additionalProperties":true}]},
1113
1136
  },
1114
- sourceLocation: {"file":"packages/game/src/remote.ts","line":97,"column":9},
1137
+ sourceLocation: {"file":"packages/game/src/remote.ts","line":103,"column":9},
1115
1138
  },
1116
1139
  'game/confirm': {
1117
1140
  service: 'gameRemote',
@@ -1162,7 +1185,7 @@ export const TYPERT_REMOTE = createTypertRemoteInfo({
1162
1185
  schema: _khorsheed_dsh_game_game_confirm_result$schema(),
1163
1186
  jsonSchema: {"oneOf":[{"type":"object","properties":{"ok":{"type":"boolean","const":false},"error":{"type":"string","enum":["not-found","denied","io","unbound","invalid","no-version","not-a-project","is-workspace","trash-failed"]}},"required":["ok","error"],"additionalProperties":true},{"type":"object","properties":{"ok":{"type":"boolean","const":true},"version":{"type":"number"}},"required":["ok","version"],"additionalProperties":true}]},
1164
1187
  },
1165
- sourceLocation: {"file":"packages/game/src/remote.ts","line":85,"column":9},
1188
+ sourceLocation: {"file":"packages/game/src/remote.ts","line":91,"column":9},
1166
1189
  },
1167
1190
  'game/deleteProject': {
1168
1191
  service: 'gameRemote',
@@ -1213,7 +1236,7 @@ export const TYPERT_REMOTE = createTypertRemoteInfo({
1213
1236
  schema: _khorsheed_dsh_game_game_deleteProject_result$schema(),
1214
1237
  jsonSchema: {"oneOf":[{"type":"object","properties":{"ok":{"type":"boolean","const":true},"trashedTo":{"type":"string"}},"required":["ok"],"additionalProperties":true},{"type":"object","properties":{"ok":{"type":"boolean","const":false},"error":{"type":"string","enum":["not-found","denied","io","unbound","invalid","no-version","not-a-project","is-workspace","trash-failed"]},"reason":{"type":"string"}},"required":["ok","error"],"additionalProperties":true}]},
1215
1238
  },
1216
- sourceLocation: {"file":"packages/game/src/remote.ts","line":61,"column":9},
1239
+ sourceLocation: {"file":"packages/game/src/remote.ts","line":67,"column":9},
1217
1240
  },
1218
1241
  'game/diff': {
1219
1242
  service: 'gameRemote',
@@ -1246,7 +1269,7 @@ export const TYPERT_REMOTE = createTypertRemoteInfo({
1246
1269
  schema: _khorsheed_dsh_game_game_diff_result$schema(),
1247
1270
  jsonSchema: {"oneOf":[{"type":"object","properties":{"ok":{"type":"boolean","const":false},"error":{"type":"string","enum":["not-found","denied","io","unbound","invalid","no-version","not-a-project","is-workspace","trash-failed"]}},"required":["ok","error"],"additionalProperties":true},{"type":"object","properties":{"ok":{"type":"boolean","const":true},"diff":{"type":"object","properties":{"added":{"type":"array","items":{"type":"string"}},"removed":{"type":"array","items":{"type":"string"}},"changed":{"type":"array","items":{"type":"string"}}},"required":["added","removed","changed"],"additionalProperties":true}},"required":["ok","diff"],"additionalProperties":true}]},
1248
1271
  },
1249
- sourceLocation: {"file":"packages/game/src/remote.ts","line":192,"column":3},
1272
+ sourceLocation: {"file":"packages/game/src/remote.ts","line":198,"column":3},
1250
1273
  },
1251
1274
  'game/dropPending': {
1252
1275
  service: 'gameRemote',
@@ -1297,7 +1320,7 @@ export const TYPERT_REMOTE = createTypertRemoteInfo({
1297
1320
  schema: _khorsheed_dsh_game_game_dropPending_result$schema(),
1298
1321
  jsonSchema: {"oneOf":[{"type":"object","properties":{"ok":{"type":"boolean","const":false},"error":{"type":"string","enum":["not-found","denied","io","unbound","invalid","no-version","not-a-project","is-workspace","trash-failed"]}},"required":["ok","error"],"additionalProperties":true},{"type":"object","properties":{"ok":{"type":"boolean","const":true}},"required":["ok"],"additionalProperties":true}]},
1299
1322
  },
1300
- sourceLocation: {"file":"packages/game/src/remote.ts","line":115,"column":9},
1323
+ sourceLocation: {"file":"packages/game/src/remote.ts","line":121,"column":9},
1301
1324
  },
1302
1325
  'game/editElement': {
1303
1326
  service: 'gameRemote',
@@ -1348,7 +1371,7 @@ export const TYPERT_REMOTE = createTypertRemoteInfo({
1348
1371
  schema: _khorsheed_dsh_game_game_editElement_result$schema(),
1349
1372
  jsonSchema: {"oneOf":[{"type":"object","properties":{"ok":{"type":"boolean","const":false},"error":{"type":"string","enum":["not-found","denied","io","unbound","invalid","no-version","not-a-project","is-workspace","trash-failed"]}},"required":["ok","error"],"additionalProperties":true},{"type":"object","properties":{"ok":{"type":"boolean","const":true},"elementJson":{"type":"string"}},"required":["ok","elementJson"],"additionalProperties":true}]},
1350
1373
  },
1351
- sourceLocation: {"file":"packages/game/src/remote.ts","line":139,"column":9},
1374
+ sourceLocation: {"file":"packages/game/src/remote.ts","line":145,"column":9},
1352
1375
  },
1353
1376
  'game/finalizeParams': {
1354
1377
  service: 'gameRemote',
@@ -1399,7 +1422,40 @@ export const TYPERT_REMOTE = createTypertRemoteInfo({
1399
1422
  schema: _khorsheed_dsh_game_game_finalizeParams_result$schema(),
1400
1423
  jsonSchema: {"oneOf":[{"type":"object","properties":{"ok":{"type":"boolean","const":false},"error":{"type":"string","enum":["not-found","denied","io","unbound","invalid","no-version","not-a-project","is-workspace","trash-failed"]}},"required":["ok","error"],"additionalProperties":true},{"type":"object","properties":{"ok":{"type":"boolean","const":true}},"required":["ok"],"additionalProperties":true}]},
1401
1424
  },
1402
- sourceLocation: {"file":"packages/game/src/remote.ts","line":127,"column":9},
1425
+ sourceLocation: {"file":"packages/game/src/remote.ts","line":133,"column":9},
1426
+ },
1427
+ 'game/liveVersion': {
1428
+ service: 'gameRemote',
1429
+ description: 'The bound project\'s newest version (cheap; the page polls it).',
1430
+ invocation: { kind: 'direct' },
1431
+ parameters: [
1432
+ {
1433
+ name: 'sessionId',
1434
+ source: 'json',
1435
+ codec: {
1436
+ mode: 'strict',
1437
+ typeSymbol: '@deepseek-ai/dsh-session/types#SessionId',
1438
+ create: _khorsheed_dsh_game_game_liveVersion_parameter_0$schema,
1439
+ schema: _khorsheed_dsh_game_game_liveVersion_parameter_0$schema(),
1440
+ jsonSchema: {"type":"string"},
1441
+ },
1442
+ strictCodec: {
1443
+ mode: 'strict',
1444
+ typeSymbol: '@deepseek-ai/dsh-session/types#SessionId',
1445
+ create: _khorsheed_dsh_game_game_liveVersion_parameter_0_strict$schema,
1446
+ schema: _khorsheed_dsh_game_game_liveVersion_parameter_0_strict$schema(),
1447
+ jsonSchema: {"type":"string"},
1448
+ },
1449
+ },
1450
+ ],
1451
+ result: {
1452
+ mode: 'strict',
1453
+ typeSymbol: '@khorsheed/dsh-game#game/liveVersion:result',
1454
+ create: _khorsheed_dsh_game_game_liveVersion_result$schema,
1455
+ schema: _khorsheed_dsh_game_game_liveVersion_result$schema(),
1456
+ jsonSchema: {"oneOf":[{"type":"null","const":null},{"type":"object","properties":{"projectId":{"type":"string"},"version":{"type":"number"},"kind":{"type":"string","enum":["none","candidate","deployed"]},"at":{"oneOf":[{"type":"null","const":null},{"type":"string"}]},"by":{"type":"string","enum":["user","agent","turn"]},"show":{"type":"object","properties":{"seq":{"type":"number"},"target":{"type":"object","properties":{"view":{"type":"string","enum":["versions","play","elements","design"]},"elementId":{"type":"string"},"chapterId":{"type":"string"}},"required":["view"],"additionalProperties":true},"note":{"type":"string","description":"What to look at, in the agent's words (shown with the notice)."},"at":{"type":"string"}},"required":["seq","target","note","at"],"additionalProperties":true,"description":"The agent's latest request to show the user something, if any since the host started."}},"required":["projectId","version","kind","at"],"additionalProperties":true}]},
1457
+ },
1458
+ sourceLocation: {"file":"packages/game/src/remote.ts","line":49,"column":3},
1403
1459
  },
1404
1460
  'game/logPlay': {
1405
1461
  service: 'gameRemote',
@@ -1450,7 +1506,7 @@ export const TYPERT_REMOTE = createTypertRemoteInfo({
1450
1506
  schema: _khorsheed_dsh_game_game_logPlay_result$schema(),
1451
1507
  jsonSchema: {"oneOf":[{"type":"object","properties":{"ok":{"type":"boolean","const":false},"error":{"type":"string","enum":["not-found","denied","io","unbound","invalid","no-version","not-a-project","is-workspace","trash-failed"]}},"required":["ok","error"],"additionalProperties":true},{"type":"object","properties":{"ok":{"type":"boolean","const":true}},"required":["ok"],"additionalProperties":true}]},
1452
1508
  },
1453
- sourceLocation: {"file":"packages/game/src/remote.ts","line":109,"column":9},
1509
+ sourceLocation: {"file":"packages/game/src/remote.ts","line":115,"column":9},
1454
1510
  },
1455
1511
  'game/pickProposal': {
1456
1512
  service: 'gameRemote',
@@ -1501,7 +1557,7 @@ export const TYPERT_REMOTE = createTypertRemoteInfo({
1501
1557
  schema: _khorsheed_dsh_game_game_pickProposal_result$schema(),
1502
1558
  jsonSchema: {"oneOf":[{"type":"object","properties":{"ok":{"type":"boolean","const":false},"error":{"type":"string","enum":["not-found","denied","io","unbound","invalid","no-version","not-a-project","is-workspace","trash-failed"]}},"required":["ok","error"],"additionalProperties":true},{"type":"object","properties":{"ok":{"type":"boolean","const":true}},"required":["ok"],"additionalProperties":true}]},
1503
1559
  },
1504
- sourceLocation: {"file":"packages/game/src/remote.ts","line":156,"column":9},
1560
+ sourceLocation: {"file":"packages/game/src/remote.ts","line":162,"column":9},
1505
1561
  },
1506
1562
  'game/projectCards': {
1507
1563
  service: 'gameRemote',
@@ -1516,7 +1572,7 @@ export const TYPERT_REMOTE = createTypertRemoteInfo({
1516
1572
  schema: _khorsheed_dsh_game_game_projectCards_result$schema(),
1517
1573
  jsonSchema: {"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"root":{"type":"string"},"updatedAt":{"type":"string"},"pitch":{"type":"string"},"artStyle":{"type":"string"},"version":{"oneOf":[{"type":"null","const":null},{"type":"number"}]},"elements":{"type":"number"},"thumb":{"type":"string","description":"The last check's screenshot (webp, base64), when there is one."}},"required":["id","title","root","updatedAt","pitch","artStyle","version","elements"],"additionalProperties":true}},
1518
1574
  },
1519
- sourceLocation: {"file":"packages/game/src/remote.ts","line":55,"column":3},
1575
+ sourceLocation: {"file":"packages/game/src/remote.ts","line":61,"column":3},
1520
1576
  },
1521
1577
  'game/reject': {
1522
1578
  service: 'gameRemote',
@@ -1567,7 +1623,7 @@ export const TYPERT_REMOTE = createTypertRemoteInfo({
1567
1623
  schema: _khorsheed_dsh_game_game_reject_result$schema(),
1568
1624
  jsonSchema: {"oneOf":[{"type":"object","properties":{"ok":{"type":"boolean","const":false},"error":{"type":"string","enum":["not-found","denied","io","unbound","invalid","no-version","not-a-project","is-workspace","trash-failed"]}},"required":["ok","error"],"additionalProperties":true},{"type":"object","properties":{"ok":{"type":"boolean","const":true}},"required":["ok"],"additionalProperties":true}]},
1569
1625
  },
1570
- sourceLocation: {"file":"packages/game/src/remote.ts","line":180,"column":9},
1626
+ sourceLocation: {"file":"packages/game/src/remote.ts","line":186,"column":9},
1571
1627
  },
1572
1628
  'game/requestRestyle': {
1573
1629
  service: 'gameRemote',
@@ -1618,7 +1674,7 @@ export const TYPERT_REMOTE = createTypertRemoteInfo({
1618
1674
  schema: _khorsheed_dsh_game_game_requestRestyle_result$schema(),
1619
1675
  jsonSchema: {"oneOf":[{"type":"object","properties":{"ok":{"type":"boolean","const":false},"error":{"type":"string","enum":["not-found","denied","io","unbound","invalid","no-version","not-a-project","is-workspace","trash-failed"]}},"required":["ok","error"],"additionalProperties":true},{"type":"object","properties":{"ok":{"type":"boolean","const":true}},"required":["ok"],"additionalProperties":true}]},
1620
1676
  },
1621
- sourceLocation: {"file":"packages/game/src/remote.ts","line":174,"column":9},
1677
+ sourceLocation: {"file":"packages/game/src/remote.ts","line":180,"column":9},
1622
1678
  },
1623
1679
  'game/resetParams': {
1624
1680
  service: 'gameRemote',
@@ -1669,7 +1725,7 @@ export const TYPERT_REMOTE = createTypertRemoteInfo({
1669
1725
  schema: _khorsheed_dsh_game_game_resetParams_result$schema(),
1670
1726
  jsonSchema: {"oneOf":[{"type":"object","properties":{"ok":{"type":"boolean","const":false},"error":{"type":"string","enum":["not-found","denied","io","unbound","invalid","no-version","not-a-project","is-workspace","trash-failed"]}},"required":["ok","error"],"additionalProperties":true},{"type":"object","properties":{"ok":{"type":"boolean","const":true}},"required":["ok"],"additionalProperties":true}]},
1671
1727
  },
1672
- sourceLocation: {"file":"packages/game/src/remote.ts","line":133,"column":9},
1728
+ sourceLocation: {"file":"packages/game/src/remote.ts","line":139,"column":9},
1673
1729
  },
1674
1730
  'game/rollback': {
1675
1731
  service: 'gameRemote',
@@ -1720,7 +1776,7 @@ export const TYPERT_REMOTE = createTypertRemoteInfo({
1720
1776
  schema: _khorsheed_dsh_game_game_rollback_result$schema(),
1721
1777
  jsonSchema: {"oneOf":[{"type":"object","properties":{"ok":{"type":"boolean","const":false},"error":{"type":"string","enum":["not-found","denied","io","unbound","invalid","no-version","not-a-project","is-workspace","trash-failed"]}},"required":["ok","error"],"additionalProperties":true},{"type":"object","properties":{"ok":{"type":"boolean","const":true}},"required":["ok"],"additionalProperties":true}]},
1722
1778
  },
1723
- sourceLocation: {"file":"packages/game/src/remote.ts","line":186,"column":9},
1779
+ sourceLocation: {"file":"packages/game/src/remote.ts","line":192,"column":9},
1724
1780
  },
1725
1781
  'game/setAutoConfirm': {
1726
1782
  service: 'gameRemote',
@@ -1771,7 +1827,7 @@ export const TYPERT_REMOTE = createTypertRemoteInfo({
1771
1827
  schema: _khorsheed_dsh_game_game_setAutoConfirm_result$schema(),
1772
1828
  jsonSchema: {"oneOf":[{"type":"object","properties":{"ok":{"type":"boolean","const":false},"error":{"type":"string","enum":["not-found","denied","io","unbound","invalid","no-version","not-a-project","is-workspace","trash-failed"]}},"required":["ok","error"],"additionalProperties":true},{"type":"object","properties":{"ok":{"type":"boolean","const":true}},"required":["ok"],"additionalProperties":true}]},
1773
1829
  },
1774
- sourceLocation: {"file":"packages/game/src/remote.ts","line":103,"column":9},
1830
+ sourceLocation: {"file":"packages/game/src/remote.ts","line":109,"column":9},
1775
1831
  },
1776
1832
  'game/setOnePager': {
1777
1833
  service: 'gameRemote',
@@ -1822,7 +1878,7 @@ export const TYPERT_REMOTE = createTypertRemoteInfo({
1822
1878
  schema: _khorsheed_dsh_game_game_setOnePager_result$schema(),
1823
1879
  jsonSchema: {"oneOf":[{"type":"object","properties":{"ok":{"type":"boolean","const":false},"error":{"type":"string","enum":["not-found","denied","io","unbound","invalid","no-version","not-a-project","is-workspace","trash-failed"]}},"required":["ok","error"],"additionalProperties":true},{"type":"object","properties":{"ok":{"type":"boolean","const":true}},"required":["ok"],"additionalProperties":true}]},
1824
1880
  },
1825
- sourceLocation: {"file":"packages/game/src/remote.ts","line":168,"column":9},
1881
+ sourceLocation: {"file":"packages/game/src/remote.ts","line":174,"column":9},
1826
1882
  },
1827
1883
  'game/setParam': {
1828
1884
  service: 'gameRemote',
@@ -1873,7 +1929,7 @@ export const TYPERT_REMOTE = createTypertRemoteInfo({
1873
1929
  schema: _khorsheed_dsh_game_game_setParam_result$schema(),
1874
1930
  jsonSchema: {"oneOf":[{"type":"object","properties":{"ok":{"type":"boolean","const":false},"error":{"type":"string","enum":["not-found","denied","io","unbound","invalid","no-version","not-a-project","is-workspace","trash-failed"]}},"required":["ok","error"],"additionalProperties":true},{"type":"object","properties":{"ok":{"type":"boolean","const":true}},"required":["ok"],"additionalProperties":true}]},
1875
1931
  },
1876
- sourceLocation: {"file":"packages/game/src/remote.ts","line":121,"column":9},
1932
+ sourceLocation: {"file":"packages/game/src/remote.ts","line":127,"column":9},
1877
1933
  },
1878
1934
  'game/state': {
1879
1935
  service: 'gameRemote',
@@ -1957,7 +2013,7 @@ export const TYPERT_REMOTE = createTypertRemoteInfo({
1957
2013
  schema: _khorsheed_dsh_game_game_turnReport_result$schema(),
1958
2014
  jsonSchema: {"oneOf":[{"type":"null","const":null},{"type":"object","properties":{"turn":{"type":"number"},"projectId":{"type":"string"},"title":{"type":"string"},"at":{"type":"string"},"version":{"oneOf":[{"type":"null","const":null},{"type":"object","properties":{"kind":{"type":"string","enum":["candidate","deployed"]},"version":{"type":"number"}},"required":["kind","version"],"additionalProperties":true}],"description":"A version that became live (or a candidate) during the turn."},"check":{"oneOf":[{"type":"null","const":null},{"type":"object","properties":{"ok":{"type":"boolean"},"fixes":{"type":"number"},"gaveUp":{"type":"boolean"},"passed":{"type":"number"},"failures":{"type":"number"}},"required":["ok","fixes","gaveUp","passed","failures"],"additionalProperties":true}],"description":"The turn's end-of-turn check, when one ran."},"elements":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"what":{"type":"string","enum":["removed","new","look","fields"]},"fields":{"type":"array","items":{"type":"string"}},"looks":{"type":"object","properties":{"before":{"oneOf":[{"type":"null","const":null},{"type":"string"}]},"after":{"type":"string"}},"required":["before","after"],"additionalProperties":true,"description":"3D looks before and after the turn (JSON), for the card's side-by-side previews."}},"required":["id","name","what"],"additionalProperties":true}},"chapters":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"}},"required":["id","title"],"additionalProperties":true}},"params":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"label":{"type":"string"},"from":{"type":"number"},"to":{"type":"number"}},"required":["key","label","to"],"additionalProperties":true}},"code":{"type":"array","items":{"type":"string"},"description":"Other changed source files (code, assets, index.html)."},"tests":{"type":"array","items":{"type":"string"}}},"required":["turn","projectId","title","at","version","check","elements","chapters","params","code","tests"],"additionalProperties":true}]},
1959
2015
  },
1960
- sourceLocation: {"file":"packages/game/src/remote.ts","line":49,"column":3},
2016
+ sourceLocation: {"file":"packages/game/src/remote.ts","line":55,"column":3},
1961
2017
  },
1962
2018
  'game/versionBundle': {
1963
2019
  service: 'gameRemote',
@@ -1990,7 +2046,7 @@ export const TYPERT_REMOTE = createTypertRemoteInfo({
1990
2046
  schema: _khorsheed_dsh_game_game_versionBundle_result$schema(),
1991
2047
  jsonSchemaError: 'typert tool schema: selected union branches overlap or have no required discriminator',
1992
2048
  },
1993
- sourceLocation: {"file":"packages/game/src/remote.ts","line":204,"column":3},
2049
+ sourceLocation: {"file":"packages/game/src/remote.ts","line":210,"column":3},
1994
2050
  },
1995
2051
  'game/workingFile': {
1996
2052
  service: 'gameRemote',
@@ -2023,7 +2079,7 @@ export const TYPERT_REMOTE = createTypertRemoteInfo({
2023
2079
  schema: _khorsheed_dsh_game_game_workingFile_result$schema(),
2024
2080
  jsonSchemaError: 'typert tool schema: selected union branches overlap or have no required discriminator',
2025
2081
  },
2026
- sourceLocation: {"file":"packages/game/src/remote.ts","line":73,"column":3},
2082
+ sourceLocation: {"file":"packages/game/src/remote.ts","line":79,"column":3},
2027
2083
  },
2028
2084
  'game/writeChapter': {
2029
2085
  service: 'gameRemote',
@@ -2074,7 +2130,7 @@ export const TYPERT_REMOTE = createTypertRemoteInfo({
2074
2130
  schema: _khorsheed_dsh_game_game_writeChapter_result$schema(),
2075
2131
  jsonSchema: {"oneOf":[{"type":"object","properties":{"ok":{"type":"boolean","const":false},"error":{"type":"string","enum":["not-found","denied","io","unbound","invalid","no-version","not-a-project","is-workspace","trash-failed"]}},"required":["ok","error"],"additionalProperties":true},{"type":"object","properties":{"ok":{"type":"boolean","const":true}},"required":["ok"],"additionalProperties":true}]},
2076
2132
  },
2077
- sourceLocation: {"file":"packages/game/src/remote.ts","line":162,"column":9},
2133
+ sourceLocation: {"file":"packages/game/src/remote.ts","line":168,"column":9},
2078
2134
  },
2079
2135
  },
2080
2136
  }, z)
@@ -10,7 +10,8 @@ const GUIDANCE = 'This session is the game-development mode. The user designs an
10
10
  + 'version that passed the checks. Use game_open / game_read for the project; write its files with the '
11
11
  + 'ordinary file tools. Whenever you changed something playable, call game_publish before you report or ask anything, '
12
12
  + 'without waiting to be asked — a question does not end the turn, so the play area would still show the old version. '
13
- + 'When a CLI member reports its changes, publish them for it (members have no publish tool). Load the game-design skill when shaping the game with the user, and the gamedev skill before '
13
+ + 'When a CLI member reports its changes, publish them for it (members have no publish tool). A publish also takes the '
14
+ + 'user\'s game tab to the result; pass show/note to choose where and say what to look at. Load the game-design skill when shaping the game with the user, and the gamedev skill before '
14
15
  + 'writing game code. Answer in the user\'s language.';
15
16
  /**
16
17
  * Register the tools, the guidance and the skills, or nothing when the core is absent.
@@ -8,7 +8,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
8
8
  * number; 试玩记录 is the log the plugin keeps.
9
9
  */
10
10
  import { useEffect, useMemo, useState } from 'react';
11
- import { artStyleLabel } from "../project.js";
11
+ import { artStyleLabel, artStyleName } from "../project.js";
12
12
  import { failureOf } from "./contract.js";
13
13
  import { Markdown } from "./markdown.js";
14
14
  import css from './GameTab.module.css';
@@ -76,7 +76,7 @@ function OnePager(props) {
76
76
  setError(null);
77
77
  setEditing(true);
78
78
  };
79
- return _jsxs("section", { className: css.onePager, children: [_jsxs("div", { className: css.onePagerHead, children: [_jsx("span", { className: css.eyebrow, children: t('design.onePager') }), _jsx("span", { className: css.metaLine, children: "game.json" }), _jsx("span", { className: css.spacer }), _jsx("button", { type: "button", className: css.styleChip, onClick: onStyle, title: t('style.change'), children: t('design.style', { style: manifest === null ? '—' : artStyleLabel(manifest.artStyle) }) }), !editing && manifest !== null && _jsx("button", { type: "button", className: css.ghost, onClick: begin, children: t('design.editOnePager') })] }), editing
79
+ return _jsxs("section", { className: css.onePager, children: [_jsxs("div", { className: css.onePagerHead, children: [_jsx("span", { className: css.eyebrow, children: t('design.onePager') }), _jsx("span", { className: css.metaLine, children: "game.json" }), _jsx("span", { className: css.spacer }), _jsx("button", { type: "button", className: css.styleChip, onClick: onStyle, title: manifest === null ? t('style.change') : `${artStyleLabel(manifest.artStyle)}\n${t('style.change')}`, children: t('design.style', { style: manifest === null ? '—' : artStyleName(manifest.artStyle) }) }), !editing && manifest !== null && _jsx("button", { type: "button", className: css.ghost, onClick: begin, children: t('design.editOnePager') })] }), editing
80
80
  ? _jsxs("div", { className: css.onePagerForm, children: [_jsxs("label", { className: css.field, children: [_jsx("span", { className: css.fieldKey, children: t('design.pitchLabel') }), _jsx("textarea", { className: css.textarea, rows: 2, value: pitch, onChange: e => { setPitch(e.target.value); } })] }), _jsxs("label", { className: css.field, children: [_jsx("span", { className: css.fieldKey, children: t('design.typesLabel') }), _jsx("input", { className: css.input, value: types, onChange: e => { setTypes(e.target.value); } })] }), _jsxs("label", { className: css.field, children: [_jsx("span", { className: css.fieldKey, children: t('design.pillarsLabel') }), _jsx("textarea", { className: css.textarea, rows: 4, value: pillars, onChange: e => { setPillars(e.target.value); } })] }), error !== null && _jsx("p", { className: css.warnText, children: error }), _jsxs("div", { className: css.rowEnd, children: [_jsx("button", { type: "button", className: css.ghost, onClick: () => { setEditing(false); }, children: t('common.cancel') }), _jsx("button", { type: "button", className: css.primary, onClick: () => {
81
81
  void api.setOnePager(sessionId, project.id, { pitch, elementTypes: types, pillars }).then(r => {
82
82
  if (failureOf(r) !== undefined) {
@@ -87,7 +87,7 @@ function OnePager(props) {
87
87
  refresh();
88
88
  });
89
89
  }, children: t('common.save') })] })] })
90
- : _jsxs(_Fragment, { children: [_jsx("p", { className: css.pitch, children: manifest?.pitch || t('design.noPitch') }), _jsx("div", { className: css.typeRow, children: (manifest?.elementTypes ?? []).map(type => _jsx("span", { className: css.typeTag, children: type }, type)) }), _jsxs("div", { className: css.pillars, children: [_jsx("span", { className: css.eyebrow, children: t('design.pillars') }), (manifest?.pillars.length ?? 0) > 0
90
+ : _jsxs(_Fragment, { children: [_jsx("p", { className: css.pitch, children: manifest?.pitch || t('design.noPitch') }), manifest !== null && artStyleName(manifest.artStyle) !== artStyleLabel(manifest.artStyle) && _jsx("p", { className: css.metaLine, children: t('design.styleFull', { style: artStyleLabel(manifest.artStyle) }) }), _jsx("div", { className: css.typeRow, children: (manifest?.elementTypes ?? []).map(type => _jsx("span", { className: css.typeTag, children: type }, type)) }), _jsxs("div", { className: css.pillars, children: [_jsx("span", { className: css.eyebrow, children: t('design.pillars') }), (manifest?.pillars.length ?? 0) > 0
91
91
  ? _jsx("ol", { className: css.pillarList, children: manifest.pillars.map((p, i) => _jsx("li", { children: p }, i)) })
92
92
  : _jsx("p", { className: css.metaLine, children: t('design.noPillars') })] })] })] });
93
93
  }
@@ -34,7 +34,9 @@ export function ElementsView(props) {
34
34
  const open = project.elements.find(e => e.id === openId);
35
35
  if (open !== undefined)
36
36
  return _jsx(ElementDetail, { ...props, element: open, onBack: () => { onOpen(null); }, types: types });
37
- const shown = project.elements.filter(e => filter === 'all' || e.type === filter);
37
+ // Newest change first (stable: elements of one file keep their order).
38
+ const shown = project.elements.filter(e => filter === 'all' || e.type === filter)
39
+ .map((e, i) => ({ e, i })).sort((a, b) => (b.e.updatedAt ?? 0) - (a.e.updatedAt ?? 0) || a.i - b.i).map(x => x.e);
38
40
  const openProposals = new Set(project.proposals.filter(p => p.picked === undefined).map(p => p.elementId));
39
41
  const toggle = (id) => { setSelected(s => (s.includes(id) ? s.filter(x => x !== id) : [...s, id])); };
40
42
  return _jsxs("div", { className: css.scroll, children: [project.elementErrors.length > 0 && _jsxs("div", { className: css.notice, "data-tone": "error", children: [_jsx("strong", { children: t('elements.errors', { count: project.elementErrors.length }) }), _jsx("ul", { className: css.plainList, children: project.elementErrors.slice(0, 6).map((f, i) => _jsxs("li", { children: [_jsx("code", { children: f.where }), " ", f.message] }, i)) })] }), project.elements.length === 0
@@ -71,7 +73,8 @@ function ElementDetail({ t, sessionId, api, project, element, onBack, insertDraf
71
73
  const [parts, setParts] = useState(null);
72
74
  const [clip, setClip] = useState(null);
73
75
  const [view, setView] = useState(null);
74
- const turnTo = (yaw) => { setView(v => ({ yaw, n: (v?.n ?? 0) + 1 })); };
76
+ const viewN = useRef(0);
77
+ const turnTo = (yaw) => { viewN.current++; setView({ yaw, n: viewN.current }); };
75
78
  useEffect(() => { setClips([]); setClip(null); setParts(null); }, [element.id]);
76
79
  useEffect(() => { setLook3d(element.look3d); }, [element.id, element.look3d]);
77
80
  const save = (next) => {
@@ -115,9 +118,9 @@ function ElementDetail({ t, sessionId, api, project, element, onBack, insertDraf
115
118
  const history = proposals.filter(p => p.picked !== undefined);
116
119
  const states = look === null ? [] : Object.keys(look.states);
117
120
  const fields = Object.entries(element.stats);
118
- return _jsxs("div", { className: css.scroll, children: [_jsx(PanelHeader, { className: css.detailHead, title: element.name, meta: _jsx("span", { title: `${element.id} · ${element.file}`, children: element.type }), onBack: onBack, backLabel: t('elements.back'), moreLabel: t('more') }), look3d !== null ? _jsxs("section", { className: css.lookPanel, children: [_jsx(Preview3D, { api: api, projectId: project.id, version: project.current?.version, look: look3d, mode: "spin", className: css.turntable, emptyLabel: t('look.preview3dEmpty'), onClips: (names, found) => { setClips(names); setParts(found); }, clip: clip, view: view }), clips.length > 0 && _jsx(ChipRow, { label: t('gltf.clips'), value: clip?.name ?? '', onSelect: name => { setClip(c => ({ name, n: (c?.n ?? 0) + 1 })); }, chips: clips.map(name => ({ id: name, label: name })) }), _jsxs("div", { className: css.toolRow, children: [_jsx("span", { className: css.metaLine, children: t('look.drag3d') }), _jsx("span", { className: css.spacer }), _jsxs("span", { role: "group", "aria-label": t('look.views'), className: css.toolRow, children: [_jsx("button", { type: "button", className: css.seg, onClick: () => { turnTo(0); }, children: t('look.front') }), _jsx("button", { type: "button", className: css.seg, onClick: () => { turnTo(90); }, children: t('look.side') }), _jsx("button", { type: "button", className: css.seg, onClick: () => { turnTo(180); }, children: t('look.back') })] }), _jsx("span", { className: css.saveState, "data-state": saving, children: saving === 'idle' ? '' : t(`look.${saving}`) })] }), look3d.kind === 'gltf' && _jsx(GltfControls, { t: t, look: look3d, onChange: save })] }) : _jsxs("section", { className: css.lookPanel, children: [_jsxs("div", { className: css.lookMain, children: [look !== null
121
+ return _jsxs("div", { className: css.scroll, children: [_jsx(PanelHeader, { className: css.detailHead, title: element.name, meta: _jsx("span", { title: `${element.id} · ${element.file}`, children: element.type }), onBack: onBack, backLabel: t('elements.back'), moreLabel: t('more') }), look3d !== null ? _jsxs("section", { className: css.lookPanel, children: [_jsx(Preview3D, { api: api, projectId: project.id, version: project.current?.version, look: look3d, mode: "spin", className: css.turntable, emptyLabel: t('look.preview3dEmpty'), onClips: (names, found) => { setClips(names); setParts(found); }, onTurned: () => { setView(null); }, clip: clip, view: view }), clips.length > 0 && _jsx(ChipRow, { label: t('gltf.clips'), value: clip?.name ?? autoClip(look3d, clips), onSelect: name => { setClip(c => ({ name, n: (c?.n ?? 0) + 1 })); }, chips: clips.map(name => ({ id: name, label: name })) }), _jsxs("div", { className: `${css.toolRow} ${css.tight}`, children: [_jsx("span", { className: css.metaLine, children: t('look.drag3d') }), _jsx("span", { className: css.spacer }), _jsx("span", { role: "group", "aria-label": t('look.views'), className: css.segGroup, children: [[0, 'look.front'], [90, 'look.side'], [180, 'look.back']].map(([yaw, key]) => _jsx("button", { type: "button", className: css.seg, "aria-pressed": view?.yaw === yaw, onClick: () => { turnTo(yaw); }, children: t(key) }, yaw)) }), _jsx("span", { className: css.saveState, "data-state": saving, children: saving === 'idle' ? '' : t(`look.${saving}`) })] }), look3d.kind === 'gltf' && _jsx(GltfControls, { t: t, look: look3d, onChange: save })] }) : _jsxs("section", { className: css.lookPanel, children: [_jsxs("div", { className: css.lookMain, children: [look !== null
119
122
  ? _jsx(LookCanvas, { look: look, state: state, box: 208, grid: true, onPaint: paint, className: css.lookEditor })
120
- : _jsxs("div", { className: css.lookEmpty, "data-tone": typeTone(types, element.type), children: [_jsx("p", { children: t('look.none') }), _jsx("button", { type: "button", className: css.ghost, onClick: () => { save(blankLook(stylePalette.length > 0 ? stylePalette : ['#000000', '#ffffff'])); }, children: t('look.start') })] }), look !== null && _jsxs("div", { className: css.lookSide, children: [_jsx("div", { className: css.lookPreview, children: _jsx(LookCanvas, { look: look, state: state, box: 64 }) }), _jsx("span", { className: css.metaLine, children: t('look.inGame') }), _jsx("span", { className: css.saveState, "data-state": saving, children: saving === 'idle' ? '' : t(`look.${saving}`) })] })] }), states.length > 0 && _jsx(ChipRow, { label: t('look.states'), value: state, onSelect: setState, chips: states.map(s => ({ id: s, label: s })) }), look !== null && _jsxs(_Fragment, { children: [_jsxs("div", { className: css.toolRow, role: "group", "aria-label": t('look.tools'), children: [_jsx("button", { type: "button", className: css.seg, "aria-pressed": tool === 'pen', onClick: () => { setTool('pen'); setRecolorFrom(null); }, children: t('look.pen') }), _jsx("button", { type: "button", className: css.seg, "aria-pressed": tool === 'recolor', onClick: () => { setTool('recolor'); }, children: t('look.recolor') }), _jsx("span", { className: css.spacer }), _jsx("span", { className: css.metaLine, children: tool === 'pen' ? t('look.penHint') : recolorFrom === null ? t('look.recolorPick') : t('look.recolorTo') })] }), tool === 'recolor' && _jsx("div", { className: css.swatches, "aria-label": t('look.ownColors'), children: look.palette.map((c, i) => _jsx("button", { type: "button", className: css.swatchBtn, "aria-pressed": recolorFrom === i, style: { background: c }, title: `${LOOK_CHARS[i]} ${c}`, onClick: () => { setRecolorFrom(i); } }, i)) }), _jsxs("div", { className: css.swatches, "aria-label": t('look.styleColors'), children: [tool === 'pen' && _jsx("button", { type: "button", className: css.swatchBtn, "data-erase": "true", "aria-pressed": color === null, title: t('look.erase'), onClick: () => { setColor(null); } }), stylePalette.map(c => _jsx("button", { type: "button", className: css.swatchBtn, style: { background: c }, title: c, "aria-pressed": tool === 'pen' && color === c, onClick: () => {
123
+ : _jsxs("div", { className: css.lookEmpty, "data-tone": typeTone(types, element.type), children: [_jsx("p", { children: t('look.none') }), _jsx("button", { type: "button", className: css.ghost, onClick: () => { save(blankLook(stylePalette.length > 0 ? stylePalette : ['#000000', '#ffffff'])); }, children: t('look.start') })] }), look !== null && _jsxs("div", { className: css.lookSide, children: [_jsx("div", { className: css.lookPreview, children: _jsx(LookCanvas, { look: look, state: state, box: 64 }) }), _jsx("span", { className: css.metaLine, children: t('look.inGame') }), _jsx("span", { className: css.saveState, "data-state": saving, children: saving === 'idle' ? '' : t(`look.${saving}`) })] })] }), states.length > 0 && _jsx(ChipRow, { label: t('look.states'), value: state, onSelect: setState, chips: states.map(s => ({ id: s, label: s })) }), look !== null && _jsxs(_Fragment, { children: [_jsxs("div", { className: `${css.toolRow} ${css.tight}`, children: [_jsxs("span", { className: css.segGroup, role: "group", "aria-label": t('look.tools'), children: [_jsx("button", { type: "button", className: css.seg, "aria-pressed": tool === 'pen', onClick: () => { setTool('pen'); setRecolorFrom(null); }, children: t('look.pen') }), _jsx("button", { type: "button", className: css.seg, "aria-pressed": tool === 'recolor', onClick: () => { setTool('recolor'); }, children: t('look.recolor') })] }), _jsx("span", { className: css.spacer }), _jsx("span", { className: css.metaLine, children: tool === 'pen' ? t('look.penHint') : recolorFrom === null ? t('look.recolorPick') : t('look.recolorTo') })] }), tool === 'recolor' && _jsx("div", { className: css.swatches, "aria-label": t('look.ownColors'), children: look.palette.map((c, i) => _jsx("button", { type: "button", className: css.swatchBtn, "aria-pressed": recolorFrom === i, style: { background: c }, title: `${LOOK_CHARS[i]} ${c}`, onClick: () => { setRecolorFrom(i); } }, i)) }), _jsxs("div", { className: css.swatches, "aria-label": t('look.styleColors'), children: [tool === 'pen' && _jsx("button", { type: "button", className: css.swatchBtn, "data-erase": "true", "aria-pressed": color === null, title: t('look.erase'), onClick: () => { setColor(null); } }), stylePalette.map(c => _jsx("button", { type: "button", className: css.swatchBtn, style: { background: c }, title: c, "aria-pressed": tool === 'pen' && color === c, onClick: () => {
121
124
  if (tool === 'pen')
122
125
  setColor(c);
123
126
  else if (recolorFrom !== null) {
@@ -155,7 +158,7 @@ function GltfControls({ t, look, onChange }) {
155
158
  const material = look.material ?? 'toon';
156
159
  const [scale, setScale] = useState(String(look.scale ?? 1));
157
160
  useEffect(() => { setScale(String(look.scale ?? 1)); }, [look.scale]);
158
- return _jsxs("div", { className: css.gltfRow, children: [_jsx("span", { className: css.metaLine, children: t('gltf.file', { src: look.src }) }), _jsx("span", { className: css.spacer }), _jsxs("span", { role: "group", "aria-label": t('gltf.material'), className: css.toolRow, children: [_jsx("button", { type: "button", className: css.seg, "aria-pressed": material === 'toon', onClick: () => { onChange({ ...look, material: 'toon' }); }, children: t('gltf.toon') }), _jsx("button", { type: "button", className: css.seg, "aria-pressed": material === 'original', onClick: () => { onChange({ ...look, material: 'original' }); }, children: t('gltf.original') })] }), _jsxs("label", { className: css.gltfScale, children: [_jsx("span", { className: css.metaLine, children: t('gltf.scale') }), _jsx("input", { className: css.input, inputMode: "decimal", value: scale, onChange: e => { setScale(e.target.value); }, onBlur: () => { const v = Number(scale); if (Number.isFinite(v) && v > 0 && v !== (look.scale ?? 1))
161
+ return _jsxs("div", { className: css.gltfRow, children: [_jsx("span", { className: css.metaLine, children: t('gltf.file', { src: look.src }) }), _jsx("span", { className: css.spacer }), _jsxs("span", { role: "group", "aria-label": t('gltf.material'), className: css.segGroup, children: [_jsx("button", { type: "button", className: css.seg, "aria-pressed": material === 'toon', onClick: () => { onChange({ ...look, material: 'toon' }); }, children: t('gltf.toon') }), _jsx("button", { type: "button", className: css.seg, "aria-pressed": material === 'original', onClick: () => { onChange({ ...look, material: 'original' }); }, children: t('gltf.original') })] }), _jsxs("label", { className: css.gltfScale, children: [_jsx("span", { className: css.metaLine, children: t('gltf.scale') }), _jsx("input", { className: css.input, inputMode: "decimal", value: scale, onChange: e => { setScale(e.target.value); }, onBlur: () => { const v = Number(scale); if (Number.isFinite(v) && v > 0 && v !== (look.scale ?? 1))
159
162
  onChange({ ...look, scale: v }); }, onKeyDown: e => { if (e.key === 'Enter')
160
163
  e.target.blur(); } })] })] });
161
164
  }
@@ -185,3 +188,8 @@ function GltfTweaks({ t, look, parts, onChange }) {
185
188
  setBone(name, { rotate: next });
186
189
  } }), _jsxs("span", { className: css.paramValue, children: [rot(name)[i], "\u00B0"] })] }, axis))] })] }, name)), shown.length > 40 && _jsx("span", { className: css.metaLine, children: t('gltf.moreBones', { count: shown.length - 40 }) })] })] });
187
190
  }
191
+ /** The clip a model plays on its own (dsh3d's rule): the look's choice, else an idle-like name, else the first. */
192
+ function autoClip(look, clips) {
193
+ const chosen = look.kind === 'gltf' ? look.animation : undefined;
194
+ return chosen !== undefined && clips.includes(chosen) ? chosen : clips.find(c => /idle|待机|stand/i.test(c)) ?? clips[0] ?? '';
195
+ }
@@ -11,7 +11,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
11
11
  import { useCallback, useEffect, useRef, useState } from 'react';
12
12
  import { PanelHeader, PanelRoot, UnderlineTabs, } from '@khorsheed/dsh-client-ui-panel-kit/src/client/index.ts';
13
13
  import { Switch, Tag } from '@deepseek-ai/dsh-client-ui-primitives';
14
- import { artStyleLabel } from "../project.js";
14
+ import { artStyleLabel, artStyleName } from "../project.js";
15
15
  import { exportHtml } from "../vfs.js";
16
16
  import { failureOf } from "./contract.js";
17
17
  import { DesignView } from "./DesignView.js";
@@ -28,7 +28,7 @@ const PlusIcon = _jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16",
28
28
  const ExportIcon = _jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: _jsx("path", { d: "M8 2.5v7M5 6.5l3 3 3-3M3 11v2.5h10V11", stroke: "currentColor", strokeWidth: "1.3", strokeLinecap: "round", strokeLinejoin: "round" }) });
29
29
  /** The game tab body. */
30
30
  export function GameTab(props) {
31
- const { sessionId, t, api, insertDraft, subscribeSessionEvents, touch, subscribeNav, takeNav } = props;
31
+ const { sessionId, t, api, insertDraft, subscribeSessionEvents, touch, subscribeNav, takeNav, reveal } = props;
32
32
  const [state, setState] = useState(null);
33
33
  const [error, setError] = useState(null);
34
34
  const [rev, setRev] = useState(0);
@@ -38,6 +38,7 @@ export function GameTab(props) {
38
38
  const [openElement, setOpenElement] = useState(null);
39
39
  const [toastText, setToastText] = useState(null);
40
40
  const [openChapter, setOpenChapter] = useState(null);
41
+ useEffect(() => reveal.mount(), [reveal]);
41
42
  const toastTimer = useRef();
42
43
  const refresh = useCallback(() => { touch(sessionId); }, [touch, sessionId]);
43
44
  const toast = useCallback((text) => {
@@ -78,7 +79,7 @@ export function GameTab(props) {
78
79
  return () => { live = false; };
79
80
  }, [api, sessionId, rev]);
80
81
  const viewProps = {
81
- t, sessionId, api, refresh, toast, insertDraft: text => insertDraft(sessionId, text), insertRef: ref => props.insertRef(sessionId, ref),
82
+ t, sessionId, api, refresh, toast, insertDraft: text => insertDraft(sessionId, text), insertRef: ref => props.insertRef(sessionId, ref), reveal,
82
83
  };
83
84
  const project = state?.bound ?? null;
84
85
  const choose = (projectId) => {
@@ -114,7 +115,10 @@ export function GameTab(props) {
114
115
  }
115
116
  /** The bound-project workbench. */
116
117
  function ProjectWorkbench(p) {
117
- const { t, sessionId, api, project, view, setView, styling, setStyling, refresh, toast, insertDraft } = p;
118
+ const { t, sessionId, api, project, view, setView, styling, setStyling, refresh, toast, insertDraft, reveal } = p;
119
+ const [, rerender] = useState(0);
120
+ useEffect(() => reveal.subscribe(() => { rerender(n => n + 1); }), [reveal]);
121
+ const notice = reveal.notice;
118
122
  const autoConfirm = project.manifest?.autoConfirm ?? true;
119
123
  const style = project.manifest?.artStyle;
120
124
  const exportCurrent = () => {
@@ -145,7 +149,8 @@ function ProjectWorkbench(p) {
145
149
  ];
146
150
  const failing = project.lastCheck !== null && !project.lastCheck.ok;
147
151
  const openProposals = project.proposals.filter(x => x.picked === undefined).length;
148
- return _jsxs(PanelRoot, { className: css.root, children: [_jsx(PanelHeader, { title: _jsxs("button", { type: "button", className: css.titleSwitch, onClick: p.onPickProject, title: t('switch'), "aria-haspopup": "dialog", children: [_jsx("span", { children: project.title }), _jsx("span", { className: css.titleCaret, "aria-hidden": "true", children: "\u25BE" })] }), meta: _jsxs("span", { className: css.headMeta, children: [style !== undefined && _jsx(Tag, { tone: "quiet", children: artStyleLabel(style) }), _jsx(Tag, { tone: "quiet", children: project.current === null ? t('meta.noVersion') : t('meta.version', { version: project.current.version }) })] }), extra: _jsx("span", { className: css.switches, children: _jsxs("span", { className: css.switch, title: autoConfirm ? t('autoConfirm.onHint') : t('autoConfirm.offHint'), children: [_jsx(Switch, { checked: autoConfirm, label: t('autoConfirm.label'), onChange: next => { void api.setAutoConfirm(sessionId, project.id, next).then(() => { toast(next ? t('toast.autoOn') : t('toast.autoOff')); refresh(); }); } }), _jsx("span", { className: css.switchLabel, children: t('autoConfirm.label') })] }) }), actions: actions, moreLabel: t('more') }), _jsx(UnderlineTabs, { label: t('tabs.label'), value: view, onChange: setView, tabs: [
152
+ // Any touch in the panel means the user is busy here: the agent's request waits as a notice.
153
+ return _jsxs(PanelRoot, { className: css.root, onPointerDownCapture: () => { reveal.touched(); }, onKeyDownCapture: () => { reveal.touched(); }, children: [_jsx(PanelHeader, { title: _jsxs("button", { type: "button", className: css.titleSwitch, onClick: p.onPickProject, title: t('switch'), "aria-haspopup": "dialog", children: [_jsx("span", { children: project.title }), _jsx("span", { className: css.titleCaret, "aria-hidden": "true", children: "\u25BE" })] }), meta: _jsxs("span", { className: css.headMeta, children: [style !== undefined && _jsx("span", { title: artStyleLabel(style), children: _jsx(Tag, { tone: "quiet", children: artStyleName(style) }) }), _jsx(Tag, { tone: "quiet", children: project.current === null ? t('meta.noVersion') : t('meta.version', { version: project.current.version }) })] }), extra: _jsx("span", { className: css.switches, children: _jsxs("span", { className: css.switch, title: autoConfirm ? t('autoConfirm.onHint') : t('autoConfirm.offHint'), children: [_jsx(Switch, { checked: autoConfirm, label: t('autoConfirm.label'), onChange: next => { void api.setAutoConfirm(sessionId, project.id, next).then(() => { toast(next ? t('toast.autoOn') : t('toast.autoOff')); refresh(); }); } }), _jsx("span", { className: css.switchLabel, children: t('autoConfirm.label') })] }) }), actions: actions, moreLabel: t('more') }), _jsx(UnderlineTabs, { label: t('tabs.label'), value: view, onChange: setView, tabs: [
149
154
  { id: 'play', label: t('tabs.play'), badge: project.candidate !== null ? '●' : undefined },
150
155
  { id: 'elements', label: t('tabs.elements'), badge: project.elementErrors.length > 0 ? '!' : openProposals > 0 ? '●' : project.elements.length || undefined },
151
156
  { id: 'design', label: t('tabs.design') },
@@ -159,7 +164,7 @@ function ProjectWorkbench(p) {
159
164
  setStyling(false);
160
165
  refresh();
161
166
  });
162
- } })] }), _jsx(CommitBar, { ...p, project: project, onOpenVersions: () => { setView('versions'); } }), view === 'play' && _jsx(PlayView, { ...p, onOpenElement: id => { p.setOpenElement(id); setView('elements'); }, onOpenVersions: () => { setView('versions'); } }), view === 'elements' && _jsx(ElementsView, { ...p, openId: p.openElement, onOpen: p.setOpenElement }), view === 'design' && _jsx(DesignView, { ...p, openChapter: p.openChapter, onOpenChapter: p.setOpenChapter, onStyle: () => { setStyling(true); } }), view === 'versions' && _jsx(VersionsView, { ...p }), p.foot] });
167
+ } })] }), _jsx(CommitBar, { ...p, project: project, onOpenVersions: () => { setView('versions'); } }), notice !== null && _jsxs("div", { className: css.navNotice, role: "status", children: [_jsx("span", { className: css.navNoticeText, children: notice.text }), _jsx("button", { type: "button", className: css.primary, onClick: () => { reveal.follow(); }, children: t('nav.go') }), _jsx("button", { type: "button", className: css.link, onClick: () => { reveal.dismiss(); }, children: t('nav.dismiss') })] }), view === 'play' && _jsx(PlayView, { ...p, onOpenElement: id => { p.setOpenElement(id); setView('elements'); }, onOpenVersions: () => { setView('versions'); } }), view === 'elements' && _jsx(ElementsView, { ...p, openId: p.openElement, onOpen: p.setOpenElement }), view === 'design' && _jsx(DesignView, { ...p, openChapter: p.openChapter, onOpenChapter: p.setOpenChapter, onStyle: () => { setStyling(true); } }), view === 'versions' && _jsx(VersionsView, { ...p }), p.foot] });
163
168
  }
164
169
  /**
165
170
  * 有新改动还没进游戏: whatever the working files hold that the play area
@@ -25,7 +25,7 @@ function fmt(value, step) {
25
25
  }
26
26
  /** The play view. */
27
27
  export function PlayView(props) {
28
- const { t, sessionId, api, project, refresh, toast, insertDraft, insertRef, onOpenElement, onOpenVersions } = props;
28
+ const { t, sessionId, api, project, refresh, toast, insertDraft, insertRef, onOpenElement, onOpenVersions, reveal } = props;
29
29
  const [which, setWhich] = useState('current');
30
30
  const [bundle, setBundle] = useState(null);
31
31
  const [error, setError] = useState(null);
@@ -42,6 +42,8 @@ export function PlayView(props) {
42
42
  // Playing is opt-in (2026-10-10): a big game should not start, and cost, on
43
43
  // every visit. A new version stops the old run and shows its poster.
44
44
  const [playing, setPlaying] = useState(false);
45
+ // A running game is never navigated away from (the agent's request waits as a notice).
46
+ useEffect(() => { reveal.setPlaying(playing); return () => { reveal.setPlaying(false); }; }, [reveal, playing]);
45
47
  const [poster, setPoster] = useState(null);
46
48
  useEffect(() => { setPlaying(false); setPicking(false); setPicked(null); }, [version, which]);
47
49
  useEffect(() => {
@@ -39,6 +39,8 @@ export interface Preview3DProps {
39
39
  readonly name: string;
40
40
  readonly n: number;
41
41
  } | null;
42
+ /** The user turned a turntable by hand (a named view no longer applies). */
43
+ readonly onTurned?: () => void;
42
44
  /** A turntable turns to this side (yaw in degrees: 0 front, 90 side, 180 back; bump `n` to repeat). */
43
45
  readonly view?: {
44
46
  readonly yaw: number;
@@ -46,5 +48,5 @@ export interface Preview3DProps {
46
48
  } | null;
47
49
  }
48
50
  /** One 3D look preview. */
49
- export declare function Preview3D({ api, projectId, version, look, mode, className, emptyLabel, placeholder, onClips, clip, view }: Preview3DProps): ReactNode;
51
+ export declare function Preview3D({ api, projectId, version, look, mode, className, emptyLabel, placeholder, onClips, onTurned, clip, view }: Preview3DProps): ReactNode;
50
52
  //# sourceMappingURL=Preview3D.d.ts.map
@@ -73,7 +73,7 @@ function stillSlot() {
73
73
  return { ready, release };
74
74
  }
75
75
  /** One 3D look preview. */
76
- export function Preview3D({ api, projectId, version, look, mode, className, emptyLabel, placeholder, onClips, clip, view }) {
76
+ export function Preview3D({ api, projectId, version, look, mode, className, emptyLabel, placeholder, onClips, onTurned, clip, view }) {
77
77
  const lookJson = JSON.stringify(look);
78
78
  const stillKey = `${projectId}:${version}:${lookJson}`;
79
79
  const [files, setFiles] = useState(null);
@@ -138,10 +138,12 @@ export function Preview3D({ api, projectId, version, look, mode, className, empt
138
138
  }
139
139
  if (data?.gamedev === 'preview-clips' && mode === 'spin')
140
140
  onClips?.(data.names, data.parts);
141
+ if (data?.gamedev === 'preview-turned' && mode === 'spin')
142
+ onTurned?.();
141
143
  };
142
144
  window.addEventListener('message', onMessage);
143
145
  return () => { window.removeEventListener('message', onMessage); };
144
- }, [mode, stillKey, onClips]);
146
+ }, [mode, stillKey, onClips, onTurned]);
145
147
  useEffect(() => {
146
148
  if (view == null || mode !== 'spin' || !booted.current)
147
149
  return;