@bitmagic/cli 0.1.19 → 0.1.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -1
- package/dist/commands/edit.js +5 -0
- package/dist/commands/edit.js.map +1 -1
- package/dist/commands/generate.d.ts +15 -0
- package/dist/commands/generate.js +87 -0
- package/dist/commands/generate.js.map +1 -1
- package/dist/editor/journal.d.ts +108 -0
- package/dist/editor/journal.js +214 -0
- package/dist/editor/journal.js.map +1 -0
- package/dist/editor/server.d.ts +7 -0
- package/dist/editor/server.js +125 -4
- package/dist/editor/server.js.map +1 -1
- package/dist/editor/shell-page.js +116 -0
- package/dist/editor/shell-page.js.map +1 -1
- package/dist/generate/prop.d.ts +47 -0
- package/dist/generate/prop.js +208 -0
- package/dist/generate/prop.js.map +1 -0
- package/dist/generate/stream.d.ts +6 -0
- package/dist/generate/stream.js +7 -2
- package/dist/generate/stream.js.map +1 -1
- package/dist/scaffold/project-files.js +31 -1
- package/dist/scaffold/project-files.js.map +1 -1
- package/package.json +3 -3
package/dist/editor/server.js
CHANGED
|
@@ -22,6 +22,8 @@ import { readProjectGameData } from '../forge/browser-host.js';
|
|
|
22
22
|
import { projectWorldJsonPath } from '../forge/run-pipeline.js';
|
|
23
23
|
import { renderEditorShell } from './shell-page.js';
|
|
24
24
|
import { buildSceneModifications } from './save.js';
|
|
25
|
+
import { deriveSceneEvents, EditorJournal } from './journal.js';
|
|
26
|
+
import { findPropAsset } from '../generate/prop.js';
|
|
25
27
|
/** Bodies are a scene snapshot, not an upload — a megabyte is already generous. */
|
|
26
28
|
const MAX_BODY_BYTES = 8 * 1024 * 1024;
|
|
27
29
|
function readBody(req) {
|
|
@@ -62,6 +64,30 @@ function worldMtimeMs(worldPath) {
|
|
|
62
64
|
return 0;
|
|
63
65
|
}
|
|
64
66
|
}
|
|
67
|
+
function isRecord(value) {
|
|
68
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* The command the agent should run to fulfil an HQ request. One definition, quoted for a shell,
|
|
72
|
+
* so the journal line, the HTTP reply and the editor's own confirmation cannot drift apart.
|
|
73
|
+
*/
|
|
74
|
+
export function hqCommand(assetId, prompt) {
|
|
75
|
+
const quoted = prompt.replace(/"/g, '\\"');
|
|
76
|
+
return `bitmagic generate prop --asset ${assetId} --prompt "${quoted}" --json`;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Whether `bitmagic generate prop` could actually regenerate this asset. Answered by the command's
|
|
80
|
+
* own precondition (`findPropAsset`), so the two cannot disagree about what is regenerable.
|
|
81
|
+
*/
|
|
82
|
+
function hasFitBox(worldPath, assetId) {
|
|
83
|
+
try {
|
|
84
|
+
findPropAsset(JSON.parse(fs.readFileSync(worldPath, 'utf-8')), assetId);
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
65
91
|
function readWorld(worldPath) {
|
|
66
92
|
const parsed = JSON.parse(fs.readFileSync(worldPath, 'utf-8'));
|
|
67
93
|
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
@@ -73,6 +99,7 @@ export async function startEditorServer(options) {
|
|
|
73
99
|
const log = options.log ?? (() => { });
|
|
74
100
|
const worldPath = projectWorldJsonPath(options.root);
|
|
75
101
|
const shell = renderEditorShell({ gamePort: options.gamePort, gameId: options.gameId });
|
|
102
|
+
const journal = new EditorJournal({ root: options.root, log });
|
|
76
103
|
/** The mtime our own last write produced. Anything else is an external edit. */
|
|
77
104
|
let lastWrittenMtimeMs = worldMtimeMs(worldPath);
|
|
78
105
|
const handleSave = async (req, res) => {
|
|
@@ -91,8 +118,13 @@ export async function startEditorServer(options) {
|
|
|
91
118
|
return;
|
|
92
119
|
}
|
|
93
120
|
let modifications;
|
|
121
|
+
let events;
|
|
94
122
|
try {
|
|
95
|
-
|
|
123
|
+
// One read serves both: the modification builder needs the levels registry, and the journal
|
|
124
|
+
// needs the pre-edit transforms to report what each object moved FROM.
|
|
125
|
+
const before = readWorld(worldPath);
|
|
126
|
+
modifications = buildSceneModifications(payload, before);
|
|
127
|
+
events = deriveSceneEvents(before, payload);
|
|
96
128
|
}
|
|
97
129
|
catch (error) {
|
|
98
130
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -107,8 +139,9 @@ export async function startEditorServer(options) {
|
|
|
107
139
|
try {
|
|
108
140
|
const outcome = applyModificationsToWorld(worldPath, modifications);
|
|
109
141
|
lastWrittenMtimeMs = worldMtimeMs(worldPath);
|
|
110
|
-
|
|
111
|
-
|
|
142
|
+
// Journalled AFTER the write lands, never before: an agent reading a `object.moved` line
|
|
143
|
+
// must be able to trust that world.json already says so.
|
|
144
|
+
journal.append(...events);
|
|
112
145
|
sendJson(res, 200, {
|
|
113
146
|
ok: true,
|
|
114
147
|
applied: outcome.applied,
|
|
@@ -125,6 +158,82 @@ export async function startEditorServer(options) {
|
|
|
125
158
|
sendJson(res, 422, { ok: false, error: message });
|
|
126
159
|
}
|
|
127
160
|
};
|
|
161
|
+
/**
|
|
162
|
+
* The "Generate high-quality version" button.
|
|
163
|
+
*
|
|
164
|
+
* The editor deliberately does not run the generation. Producing the mesh means calling the
|
|
165
|
+
* Asset Forger with credentials that are server-side only and never reach a creator's machine,
|
|
166
|
+
* so the editor's job is to capture the human's INTENT precisely and hand it to the agent, which
|
|
167
|
+
* is the thing in this lane that runs long, paid jobs.
|
|
168
|
+
*
|
|
169
|
+
* The confirmed text is saved onto the asset before anything else. That mirrors the Creator, and
|
|
170
|
+
* for the same reason: a forger placeholder often carries no description at all, and the
|
|
171
|
+
* description the human just wrote is worth keeping whether or not a generation ever follows.
|
|
172
|
+
*/
|
|
173
|
+
const handleHqRequest = async (req, res) => {
|
|
174
|
+
let body;
|
|
175
|
+
try {
|
|
176
|
+
body = JSON.parse(await readBody(req));
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
sendJson(res, 400, { ok: false, error: 'Request body must be JSON' });
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
const { assetId, prompt } = body;
|
|
183
|
+
if (typeof assetId !== 'string' || typeof prompt !== 'string' || prompt.trim() === '') {
|
|
184
|
+
sendJson(res, 400, { ok: false, error: 'assetId and a non-empty prompt are required' });
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
const assetName = typeof body.assetName === 'string' && body.assetName !== '' ? body.assetName : assetId;
|
|
188
|
+
const description = prompt.trim();
|
|
189
|
+
try {
|
|
190
|
+
applyModificationsToWorld(worldPath, [{
|
|
191
|
+
type: 'updateRoot',
|
|
192
|
+
path: ['assets'],
|
|
193
|
+
predicate: (item) => isRecord(item) && item.id === assetId,
|
|
194
|
+
// An updater, not a replacement: the asset carries fitBox, sourceGlbUrl and the rest, and
|
|
195
|
+
// the generation the agent runs later depends on every one of them.
|
|
196
|
+
value: (item) => ({ ...(isRecord(item) ? item : {}), description }),
|
|
197
|
+
}]);
|
|
198
|
+
lastWrittenMtimeMs = worldMtimeMs(worldPath);
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
202
|
+
log(`[editor] could not save the description for ${assetId}: ${message}`);
|
|
203
|
+
sendJson(res, 422, { ok: false, error: message });
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
// Only forge-baked assets carry a fitBox, and without one there is no envelope to voxelize
|
|
207
|
+
// to — `bitmagic generate prop` refuses. The engine offers its button on any asset flagged
|
|
208
|
+
// `placeholder`, so that combination is reachable, and handing the agent a command that cannot
|
|
209
|
+
// succeed is worse than saying so. The description is kept either way: it is what the human
|
|
210
|
+
// meant this object to be, and the asset agent reads it.
|
|
211
|
+
const generatable = hasFitBox(worldPath, assetId);
|
|
212
|
+
if (!generatable) {
|
|
213
|
+
log(`[editor] ${assetName} has no fitBox — description saved, but it cannot be regenerated.`);
|
|
214
|
+
sendJson(res, 200, {
|
|
215
|
+
ok: true,
|
|
216
|
+
canGenerate: false,
|
|
217
|
+
reason: 'This asset has no fitBox, so there is no size to generate it to. '
|
|
218
|
+
+ 'Only forge-baked assets can be regenerated.',
|
|
219
|
+
worldMtimeMs: lastWrittenMtimeMs,
|
|
220
|
+
});
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
journal.append({
|
|
224
|
+
event: 'hq.requested',
|
|
225
|
+
assetId,
|
|
226
|
+
assetName,
|
|
227
|
+
prompt: description,
|
|
228
|
+
command: hqCommand(assetId, description),
|
|
229
|
+
});
|
|
230
|
+
sendJson(res, 200, {
|
|
231
|
+
ok: true,
|
|
232
|
+
canGenerate: true,
|
|
233
|
+
command: hqCommand(assetId, description),
|
|
234
|
+
worldMtimeMs: lastWrittenMtimeMs,
|
|
235
|
+
});
|
|
236
|
+
};
|
|
128
237
|
const server = http.createServer((req, res) => {
|
|
129
238
|
void (async () => {
|
|
130
239
|
writeCors(res);
|
|
@@ -165,6 +274,10 @@ export async function startEditorServer(options) {
|
|
|
165
274
|
await handleSave(req, res);
|
|
166
275
|
return;
|
|
167
276
|
}
|
|
277
|
+
if (req.method === 'POST' && url === '/api/editor/hq-request') {
|
|
278
|
+
await handleHqRequest(req, res);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
168
281
|
sendJson(res, 404, { ok: false, error: `No route for ${req.method ?? '?'} ${url}` });
|
|
169
282
|
})();
|
|
170
283
|
});
|
|
@@ -177,8 +290,16 @@ export async function startEditorServer(options) {
|
|
|
177
290
|
});
|
|
178
291
|
server.listen(options.port, '127.0.0.1', resolve);
|
|
179
292
|
});
|
|
293
|
+
const url = `http://localhost:${options.port}/`;
|
|
294
|
+
journal.append({
|
|
295
|
+
event: 'session.started',
|
|
296
|
+
gameId: options.gameId,
|
|
297
|
+
gameUrl: `http://localhost:${options.gamePort}/`,
|
|
298
|
+
editorUrl: url,
|
|
299
|
+
});
|
|
180
300
|
return {
|
|
181
|
-
url
|
|
301
|
+
url,
|
|
302
|
+
journalPath: journal.path,
|
|
182
303
|
close: () => new Promise((resolve) => server.close(() => resolve())),
|
|
183
304
|
};
|
|
184
305
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/editor/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAE7B,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACxC,OAAO,EAAE,yBAAyB,EAAE,MAAM,iCAAiC,CAAC;AAC5E,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,uBAAuB,EAAqB,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/editor/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -70,6 +70,21 @@ export function renderEditorShell(options) {
|
|
|
70
70
|
.banner.show { display: flex; }
|
|
71
71
|
#terrain-banner { background: #3d1f1c; border-bottom-color: #6b2f28; color: #f3c3bd; }
|
|
72
72
|
#frame { display: block; flex: 1; width: 100%; border: 0; min-height: 0; }
|
|
73
|
+
#hq-overlay { display: none; position: fixed; inset: 0; background: rgba(8,8,12,.72);
|
|
74
|
+
align-items: center; justify-content: center; z-index: 10; }
|
|
75
|
+
#hq-overlay.show { display: flex; }
|
|
76
|
+
#hq-dialog { width: min(560px, 92vw); background: #17171d; border: 1px solid #34343f;
|
|
77
|
+
border-radius: 8px; padding: 18px; }
|
|
78
|
+
#hq-dialog h2 { margin: 0 0 6px; font-size: 15px; }
|
|
79
|
+
#hq-dialog p { margin: 0 0 12px; color: #9a9aab; }
|
|
80
|
+
#hq-prompt { width: 100%; min-height: 88px; resize: vertical; padding: 8px; border-radius: 6px;
|
|
81
|
+
background: #101014; color: #e6e6ea; border: 1px solid #34343f; font: inherit; }
|
|
82
|
+
#hq-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 12px; }
|
|
83
|
+
#hq-confirm { background: #3352c4; border-color: #3f61d8; }
|
|
84
|
+
#hq-note { display: none; margin-top: 10px; padding: 10px; border-radius: 6px;
|
|
85
|
+
background: #101014; border: 1px solid #26262e; }
|
|
86
|
+
#hq-note.show { display: block; }
|
|
87
|
+
#hq-note code { display: block; margin-top: 6px; color: #9fd0a8; word-break: break-all; }
|
|
73
88
|
</style>
|
|
74
89
|
</head>
|
|
75
90
|
<body>
|
|
@@ -91,6 +106,22 @@ export function renderEditorShell(options) {
|
|
|
91
106
|
<button id="terrain-discard" type="button">Discard terrain edits</button>
|
|
92
107
|
</div>
|
|
93
108
|
<iframe id="frame" allow="autoplay; fullscreen; xr-spatial-tracking; clipboard-write"></iframe>
|
|
109
|
+
<div id="hq-overlay">
|
|
110
|
+
<div id="hq-dialog">
|
|
111
|
+
<h2>Generate a high-quality <span id="hq-name"></span></h2>
|
|
112
|
+
<p>Describe what this object should be. The text is saved as the asset's description and
|
|
113
|
+
used as the generation prompt.</p>
|
|
114
|
+
<textarea id="hq-prompt" placeholder="e.g. a weathered stone archway covered in moss"></textarea>
|
|
115
|
+
<div id="hq-actions">
|
|
116
|
+
<button id="hq-cancel" type="button">Cancel</button>
|
|
117
|
+
<button id="hq-confirm" type="button">Hand to my agent</button>
|
|
118
|
+
</div>
|
|
119
|
+
<div id="hq-note">
|
|
120
|
+
<span id="hq-note-text"></span>
|
|
121
|
+
<code id="hq-command"></code>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
94
125
|
<script>
|
|
95
126
|
(function () {
|
|
96
127
|
'use strict';
|
|
@@ -178,6 +209,10 @@ export function renderEditorShell(options) {
|
|
|
178
209
|
markersDirty = true;
|
|
179
210
|
break;
|
|
180
211
|
|
|
212
|
+
case 'GENERATE_HQ_ASSET':
|
|
213
|
+
openHqDialog(message.assetId, message.assetName);
|
|
214
|
+
break;
|
|
215
|
+
|
|
181
216
|
// No other tabs exist here, and asset re-baking is a "bitmagic generate" concern.
|
|
182
217
|
case 'SWITCH_TO_TAB':
|
|
183
218
|
case 'OPEN_ASSET_ACTION':
|
|
@@ -185,6 +220,87 @@ export function renderEditorShell(options) {
|
|
|
185
220
|
}
|
|
186
221
|
});
|
|
187
222
|
|
|
223
|
+
/**
|
|
224
|
+
* The "Generate high-quality version" button in the engine's object inspector.
|
|
225
|
+
*
|
|
226
|
+
* The engine only announces the intent — the HOST owns the flow from here, in the web lane too.
|
|
227
|
+
* There, the Creator collects a description and POSTs a background job. Here the editor cannot
|
|
228
|
+
* generate anything (the Asset Forger credentials are server-side and never reach this machine),
|
|
229
|
+
* so it collects the same description, saves it onto the asset, and journals the request for the
|
|
230
|
+
* agent to run. A placeholder often carries no description at all, which is why the text is
|
|
231
|
+
* asked for rather than assumed.
|
|
232
|
+
*/
|
|
233
|
+
var hqOverlay = document.getElementById('hq-overlay');
|
|
234
|
+
var hqNote = document.getElementById('hq-note');
|
|
235
|
+
var hqPrompt = document.getElementById('hq-prompt');
|
|
236
|
+
var hqAsset = null;
|
|
237
|
+
|
|
238
|
+
function closeHqDialog() {
|
|
239
|
+
hqOverlay.classList.remove('show');
|
|
240
|
+
hqNote.classList.remove('show');
|
|
241
|
+
hqAsset = null;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async function openHqDialog(assetId, assetName) {
|
|
245
|
+
if (!assetId) return;
|
|
246
|
+
hqAsset = { id: assetId, name: assetName || assetId };
|
|
247
|
+
document.getElementById('hq-name').textContent = hqAsset.name;
|
|
248
|
+
hqNote.classList.remove('show');
|
|
249
|
+
// Prefill from the description ON DISK rather than anything cached here: the record the save
|
|
250
|
+
// patches is the stored one, and the agent may have written a description since boot.
|
|
251
|
+
var existing = '';
|
|
252
|
+
try {
|
|
253
|
+
var project = await (await fetch('/api/game-data')).json();
|
|
254
|
+
var asset = ((project.gameData && project.gameData.assets) || [])
|
|
255
|
+
.filter(function (a) { return a && a.id === assetId; })[0];
|
|
256
|
+
if (asset && typeof asset.description === 'string') existing = asset.description;
|
|
257
|
+
} catch (error) {
|
|
258
|
+
// No description to prefill is a worse dialog, not a broken one.
|
|
259
|
+
}
|
|
260
|
+
hqPrompt.value = existing;
|
|
261
|
+
hqOverlay.classList.add('show');
|
|
262
|
+
hqPrompt.focus();
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async function confirmHq() {
|
|
266
|
+
if (!hqAsset) return;
|
|
267
|
+
var prompt = hqPrompt.value.trim();
|
|
268
|
+
if (prompt === '') { hqPrompt.focus(); return; }
|
|
269
|
+
var response = await fetch('/api/editor/hq-request', {
|
|
270
|
+
method: 'POST',
|
|
271
|
+
headers: { 'Content-Type': 'application/json' },
|
|
272
|
+
body: JSON.stringify({ assetId: hqAsset.id, assetName: hqAsset.name, prompt: prompt })
|
|
273
|
+
});
|
|
274
|
+
var result = await response.json();
|
|
275
|
+
if (!response.ok || !result.ok) {
|
|
276
|
+
setStatus('error', result.error || 'Could not record the request');
|
|
277
|
+
closeHqDialog();
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
lastKnownMtime = result.worldMtimeMs || lastKnownMtime;
|
|
281
|
+
// The description is saved either way; only the handoff depends on whether this asset can
|
|
282
|
+
// actually be regenerated. Saying so beats printing a command that would refuse to run.
|
|
283
|
+
if (result.canGenerate === false) {
|
|
284
|
+
document.getElementById('hq-note-text').textContent =
|
|
285
|
+
'Description saved. ' + (result.reason || 'This asset cannot be regenerated.');
|
|
286
|
+
document.getElementById('hq-command').textContent = '';
|
|
287
|
+
hqNote.classList.add('show');
|
|
288
|
+
setStatus('saved', 'Description saved');
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
document.getElementById('hq-note-text').textContent =
|
|
292
|
+
'Saved and journalled. Generating the mesh needs credentials this editor does not hold, '
|
|
293
|
+
+ 'so your agent runs it:';
|
|
294
|
+
document.getElementById('hq-command').textContent = result.command;
|
|
295
|
+
hqNote.classList.add('show');
|
|
296
|
+
setStatus('saved', 'Handed to your agent');
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
document.getElementById('hq-cancel').addEventListener('click', closeHqDialog);
|
|
300
|
+
document.getElementById('hq-confirm').addEventListener('click', function () {
|
|
301
|
+
void confirmHq();
|
|
302
|
+
});
|
|
303
|
+
|
|
188
304
|
async function boot(gen) {
|
|
189
305
|
loaded = false;
|
|
190
306
|
markersDirty = false;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shell-page.js","sourceRoot":"","sources":["../../src/editor/shell-page.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,oGAAoG;AACpG,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAE7B,kGAAkG;AAClG,SAAS,UAAU,CAAC,KAAa;IAC/B,OAAO,KAAK;SACT,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAC3B,CAAC;AASD,MAAM,UAAU,iBAAiB,CAAC,OAA2B;IAC3D,MAAM,OAAO,GAAG,oBAAoB,OAAO,CAAC,QAAQ,2BAA2B,kBAAkB,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;IACpH,OAAO;;;;yBAIgB,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC
|
|
1
|
+
{"version":3,"file":"shell-page.js","sourceRoot":"","sources":["../../src/editor/shell-page.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,oGAAoG;AACpG,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAE7B,kGAAkG;AAClG,SAAS,UAAU,CAAC,KAAa;IAC/B,OAAO,KAAK;SACT,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAC3B,CAAC;AASD,MAAM,UAAU,iBAAiB,CAAC,OAA2B;IAC3D,MAAM,OAAO,GAAG,oBAAoB,OAAO,CAAC,QAAQ,2BAA2B,kBAAkB,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;IACpH,OAAO;;;;yBAIgB,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uBA6C5B,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mBAmC9B,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;kBACxB,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoUjC,CAAC;AACF,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Environment } from '../config/environments.js';
|
|
2
|
+
import type { ProjectContext } from '../project/context.js';
|
|
3
|
+
interface FitBox {
|
|
4
|
+
x: number;
|
|
5
|
+
z: number;
|
|
6
|
+
height: number;
|
|
7
|
+
}
|
|
8
|
+
export interface PropVoxelizeOptions {
|
|
9
|
+
context: ProjectContext;
|
|
10
|
+
environment: Environment;
|
|
11
|
+
token: string;
|
|
12
|
+
assetId: string;
|
|
13
|
+
prompt: string;
|
|
14
|
+
glbUrl: string;
|
|
15
|
+
log: (message: string) => void;
|
|
16
|
+
}
|
|
17
|
+
export interface PropVoxelizeResult {
|
|
18
|
+
assetId: string;
|
|
19
|
+
assetName: string;
|
|
20
|
+
assetUrl?: string;
|
|
21
|
+
/** How many placed instances now point at the upgraded asset. */
|
|
22
|
+
instanceCount: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The asset this command upgrades, with the `fitBox` the bake has to be constrained to.
|
|
26
|
+
*
|
|
27
|
+
* A missing `fitBox` is refused rather than defaulted. It records the size the object occupied
|
|
28
|
+
* when the level was baked, and every instance was placed against it — voxelizing to a different
|
|
29
|
+
* envelope produces an asset that is the wrong size everywhere it already sits, which nothing
|
|
30
|
+
* downstream would flag.
|
|
31
|
+
*/
|
|
32
|
+
export declare function findPropAsset(world: Record<string, unknown>, assetId: string): {
|
|
33
|
+
asset: Record<string, unknown>;
|
|
34
|
+
fitBox: FitBox;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Building-scale boxes voxelize hollow (a closed shell); prop-scale fills solid. Same thresholds
|
|
38
|
+
* as the HQ job — a solid-filled building is millions of wasted voxels, and a hollow prop reads as
|
|
39
|
+
* a shell the moment anything clips into it.
|
|
40
|
+
*/
|
|
41
|
+
export declare function shouldFillInterior(fitBox: FitBox): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Steps 2 and 3. Split from the command so the browser work is callable directly on a retry that
|
|
44
|
+
* already has a GLB, and so the command file stays about flags and output.
|
|
45
|
+
*/
|
|
46
|
+
export declare function voxelizeAndInstall(options: PropVoxelizeOptions): Promise<PropVoxelizeResult>;
|
|
47
|
+
export {};
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `bitmagic generate prop` — replace a placeholder asset with a generated, voxelized one.
|
|
3
|
+
*
|
|
4
|
+
* This is the other half of the editor's "Generate high-quality version" button. The editor
|
|
5
|
+
* records the request (`hq.requested` in `.bitmagic/edit/events.jsonl`) because it cannot do this
|
|
6
|
+
* itself; this is what the agent runs to fulfil it. It also stands alone: any placeholder asset
|
|
7
|
+
* can be upgraded from the command line without the editor ever being open.
|
|
8
|
+
*
|
|
9
|
+
* Three steps, mirroring `game-play-agent/src/mastra/utils/hq-asset-jobs.ts` — the proven chain:
|
|
10
|
+
*
|
|
11
|
+
* 1. Ask api-server for a mesh (`POST /api/cli/v1/assets/prop/stream`). Asset Forger credentials
|
|
12
|
+
* live there and never reach this machine, which is the whole reason the editor hands this to
|
|
13
|
+
* the agent rather than doing it inline.
|
|
14
|
+
* 2. Voxelize the GLB in a browser, constrained to the asset's recorded `fitBox`. Only the
|
|
15
|
+
* engine can do this, so the CLI drives its own headless Chrome — the same arrangement
|
|
16
|
+
* `bitmagic forge` uses for its archetypes.
|
|
17
|
+
* 3. Upsert the result into `assets[]` under the SAME id, which is what upgrades every already
|
|
18
|
+
* placed instance instead of adding a second asset beside them.
|
|
19
|
+
*
|
|
20
|
+
* The GLB survives a failure in steps 2-3: it is reported in the error so a retry can be pointed
|
|
21
|
+
* at it rather than paying for the mesh twice.
|
|
22
|
+
*/
|
|
23
|
+
import * as fs from 'fs';
|
|
24
|
+
import { spawn } from 'child_process';
|
|
25
|
+
import { CliError } from '../errors.js';
|
|
26
|
+
import { applyModificationsToWorld } from '../forge/apply-modifications.js';
|
|
27
|
+
import { ForgeBrowserHost, readProjectGameData } from '../forge/browser-host.js';
|
|
28
|
+
import { projectWorldJsonPath } from '../forge/run-pipeline.js';
|
|
29
|
+
import { startUploadProxy } from '../forge/upload-proxy.js';
|
|
30
|
+
import { reserveCorsAllowedPort } from '../local-port.js';
|
|
31
|
+
import { projectBin } from '../project/context.js';
|
|
32
|
+
import { waitForServer } from '../verify/wait-for-server.js';
|
|
33
|
+
/** Voxelizing a dense mesh is minutes of work in the browser, not seconds. */
|
|
34
|
+
const VOXELIZE_TIMEOUT_MS = 10 * 60 * 1000;
|
|
35
|
+
/**
|
|
36
|
+
* Bake resolution. Taken from the HQ job rather than re-derived: these two values are what the
|
|
37
|
+
* web lane has always produced HQ assets at, and a project should not get a different-looking
|
|
38
|
+
* upgrade depending on which lane asked for it.
|
|
39
|
+
*/
|
|
40
|
+
const MIN_VOXEL_SIZE = 0.1;
|
|
41
|
+
const MAX_VOXEL_SIZE = 0.5;
|
|
42
|
+
function isRecord(value) {
|
|
43
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
44
|
+
}
|
|
45
|
+
function readWorld(worldPath) {
|
|
46
|
+
let parsed;
|
|
47
|
+
try {
|
|
48
|
+
parsed = JSON.parse(fs.readFileSync(worldPath, 'utf-8'));
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
throw new CliError(`Cannot read ${worldPath}. Is this a Bitmagic project?`);
|
|
52
|
+
}
|
|
53
|
+
if (!isRecord(parsed))
|
|
54
|
+
throw new CliError(`${worldPath} does not contain a world object.`);
|
|
55
|
+
return parsed;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The asset this command upgrades, with the `fitBox` the bake has to be constrained to.
|
|
59
|
+
*
|
|
60
|
+
* A missing `fitBox` is refused rather than defaulted. It records the size the object occupied
|
|
61
|
+
* when the level was baked, and every instance was placed against it — voxelizing to a different
|
|
62
|
+
* envelope produces an asset that is the wrong size everywhere it already sits, which nothing
|
|
63
|
+
* downstream would flag.
|
|
64
|
+
*/
|
|
65
|
+
export function findPropAsset(world, assetId) {
|
|
66
|
+
const assets = Array.isArray(world.assets) ? world.assets : [];
|
|
67
|
+
const asset = assets.find((item) => isRecord(item) && item.id === assetId);
|
|
68
|
+
if (!isRecord(asset)) {
|
|
69
|
+
throw new CliError(`No asset with id "${assetId}" in this project's world.json. `
|
|
70
|
+
+ 'Check the id — the editor puts it in the command it journals.');
|
|
71
|
+
}
|
|
72
|
+
const fitBox = asset.fitBox;
|
|
73
|
+
if (!isRecord(fitBox)
|
|
74
|
+
|| typeof fitBox.x !== 'number' || typeof fitBox.z !== 'number' || typeof fitBox.height !== 'number') {
|
|
75
|
+
throw new CliError(`Asset "${assetId}" has no fitBox, so there is no size to voxelize it to. `
|
|
76
|
+
+ 'Only forge-baked assets can be regenerated this way.');
|
|
77
|
+
}
|
|
78
|
+
return { asset, fitBox: { x: fitBox.x, z: fitBox.z, height: fitBox.height } };
|
|
79
|
+
}
|
|
80
|
+
/** Placed instances pointing at this asset — reported so the caller sees the blast radius. */
|
|
81
|
+
function countInstances(world, assetId) {
|
|
82
|
+
const objects = Array.isArray(world.environmentObjects) ? world.environmentObjects : [];
|
|
83
|
+
return objects.filter((item) => isRecord(item) && item.assetId === assetId).length;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Building-scale boxes voxelize hollow (a closed shell); prop-scale fills solid. Same thresholds
|
|
87
|
+
* as the HQ job — a solid-filled building is millions of wasted voxels, and a hollow prop reads as
|
|
88
|
+
* a shell the moment anything clips into it.
|
|
89
|
+
*/
|
|
90
|
+
export function shouldFillInterior(fitBox) {
|
|
91
|
+
return !(fitBox.height > 6 || fitBox.x * fitBox.z > 40);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Steps 2 and 3. Split from the command so the browser work is callable directly on a retry that
|
|
95
|
+
* already has a GLB, and so the command file stays about flags and output.
|
|
96
|
+
*/
|
|
97
|
+
export async function voxelizeAndInstall(options) {
|
|
98
|
+
const { context, environment, token, assetId, prompt, glbUrl, log } = options;
|
|
99
|
+
const worldPath = projectWorldJsonPath(context.root);
|
|
100
|
+
const world = readWorld(worldPath);
|
|
101
|
+
const { asset, fitBox } = findPropAsset(world, assetId);
|
|
102
|
+
const assetName = typeof asset.name === 'string' ? asset.name : assetId;
|
|
103
|
+
const port = await reserveCorsAllowedPort();
|
|
104
|
+
if (port === null) {
|
|
105
|
+
throw new CliError('No free port between 3000 and 3199, which is the only range the asset CDN serves. '
|
|
106
|
+
+ 'Stop something in that range and retry — the generated mesh is kept, so the retry is quick.');
|
|
107
|
+
}
|
|
108
|
+
const vite = projectBin(context.root, 'vite');
|
|
109
|
+
let server;
|
|
110
|
+
let host;
|
|
111
|
+
let uploadProxy;
|
|
112
|
+
try {
|
|
113
|
+
server = spawn(vite, ['--port', String(port), '--strictPort'], {
|
|
114
|
+
cwd: context.root,
|
|
115
|
+
stdio: ['ignore', 'ignore', 'pipe'],
|
|
116
|
+
});
|
|
117
|
+
let viteStderr = '';
|
|
118
|
+
server.stderr?.on('data', (chunk) => { viteStderr += String(chunk); });
|
|
119
|
+
try {
|
|
120
|
+
await waitForServer(port, 30_000);
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
throw new CliError(`The project's dev server did not come up on port ${port}, so the mesh cannot be voxelized.`
|
|
124
|
+
+ (viteStderr.trim() ? `\n${viteStderr.trim()}` : '')
|
|
125
|
+
+ `\nThe generated mesh is kept at ${glbUrl} — fix that and retry.`);
|
|
126
|
+
}
|
|
127
|
+
// The engine uploads the baked .vxl itself, to a presigned URL it asks for at a hardcoded
|
|
128
|
+
// path with no credentials. The proxy translates that into the authenticated api-server route
|
|
129
|
+
// and keeps the token out of the page — see forge/upload-proxy.ts.
|
|
130
|
+
uploadProxy = await startUploadProxy({
|
|
131
|
+
apiUrl: environment.apiUrl,
|
|
132
|
+
token,
|
|
133
|
+
gameId: context.metadata.gameId,
|
|
134
|
+
log,
|
|
135
|
+
});
|
|
136
|
+
host = new ForgeBrowserHost({
|
|
137
|
+
gamePort: port,
|
|
138
|
+
gameId: context.metadata.gameId,
|
|
139
|
+
gameData: readProjectGameData(context.root, context.metadata),
|
|
140
|
+
agentUrl: uploadProxy.url,
|
|
141
|
+
log,
|
|
142
|
+
});
|
|
143
|
+
await host.launch();
|
|
144
|
+
await host.loadGame();
|
|
145
|
+
log(`Voxelizing "${assetName}" to its recorded fitBox…`);
|
|
146
|
+
const result = await host.transport.sendAndWait({
|
|
147
|
+
type: 'CREATE_ASSET_FROM_GLB_URL',
|
|
148
|
+
requestId: `prop-${assetId}-${port}`,
|
|
149
|
+
name: assetName,
|
|
150
|
+
glbUrl,
|
|
151
|
+
// The SAME id, which is what makes this an upgrade of every placed instance rather than
|
|
152
|
+
// a new asset sitting beside them.
|
|
153
|
+
assetId,
|
|
154
|
+
options: {
|
|
155
|
+
minVoxelSize: MIN_VOXEL_SIZE,
|
|
156
|
+
maxVoxelSize: MAX_VOXEL_SIZE,
|
|
157
|
+
fitBox,
|
|
158
|
+
fillInterior: shouldFillInterior(fitBox),
|
|
159
|
+
placeholder: false,
|
|
160
|
+
},
|
|
161
|
+
}, 'ASSET_FROM_GLB_URL_RESULT', VOXELIZE_TIMEOUT_MS, { maxRetries: 1 });
|
|
162
|
+
if (!result) {
|
|
163
|
+
throw new CliError(`Voxelizing "${assetName}" timed out. The generated mesh is kept at ${glbUrl} — retry with `
|
|
164
|
+
+ '`--glb-url` to skip the paid generation step.');
|
|
165
|
+
}
|
|
166
|
+
if (result.success === false || !isRecord(result.asset)) {
|
|
167
|
+
const reason = typeof result.error === 'string' ? result.error : 'unknown error';
|
|
168
|
+
throw new CliError(`Voxelizing "${assetName}" failed: ${reason}. The generated mesh is kept at ${glbUrl}.`);
|
|
169
|
+
}
|
|
170
|
+
const engineAsset = result.asset;
|
|
171
|
+
// The voxelize handler reads `description` from the GLB's scene extras, which the world-forger
|
|
172
|
+
// embeds but the Asset Forger does not — so a regenerated asset comes back with none. Carrying
|
|
173
|
+
// the prompt across is what keeps `placeholder: false` unambiguous: a described-and-false asset
|
|
174
|
+
// has been regenerated, an undescribed one never was.
|
|
175
|
+
const upgraded = {
|
|
176
|
+
...engineAsset,
|
|
177
|
+
description: typeof engineAsset.description === 'string' && engineAsset.description.trim() !== ''
|
|
178
|
+
? engineAsset.description
|
|
179
|
+
: prompt,
|
|
180
|
+
placeholder: false,
|
|
181
|
+
hqGeneratedAt: new Date().toISOString(),
|
|
182
|
+
};
|
|
183
|
+
const modification = {
|
|
184
|
+
type: 'upsertRoot',
|
|
185
|
+
path: ['assets'],
|
|
186
|
+
predicate: (item) => isRecord(item) && item.id === assetId,
|
|
187
|
+
value: upgraded,
|
|
188
|
+
};
|
|
189
|
+
applyModificationsToWorld(worldPath, [modification]);
|
|
190
|
+
return {
|
|
191
|
+
assetId,
|
|
192
|
+
assetName,
|
|
193
|
+
...(typeof upgraded.url === 'string' ? { assetUrl: upgraded.url } : {}),
|
|
194
|
+
instanceCount: countInstances(world, assetId),
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
finally {
|
|
198
|
+
// Ordered: the page first (it may still be mid-upload), then the proxy it was uploading
|
|
199
|
+
// through, then the server underneath both.
|
|
200
|
+
if (host)
|
|
201
|
+
await host.close();
|
|
202
|
+
if (uploadProxy)
|
|
203
|
+
await uploadProxy.close();
|
|
204
|
+
if (server)
|
|
205
|
+
server.kill('SIGTERM');
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
//# sourceMappingURL=prop.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prop.js","sourceRoot":"","sources":["../../src/generate/prop.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,6 +5,12 @@ export interface GenerationOutcome {
|
|
|
5
5
|
success: boolean;
|
|
6
6
|
message: string;
|
|
7
7
|
patches: WorldPatch[];
|
|
8
|
+
/**
|
|
9
|
+
* Only the `prop` type sets this. That generator produces a mesh rather than a finished asset —
|
|
10
|
+
* the caller voxelizes it in a browser before there is anything to write — so for `prop` this,
|
|
11
|
+
* not `patches`, is the result that matters. Absent for every other type.
|
|
12
|
+
*/
|
|
13
|
+
glbUrl?: string;
|
|
8
14
|
}
|
|
9
15
|
export interface StreamDeps {
|
|
10
16
|
fetch: typeof globalThis.fetch;
|