@crowdedkingdoms/crowdyjs 8.17.0 → 8.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +7 -0
  2. package/dist/domains/marketplace.d.ts +7 -1
  3. package/dist/domains/marketplace.d.ts.map +1 -1
  4. package/dist/domains/marketplace.js +10 -1
  5. package/dist/domains/playerCompute.d.ts +7 -1
  6. package/dist/domains/playerCompute.d.ts.map +1 -1
  7. package/dist/domains/playerCompute.js +10 -1
  8. package/dist/generated/graphql.d.ts +117 -16
  9. package/dist/generated/graphql.d.ts.map +1 -1
  10. package/dist/generated/graphql.js +10 -8
  11. package/dist/index.d.ts +5 -2
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +4 -2
  14. package/dist/kit/npcs.d.ts.map +1 -1
  15. package/dist/kit/social.d.ts.map +1 -1
  16. package/dist/live-coding/ide.d.ts +16 -0
  17. package/dist/live-coding/ide.d.ts.map +1 -0
  18. package/dist/live-coding/ide.js +399 -0
  19. package/dist/live-coding/live-coding-controller.d.ts +2 -0
  20. package/dist/live-coding/live-coding-controller.d.ts.map +1 -1
  21. package/dist/live-coding/live-coding-controller.js +1 -0
  22. package/dist/player-runtime/glue-runtime.d.ts +92 -0
  23. package/dist/player-runtime/glue-runtime.d.ts.map +1 -0
  24. package/dist/player-runtime/glue-runtime.js +222 -0
  25. package/dist/player-runtime/glue-sab.d.ts +55 -0
  26. package/dist/player-runtime/glue-sab.d.ts.map +1 -0
  27. package/dist/player-runtime/glue-sab.js +79 -0
  28. package/dist/player-runtime/player-code-broker.d.ts +16 -0
  29. package/dist/player-runtime/player-code-broker.d.ts.map +1 -1
  30. package/dist/player-runtime/player-code-broker.js +38 -3
  31. package/dist/player-runtime/player-glue-worker.d.ts +26 -54
  32. package/dist/player-runtime/player-glue-worker.d.ts.map +1 -1
  33. package/dist/player-runtime/player-glue-worker.js +130 -111
  34. package/package.json +10 -3
@@ -0,0 +1,399 @@
1
+ import { LiveCodingController, } from './live-coding-controller.js';
2
+ import { mountLiveCoding, } from './mount.js';
3
+ import { PLAYER_CODE_TEMPLATES, } from './templates.js';
4
+ let vscodeServicesPromise = null;
5
+ /**
6
+ * Lazy Monaco live-coding IDE. Monaco and the language-client stack stay out
7
+ * of the importing game's hot bundle until this async mount is called.
8
+ * Missing LSP configuration degrades to the dependency-free textarea panel.
9
+ */
10
+ export async function mountLiveCodingIDE(el, options) {
11
+ const token = resolveToken(options.appToken);
12
+ if (!options.languageServiceUrl || !token) {
13
+ return mountLiveCoding(el, options);
14
+ }
15
+ if (options.editorWorkerFactory) {
16
+ globalThis.MonacoEnvironment = { getWorker: options.editorWorkerFactory };
17
+ }
18
+ try {
19
+ const [monaco, languageClientModule, wsJsonRpc, vscodeWrapperModule, workerFactoryModule,] = await Promise.all([
20
+ import('@codingame/monaco-vscode-editor-api'),
21
+ import('monaco-languageclient'),
22
+ import('vscode-ws-jsonrpc'),
23
+ import('monaco-languageclient/vscodeApiWrapper'),
24
+ import('monaco-languageclient/workerFactory'),
25
+ ]);
26
+ if (!vscodeServicesPromise) {
27
+ const wrapper = new vscodeWrapperModule.MonacoVscodeApiWrapper({
28
+ $type: 'classic',
29
+ viewsConfig: { $type: 'EditorService' },
30
+ userConfiguration: {
31
+ json: JSON.stringify({
32
+ 'workbench.colorTheme': 'Default Dark Modern',
33
+ 'editor.wordBasedSuggestions': 'off',
34
+ }),
35
+ },
36
+ monacoWorkerFactory: workerFactoryModule.configureDefaultWorkerFactory,
37
+ });
38
+ vscodeServicesPromise = wrapper.start();
39
+ }
40
+ await vscodeServicesPromise;
41
+ monaco.languages.register({ id: 'rust', extensions: ['.rs'] });
42
+ installRustSyntaxHighlighting(monaco);
43
+ monaco.languages.register({ id: 'toml', extensions: ['.toml'] });
44
+ const templates = options.templates ?? PLAYER_CODE_TEMPLATES;
45
+ const root = document.createElement('div');
46
+ root.className = 'ck-live-coding ck-live-coding-ide';
47
+ root.style.cssText =
48
+ 'display:grid;grid-template-rows:auto auto minmax(320px,1fr) auto auto;' +
49
+ 'gap:6px;min-height:520px;';
50
+ const controls = document.createElement('div');
51
+ controls.className = 'ck-live-coding-controls';
52
+ const target = select(['server', 'client']);
53
+ const template = document.createElement('select');
54
+ const deploy = button('Deploy');
55
+ const draft = button('Deploy draft');
56
+ const stop = button('Stop');
57
+ controls.append(target, template, deploy, draft, stop);
58
+ const tabs = document.createElement('div');
59
+ tabs.className = 'ck-live-coding-tabs';
60
+ tabs.style.cssText = 'display:flex;gap:4px;flex-wrap:wrap;';
61
+ const editorHost = document.createElement('div');
62
+ editorHost.className = 'ck-live-coding-monaco';
63
+ editorHost.style.cssText = 'min-height:320px;border:1px solid #333;';
64
+ const problems = document.createElement('div');
65
+ problems.className = 'ck-live-coding-problems';
66
+ const status = document.createElement('pre');
67
+ status.className = 'ck-live-coding-status';
68
+ const meter = document.createElement('div');
69
+ meter.className = 'ck-live-coding-meter';
70
+ root.append(controls, tabs, editorHost, problems, status, meter);
71
+ el.appendChild(root);
72
+ const controller = new LiveCodingController({
73
+ ...options,
74
+ onStatus: (value) => renderStatus(status, meter, value),
75
+ });
76
+ const editor = monaco.editor.create(editorHost, {
77
+ automaticLayout: true,
78
+ minimap: { enabled: false },
79
+ theme: 'vs-dark',
80
+ fontSize: 14,
81
+ tabSize: 2,
82
+ });
83
+ let models = [];
84
+ let languageClient = null;
85
+ let socket = null;
86
+ let modelRootUri = 'file:///player-mod';
87
+ const sourceJson = () => JSON.stringify(Object.fromEntries(models.map(({ path, model }) => [path, model.getValue()])));
88
+ const showModel = (path) => {
89
+ const found = models.find((entry) => entry.path === path);
90
+ if (found)
91
+ editor.setModel(found.model);
92
+ for (const child of Array.from(tabs.children)) {
93
+ child.dataset.active =
94
+ child.dataset.path === path ? 'true' : 'false';
95
+ }
96
+ };
97
+ const setFiles = (sourceFilesJson) => {
98
+ for (const entry of models)
99
+ entry.model.dispose();
100
+ models = [];
101
+ tabs.replaceChildren();
102
+ const files = parseFiles(sourceFilesJson);
103
+ for (const [path, content] of Object.entries(files)) {
104
+ const uri = monaco.Uri.parse(`${modelRootUri}/${path}`);
105
+ const model = monaco.editor.createModel(content, path.endsWith('.rs') ? 'rust' : path.endsWith('.toml') ? 'toml' : 'plaintext', uri);
106
+ models.push({ path, model });
107
+ const tab = button(path);
108
+ tab.dataset.path = path;
109
+ tab.addEventListener('click', () => showModel(path));
110
+ tabs.appendChild(tab);
111
+ }
112
+ const preferred = models.find((entry) => entry.path === 'src/lib.rs') ?? models[0];
113
+ if (preferred)
114
+ showModel(preferred.path);
115
+ };
116
+ const refreshTemplates = () => {
117
+ template.replaceChildren();
118
+ const matching = templates.filter((item) => item.target === target.value);
119
+ for (const item of matching) {
120
+ template.append(new Option(item.title, item.id));
121
+ }
122
+ if (matching[0])
123
+ setFiles(matching[0].sourceFilesJson);
124
+ };
125
+ target.addEventListener('change', refreshTemplates);
126
+ template.addEventListener('change', () => {
127
+ const selected = templates.find((item) => item.id === template.value);
128
+ if (selected)
129
+ setFiles(selected.sourceFilesJson);
130
+ });
131
+ const doDeploy = (draftMode) => {
132
+ void controller
133
+ .deploy({
134
+ name: options.moduleName ?? 'scratch-mod',
135
+ target: target.value,
136
+ sourceFilesJson: sourceJson(),
137
+ draft: draftMode || options.draftByDefault,
138
+ })
139
+ .catch((error) => {
140
+ status.textContent = `error: ${error.message}`;
141
+ });
142
+ };
143
+ deploy.addEventListener('click', () => doDeploy(false));
144
+ draft.addEventListener('click', () => doDeploy(true));
145
+ stop.addEventListener('click', () => controller.stop());
146
+ const markerSubscription = monaco.editor.onDidChangeMarkers(() => {
147
+ const markers = monaco.editor.getModelMarkers({});
148
+ const errors = markers.filter((marker) => marker.severity === monaco.MarkerSeverity.Error).length;
149
+ const warnings = markers.filter((marker) => marker.severity === monaco.MarkerSeverity.Warning).length;
150
+ problems.textContent = `${errors} error(s) · ${warnings} warning(s)`;
151
+ });
152
+ refreshTemplates();
153
+ const authenticated = await authenticatedSocket(options.languageServiceUrl, token, String(options.appId), sourceJson());
154
+ socket = authenticated.socket;
155
+ const initialSource = sourceJson();
156
+ modelRootUri = authenticated.workspaceUri.replace(/\/$/, '');
157
+ setFiles(initialSource);
158
+ const adapter = websocketAdapter(socket);
159
+ const reader = new wsJsonRpc.WebSocketMessageReader(adapter);
160
+ const writer = new wsJsonRpc.WebSocketMessageWriter(adapter);
161
+ languageClient = new languageClientModule.MonacoLanguageClient({
162
+ name: 'Crowdy Rust',
163
+ clientOptions: {
164
+ documentSelector: [{ language: 'rust' }, { language: 'toml' }],
165
+ workspaceFolder: {
166
+ uri: monaco.Uri.parse(modelRootUri),
167
+ name: 'player-mod',
168
+ index: 0,
169
+ },
170
+ },
171
+ messageTransports: { reader, writer },
172
+ });
173
+ await languageClient.start();
174
+ void controller.refreshUsage().catch(() => { });
175
+ return {
176
+ controller,
177
+ destroy: () => {
178
+ markerSubscription.dispose();
179
+ controller.stop();
180
+ void languageClient?.stop().catch(() => { });
181
+ socket?.close();
182
+ editor.dispose();
183
+ for (const entry of models)
184
+ entry.model.dispose();
185
+ root.remove();
186
+ },
187
+ };
188
+ }
189
+ catch (error) {
190
+ console.warn('Monaco live-coding IDE unavailable; using textarea fallback', error);
191
+ el.replaceChildren();
192
+ return mountLiveCoding(el, options);
193
+ }
194
+ }
195
+ function installRustSyntaxHighlighting(monaco) {
196
+ const keywords = new Set([
197
+ 'as',
198
+ 'async',
199
+ 'await',
200
+ 'break',
201
+ 'const',
202
+ 'continue',
203
+ 'crate',
204
+ 'dyn',
205
+ 'else',
206
+ 'enum',
207
+ 'extern',
208
+ 'false',
209
+ 'fn',
210
+ 'for',
211
+ 'if',
212
+ 'impl',
213
+ 'in',
214
+ 'let',
215
+ 'loop',
216
+ 'match',
217
+ 'mod',
218
+ 'move',
219
+ 'mut',
220
+ 'pub',
221
+ 'ref',
222
+ 'return',
223
+ 'self',
224
+ 'static',
225
+ 'struct',
226
+ 'super',
227
+ 'trait',
228
+ 'true',
229
+ 'type',
230
+ 'unsafe',
231
+ 'use',
232
+ 'where',
233
+ 'while',
234
+ ]);
235
+ const types = new Set([
236
+ 'Self',
237
+ 'String',
238
+ 'Vec',
239
+ 'Option',
240
+ 'Result',
241
+ 'bool',
242
+ 'char',
243
+ 'str',
244
+ 'usize',
245
+ 'isize',
246
+ 'u8',
247
+ 'u16',
248
+ 'u32',
249
+ 'u64',
250
+ 'u128',
251
+ 'i8',
252
+ 'i16',
253
+ 'i32',
254
+ 'i64',
255
+ 'i128',
256
+ 'f32',
257
+ 'f64',
258
+ ]);
259
+ const state = {
260
+ clone() {
261
+ return this;
262
+ },
263
+ equals(other) {
264
+ return other === this;
265
+ },
266
+ };
267
+ monaco.languages.setTokensProvider('rust', {
268
+ getInitialState: () => state,
269
+ tokenize: (line, currentState) => {
270
+ const tokens = [];
271
+ let index = 0;
272
+ while (index < line.length) {
273
+ const rest = line.slice(index);
274
+ let match;
275
+ if ((match = rest.match(/^\s+/))) {
276
+ tokens.push({ startIndex: index, scopes: '' });
277
+ }
278
+ else if ((match = rest.match(/^\/\/.*$/))) {
279
+ tokens.push({ startIndex: index, scopes: 'comment.rust' });
280
+ }
281
+ else if ((match = rest.match(/^\/\*.*?(?:\*\/|$)/))) {
282
+ tokens.push({ startIndex: index, scopes: 'comment.rust' });
283
+ }
284
+ else if ((match = rest.match(/^"(?:\\.|[^"\\])*"?/))) {
285
+ tokens.push({ startIndex: index, scopes: 'string.rust' });
286
+ }
287
+ else if ((match = rest.match(/^(?:0x[\da-fA-F_]+|\d[\d_]*)/))) {
288
+ tokens.push({ startIndex: index, scopes: 'number.rust' });
289
+ }
290
+ else if ((match = rest.match(/^[A-Za-z_]\w*!/))) {
291
+ tokens.push({ startIndex: index, scopes: 'macro.rust' });
292
+ }
293
+ else if ((match = rest.match(/^[A-Za-z_]\w*/))) {
294
+ const word = match[0];
295
+ tokens.push({
296
+ startIndex: index,
297
+ scopes: keywords.has(word)
298
+ ? 'keyword.rust'
299
+ : types.has(word)
300
+ ? 'type.rust'
301
+ : 'identifier.rust',
302
+ });
303
+ }
304
+ else {
305
+ match = rest.match(/^./);
306
+ tokens.push({ startIndex: index, scopes: 'delimiter.rust' });
307
+ }
308
+ index += match?.[0].length ?? 1;
309
+ }
310
+ return { tokens, endState: currentState };
311
+ },
312
+ });
313
+ }
314
+ function resolveToken(token) {
315
+ const value = typeof token === 'function' ? token() : token;
316
+ return value && value.length > 0 ? value : null;
317
+ }
318
+ function parseFiles(sourceFilesJson) {
319
+ const parsed = JSON.parse(sourceFilesJson);
320
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
321
+ throw new Error('sourceFilesJson must be an object');
322
+ }
323
+ return Object.fromEntries(Object.entries(parsed).map(([path, value]) => {
324
+ if (typeof value !== 'string')
325
+ throw new Error(`Invalid source file ${path}`);
326
+ return [path, value];
327
+ }));
328
+ }
329
+ async function authenticatedSocket(url, token, appId, sourceFilesJson) {
330
+ const socket = new WebSocket(url);
331
+ let workspaceUri = 'file:///player-mod';
332
+ await new Promise((resolve, reject) => {
333
+ const timeout = setTimeout(() => reject(new Error('language service timeout')), 10000);
334
+ socket.addEventListener('open', () => {
335
+ socket.send(JSON.stringify({
336
+ type: 'authenticate',
337
+ token,
338
+ appId,
339
+ sourceFilesJson,
340
+ }));
341
+ }, { once: true });
342
+ socket.addEventListener('message', (event) => {
343
+ try {
344
+ const message = JSON.parse(String(event.data));
345
+ if (message.type !== 'ready' || !message.workspaceUri) {
346
+ throw new Error('language service refused session');
347
+ }
348
+ workspaceUri = message.workspaceUri;
349
+ clearTimeout(timeout);
350
+ resolve();
351
+ }
352
+ catch (error) {
353
+ clearTimeout(timeout);
354
+ reject(error);
355
+ }
356
+ }, { once: true });
357
+ socket.addEventListener('close', (event) => {
358
+ clearTimeout(timeout);
359
+ reject(new Error(event.reason || 'language service closed'));
360
+ }, { once: true });
361
+ });
362
+ return { socket, workspaceUri };
363
+ }
364
+ function websocketAdapter(socket) {
365
+ return {
366
+ send: (content) => socket.send(content),
367
+ onMessage: (callback) => socket.addEventListener('message', (event) => callback(event.data)),
368
+ onError: (callback) => socket.addEventListener('error', (event) => callback(event)),
369
+ onClose: (callback) => socket.addEventListener('close', (event) => callback(event.code, event.reason)),
370
+ dispose: () => socket.close(),
371
+ };
372
+ }
373
+ function select(values) {
374
+ const element = document.createElement('select');
375
+ for (const value of values)
376
+ element.append(new Option(value, value));
377
+ return element;
378
+ }
379
+ function button(label) {
380
+ const element = document.createElement('button');
381
+ element.type = 'button';
382
+ element.textContent = label;
383
+ return element;
384
+ }
385
+ function renderStatus(status, meter, value) {
386
+ status.textContent = [
387
+ `[${value.target}] ${value.phase}`,
388
+ value.message,
389
+ value.compileLog,
390
+ ]
391
+ .filter(Boolean)
392
+ .join('\n');
393
+ if (value.usage) {
394
+ meter.textContent =
395
+ `units ${value.usage.hourUnitsUsed}/${value.usage.unitsPerHour ?? '∞'} · ` +
396
+ `compiles ${value.usage.compilesThisHour}/${value.usage.maxCompilesPerHour} · ` +
397
+ `gate ${value.usage.gateStatus}${value.usage.gateReason ? ` (${value.usage.gateReason})` : ''}`;
398
+ }
399
+ }
@@ -29,6 +29,8 @@ export interface LiveCodingControllerOptions {
29
29
  /** Page-side host-call router for the broker (World Stores reads + owner effects). */
30
30
  onHostCall: PlayerCodeBrokerOptions['onHostCall'];
31
31
  onPresentation?: PlayerCodeBrokerOptions['onPresentation'];
32
+ /** Client tick cadence (ms); the worker self-drives `tick` for HUD-style mods. */
33
+ clientTickIntervalMs?: number;
32
34
  onStatus?: (status: LiveCodingStatus) => void;
33
35
  /** Poll interval for compile status (ms). */
34
36
  pollMs?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"live-coding-controller.d.ts","sourceRoot":"","sources":["../../src/live-coding/live-coding-controller.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AACpE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,EACL,gBAAgB,EAChB,KAAK,uBAAuB,EAC5B,KAAK,oBAAoB,EAC1B,MAAM,yCAAyC,CAAC;AAEjD,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEnD,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EACD,MAAM,GACN,WAAW,GACX,WAAW,GACX,gBAAgB,GAChB,UAAU,GACV,SAAS,GACT,OAAO,CAAC;IACZ,MAAM,EAAE,gBAAgB,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,sEAAsE;IACtE,KAAK,CAAC,EAAE;QACN,aAAa,EAAE,MAAM,CAAC;QACtB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;QAC5B,gBAAgB,EAAE,MAAM,CAAC;QACzB,kBAAkB,EAAE,MAAM,CAAC;QAC3B,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;KAC3B,CAAC;CACH;AAED,MAAM,WAAW,2BAA2B;IAC1C,aAAa,EAAE,IAAI,CACjB,gBAAgB,EAChB,QAAQ,GAAG,YAAY,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,GAAG,eAAe,CACnF,CAAC;IACF,YAAY,CAAC,EAAE,IAAI,CAAC,eAAe,EAAE,SAAS,CAAC,CAAC;IAChD,wDAAwD;IACxD,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,oBAAoB,CAAC;IAC3B,6DAA6D;IAC7D,SAAS,EAAE,MAAM,GAAG,GAAG,CAAC;IACxB,sFAAsF;IACtF,UAAU,EAAE,uBAAuB,CAAC,YAAY,CAAC,CAAC;IAClD,cAAc,CAAC,EAAE,uBAAuB,CAAC,gBAAgB,CAAC,CAAC;IAC3D,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAC9C,6CAA6C;IAC7C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yDAAyD;IACzD,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,uBAAuB,KAAK,gBAAgB,CAAC;CACxE;AAED;;;;;;GAMG;AACH,qBAAa,oBAAoB;IAInB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAHpC,OAAO,CAAC,MAAM,CAAiC;IAC/C,OAAO,CAAC,MAAM,CAAyD;gBAE1C,OAAO,EAAE,2BAA2B;IAEjE,SAAS,IAAI,gBAAgB;IAI7B,OAAO,CAAC,GAAG;IAKX,OAAO,CAAC,KAAK;IAMb,oDAAoD;IAC9C,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAgBnC;;;;;OAKG;IACG,MAAM,CAAC,KAAK,EAAE;QAClB,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,gBAAgB,CAAC;QACzB,eAAe,EAAE,MAAM,CAAC;QACxB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,KAAK,CAAC,EAAE,OAAO,CAAC;KACjB,GAAG,OAAO,CAAC,IAAI,CAAC;YA6CH,WAAW;YAoBX,SAAS;IA2BvB,IAAI,IAAI,IAAI;CAKb"}
1
+ {"version":3,"file":"live-coding-controller.d.ts","sourceRoot":"","sources":["../../src/live-coding/live-coding-controller.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AACpE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,EACL,gBAAgB,EAChB,KAAK,uBAAuB,EAC5B,KAAK,oBAAoB,EAC1B,MAAM,yCAAyC,CAAC;AAEjD,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEnD,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EACD,MAAM,GACN,WAAW,GACX,WAAW,GACX,gBAAgB,GAChB,UAAU,GACV,SAAS,GACT,OAAO,CAAC;IACZ,MAAM,EAAE,gBAAgB,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,sEAAsE;IACtE,KAAK,CAAC,EAAE;QACN,aAAa,EAAE,MAAM,CAAC;QACtB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;QAC5B,gBAAgB,EAAE,MAAM,CAAC;QACzB,kBAAkB,EAAE,MAAM,CAAC;QAC3B,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;KAC3B,CAAC;CACH;AAED,MAAM,WAAW,2BAA2B;IAC1C,aAAa,EAAE,IAAI,CACjB,gBAAgB,EAChB,QAAQ,GAAG,YAAY,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,GAAG,eAAe,CACnF,CAAC;IACF,YAAY,CAAC,EAAE,IAAI,CAAC,eAAe,EAAE,SAAS,CAAC,CAAC;IAChD,wDAAwD;IACxD,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,oBAAoB,CAAC;IAC3B,6DAA6D;IAC7D,SAAS,EAAE,MAAM,GAAG,GAAG,CAAC;IACxB,sFAAsF;IACtF,UAAU,EAAE,uBAAuB,CAAC,YAAY,CAAC,CAAC;IAClD,cAAc,CAAC,EAAE,uBAAuB,CAAC,gBAAgB,CAAC,CAAC;IAC3D,kFAAkF;IAClF,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAC9C,6CAA6C;IAC7C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yDAAyD;IACzD,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,uBAAuB,KAAK,gBAAgB,CAAC;CACxE;AAED;;;;;;GAMG;AACH,qBAAa,oBAAoB;IAInB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAHpC,OAAO,CAAC,MAAM,CAAiC;IAC/C,OAAO,CAAC,MAAM,CAAyD;gBAE1C,OAAO,EAAE,2BAA2B;IAEjE,SAAS,IAAI,gBAAgB;IAI7B,OAAO,CAAC,GAAG;IAKX,OAAO,CAAC,KAAK;IAMb,oDAAoD;IAC9C,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAgBnC;;;;;OAKG;IACG,MAAM,CAAC,KAAK,EAAE;QAClB,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,gBAAgB,CAAC;QACzB,eAAe,EAAE,MAAM,CAAC;QACxB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,KAAK,CAAC,EAAE,OAAO,CAAC;KACjB,GAAG,OAAO,CAAC,IAAI,CAAC;YA6CH,WAAW;YAoBX,SAAS;IA4BvB,IAAI,IAAI,IAAI;CAKb"}
@@ -119,6 +119,7 @@ export class LiveCodingController {
119
119
  fuelPerDispatch: artifact.fuelPerDispatch,
120
120
  onHostCall: this.options.onHostCall,
121
121
  onPresentation: this.options.onPresentation,
122
+ tickIntervalMs: this.options.clientTickIntervalMs ?? 1000,
122
123
  };
123
124
  const broker = this.options.brokerFactory?.(options) ?? new PlayerCodeBroker(options);
124
125
  if (this.broker) {
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Factored, environment-agnostic core of the platform glue (player compute
3
+ * P3/P5). This is the ONLY platform code that shares an execution context
4
+ * with an untrusted player module, so it is deliberately tiny and auditable
5
+ * and has NO dependency on worker globals, the DOM, or the SDK client — the
6
+ * browser worker entry ([player-glue-worker.ts]) and the Node integration
7
+ * test both drive this same core.
8
+ *
9
+ * ABI (matches crowdy-compute-sdk `lib.rs`): the guest imports module `ck`
10
+ * with `log`, `now_ms`, `state_get`, `state_set`, and the JSON gateway
11
+ * `host_call(ptr,len) -> u64` (packed `resp_ptr<<32 | resp_len`, guest frees
12
+ * with `ck_free`), plus `wasi_snapshot_preview1.random_get`. The guest
13
+ * exports `memory`, `ck_alloc`, `ck_free`, and the module hooks `init`,
14
+ * `tick(dt_ms)`, `handle_invoke(ptr,len)->u64`, optional `on_event(ptr,len)`.
15
+ *
16
+ * `host_call` is SYNCHRONOUS from the guest's view. The single synchronous
17
+ * dependency this core takes is `hostCallSync(reqBytes) -> respBytes`; the
18
+ * browser/Node entry realizes it with a SharedArrayBuffer + `Atomics.wait`
19
+ * (the worker blocks; the page/broker services the async SDK call and writes
20
+ * the reply back). Everything else here is pure.
21
+ */
22
+ /** The host-call names surfaced to a guest (mirrors the broker allowlist). */
23
+ export declare const GLUE_HOST_FUNCTIONS: readonly ["container_create", "container_get", "containers_list", "container_delete", "property_set", "model_invoke", "user_state_get", "user_state_set", "grid_state_get", "grid_state_set", "chunk_get", "voxels_list", "actors_list", "actors_list_radius", "voxel_set", "emit_spatial", "hud_set", "overlay_draw", "grid_permission_check"];
24
+ export interface GlueInitMessage {
25
+ type: 'init';
26
+ artifact: ArrayBuffer;
27
+ authority: 'player';
28
+ fuelPerDispatch?: string;
29
+ watchdogMs?: number;
30
+ /** Local client tick cadence in ms (0/undefined => no self-tick). */
31
+ tickIntervalMs?: number;
32
+ }
33
+ /** Parse the fuel budget the broker forwards; undefined/invalid => unbounded (server still meters). */
34
+ export declare function parseFuelBudget(raw: string | undefined): bigint | null;
35
+ /** A dispatch outcome the worker reports back to the broker. */
36
+ export type GlueDispatchResult = {
37
+ ok: true;
38
+ } | {
39
+ ok: false;
40
+ reason: 'fuel' | 'watchdog' | 'trap';
41
+ detail?: string;
42
+ };
43
+ /**
44
+ * Wrap a single guest dispatch with the wall-clock watchdog. The fuel trap is
45
+ * enforced inside the gas-injected module; this guards against a hang that
46
+ * spins without consuming fuel. Pure and unit-testable.
47
+ */
48
+ export declare function runWithWatchdog(dispatch: () => unknown, watchdogMs: number, now?: () => number): Promise<GlueDispatchResult>;
49
+ /** The minimal guest-instance surface the runtime drives (a real WebAssembly.Instance satisfies it). */
50
+ export interface GuestExports {
51
+ memory: {
52
+ buffer: ArrayBuffer;
53
+ };
54
+ ck_alloc(len: number): number;
55
+ ck_free?(ptr: number, len: number): void;
56
+ init?(): void;
57
+ tick?(dtMs: number): void;
58
+ handle_invoke?(ptr: number, len: number): bigint | number;
59
+ on_event?(ptr: number, len: number): void;
60
+ }
61
+ export interface GlueRuntimeOptions {
62
+ /** Synchronous host-API gateway: JSON request bytes in, SDK Response-envelope bytes out. */
63
+ hostCallSync: (reqBytes: Uint8Array) => Uint8Array;
64
+ /** debug/info/warn/error sink for guest `ck.log` (optional). */
65
+ onLog?: (level: number, message: string) => void;
66
+ /** Deterministic-enough randomness for the guest `random_get` (defaults to crypto). */
67
+ randomFill?: (buf: Uint8Array) => void;
68
+ now?: () => number;
69
+ }
70
+ /**
71
+ * Drives one untrusted guest module: builds the `ck` + wasi import table,
72
+ * instantiates the artifact, and marshals the synchronous `host_call`
73
+ * gateway across guest linear memory. Durable client state is kept in-worker
74
+ * (a client module's blob is ephemeral per session — the durable store is a
75
+ * host_call away for anything that must survive).
76
+ */
77
+ export declare class GlueRuntime {
78
+ private readonly options;
79
+ private exports;
80
+ private stateBlob;
81
+ constructor(options: GlueRuntimeOptions);
82
+ /** The import object handed to `WebAssembly.instantiate`. Guest sees only these. */
83
+ buildImports(getExports: () => GuestExports | null): WebAssembly.Imports;
84
+ instantiate(artifact: ArrayBuffer): Promise<void>;
85
+ /** Run the module's `init` export (once, after instantiate). */
86
+ init(): void;
87
+ /** Run one `tick(dt_ms)`. Throws propagate to the caller's watchdog wrapper. */
88
+ tick(dtMs: number): void;
89
+ /** Invoke the module with an opaque payload; returns the reply bytes (copied out). */
90
+ invoke(payload: Uint8Array): Uint8Array;
91
+ }
92
+ //# sourceMappingURL=glue-runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glue-runtime.d.ts","sourceRoot":"","sources":["../../src/player-runtime/glue-runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,8EAA8E;AAC9E,eAAO,MAAM,mBAAmB,iVAoBtB,CAAC;AAEX,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,WAAW,CAAC;IACtB,SAAS,EAAE,QAAQ,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qEAAqE;IACrE,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,uGAAuG;AACvG,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAQtE;AAED,gEAAgE;AAChE,MAAM,MAAM,kBAAkB,GAC1B;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GACZ;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzE;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,QAAQ,EAAE,MAAM,OAAO,EACvB,UAAU,EAAE,MAAM,EAClB,GAAG,GAAE,MAAM,MAAyB,GACnC,OAAO,CAAC,kBAAkB,CAAC,CAe7B;AAED,wGAAwG;AACxG,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE;QAAE,MAAM,EAAE,WAAW,CAAA;KAAE,CAAC;IAChC,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;IAC9B,OAAO,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,IAAI,CAAC,IAAI,IAAI,CAAC;IACd,IAAI,CAAC,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,aAAa,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IAC1D,QAAQ,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3C;AAED,MAAM,WAAW,kBAAkB;IACjC,4FAA4F;IAC5F,YAAY,EAAE,CAAC,QAAQ,EAAE,UAAU,KAAK,UAAU,CAAC;IACnD,gEAAgE;IAChE,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACjD,uFAAuF;IACvF,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,UAAU,KAAK,IAAI,CAAC;IACvC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAgBD;;;;;;GAMG;AACH,qBAAa,WAAW;IAIV,OAAO,CAAC,QAAQ,CAAC,OAAO;IAHpC,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,SAAS,CAAiC;gBAErB,OAAO,EAAE,kBAAkB;IAExD,oFAAoF;IACpF,YAAY,CAAC,UAAU,EAAE,MAAM,YAAY,GAAG,IAAI,GAAG,WAAW,CAAC,OAAO;IAkFlE,WAAW,CAAC,QAAQ,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAUvD,gEAAgE;IAChE,IAAI,IAAI,IAAI;IAIZ,gFAAgF;IAChF,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAIxB,sFAAsF;IACtF,MAAM,CAAC,OAAO,EAAE,UAAU,GAAG,UAAU;CAcxC"}