@crowdedkingdoms/crowdyjs 17.13.0-dev.1 → 18.0.0-dev.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.
Files changed (284) hide show
  1. package/MIGRATION.md +163 -1
  2. package/README.md +142 -146
  3. package/dist/crowdy-client.d.ts +12 -31
  4. package/dist/crowdy-client.d.ts.map +1 -1
  5. package/dist/crowdy-client.js +15 -37
  6. package/dist/crowdy-dsh/bridge.d.ts.map +1 -1
  7. package/dist/crowdy-dsh/bridge.js +3 -6
  8. package/dist/crowdy-studio/controller.d.ts +59 -57
  9. package/dist/crowdy-studio/controller.d.ts.map +1 -1
  10. package/dist/crowdy-studio/controller.js +268 -206
  11. package/dist/crowdy-studio/diagnostics.d.ts +3 -12
  12. package/dist/crowdy-studio/diagnostics.d.ts.map +1 -1
  13. package/dist/crowdy-studio/dom-shell.d.ts +2 -4
  14. package/dist/crowdy-studio/dom-shell.d.ts.map +1 -1
  15. package/dist/crowdy-studio/dom-shell.js +16 -47
  16. package/dist/crowdy-studio/embed/hud-layer.d.ts +1 -1
  17. package/dist/crowdy-studio/embed/panel.d.ts +3 -12
  18. package/dist/crowdy-studio/embed/panel.d.ts.map +1 -1
  19. package/dist/crowdy-studio/embed/panel.js +3 -6
  20. package/dist/crowdy-studio/index.d.ts +1 -3
  21. package/dist/crowdy-studio/index.d.ts.map +1 -1
  22. package/dist/crowdy-studio/index.js +0 -2
  23. package/dist/crowdy-studio/models.d.ts +3 -4
  24. package/dist/crowdy-studio/models.d.ts.map +1 -1
  25. package/dist/crowdy-studio/starter-projects.d.ts +8 -4
  26. package/dist/crowdy-studio/starter-projects.d.ts.map +1 -1
  27. package/dist/crowdy-studio/starter-projects.js +83 -100
  28. package/dist/domains/appAccess.d.ts +14 -1
  29. package/dist/domains/appAccess.d.ts.map +1 -1
  30. package/dist/domains/appAccess.js +37 -1
  31. package/dist/domains/exec.d.ts +162 -15
  32. package/dist/domains/exec.d.ts.map +1 -1
  33. package/dist/domains/exec.js +180 -18
  34. package/dist/domains/host.d.ts +3 -3
  35. package/dist/domains/host.js +3 -3
  36. package/dist/domains/marketplace.d.ts +9 -58
  37. package/dist/domains/marketplace.d.ts.map +1 -1
  38. package/dist/domains/marketplace.js +9 -106
  39. package/dist/domains/playerWallet.d.ts +6 -12
  40. package/dist/domains/playerWallet.d.ts.map +1 -1
  41. package/dist/domains/playerWallet.js +6 -21
  42. package/dist/errors.d.ts +7 -13
  43. package/dist/errors.d.ts.map +1 -1
  44. package/dist/errors.js +6 -10
  45. package/dist/generated/graphql.d.ts +1384 -7977
  46. package/dist/generated/graphql.d.ts.map +1 -1
  47. package/dist/generated/graphql.js +22 -321
  48. package/dist/grid-mods/exec-client-halves.d.ts +156 -0
  49. package/dist/grid-mods/exec-client-halves.d.ts.map +1 -0
  50. package/dist/grid-mods/exec-client-halves.js +356 -0
  51. package/dist/grid-mods/grid-host-calls.d.ts +29 -15
  52. package/dist/grid-mods/grid-host-calls.d.ts.map +1 -1
  53. package/dist/grid-mods/grid-host-calls.js +40 -44
  54. package/dist/grid-mods/index.d.ts +1 -0
  55. package/dist/grid-mods/index.d.ts.map +1 -1
  56. package/dist/grid-mods/index.js +1 -0
  57. package/dist/grid-mods/mod-runtime.d.ts +21 -6
  58. package/dist/grid-mods/mod-runtime.d.ts.map +1 -1
  59. package/dist/grid-mods/mod-runtime.js +6 -2
  60. package/dist/grid-scope.d.ts +0 -188
  61. package/dist/grid-scope.d.ts.map +1 -1
  62. package/dist/grid-scope.js +0 -69
  63. package/dist/index.d.ts +12 -18
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +11 -16
  66. package/dist/kit/actions.d.ts +5 -9
  67. package/dist/kit/actions.d.ts.map +1 -1
  68. package/dist/kit/actions.js +5 -9
  69. package/dist/kit/index.d.ts +2 -33
  70. package/dist/kit/index.d.ts.map +1 -1
  71. package/dist/kit/index.js +1 -32
  72. package/dist/kit/kit.d.ts +8 -208
  73. package/dist/kit/kit.d.ts.map +1 -1
  74. package/dist/kit/kit.js +5 -163
  75. package/dist/kit/social.d.ts +1 -3
  76. package/dist/kit/social.d.ts.map +1 -1
  77. package/dist/kit/social.js +1 -3
  78. package/dist/kit/wire.d.ts +16 -5
  79. package/dist/kit/wire.d.ts.map +1 -1
  80. package/dist/kit/wire.js +19 -5
  81. package/dist/live-coding/assets/browser-authoring-index.json +1 -1
  82. package/dist/live-coding/assets/manifest.json +1 -1
  83. package/dist/live-coding/browser-authoring-index.generated.d.ts.map +1 -1
  84. package/dist/live-coding/browser-authoring-index.generated.js +1496 -4386
  85. package/dist/live-coding/rust-analysis.d.ts.map +1 -1
  86. package/dist/live-coding/rust-analysis.js +31 -23
  87. package/dist/player-runtime/client-host-calls.d.ts +8 -0
  88. package/dist/player-runtime/client-host-calls.d.ts.map +1 -0
  89. package/dist/player-runtime/client-host-calls.js +28 -0
  90. package/dist/player-runtime/glue-runtime.d.ts +27 -10
  91. package/dist/player-runtime/glue-runtime.d.ts.map +1 -1
  92. package/dist/player-runtime/glue-runtime.js +40 -45
  93. package/dist/player-runtime/glue-sab.d.ts +2 -2
  94. package/dist/player-runtime/glue-sab.js +2 -2
  95. package/dist/player-runtime/player-code-broker.d.ts +80 -24
  96. package/dist/player-runtime/player-code-broker.d.ts.map +1 -1
  97. package/dist/player-runtime/player-code-broker.js +169 -41
  98. package/dist/player-runtime/player-glue-worker.d.ts +8 -8
  99. package/dist/player-runtime/player-glue-worker.d.ts.map +1 -1
  100. package/dist/player-runtime/player-glue-worker.js +26 -9
  101. package/dist/stores/actors.d.ts +12 -2
  102. package/dist/stores/actors.d.ts.map +1 -1
  103. package/dist/stores/actors.js +43 -18
  104. package/dist/stores/chunks.d.ts +12 -0
  105. package/dist/stores/chunks.d.ts.map +1 -1
  106. package/dist/stores/chunks.js +112 -39
  107. package/dist/stores/durable.d.ts.map +1 -1
  108. package/dist/stores/durable.js +2 -1
  109. package/dist/stores/inbox.d.ts +1 -1
  110. package/dist/stores/inbox.d.ts.map +1 -1
  111. package/dist/stores/index.d.ts +0 -11
  112. package/dist/stores/index.d.ts.map +1 -1
  113. package/dist/stores/index.js +0 -4
  114. package/dist/stores/session.d.ts +0 -2
  115. package/dist/stores/session.d.ts.map +1 -1
  116. package/package.json +1 -1
  117. package/dist/crowdy-studio/model-lint-log.d.ts +0 -76
  118. package/dist/crowdy-studio/model-lint-log.d.ts.map +0 -1
  119. package/dist/crowdy-studio/model-lint-log.js +0 -93
  120. package/dist/crowdy-studio/model-lint.d.ts +0 -61
  121. package/dist/crowdy-studio/model-lint.d.ts.map +0 -1
  122. package/dist/crowdy-studio/model-lint.js +0 -45
  123. package/dist/domains/compute.d.ts +0 -208
  124. package/dist/domains/compute.d.ts.map +0 -1
  125. package/dist/domains/compute.js +0 -274
  126. package/dist/domains/controlPlane.d.ts +0 -38
  127. package/dist/domains/controlPlane.d.ts.map +0 -1
  128. package/dist/domains/controlPlane.js +0 -43
  129. package/dist/domains/gameModel.d.ts +0 -1174
  130. package/dist/domains/gameModel.d.ts.map +0 -1
  131. package/dist/domains/gameModel.js +0 -1333
  132. package/dist/domains/playerCompute.d.ts +0 -91
  133. package/dist/domains/playerCompute.d.ts.map +0 -1
  134. package/dist/domains/playerCompute.js +0 -136
  135. package/dist/domains/playerModel.d.ts +0 -20
  136. package/dist/domains/playerModel.d.ts.map +0 -1
  137. package/dist/domains/playerModel.js +0 -54
  138. package/dist/kit/abilities.d.ts +0 -66
  139. package/dist/kit/abilities.d.ts.map +0 -1
  140. package/dist/kit/abilities.js +0 -71
  141. package/dist/kit/blueprints/combat.d.ts +0 -81
  142. package/dist/kit/blueprints/combat.d.ts.map +0 -1
  143. package/dist/kit/blueprints/combat.js +0 -357
  144. package/dist/kit/blueprints/core.d.ts +0 -222
  145. package/dist/kit/blueprints/core.d.ts.map +0 -1
  146. package/dist/kit/blueprints/core.js +0 -185
  147. package/dist/kit/blueprints/decks.d.ts +0 -50
  148. package/dist/kit/blueprints/decks.d.ts.map +0 -1
  149. package/dist/kit/blueprints/decks.js +0 -163
  150. package/dist/kit/blueprints/economy.d.ts +0 -88
  151. package/dist/kit/blueprints/economy.d.ts.map +0 -1
  152. package/dist/kit/blueprints/economy.js +0 -533
  153. package/dist/kit/blueprints/guild.d.ts +0 -51
  154. package/dist/kit/blueprints/guild.d.ts.map +0 -1
  155. package/dist/kit/blueprints/guild.js +0 -50
  156. package/dist/kit/blueprints/index.d.ts +0 -23
  157. package/dist/kit/blueprints/index.d.ts.map +0 -1
  158. package/dist/kit/blueprints/index.js +0 -22
  159. package/dist/kit/blueprints/inventory.d.ts +0 -84
  160. package/dist/kit/blueprints/inventory.d.ts.map +0 -1
  161. package/dist/kit/blueprints/inventory.js +0 -257
  162. package/dist/kit/blueprints/leaderboards.d.ts +0 -49
  163. package/dist/kit/blueprints/leaderboards.d.ts.map +0 -1
  164. package/dist/kit/blueprints/leaderboards.js +0 -126
  165. package/dist/kit/blueprints/liveops.d.ts +0 -42
  166. package/dist/kit/blueprints/liveops.d.ts.map +0 -1
  167. package/dist/kit/blueprints/liveops.js +0 -170
  168. package/dist/kit/blueprints/locks.d.ts +0 -82
  169. package/dist/kit/blueprints/locks.d.ts.map +0 -1
  170. package/dist/kit/blueprints/locks.js +0 -155
  171. package/dist/kit/blueprints/loot.d.ts +0 -90
  172. package/dist/kit/blueprints/loot.d.ts.map +0 -1
  173. package/dist/kit/blueprints/loot.js +0 -229
  174. package/dist/kit/blueprints/matches.d.ts +0 -84
  175. package/dist/kit/blueprints/matches.d.ts.map +0 -1
  176. package/dist/kit/blueprints/matches.js +0 -349
  177. package/dist/kit/blueprints/moderation.d.ts +0 -32
  178. package/dist/kit/blueprints/moderation.d.ts.map +0 -1
  179. package/dist/kit/blueprints/moderation.js +0 -140
  180. package/dist/kit/blueprints/npcs.d.ts +0 -78
  181. package/dist/kit/blueprints/npcs.d.ts.map +0 -1
  182. package/dist/kit/blueprints/npcs.js +0 -125
  183. package/dist/kit/blueprints/plots.d.ts +0 -63
  184. package/dist/kit/blueprints/plots.d.ts.map +0 -1
  185. package/dist/kit/blueprints/plots.js +0 -151
  186. package/dist/kit/blueprints/progression.d.ts +0 -68
  187. package/dist/kit/blueprints/progression.d.ts.map +0 -1
  188. package/dist/kit/blueprints/progression.js +0 -324
  189. package/dist/kit/blueprints/quests.d.ts +0 -72
  190. package/dist/kit/blueprints/quests.d.ts.map +0 -1
  191. package/dist/kit/blueprints/quests.js +0 -285
  192. package/dist/kit/blueprints/worldsim.d.ts +0 -91
  193. package/dist/kit/blueprints/worldsim.d.ts.map +0 -1
  194. package/dist/kit/blueprints/worldsim.js +0 -468
  195. package/dist/kit/combat.d.ts +0 -141
  196. package/dist/kit/combat.d.ts.map +0 -1
  197. package/dist/kit/combat.js +0 -196
  198. package/dist/kit/decks.d.ts +0 -157
  199. package/dist/kit/decks.d.ts.map +0 -1
  200. package/dist/kit/decks.js +0 -186
  201. package/dist/kit/director.d.ts +0 -88
  202. package/dist/kit/director.d.ts.map +0 -1
  203. package/dist/kit/director.js +0 -86
  204. package/dist/kit/economy.d.ts +0 -289
  205. package/dist/kit/economy.d.ts.map +0 -1
  206. package/dist/kit/economy.js +0 -427
  207. package/dist/kit/engine.d.ts +0 -48
  208. package/dist/kit/engine.d.ts.map +0 -1
  209. package/dist/kit/engine.js +0 -113
  210. package/dist/kit/features.d.ts +0 -60
  211. package/dist/kit/features.d.ts.map +0 -1
  212. package/dist/kit/features.js +0 -65
  213. package/dist/kit/instances.d.ts +0 -52
  214. package/dist/kit/instances.d.ts.map +0 -1
  215. package/dist/kit/instances.js +0 -60
  216. package/dist/kit/inventory.d.ts +0 -129
  217. package/dist/kit/inventory.d.ts.map +0 -1
  218. package/dist/kit/inventory.js +0 -200
  219. package/dist/kit/leaderboards.d.ts +0 -86
  220. package/dist/kit/leaderboards.d.ts.map +0 -1
  221. package/dist/kit/leaderboards.js +0 -133
  222. package/dist/kit/liveops.d.ts +0 -112
  223. package/dist/kit/liveops.d.ts.map +0 -1
  224. package/dist/kit/liveops.js +0 -183
  225. package/dist/kit/loot.d.ts +0 -128
  226. package/dist/kit/loot.d.ts.map +0 -1
  227. package/dist/kit/loot.js +0 -160
  228. package/dist/kit/matches.d.ts +0 -301
  229. package/dist/kit/matches.d.ts.map +0 -1
  230. package/dist/kit/matches.js +0 -458
  231. package/dist/kit/matchmaking.d.ts +0 -57
  232. package/dist/kit/matchmaking.d.ts.map +0 -1
  233. package/dist/kit/matchmaking.js +0 -59
  234. package/dist/kit/minigames.d.ts +0 -30
  235. package/dist/kit/minigames.d.ts.map +0 -1
  236. package/dist/kit/minigames.js +0 -38
  237. package/dist/kit/mobs.d.ts +0 -90
  238. package/dist/kit/mobs.d.ts.map +0 -1
  239. package/dist/kit/mobs.js +0 -98
  240. package/dist/kit/moderation.d.ts +0 -73
  241. package/dist/kit/moderation.d.ts.map +0 -1
  242. package/dist/kit/moderation.js +0 -103
  243. package/dist/kit/movement.d.ts +0 -78
  244. package/dist/kit/movement.d.ts.map +0 -1
  245. package/dist/kit/movement.js +0 -103
  246. package/dist/kit/notifications.d.ts +0 -45
  247. package/dist/kit/notifications.d.ts.map +0 -1
  248. package/dist/kit/notifications.js +0 -67
  249. package/dist/kit/npcs.d.ts +0 -226
  250. package/dist/kit/npcs.d.ts.map +0 -1
  251. package/dist/kit/npcs.js +0 -141
  252. package/dist/kit/objects.d.ts +0 -116
  253. package/dist/kit/objects.d.ts.map +0 -1
  254. package/dist/kit/objects.js +0 -119
  255. package/dist/kit/pets.d.ts +0 -85
  256. package/dist/kit/pets.d.ts.map +0 -1
  257. package/dist/kit/pets.js +0 -96
  258. package/dist/kit/plots.d.ts +0 -85
  259. package/dist/kit/plots.d.ts.map +0 -1
  260. package/dist/kit/plots.js +0 -113
  261. package/dist/kit/progression.d.ts +0 -181
  262. package/dist/kit/progression.d.ts.map +0 -1
  263. package/dist/kit/progression.js +0 -265
  264. package/dist/kit/quests.d.ts +0 -171
  265. package/dist/kit/quests.d.ts.map +0 -1
  266. package/dist/kit/quests.js +0 -237
  267. package/dist/kit/racing.d.ts +0 -85
  268. package/dist/kit/racing.d.ts.map +0 -1
  269. package/dist/kit/racing.js +0 -99
  270. package/dist/kit/shared.d.ts +0 -73
  271. package/dist/kit/shared.d.ts.map +0 -1
  272. package/dist/kit/shared.js +0 -101
  273. package/dist/kit/telemetry.d.ts +0 -66
  274. package/dist/kit/telemetry.d.ts.map +0 -1
  275. package/dist/kit/telemetry.js +0 -133
  276. package/dist/kit/territory.d.ts +0 -97
  277. package/dist/kit/territory.d.ts.map +0 -1
  278. package/dist/kit/territory.js +0 -114
  279. package/dist/kit/worldsim.d.ts +0 -263
  280. package/dist/kit/worldsim.d.ts.map +0 -1
  281. package/dist/kit/worldsim.js +0 -268
  282. package/dist/stores/model.d.ts +0 -81
  283. package/dist/stores/model.d.ts.map +0 -1
  284. package/dist/stores/model.js +0 -163
@@ -1,15 +1,17 @@
1
1
  import { PlayerCodeBroker, } from '../player-runtime/player-code-broker.js';
2
+ import { CrowdyError, CrowdyGraphQLError } from '../errors.js';
2
3
  import { digestCanonicalJson, sha256Digest, } from '../player-host/json-schema.js';
3
4
  import { parseRustcDiagnostics } from './diagnostics.js';
4
5
  import { githubNewRepositoryUrl, githubRepositorySlug } from './github/new-repo.js';
5
6
  import { cloneCrowdyStudioProject, crowdyStudioFileKey, normalizeCrowdyStudioPath, projectTargets, CrowdyStudioOfflineError, CrowdyStudioRevisionConflictError, } from './models.js';
6
- import { parseClientTickIntervalMs } from './client-tick-interval.js';
7
7
  import { createCrowdyStudioStarterProject, } from './starter-projects.js';
8
8
  /** As ck-exec's mod names. */
9
9
  const MOD_NAME = /^[a-z0-9_-]{1,48}$/;
10
10
  /** As a ck-exec build's crate names. */
11
11
  const CRATE_NAME = /^[a-z][a-z0-9_-]{0,63}$/;
12
+ /** ck-exec's server log levels (`ExecLogLine.level`); a CLIENT half's are crowdy-client-sdk's. */
12
13
  const LOG_LEVELS = ['error', 'warn', 'info', 'debug'];
14
+ const PREVIEW_LOG_LINES_KEPT = 100;
13
15
  class OperationCancelledError extends Error {
14
16
  }
15
17
  /**
@@ -21,7 +23,6 @@ export class CrowdyStudioController {
21
23
  constructor(options) {
22
24
  this.options = options;
23
25
  this.state = {
24
- serverEngine: 'player-compute',
25
26
  projects: [],
26
27
  project: null,
27
28
  personalLibraryFiles: [],
@@ -38,9 +39,7 @@ export class CrowdyStudioController {
38
39
  buildOutput: '',
39
40
  authoritativeDiagnostics: [],
40
41
  localDiagnostics: [],
41
- runs: [],
42
42
  logs: [],
43
- usage: null,
44
43
  wallet: null,
45
44
  invokeResult: null,
46
45
  };
@@ -60,14 +59,15 @@ export class CrowdyStudioController {
60
59
  this.pageVisible = true;
61
60
  this.destroyed = false;
62
61
  this.modConnection = null;
62
+ this.modLogLines = [];
63
+ this.previewLogLines = [];
64
+ this.previewLogSeq = 0;
63
65
  // ----- GitHub repository card ------------------------------------------
64
66
  this.githubStatusGeneration = 0;
65
- const serverEngine = options.serverEngine ?? (options.mods ? 'ck-exec' : 'player-compute');
66
- if (serverEngine === 'ck-exec' && !options.mods) {
67
- throw new Error("serverEngine 'ck-exec' needs the mods option (client.exec)");
67
+ if (!options.mods) {
68
+ throw new Error('Crowdy Studio needs mods (client.exec): the SERVER target runs as a ck-exec mod');
68
69
  }
69
- this.mods = serverEngine === 'ck-exec' ? options.mods : null;
70
- this.state = { ...this.state, serverEngine };
70
+ this.mods = options.mods;
71
71
  if (options.onStateChange)
72
72
  this.listeners.add(options.onStateChange);
73
73
  }
@@ -197,7 +197,7 @@ export class CrowdyStudioController {
197
197
  if (this.state.project && !(await this.saveNow())) {
198
198
  throw new Error('Resolve or retry the current project save before creating another');
199
199
  }
200
- const modStarter = this.mods && projectTargets(options.kind).includes('SERVER')
200
+ const modStarter = projectTargets(options.kind).includes('SERVER')
201
201
  ? await this.mods.modStarter(this.options.appId)
202
202
  : undefined;
203
203
  const input = createCrowdyStudioStarterProject({
@@ -293,8 +293,7 @@ export class CrowdyStudioController {
293
293
  buildOutput: '',
294
294
  authoritativeDiagnostics: [],
295
295
  localDiagnostics: [],
296
- runs: [],
297
- logs: [],
296
+ logs: this.clearLogLines(),
298
297
  invokeResult: null,
299
298
  github: null,
300
299
  githubMessage: undefined,
@@ -431,13 +430,10 @@ export class CrowdyStudioController {
431
430
  project.metadata = {
432
431
  ...project.metadata,
433
432
  ...patch,
434
- pairingPreference: patch.pairingPreference ?? project.metadata.pairingPreference,
433
+ pairingPreference: project.metadata.pairingPreference,
435
434
  };
436
435
  this.markEdited();
437
436
  }
438
- setPairingPreference(preference) {
439
- this.updateSettings({ pairingPreference: preference });
440
- }
441
437
  setLocalDiagnostics(diagnostics) {
442
438
  this.update({ localDiagnostics: [...diagnostics] });
443
439
  }
@@ -864,7 +860,7 @@ export class CrowdyStudioController {
864
860
  try {
865
861
  if (targets.length === 1) {
866
862
  const target = targets[0];
867
- const compiled = await this.compileTarget(project, target, draft, operation);
863
+ const compiled = await this.compileTarget(project, target, operation);
868
864
  if (!compiled) {
869
865
  return {
870
866
  deployment: draft ? 'DRAFT' : 'LIVE',
@@ -883,8 +879,8 @@ export class CrowdyStudioController {
883
879
  }
884
880
  else {
885
881
  // Compile the client first so a client failure never publishes a new
886
- // server version or alters the currently live pairing requirement.
887
- const client = await this.compileTarget(project, 'CLIENT', draft, operation);
882
+ // server version.
883
+ const client = await this.compileTarget(project, 'CLIENT', operation);
888
884
  if (!client) {
889
885
  return {
890
886
  deployment: draft ? 'DRAFT' : 'LIVE',
@@ -894,7 +890,7 @@ export class CrowdyStudioController {
894
890
  message: this.state.runtime.message ?? 'Client compilation failed',
895
891
  };
896
892
  }
897
- const server = await this.compileTarget(project, 'SERVER', draft, operation);
893
+ const server = await this.compileTarget(project, 'SERVER', operation);
898
894
  if (!server) {
899
895
  return {
900
896
  deployment: draft ? 'DRAFT' : 'LIVE',
@@ -906,17 +902,6 @@ export class CrowdyStudioController {
906
902
  }
907
903
  this.checkOperation(operation);
908
904
  // A mod has no client pairing: its players call it by name.
909
- if (!this.mods) {
910
- const requiredClientName = project.metadata.pairingPreference === 'REQUIRED'
911
- ? client.name
912
- : null;
913
- await this.options.playerCompute.setRequires({
914
- ...this.scope(),
915
- serverName: server.name,
916
- requiredClientName,
917
- });
918
- this.checkOperation(operation);
919
- }
920
905
  await this.enableServer(server.name, operation);
921
906
  await this.runClient(client, operation);
922
907
  }
@@ -967,7 +952,7 @@ export class CrowdyStudioController {
967
952
  }
968
953
  }
969
954
  }
970
- async compileTarget(project, target, draft, operation) {
955
+ async compileTarget(project, target, operation) {
971
956
  if (!this.canTarget(target, 'write')) {
972
957
  throw new Error(`${target} authoring is unavailable on this grid`);
973
958
  }
@@ -982,39 +967,76 @@ export class CrowdyStudioController {
982
967
  message: `Submitting ${name}`,
983
968
  },
984
969
  });
985
- if (target === 'SERVER' && this.mods) {
986
- return this.buildMod(name, files, operation);
970
+ return target === 'SERVER'
971
+ ? this.buildMod(name, files, operation)
972
+ : this.buildClientHalf(name, files, operation);
973
+ }
974
+ /**
975
+ * The SERVER target as a ck-exec mod: the project's server crate files, built on the
976
+ * platform, then deployed to this grid (switched off until {@link enableServer}). Other
977
+ * project files, such as grid program assets, are not part of the crate.
978
+ */
979
+ async buildMod(name, files, operation) {
980
+ const mods = this.mods;
981
+ const problem = modNameProblem(this.requireProject());
982
+ if (problem)
983
+ throw new Error(problem);
984
+ const { appId, gridId } = this.scope();
985
+ const queued = await mods.modBuild(appId, {
986
+ name: crateName(name),
987
+ files: crateFiles(files),
988
+ });
989
+ this.checkOperation(operation);
990
+ const built = await this.awaitModBuild('SERVER', name, queued.buildId, operation);
991
+ if (!built)
992
+ return null;
993
+ const mod = await mods.modDeploy(appId, gridId, name, built.buildId);
994
+ this.checkOperation(operation);
995
+ return { target: 'SERVER', name, versionId: String(mod.version) };
996
+ }
997
+ /**
998
+ * The CLIENT target as a ck-exec mod's CLIENT half: the project's `crowdy-client-sdk` crate,
999
+ * built on the platform (`modClientBuild`, a build of `kind` `client`). It is attached to the
1000
+ * project's mod when the project runs ({@link attachClientHalf}).
1001
+ */
1002
+ async buildClientHalf(name, files, operation) {
1003
+ const problem = modNameProblem(this.requireProject());
1004
+ if (problem)
1005
+ throw new Error(problem);
1006
+ const cargo = files.find((file) => file.path === 'Cargo.toml')?.content ?? '';
1007
+ if (/^\s*crowdy-compute-sdk\s*=/m.test(cargo)) {
1008
+ this.recordBuild('CLIENT', LEGACY_CLIENT_CRATE);
1009
+ this.update({
1010
+ runtime: {
1011
+ phase: 'COMPILE_FAILED',
1012
+ target: 'CLIENT',
1013
+ message: `${name} is a legacy player compute crate; a CLIENT half builds on crowdy-client-sdk`,
1014
+ },
1015
+ });
1016
+ return null;
987
1017
  }
988
- // The server resolves the source from the project itself: the saved
989
- // files at the project revision, or — for a GITHUB project — the rust at
990
- // the commit the mirror is at. No file bodies travel with a deploy.
991
- const deployed = await this.options.playerCompute.deploy({
992
- ...this.scope(),
993
- projectId: project.projectId,
994
- name,
995
- target: target,
996
- ...(project.github?.sha ? { commitSha: project.github.sha } : {}),
997
- tickHz: target === 'SERVER' ? 1 : undefined,
998
- draft,
1018
+ const queued = await this.mods.modClientBuild(this.options.appId, {
1019
+ name: crateName(name),
1020
+ files: crateFiles(files),
999
1021
  });
1000
1022
  this.checkOperation(operation);
1023
+ const built = await this.awaitModBuild('CLIENT', name, queued.buildId, operation);
1024
+ return built ? { target: 'CLIENT', name, versionId: built.buildId } : null;
1025
+ }
1026
+ /** Polls a mod build, a mod's or a CLIENT half's, recording its log; null when it did not succeed. */
1027
+ async awaitModBuild(target, name, buildId, operation) {
1028
+ const mods = this.mods;
1001
1029
  const limit = this.options.compilePollLimit ?? 60;
1002
1030
  const pollMs = this.options.compilePollMs ?? 1500;
1003
1031
  for (let attempt = 0; attempt < limit; attempt++) {
1004
- const versions = await this.options.playerCompute.versions({
1005
- ...this.scope(),
1006
- name,
1007
- });
1032
+ const b = await mods.modBuildStatus(this.options.appId, buildId);
1008
1033
  this.checkOperation(operation);
1009
- const version = versions.find((candidate) => candidate.versionId === deployed.versionId);
1010
- const status = version?.compileStatus.toLowerCase();
1011
- if (status === 'succeeded' || status === 'success') {
1012
- this.recordBuild(target, version?.compileLog ?? '');
1013
- return { target, name, versionId: deployed.versionId };
1034
+ if (b.status === 'succeeded') {
1035
+ this.recordBuild(target, b.log ?? '');
1036
+ return b;
1014
1037
  }
1015
- if (status === 'failed' || status === 'error') {
1016
- const log = version?.compileLog ?? 'Compilation failed without output';
1017
- this.recordBuild(target, log);
1038
+ if (b.status === 'failed') {
1039
+ this.recordBuild(target, b.log ?? 'Compilation failed without output');
1018
1040
  this.update({
1019
1041
  runtime: {
1020
1042
  phase: 'COMPILE_FAILED',
@@ -1035,54 +1057,98 @@ export class CrowdyStudioController {
1035
1057
  return null;
1036
1058
  }
1037
1059
  /**
1038
- * The SERVER target as a ck-exec mod: the project's server crate files, built on the
1039
- * platform, then deployed to this grid (switched off until {@link enableServer}). Other
1040
- * project files, such as grid program assets, are not part of the crate.
1060
+ * Attaches a built CLIENT half to the project's mod, consents to it as its author (the API
1061
+ * serves a CLIENT half only to a player who consented or trusts its author, the author too) and
1062
+ * fetches the served module for this browser's preview. A CLIENT-only project first makes sure
1063
+ * the mod it rides is the player's and switched on.
1041
1064
  */
1042
- async buildMod(name, files, operation) {
1065
+ async attachClientHalf(compiled, operation) {
1043
1066
  const mods = this.mods;
1044
- if (!MOD_NAME.test(name)) {
1045
- throw new Error(`The server module name '${name}' must be 1-48 lowercase letters, digits, - or _ to run as a mod`);
1046
- }
1047
1067
  const { appId, gridId } = this.scope();
1048
- const queued = await mods.modBuild(appId, {
1049
- name: CRATE_NAME.test(name) ? name : `mod-${name}`,
1050
- files: files
1051
- .filter((file) => isModCrateFile(file.path))
1052
- .map((file) => ({ path: file.path, content: file.content })),
1068
+ const project = this.requireProject();
1069
+ const modName = projectModName(project);
1070
+ if (!projectTargets(project.kind).includes('SERVER')) {
1071
+ await this.ensureClientOnlyMod(modName, operation);
1072
+ }
1073
+ this.update({
1074
+ runtime: { phase: 'ENABLING', target: 'CLIENT', message: `Attaching ${compiled.name} to ${modName}` },
1053
1075
  });
1076
+ const attached = await mods.modClientDeploy(appId, gridId, modName, compiled.versionId);
1054
1077
  this.checkOperation(operation);
1055
- const limit = this.options.compilePollLimit ?? 60;
1056
- const pollMs = this.options.compilePollMs ?? 1500;
1057
- for (let attempt = 0; attempt < limit; attempt++) {
1058
- const b = await mods.modBuildStatus(appId, queued.buildId);
1059
- this.checkOperation(operation);
1060
- if (b.status === 'succeeded') {
1061
- this.recordBuild('SERVER', b.log ?? '');
1062
- const mod = await mods.modDeploy(appId, gridId, name, b.buildId);
1063
- this.checkOperation(operation);
1064
- return { target: 'SERVER', name, versionId: String(mod.version) };
1078
+ await mods.consentClientMod(appId, attached.modId, attached.capabilityHash);
1079
+ this.checkOperation(operation);
1080
+ this.recordNote('CLIENT', `Attached to mod '${modName}' as CLIENT version ${attached.clientVersion} (capabilities ` +
1081
+ `${attached.capabilityHash}; ticks every ${attached.tickIntervalMs} ms). Visitors of grid ` +
1082
+ `${gridId} run it once they consent to it or trust you; you consented to it as its author.`);
1083
+ let artifact;
1084
+ try {
1085
+ artifact = await mods.modClientArtifactBytes(appId, attached.modId);
1086
+ }
1087
+ catch (error) {
1088
+ if (error instanceof CrowdyGraphQLError && error.code === 'NOT_FOUND') {
1089
+ throw new CrowdyError({
1090
+ message: `${compiled.name} is attached to mod '${modName}', but its preview did not load: the ` +
1091
+ 'API serves a CLIENT half only while its mod is switched on and not held, to a player ' +
1092
+ `with run_client_code who stands in grid ${gridId}`,
1093
+ cause: error,
1094
+ });
1065
1095
  }
1066
- if (b.status === 'failed') {
1067
- this.recordBuild('SERVER', b.log ?? 'Compilation failed without output');
1068
- this.update({
1069
- runtime: {
1070
- phase: 'COMPILE_FAILED',
1071
- target: 'SERVER',
1072
- message: `${name} failed to compile`,
1073
- },
1096
+ if (error instanceof CrowdyGraphQLError && error.code === 'RATE_LIMITED') {
1097
+ throw new CrowdyError({
1098
+ message: `${compiled.name} is attached to mod '${modName}', but its preview was fetched too ` +
1099
+ 'often (12 a minute); deploy again in a minute',
1100
+ cause: error,
1074
1101
  });
1075
- return null;
1076
1102
  }
1077
- await this.sleep(pollMs);
1103
+ throw error;
1104
+ }
1105
+ this.checkOperation(operation);
1106
+ if (artifact.digest !== attached.digest || artifact.clientVersion !== attached.clientVersion) {
1107
+ throw new Error('The served CLIENT half is not the one just attached; deploy again');
1108
+ }
1109
+ return artifact;
1110
+ }
1111
+ /**
1112
+ * A CLIENT half rides a mod: without a mod of this name running as the player on the grid,
1113
+ * the mod starter's server half is deployed under it first, and the log says so. The mod is
1114
+ * switched on, since only a running mod's CLIENT half is served.
1115
+ */
1116
+ async ensureClientOnlyMod(name, operation) {
1117
+ const mods = this.mods;
1118
+ const { appId, gridId } = this.scope();
1119
+ const mine = (await mods.myMods(appId)).find((mod) => String(mod.gridId) === gridId && mod.name === name);
1120
+ this.checkOperation(operation);
1121
+ if (!mine) {
1122
+ if (!this.canTarget('SERVER', 'write') || !this.canTarget('SERVER', 'run')) {
1123
+ throw new Error(`A CLIENT half rides a mod, and grid ${gridId} has no mod '${name}' of yours; deploying one needs SERVER write and run permissions here`);
1124
+ }
1125
+ this.update({
1126
+ runtime: { phase: 'COMPILING', target: 'SERVER', message: `Deploying the mod starter as ${name}` },
1127
+ });
1128
+ const starter = await mods.modStarter(appId);
1078
1129
  this.checkOperation(operation);
1130
+ const queued = await mods.modBuild(appId, { name: crateName(name), files: starter.files });
1131
+ this.checkOperation(operation);
1132
+ const built = await this.awaitModBuild('SERVER', name, queued.buildId, operation);
1133
+ if (!built) {
1134
+ throw new Error(`The mod starter did not build, so the CLIENT half has no mod '${name}' to ride`);
1135
+ }
1136
+ await mods.modDeploy(appId, gridId, name, built.buildId);
1137
+ this.checkOperation(operation);
1138
+ this.recordNote('SERVER', `Grid ${gridId} had no mod '${name}' of yours, and a CLIENT half rides a mod: deployed the ` +
1139
+ `ck-exec mod starter (${starter.crate}) as '${name}', its server half.`);
1079
1140
  }
1080
- const timeout = `Compilation timed out after ${limit} polls`;
1081
- this.recordBuild('SERVER', timeout);
1141
+ if (!mine?.enabled) {
1142
+ await mods.modSetEnabled(appId, gridId, name, true);
1143
+ this.checkOperation(operation);
1144
+ if (mine)
1145
+ this.recordNote('SERVER', `Switched mod '${name}' on, so its CLIENT half is served.`);
1146
+ }
1147
+ }
1148
+ recordNote(target, note) {
1082
1149
  this.update({
1083
- runtime: { phase: 'COMPILE_FAILED', target: 'SERVER', message: timeout },
1150
+ buildOutput: [this.state.buildOutput, `## ${target}\n${note}`].filter(Boolean).join('\n\n'),
1084
1151
  });
1085
- return null;
1086
1152
  }
1087
1153
  recordBuild(target, log) {
1088
1154
  const section = `## ${target}\n${log || 'Compiled successfully.'}`;
@@ -1106,44 +1172,35 @@ export class CrowdyStudioController {
1106
1172
  this.checkOperation(operation);
1107
1173
  }
1108
1174
  async setServerEnabled(name, enabled) {
1109
- if (this.mods) {
1110
- const { appId, gridId } = this.scope();
1111
- await this.mods.modSetEnabled(appId, gridId, name, enabled);
1112
- return;
1113
- }
1114
- await this.options.playerCompute.setEnabled({
1115
- ...this.scope(),
1116
- name,
1117
- enabled,
1118
- });
1175
+ const { appId, gridId } = this.scope();
1176
+ await this.mods.modSetEnabled(appId, gridId, name, enabled);
1119
1177
  }
1120
1178
  async runClient(compiled, operation) {
1121
1179
  if (!this.canTarget('CLIENT', 'run')) {
1122
1180
  throw new Error(`${compiled.name} compiled successfully, but run_client_code is unavailable on this grid`);
1123
1181
  }
1124
1182
  const runtime = this.clientRuntimeOptions();
1125
- const artifact = await this.options.playerCompute.artifactBytes({
1126
- ...this.scope(),
1127
- name: compiled.name,
1128
- versionId: compiled.versionId,
1129
- });
1130
- this.checkOperation(operation);
1131
- if (artifact.versionId !== compiled.versionId ||
1132
- !artifact.artifactHash ||
1133
- artifact.bytes.byteLength === 0) {
1134
- throw new Error('Client artifact did not match the compiled project version');
1135
- }
1183
+ const half = await this.attachClientHalf(compiled, operation);
1184
+ let broker = null;
1136
1185
  const brokerOptions = {
1137
1186
  ...runtime,
1138
- artifactHash: artifact.artifactHash,
1139
- fuelPerDispatch: artifact.fuelPerDispatch,
1187
+ engine: 'ck-exec',
1188
+ moduleName: half.name,
1189
+ artifactHash: half.digest,
1190
+ fuelPerDispatch: half.fuelPerDispatch,
1191
+ consentedHostCalls: half.capabilitySummary.hostFunctions,
1140
1192
  onPresentation: this.options.onPresentation,
1141
- tickIntervalMs: this.options.clientTickIntervalMs ??
1142
- parseClientTickIntervalMs(this.requireProject().files.find((file) => file.target === 'CLIENT' && file.path === 'Cargo.toml')?.content),
1193
+ // Only the preview running now logs into the project's Logs.
1194
+ onLog: (line) => {
1195
+ if (broker && this.broker === broker)
1196
+ this.recordPreviewLog(compiled.name, line);
1197
+ },
1198
+ tickIntervalMs: this.options.clientTickIntervalMs ?? half.tickIntervalMs,
1143
1199
  };
1144
- const broker = this.options.brokerFactory?.(brokerOptions) ??
1145
- new PlayerCodeBroker(brokerOptions);
1146
- await broker.start(artifact.bytes);
1200
+ broker =
1201
+ this.options.brokerFactory?.(brokerOptions) ??
1202
+ new PlayerCodeBroker(brokerOptions);
1203
+ await broker.start(half.bytes);
1147
1204
  this.checkOperation(operation);
1148
1205
  const previous = this.broker;
1149
1206
  this.broker = broker;
@@ -1170,15 +1227,15 @@ export class CrowdyStudioController {
1170
1227
  this.broker = null;
1171
1228
  }
1172
1229
  }
1173
- if (projectTargets(project.kind).includes('SERVER')) {
1174
- serverStopped = false;
1175
- try {
1176
- await this.setServerEnabled(moduleNameFor(project, 'SERVER'), false);
1177
- serverStopped = true;
1178
- }
1179
- catch (error) {
1180
- failures.push(`Server: ${errorMessage(error)}`);
1181
- }
1230
+ // A CLIENT-only project's CLIENT half rides a mod too: switched off, it is no longer served
1231
+ // to visitors.
1232
+ serverStopped = false;
1233
+ try {
1234
+ await this.setServerEnabled(projectModName(project), false);
1235
+ serverStopped = true;
1236
+ }
1237
+ catch (error) {
1238
+ failures.push(`Server: ${errorMessage(error)}`);
1182
1239
  }
1183
1240
  const result = { serverStopped, clientStopped, failures };
1184
1241
  this.update({
@@ -1201,18 +1258,7 @@ export class CrowdyStudioController {
1201
1258
  if (!projectTargets(project.kind).includes('SERVER')) {
1202
1259
  throw new Error('Invoke requires a SERVER target');
1203
1260
  }
1204
- if (this.mods) {
1205
- const result = await this.callMod(moduleNameFor(project, 'SERVER'), exportName.trim() || 'state', paramsJson);
1206
- this.checkAgentOperation(agentOperation);
1207
- this.update({ invokeResult: result });
1208
- return result;
1209
- }
1210
- const result = await this.options.playerCompute.invoke({
1211
- ...this.scope(),
1212
- moduleName: moduleNameFor(project, 'SERVER'),
1213
- exportName: exportName.trim() || 'invoke',
1214
- paramsJson: paramsJson?.trim() || null,
1215
- });
1261
+ const result = await this.callMod(moduleNameFor(project, 'SERVER'), exportName.trim() || 'state', paramsJson);
1216
1262
  this.checkAgentOperation(agentOperation);
1217
1263
  this.update({ invokeResult: result });
1218
1264
  return result;
@@ -1279,54 +1325,47 @@ export class CrowdyStudioController {
1279
1325
  this.modConnection = null;
1280
1326
  void open?.connection.then((connection) => connection.close(), () => { });
1281
1327
  }
1328
+ clearLogLines() {
1329
+ this.modLogLines = [];
1330
+ this.previewLogLines = [];
1331
+ return [];
1332
+ }
1333
+ /** The mod's lines and the preview's, newest first. */
1334
+ mergedLogLines() {
1335
+ return [...this.previewLogLines, ...this.modLogLines].sort((a, b) => (Date.parse(b.at) || 0) - (Date.parse(a.at) || 0));
1336
+ }
1337
+ /** A `crowdy::log` line of the CLIENT half this browser previews; the last 100 are kept. */
1338
+ recordPreviewLog(moduleName, line) {
1339
+ if (this.destroyed)
1340
+ return;
1341
+ this.previewLogSeq += 1;
1342
+ this.previewLogLines = [
1343
+ {
1344
+ id: `preview-${this.previewLogSeq}`,
1345
+ source: 'preview',
1346
+ moduleName,
1347
+ level: line.level,
1348
+ at: new Date().toISOString(),
1349
+ text: line.message,
1350
+ },
1351
+ ...this.previewLogLines,
1352
+ ].slice(0, PREVIEW_LOG_LINES_KEPT);
1353
+ this.update({ logs: this.mergedLogLines() });
1354
+ }
1282
1355
  async refreshSurface(surface) {
1283
1356
  if (!this.state.project)
1284
1357
  return;
1285
- const serverName = this.state.project.metadata.serverModuleName;
1286
- if (this.mods && surface !== 'usage') {
1287
- // ck-exec records no runs; a mod's log is its own `ctx.log` lines.
1288
- if (surface === 'runs') {
1289
- this.update({ runs: [] });
1290
- return;
1291
- }
1292
- const lines = serverName?.trim()
1293
- ? await this.mods.modLogs(this.options.appId, this.options.gridId, serverName.trim(), {
1294
- limit: 50,
1295
- })
1296
- : [];
1297
- this.update({ logs: lines.map((line) => modLogRow(serverName.trim(), line)) });
1298
- return;
1299
- }
1300
- if (surface === 'runs') {
1301
- this.update({
1302
- runs: await this.options.playerCompute.runs({
1303
- ...this.scope(),
1304
- ...(serverName ? { moduleName: serverName } : {}),
1305
- limit: 50,
1306
- offset: 0,
1307
- }),
1308
- });
1309
- return;
1310
- }
1311
1358
  if (surface === 'logs') {
1312
- this.update({
1313
- logs: await this.options.playerCompute.logs({
1314
- ...this.scope(),
1315
- ...(serverName ? { moduleName: serverName } : {}),
1316
- limit: 50,
1317
- }),
1318
- });
1359
+ // The `ctx.log` lines of the project's mod, which a CLIENT-only project's CLIENT half rides.
1360
+ const name = projectModNameOrNull(this.state.project);
1361
+ const lines = name
1362
+ ? await this.mods.modLogs(this.options.appId, this.options.gridId, name, { limit: 50 })
1363
+ : [];
1364
+ this.modLogLines = lines.map((line) => modLogLine(name, line));
1365
+ this.update({ logs: this.mergedLogLines() });
1319
1366
  return;
1320
1367
  }
1321
- // Player compute's units and compile quota: with a mod, only a CLIENT target spends them.
1322
- const legacyUsage = !this.mods || projectTargets(this.state.project.kind).includes('CLIENT');
1323
- const [usage, wallet] = await Promise.all([
1324
- legacyUsage
1325
- ? this.options.playerCompute.usage({ appId: this.options.appId })
1326
- : Promise.resolve(null),
1327
- this.options.playerWallet?.balance() ?? Promise.resolve(null),
1328
- ]);
1329
- this.update({ usage: usage ? normalizeUsage(usage) : null, wallet });
1368
+ this.update({ wallet: (await this.options.playerWallet?.balance()) ?? null });
1330
1369
  }
1331
1370
  destroy() {
1332
1371
  if (this.destroyed)
@@ -1593,18 +1632,53 @@ function isModCrateFile(path) {
1593
1632
  path === 'README.md' ||
1594
1633
  (path.startsWith('src/') && path.endsWith('.rs')));
1595
1634
  }
1596
- function modLogRow(moduleName, line) {
1597
- const level = LOG_LEVELS[line.level] ?? 'debug';
1635
+ function crateFiles(files) {
1636
+ return files
1637
+ .filter((file) => isModCrateFile(file.path))
1638
+ .map((file) => ({ path: file.path, content: file.content }));
1639
+ }
1640
+ /** A build's crate name for a module: its own when it is one, else `mod-<name>`. */
1641
+ function crateName(name) {
1642
+ return CRATE_NAME.test(name) ? name : `mod-${name}`;
1643
+ }
1644
+ /**
1645
+ * The mod a ck-exec project runs as: its SERVER module's name, or for a CLIENT-only project the
1646
+ * CLIENT module's, which names the mod its CLIENT half rides.
1647
+ */
1648
+ function projectModName(project) {
1649
+ return moduleNameFor(project, projectTargets(project.kind).includes('SERVER') ? 'SERVER' : 'CLIENT');
1650
+ }
1651
+ function projectModNameOrNull(project) {
1652
+ try {
1653
+ return projectModName(project);
1654
+ }
1655
+ catch {
1656
+ return null;
1657
+ }
1658
+ }
1659
+ /** Why a ck-exec project's mod name cannot be a mod's, or null. */
1660
+ function modNameProblem(project) {
1661
+ const name = projectModName(project);
1662
+ if (MOD_NAME.test(name))
1663
+ return null;
1664
+ return projectTargets(project.kind).includes('SERVER')
1665
+ ? `The server module name '${name}' must be 1-48 lowercase letters, digits, - or _ to run as a mod`
1666
+ : `The CLIENT module name '${name}' names the mod its CLIENT half rides, so it must be 1-48 lowercase letters, digits, - or _`;
1667
+ }
1668
+ const LEGACY_CLIENT_CRATE = 'This CLIENT crate depends on crowdy-compute-sdk, legacy player compute. On ck-exec a CLIENT ' +
1669
+ 'target is a mod\u2019s CLIENT half, one crowdy-client-sdk crate: in Cargo.toml replace the ' +
1670
+ 'crowdy-compute-sdk line with crowdy-client-sdk = "0.1.0", and in src/ use crowdy_client_sdk ' +
1671
+ 'in place of crowdy_compute_sdk. Its host calls are the same less the Game Model, sessions and ' +
1672
+ 'grid state (grid_state_get / grid_state_set): the mod\u2019s server half, a hub keyed by the ' +
1673
+ 'grid, holds grid state now.';
1674
+ function modLogLine(moduleName, line) {
1598
1675
  return {
1599
- runId: line.id,
1676
+ id: line.id,
1677
+ source: 'mod',
1600
1678
  moduleName,
1601
- triggerSource: level,
1602
- startedAt: line.at,
1603
- durationUs: 0,
1604
- fuelUsed: '0',
1605
- success: level !== 'error',
1606
- errorMessage: line.text,
1607
- level,
1679
+ level: LOG_LEVELS[line.level] ?? 'debug',
1680
+ at: line.at,
1681
+ text: line.text,
1608
1682
  };
1609
1683
  }
1610
1684
  function moduleNameFor(project, target) {
@@ -1647,18 +1721,6 @@ function upsertReference(files, next) {
1647
1721
  ...files.filter((file) => file.source !== next.source || file.id !== next.id),
1648
1722
  ];
1649
1723
  }
1650
- function normalizeUsage(value) {
1651
- return {
1652
- hourUnitsUsed: String(value.hourUnitsUsed),
1653
- dayUnitsUsed: String(value.dayUnitsUsed),
1654
- unitsPerHour: value.unitsPerHour == null ? null : String(value.unitsPerHour),
1655
- unitsPerDay: value.unitsPerDay == null ? null : String(value.unitsPerDay),
1656
- compilesThisHour: value.compilesThisHour,
1657
- maxCompilesPerHour: value.maxCompilesPerHour,
1658
- gateStatus: value.gateStatus,
1659
- gateReason: value.gateReason ?? null,
1660
- };
1661
- }
1662
1724
  function applyValidatedPatch(baseline, input) {
1663
1725
  if (input.changes.length < 1 || input.changes.length > 16) {
1664
1726
  throw new Error('Atomic patch must contain 1 to 16 file changes');