@bitmagic/cli 0.1.70 → 0.1.71-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.
- package/assets/roblox-port/README.md +19 -0
- package/assets/roblox-port/SKILL.md +154 -0
- package/assets/roblox-port/build-studio-update.mjs +41 -0
- package/assets/roblox-port/export-packed-mesh.mjs +166 -0
- package/assets/roblox-port/lib.mjs +193 -0
- package/assets/roblox-port/port-state.mjs +129 -0
- package/assets/roblox-port/references/assets.md +100 -0
- package/assets/roblox-port/references/source-and-runtime.md +68 -0
- package/assets/roblox-port/references/state-contracts.md +129 -0
- package/assets/roblox-port/references/studio-and-updates.md +119 -0
- package/assets/roblox-port/references/verification.md +63 -0
- package/assets/roblox-port/studio-runtime.luau +347 -0
- package/assets/roblox-port/tests/mesh.test.mjs +81 -0
- package/assets/roblox-port/tests/state.test.mjs +148 -0
- package/assets/roblox-port/tests/studio-mock.luau +109 -0
- package/assets/roblox-port/tests/studio-runtime.test.mjs +309 -0
- package/dist/commands/generate.js +12 -0
- package/dist/commands/generate.js.map +1 -1
- package/dist/generate/model.d.ts +2 -0
- package/dist/generate/model.js +1 -0
- package/dist/generate/model.js.map +1 -1
- package/dist/generate/prop.d.ts +7 -0
- package/dist/generate/prop.js +2 -0
- package/dist/generate/prop.js.map +1 -1
- package/dist/scaffold/project-files.js +1 -1
- package/dist/scaffold/project-files.js.map +1 -1
- package/dist/scaffold/project.js +2 -0
- package/dist/scaffold/project.js.map +1 -1
- package/dist/scaffold/roblox-skill-files.d.ts +2 -0
- package/dist/scaffold/roblox-skill-files.js +16 -0
- package/dist/scaffold/roblox-skill-files.js.map +1 -0
- package/dist/scaffold/skill-files.js +2 -1
- package/dist/scaffold/skill-files.js.map +1 -1
- package/dist/tools/bundles.js +6 -0
- package/dist/tools/bundles.js.map +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { randomUUID } from 'node:crypto';
|
|
5
|
+
import { parseArgs } from 'node:util';
|
|
6
|
+
import { pathToFileURL } from 'node:url';
|
|
7
|
+
import { VERSION, assert, hash, equal, readJson, writeJson, loadProject, readBase, materialize, assertTarget, reconcile, verifyReadback, validatePlan, withLock } from './lib.mjs';
|
|
8
|
+
|
|
9
|
+
export function init(sourceRoot, portRoot, environment) {
|
|
10
|
+
const source = fs.realpathSync(sourceRoot);
|
|
11
|
+
const parent = fs.realpathSync(path.dirname(path.resolve(portRoot)));
|
|
12
|
+
const root = path.join(parent, path.basename(portRoot));
|
|
13
|
+
assert(root !== source && !root.startsWith(`${source}${path.sep}`), 'Port workspace must be outside the source project');
|
|
14
|
+
assert(!fs.existsSync(root), 'Output already exists; do not overwrite an existing project');
|
|
15
|
+
const metadata = readJson(path.join(source, 'bitmagic.json'));
|
|
16
|
+
assert(typeof metadata.gameId === 'string' && typeof metadata.engineVersion === 'string', 'Missing Bitmagic source identity');
|
|
17
|
+
const env = environment ?? metadata.environment;
|
|
18
|
+
assert(typeof env === 'string' && env.length > 0, 'Explicit source environment required');
|
|
19
|
+
assert(!metadata.environment || env === metadata.environment, 'Source environment mismatch');
|
|
20
|
+
const project = { schemaVersion: VERSION, projectId: randomUUID(), targetId: randomUUID(), source: { root: source, gameId: metadata.gameId, engineVersion: metadata.engineVersion, environment: env }, target: { placeId: null, universeId: null }, runtimeProfile: 'private-single-player' };
|
|
21
|
+
fs.mkdirSync(root);
|
|
22
|
+
for (const dir of ['generated/shared', 'generated/client', 'generated/server', 'overrides', 'tests', 'state', '.bitmagic/roblox/runs']) fs.mkdirSync(path.join(root, dir), { recursive: true });
|
|
23
|
+
writeJson(path.join(root, 'project.json'), project, true);
|
|
24
|
+
writeJson(path.join(root, 'incoming.json'), { schemaVersion: VERSION, sourceFiles: ['bitmagic.json'], scripts: [] }, true);
|
|
25
|
+
writeJson(path.join(root, 'port-contract.json'), { schemaVersion: VERSION, features: [], deviations: [] }, true);
|
|
26
|
+
writeJson(path.join(root, 'assets.lock.json'), { schemaVersion: VERSION, assets: [] }, true);
|
|
27
|
+
fs.writeFileSync(path.join(root, '.gitignore'), '.bitmagic/\nstate/lock\n', { flag: 'wx' });
|
|
28
|
+
return { schemaVersion: VERSION, root, projectId: project.projectId, targetId: project.targetId };
|
|
29
|
+
}
|
|
30
|
+
export function prepare(root, live, run, { bind = false, adopt = false } = {}) {
|
|
31
|
+
return withLock(root, () => {
|
|
32
|
+
const project = loadProject(root);
|
|
33
|
+
assertTarget(project, live, bind);
|
|
34
|
+
assert(live.pendingRun === null, 'PARTIAL_APPLY: resume or roll back the existing run first');
|
|
35
|
+
const incoming = materialize(root, project), base = readBase(root);
|
|
36
|
+
assert(!base || live.buildId === base.planHash, 'BUILD_MISMATCH: recover the accepted baseline before planning');
|
|
37
|
+
const result = reconcile(project, incoming.scripts, base, live, adopt);
|
|
38
|
+
const body = { schemaVersion: VERSION, runId: randomUUID(), project, baseHash: hash(base), baselineBefore: base, sourceHash: incoming.sourceHash, sourceFiles: incoming.sourceFiles, packageHash: incoming.packageHash, targetBefore: live.target, buildBefore: live.buildId, snapshot: live, generated: incoming.scripts, ...result };
|
|
39
|
+
const plan = { ...body, planHash: hash(body) };
|
|
40
|
+
assert(!fs.existsSync(run), 'Run directory already exists; use its plan or choose a new run');
|
|
41
|
+
fs.mkdirSync(run, { recursive: true, mode: 0o700 });
|
|
42
|
+
writeJson(path.join(run, 'plan.json'), plan, true);
|
|
43
|
+
writeJson(path.join(run, 'before.json'), live, true);
|
|
44
|
+
writeJson(path.join(run, 'journal.json'), { schemaVersion: VERSION, stage: result.conflicts.length ? 'conflict' : 'prepared', planHash: plan.planHash }, true);
|
|
45
|
+
return plan;
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
export function checkFresh(root, plan) {
|
|
49
|
+
validatePlan(plan);
|
|
50
|
+
const project = loadProject(root), incoming = materialize(root, project);
|
|
51
|
+
assert(equal(project, plan.project), 'Project binding changed; prepare again');
|
|
52
|
+
assert(incoming.sourceHash === plan.sourceHash && incoming.packageHash === plan.packageHash, 'SOURCE_CHANGED: prepare a new plan');
|
|
53
|
+
const base = readBase(root);
|
|
54
|
+
assert(hash(base) === plan.baseHash || base?.planHash === plan.planHash, 'Baseline advanced since planning');
|
|
55
|
+
}
|
|
56
|
+
export function accept(root, run, live) {
|
|
57
|
+
return withLock(root, () => {
|
|
58
|
+
const plan = readJson(path.join(run, 'plan.json'));
|
|
59
|
+
checkFresh(root, plan); verifyReadback(plan, live);
|
|
60
|
+
writeJson(path.join(run, 'after.json'), live);
|
|
61
|
+
const base = { schemaVersion: VERSION, planHash: plan.planHash, sourceHash: plan.sourceHash, packageHash: plan.packageHash, generated: plan.generated, applied: plan.desired };
|
|
62
|
+
// The accepted GENERATED values remain the three-way base. Kept live overrides are recorded
|
|
63
|
+
// separately; making them the generated base would erase them on the next unchanged export.
|
|
64
|
+
writeJson(path.join(root, 'state/base.json'), base);
|
|
65
|
+
writeJson(path.join(run, 'journal.json'), { schemaVersion: VERSION, stage: 'applied', planHash: plan.planHash });
|
|
66
|
+
return { schemaVersion: VERSION, stage: 'applied', buildId: plan.planHash, verified: false, saveStatus: 'pending' };
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
export function recordSave(root, receipt) {
|
|
70
|
+
return withLock(root, () => {
|
|
71
|
+
const base = readBase(root); assert(base, 'No accepted Studio build');
|
|
72
|
+
assert(receipt.buildId === base.planHash, 'Save receipt is for a different build');
|
|
73
|
+
assert(['pending', 'saved', 'unsupported', 'failed', 'unconfirmed'].includes(receipt.status), 'Invalid save status');
|
|
74
|
+
if (receipt.status === 'saved') {
|
|
75
|
+
assert(typeof receipt.destination === 'string' && receipt.destination.length > 0, 'Saved destination required');
|
|
76
|
+
assert(typeof receipt.evidence === 'string' && receipt.evidence.length > 0, 'Save evidence required');
|
|
77
|
+
assert(receipt.published === false, 'Receipt must identify a non-publishing save');
|
|
78
|
+
}
|
|
79
|
+
writeJson(path.join(root, 'state/save.json'), { schemaVersion: VERSION, ...receipt });
|
|
80
|
+
return receipt;
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
export function acceptRollback(root, run, receipt) {
|
|
84
|
+
return withLock(root, () => {
|
|
85
|
+
const plan = validatePlan(readJson(path.join(run, 'plan.json')));
|
|
86
|
+
assert(equal(loadProject(root), plan.project), 'Project binding changed');
|
|
87
|
+
assert(receipt.stage === 'rolled-back' && receipt.planHash === plan.planHash && receipt.failures.length === 0, 'Rollback incomplete or for another plan');
|
|
88
|
+
const restored = receipt.snapshot;
|
|
89
|
+
// Sorting affects order only, not what was preserved. Compare exact editor source and metadata.
|
|
90
|
+
const sorted = snapshot => ({ ...snapshot, scripts: [...snapshot.scripts].sort((a, b) => JSON.stringify(a.path).localeCompare(JSON.stringify(b.path))) });
|
|
91
|
+
assert(equal(sorted(restored), sorted(plan.snapshot)), 'Rollback readback differs; reconcile before changing the local baseline');
|
|
92
|
+
const base = readBase(root);
|
|
93
|
+
assert(hash(base) === plan.baseHash || base?.planHash === plan.planHash, 'A later baseline must not be rolled back');
|
|
94
|
+
if (plan.baselineBefore === null) {
|
|
95
|
+
const file = path.join(root, 'state/base.json');
|
|
96
|
+
if (fs.existsSync(file)) fs.unlinkSync(file);
|
|
97
|
+
} else writeJson(path.join(root, 'state/base.json'), plan.baselineBefore);
|
|
98
|
+
writeJson(path.join(run, 'rollback.json'), receipt);
|
|
99
|
+
writeJson(path.join(run, 'journal.json'), { schemaVersion: VERSION, stage: 'rolled-back', planHash: plan.planHash });
|
|
100
|
+
return { schemaVersion: VERSION, stage: 'rolled-back', buildId: plan.buildBefore };
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
export function status(root) {
|
|
104
|
+
const project = loadProject(root), base = readBase(root);
|
|
105
|
+
const saveFile = path.join(root, 'state/save.json'), save = fs.existsSync(saveFile) ? readJson(saveFile) : null;
|
|
106
|
+
const current = base ? materialize(root, project) : null;
|
|
107
|
+
const stale = base ? current.sourceHash !== base.sourceHash || current.packageHash !== base.packageHash : false;
|
|
108
|
+
return { schemaVersion: VERSION, projectId: project.projectId, targetId: project.targetId, stage: base ? 'applied' : 'prepared', buildId: base?.planHash ?? null, localChanges: stale, saveStatus: save && save.buildId === base?.planHash && !stale ? save.status : 'pending', studioReadbackRequired: true };
|
|
109
|
+
}
|
|
110
|
+
function main() {
|
|
111
|
+
const { values: a, positionals } = parseArgs({ allowPositionals: true, options: { source: { type: 'string' }, project: { type: 'string' }, environment: { type: 'string' }, live: { type: 'string' }, run: { type: 'string' }, receipt: { type: 'string' }, 'bind-target': { type: 'boolean' }, adopt: { type: 'boolean' }, help: { type: 'boolean' } } });
|
|
112
|
+
if (a.help) { console.log('port-state.mjs init --source <source> --project <new-port> [--environment dev]\nprepare --project <port> --live <snapshot.json> --run <new-run-dir> [--bind-target] [--adopt]\naccept --project <port> --run <run-dir> --live <readback.json>\naccept-rollback --project <port> --run <run-dir> --receipt <rollback.json>\nstatus --project <port>\nrecord-save --project <port> --receipt <save.json>'); return; }
|
|
113
|
+
assert(a.project, '--project required');
|
|
114
|
+
let result;
|
|
115
|
+
switch (positionals[0]) {
|
|
116
|
+
case 'init': assert(a.source, '--source required'); result = init(a.source, a.project, a.environment); break;
|
|
117
|
+
case 'prepare': assert(a.live && a.run, '--live and --run required'); result = prepare(a.project, readJson(a.live), a.run, { bind: a['bind-target'], adopt: a.adopt }); break;
|
|
118
|
+
case 'accept': assert(a.live && a.run, '--live and --run required'); result = accept(a.project, a.run, readJson(a.live)); break;
|
|
119
|
+
case 'accept-rollback': assert(a.receipt && a.run, '--receipt and --run required'); result = acceptRollback(a.project, a.run, readJson(a.receipt)); break;
|
|
120
|
+
case 'status': result = status(a.project); break;
|
|
121
|
+
case 'record-save': assert(a.receipt, '--receipt required'); result = recordSave(a.project, readJson(a.receipt)); break;
|
|
122
|
+
default: throw Error('Unknown command; use --help');
|
|
123
|
+
}
|
|
124
|
+
console.log(JSON.stringify(result));
|
|
125
|
+
if (result.conflicts?.length) process.exitCode = 2;
|
|
126
|
+
}
|
|
127
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(fs.realpathSync(process.argv[1])).href) {
|
|
128
|
+
try { main(); } catch (error) { console.error(error.message); process.exitCode = 1; }
|
|
129
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Original assets and packed geometry
|
|
2
|
+
|
|
3
|
+
For each reachable asset try: permission-valid cached original → supported direct import →
|
|
4
|
+
deterministic conversion → faithful native approximation → licensed/generated replacement.
|
|
5
|
+
Record a valid attempted route or concrete preflight blocker, not blind repeated retries. Missing
|
|
6
|
+
gameplay-critical content blocks completion unless the creator explicitly changes the contract.
|
|
7
|
+
|
|
8
|
+
Use the source bytes, not screenshots, whenever possible. Packed mesh JSON, articulated components,
|
|
9
|
+
procedural buildings and audio-bank URLs count as original content. Retain source bytes, decoder
|
|
10
|
+
version, conversion recipe and hash, target owner/type/ID/version, upload operation, and evidence.
|
|
11
|
+
Reuse by bytes + recipe + importer version + owner/permission context, not filename.
|
|
12
|
+
|
|
13
|
+
Studio's importer and Open Cloud asset endpoints have distinct format/settings capabilities.
|
|
14
|
+
Discover the current route. GLB/glTF Model creation is documented; a raw Mesh upload is not a
|
|
15
|
+
general geometry uploader, and content updates are more restricted than asset creation. Tool
|
|
16
|
+
`insert_asset` inserts an existing asset; `store_image` produces a tool reference, not a permanent
|
|
17
|
+
game image. Neither transfers all local models/audio automatically.
|
|
18
|
+
|
|
19
|
+
## Offline packed-mesh helper
|
|
20
|
+
|
|
21
|
+
`export-packed-mesh.mjs` supports these explicit, little-endian layouts only:
|
|
22
|
+
|
|
23
|
+
| Layout | Buffers | Other semantics |
|
|
24
|
+
| --- | --- | --- |
|
|
25
|
+
| `bitmagic-packed-v1`, kit `version: 1` | Unindexed float32 XYZ positions/normals/RGB colors | Optional pivot, height and muzzleHeight. |
|
|
26
|
+
| `bitmagic-packed-v2`, kit `version: 2` | int16 positions × quantum; normalized int8 normals; uint8 colors; uint16 indices | Ordered `[start,count,materialIndex]` groups and articulated pivots. |
|
|
27
|
+
|
|
28
|
+
Unknown fields/layouts fail. Read the actual source decoder before selecting one. The helper does
|
|
29
|
+
not extract arbitrary procedural code, skin/retarget skeletal animation, bake textures, import
|
|
30
|
+
into Studio, upload assets, or prove visual parity. Those remain explicit conversion tasks.
|
|
31
|
+
|
|
32
|
+
Save a recipe in the port workspace. This calibration example preserves source axes and scale;
|
|
33
|
+
choose measured values for the actual game:
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"schemaVersion": 1,
|
|
38
|
+
"format": "bitmagic-packed-v1",
|
|
39
|
+
"meshIds": ["townhall"],
|
|
40
|
+
"basis": [1,0,0, 0,1,0, 0,0,1],
|
|
41
|
+
"scale": 1,
|
|
42
|
+
"materials": [{
|
|
43
|
+
"name": "source-painted",
|
|
44
|
+
"doubleSided": false,
|
|
45
|
+
"pbrMetallicRoughness": {
|
|
46
|
+
"baseColorFactor": [1,1,1,1],
|
|
47
|
+
"metallicFactor": 0,
|
|
48
|
+
"roughnessFactor": 0.8
|
|
49
|
+
}
|
|
50
|
+
}]
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
```text
|
|
55
|
+
node "<bundle>/export-packed-mesh.mjs" --input <original-kit.json> --recipe <recipe.json> --out <new-model.glb>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
It emits GLB plus `<new-model.glb>.json` with hashes, mesh IDs, triangles, vertices and original
|
|
59
|
+
pivot metadata. It preserves pivot translations, vertex colors and material groups, transforms
|
|
60
|
+
normals, and reverses winding for a reflected basis. Select only reachable mesh IDs; articulated
|
|
61
|
+
components must share the intended hierarchy after native import. Preserve muzzle/footprint
|
|
62
|
+
metadata in native code even if the importer ignores GLB extras.
|
|
63
|
+
|
|
64
|
+
Verify silhouette, dimensions, pivot articulation, colors/PBR, grounding, sockets, collision roles
|
|
65
|
+
and animation timing after import. Vertex colors surviving a GLB export do not prove the chosen
|
|
66
|
+
Roblox import preserves their appearance. If necessary bake an atlas and record that conversion.
|
|
67
|
+
Gray replacement materials are not an acceptable silent success. Use inexpensive dedicated
|
|
68
|
+
collision geometry, and disable collision/touch/query for decorative content where appropriate.
|
|
69
|
+
|
|
70
|
+
Zero-area triangles are rejected by default. If inspection confirms that these are degenerate
|
|
71
|
+
source faces, set `"degenerateTriangles": "drop"` in the recipe. The receipt records original,
|
|
72
|
+
retained and dropped triangle counts; the converter removes unused vertices too. It never
|
|
73
|
+
invents normals for retained surfaces. Include the cleanup in the accepted conversion evidence.
|
|
74
|
+
|
|
75
|
+
## Uploads, permissions and native reconstruction
|
|
76
|
+
|
|
77
|
+
Use only the selected creator/group owner and declared dependencies. Asset upload, moderation,
|
|
78
|
+
experience permissions, and working playback/rendering are separate states. Private ownership
|
|
79
|
+
does not prove a target experience can play audio. If asset permissions need an existing place,
|
|
80
|
+
ask the creator to establish that in Studio; do not create/publish an experience yourself.
|
|
81
|
+
|
|
82
|
+
Track operations before polling; after ambiguous timeouts query their actual outcome before
|
|
83
|
+
creating duplicates. Bound retries/concurrency and follow throttling. Preserve previous asset
|
|
84
|
+
mappings when changed content requires a new ID. Avoid auto-updating live package dependencies.
|
|
85
|
+
Never upload secrets, unrelated local files, recovery archives, or signed URL credentials.
|
|
86
|
+
|
|
87
|
+
Keep original voices, cue timing, subtitles, cooldowns, overlap caps and ducking. Generic growls
|
|
88
|
+
are a replacement, not proof the original voice pack transferred. Source cosmetics/content changes
|
|
89
|
+
are intentional overrides only when requested; do not infer them from platform stereotypes.
|
|
90
|
+
|
|
91
|
+
Store native construction recipes and per-object identities/property snapshots in the port
|
|
92
|
+
workspace. Recipes must support scoped updates; do not run a full rebuild that erases unrelated
|
|
93
|
+
models. Inspect imported marketplace models for embedded scripts before executing them.
|
|
94
|
+
|
|
95
|
+
When an image-upload tool needs temporary HTTP hosting, expose only the intended export directory,
|
|
96
|
+
bind narrowly, record the owned PID/port, and stop it and its children after the transfer.
|
|
97
|
+
|
|
98
|
+
- [Studio importer](https://create.roblox.com/docs/studio/importer).
|
|
99
|
+
- [Assets API guide](https://create.roblox.com/docs/cloud/guides/usage-assets).
|
|
100
|
+
- [Audio assets](https://create.roblox.com/docs/audio/assets).
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Source contracts and native runtime
|
|
2
|
+
|
|
3
|
+
Inventory reachable code and data from `Game.ts`, `game.json`, `world.json`, imported JSON,
|
|
4
|
+
asset registries, audio banks, procedural geometry and engine calls. Classify unused experiments
|
|
5
|
+
separately; a cinematic mesh is not automatically the ordinary crowd model. Do not execute
|
|
6
|
+
arbitrary source to discover assets. If runtime extraction is necessary, use an explicitly scoped
|
|
7
|
+
capture with controlled networking/side effects and retain the extraction recipe.
|
|
8
|
+
|
|
9
|
+
Each feature contract records:
|
|
10
|
+
|
|
11
|
+
- Stable ID; source files and enclosing symbols; source input hash.
|
|
12
|
+
- Enter/exit state transitions, input gates, pause/cleanup, camera and audio owners.
|
|
13
|
+
- Numeric rules: costs, unlocks, cooldowns, targeting, damage shapes, waves, rewards, loss conditions.
|
|
14
|
+
- Presentation: model variants, animation events, UI behavior, voice/subtitle cues, VFX.
|
|
15
|
+
- Native strategy and dependencies; fidelity category and intentional deviations.
|
|
16
|
+
- A test with explicit inputs and expected outputs/events; independent verification evidence.
|
|
17
|
+
|
|
18
|
+
Preserve all chapters and tutorial stages, not just an initial playable slice. A result flow that
|
|
19
|
+
returns to the map, selects the next chapter, and waits for the player differs from automatically
|
|
20
|
+
starting the next chapter. Session carry-over differs from account progress after rejoining.
|
|
21
|
+
|
|
22
|
+
## Translation
|
|
23
|
+
|
|
24
|
+
Extract tables mechanically. Native Luau is a semantic rewrite for browser/Bitmagic APIs, not
|
|
25
|
+
syntax substitution. Explicitly account for zero/one-based indexes, JavaScript truthiness,
|
|
26
|
+
`undefined`/`null`, Maps/Sets, class initialization, promises, timers and event lifetimes.
|
|
27
|
+
`roblox-ts` cannot provide Three.js, WebGL, DOM/CSS, browser audio or Bitmagic runtime bindings.
|
|
28
|
+
Unknown engine calls become conversion work, not empty stubs or swallowed exceptions.
|
|
29
|
+
|
|
30
|
+
Use modules with distinct rules, presentation and platform integrations. Stable local events
|
|
31
|
+
such as phase changes, volleys, splash/piercing hits, resource changes and tutorials can connect
|
|
32
|
+
them without becoming network remotes. Preserve event order and single consumption. Keep deliberate
|
|
33
|
+
Roblox extensions in `overrides/` or explicit hooks, with source mappings to the original contract.
|
|
34
|
+
|
|
35
|
+
## Authority
|
|
36
|
+
|
|
37
|
+
For a private single-player simulation, local input, placement preview, camera, rules and visuals
|
|
38
|
+
can remain local. Shared world state and consequential multiplayer outcomes need server authority.
|
|
39
|
+
Purchases, ownership and trusted currencies/rewards always need their appropriate server checks.
|
|
40
|
+
Do not grant trusted rewards from client-reported victories. Choose and document a persistence
|
|
41
|
+
trust policy; serializing a client payload does not establish integrity.
|
|
42
|
+
|
|
43
|
+
Existing target commerce stays intact. Do not add commerce, ads, ownership claims or receipts just
|
|
44
|
+
because a source game is being ported. Preserve coordinated receipt handling and deduplication.
|
|
45
|
+
|
|
46
|
+
## Coordinates, UI and performance
|
|
47
|
+
|
|
48
|
+
Use an asymmetric calibration object, floor, forward arrow and known footprint to establish a
|
|
49
|
+
source-to-target basis and scale. Transform normals, winding, negative scales, pivots, animation,
|
|
50
|
+
projectile sockets, ranges, movement and camera distances consistently. Test gameplay facing
|
|
51
|
+
separately from camera facing. One source unit is not automatically one stud.
|
|
52
|
+
|
|
53
|
+
Map UI intent into native GUI controls, including portraits/icons, safe areas, scrolling,
|
|
54
|
+
controller focus where needed, readable text and touch confirmation. Check the world space left
|
|
55
|
+
visible for placement. Combat should hide construction UI when the contract requires it.
|
|
56
|
+
Use one camera owner per phase and hand over from the rendered pose without snapping/resetting.
|
|
57
|
+
|
|
58
|
+
Pool transient visuals/audio, bound overlap and allocations, and separate simulation scheduling
|
|
59
|
+
from rendering. Avoid per-enemy scripts, physics bodies and pathfinding for crowds unless required.
|
|
60
|
+
Keep render-step work narrow. Measure before reducing visible density; sampling a small stable
|
|
61
|
+
subset is a disclosed fidelity compromise, not a proven platform limit. Record dropped simulation
|
|
62
|
+
time separately from rendering FPS. Never ship the case study's numeric budgets as universal rules.
|
|
63
|
+
|
|
64
|
+
Official references, consulted as needed:
|
|
65
|
+
|
|
66
|
+
- [Client/server runtime](https://create.roblox.com/docs/projects/client-server).
|
|
67
|
+
- [Task scheduler](https://create.roblox.com/docs/performance-optimization/microprofiler/task-scheduler).
|
|
68
|
+
- [Bitmagic coordinate conventions](../../../../engine/agent-docs/coordinate-system.md) when available in the source project; otherwise read its installed engine documentation directly.
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# Port workspace and helper contracts
|
|
2
|
+
|
|
3
|
+
The helper bundle is found with `bitmagic tools path roblox-port --json`. Use its returned `path`
|
|
4
|
+
as `<bundle>` below. The normal route executes it in place; `tools install roblox-port` is optional
|
|
5
|
+
and copies helpers into `tools/roblox-port`, never into generated game modules. All helper commands
|
|
6
|
+
are local-only. Inputs/outputs are explicit files; helpers never choose a Studio session.
|
|
7
|
+
|
|
8
|
+
## Initialize
|
|
9
|
+
|
|
10
|
+
```text
|
|
11
|
+
node "<bundle>/port-state.mjs" init --source <absolute-bitmagic-project> --project <new-sibling-port>
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The source must have a game ID and engine version in `bitmagic.json`. If it lacks an environment,
|
|
15
|
+
provide `--environment` with the confirmed source environment. The output must not exist or nest
|
|
16
|
+
inside the source. Initialization creates stable project/target UUIDs, empty inventories and
|
|
17
|
+
generated/override/test/state directories. Fill the inventories before using them; initialization
|
|
18
|
+
is not a generated or verified game. Existing ports need explicit mapping/adoption, not `init`
|
|
19
|
+
over their files. An empty sibling workspace can hold an adopted port's copied local sources.
|
|
20
|
+
|
|
21
|
+
Commit `project.json`, `incoming.json`, `port-contract.json`, `assets.lock.json`, generated source,
|
|
22
|
+
overrides, tests and `state/base.json` according to the project's VCS policy. Keep journals and
|
|
23
|
+
recovery snapshots under `.bitmagic/roblox/runs/`; they are ignored for sharing, but retain them
|
|
24
|
+
until recovery is no longer needed. Only conversion cache is disposable. Never store credentials
|
|
25
|
+
in these files, asset recipes, Lua source, tool outputs, or archives.
|
|
26
|
+
|
|
27
|
+
`project.json` contains schema version 1, immutable managed project/target IDs, source identity,
|
|
28
|
+
runtime profile, and nullable target `placeId`/`universeId` strings. Fill known target IDs before
|
|
29
|
+
applying. A null binding may be used for an explicitly selected unsaved template. Do not store a
|
|
30
|
+
transient Studio session ID as the durable identity. Reconcile later owner-created cloud IDs while
|
|
31
|
+
retaining the managed UUIDs.
|
|
32
|
+
|
|
33
|
+
## Generated sources
|
|
34
|
+
|
|
35
|
+
Prepare `incoming.json`:
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"schemaVersion": 1,
|
|
40
|
+
"sourceFiles": ["bitmagic.json", "src/work/Game.ts", "src/work/game.json", "src/work/world.json"],
|
|
41
|
+
"scripts": [
|
|
42
|
+
{
|
|
43
|
+
"id": "rules",
|
|
44
|
+
"path": ["ReplicatedStorage", "BitmagicGame", "Rules"],
|
|
45
|
+
"className": "ModuleScript",
|
|
46
|
+
"file": "generated/shared/Rules.luau"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"id": "client-entry",
|
|
50
|
+
"path": ["StarterPlayer", "StarterPlayerScripts", "BitmagicGame", "Main"],
|
|
51
|
+
"className": "LocalScript",
|
|
52
|
+
"file": "generated/client/Main.client.luau",
|
|
53
|
+
"enabled": true
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Declare **every source file used by conversion**, including imported rules, packed art and audio
|
|
60
|
+
registries, plus relevant engine dependencies. `sourceFiles` is an explicit inventory, not an
|
|
61
|
+
automatic dependency scanner; undeclared changes cannot be detected by the helper. Paths are
|
|
62
|
+
relative to the source root, generated files relative to the port root; escaping symlinks and
|
|
63
|
+
traversal are rejected. Allowed root services are ReplicatedStorage, ServerScriptService,
|
|
64
|
+
ServerStorage, StarterPlayer, StarterGui and Workspace. Use simple generated path segments;
|
|
65
|
+
live creator names, including spaces and Unicode, are preserved by stable ID. Existing
|
|
66
|
+
Folder/Model/StarterPlayerScripts/StarterCharacterScripts containers can hold scripts; missing
|
|
67
|
+
intermediate containers become Folders. Explicitly create special native containers beforehand.
|
|
68
|
+
|
|
69
|
+
Every Script/LocalScript needs an explicit `enabled`; ModuleScripts omit it. Stable IDs do not
|
|
70
|
+
change when names/paths change. The source helper supports up to 256 scripts, each at most 1 MiB.
|
|
71
|
+
It does not remove omitted scripts, change classes, or reconcile native models/properties.
|
|
72
|
+
Retirement and class migration require a separate backed-up, explicit operation and baseline
|
|
73
|
+
migration. Resolve an unsupported operation instead of hand-editing a prepared plan.
|
|
74
|
+
|
|
75
|
+
## Feature and asset ledgers
|
|
76
|
+
|
|
77
|
+
`port-contract.json` has schema version 1 with `features` and `deviations` arrays. Each feature
|
|
78
|
+
needs stable `id`, source files/symbols, `required`, target strategy, fidelity, dependencies,
|
|
79
|
+
test fixture and verification evidence. Accepted differences record the creator's decision and
|
|
80
|
+
affected feature IDs. This is the agent's semantic contract; the source helper does not certify it.
|
|
81
|
+
|
|
82
|
+
`assets.lock.json` has schema version 1 and `assets`. For each asset record `id`, source path/hash,
|
|
83
|
+
provenance, recipe/version/hash, target owner/type/ID/version, operation ID, moderation, permissions,
|
|
84
|
+
and validation evidence. Store service IDs as strings. Preserve earlier mappings for recovery.
|
|
85
|
+
|
|
86
|
+
Track native objects/settings outside the script helper in a reviewable scene inventory and
|
|
87
|
+
recipes with expected-value updates and durable before-snapshots. A full game includes this state.
|
|
88
|
+
|
|
89
|
+
## Plan, apply, accept
|
|
90
|
+
|
|
91
|
+
See [studio-and-updates.md](studio-and-updates.md) for the exact commands. A run directory contains
|
|
92
|
+
immutable `plan.json` and `before.json` written before Studio mutation, followed by journal and
|
|
93
|
+
readback. The plan binds source/package hashes, target identity, expected live values and baseline.
|
|
94
|
+
Do not edit its hash or values to force a mismatch through. Prepare again after resolving the cause.
|
|
95
|
+
|
|
96
|
+
`accept` means exact managed source readback passed, **not** gameplay, visuals or performance passed.
|
|
97
|
+
It stores generated values and applied values separately in `state/base.json`; keep both. An
|
|
98
|
+
interrupted forward apply can be replayed from the same plan after inspecting target state, or
|
|
99
|
+
conditionally rolled back. New plans are refused while Studio reports an incomplete run.
|
|
100
|
+
|
|
101
|
+
Local helper operations use `state/lock`. A stale lock after a killed local process needs inspection:
|
|
102
|
+
verify the owning PID no longer exists and no other task uses the port before removing it. Never
|
|
103
|
+
remove a lock merely because a command is slow. The lock does not prevent human/Team Create edits;
|
|
104
|
+
Studio expected-value checks still apply.
|
|
105
|
+
|
|
106
|
+
## Save evidence and completion
|
|
107
|
+
|
|
108
|
+
Record actual tool/manual evidence in a save receipt, never infer it from local source output:
|
|
109
|
+
|
|
110
|
+
```json
|
|
111
|
+
{
|
|
112
|
+
"buildId": "the-accepted-plan-hash",
|
|
113
|
+
"status": "saved",
|
|
114
|
+
"destination": "confirmed place file or non-published draft destination",
|
|
115
|
+
"evidence": "actual save operation receipt or verified file evidence",
|
|
116
|
+
"published": false
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
```text
|
|
121
|
+
node "<bundle>/port-state.mjs" record-save --project <port> --receipt <save.json>
|
|
122
|
+
node "<bundle>/port-state.mjs" status --project <port>
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Statuses are `pending`, `saved`, `unsupported`, `failed`, and `unconfirmed`. The helper validates
|
|
126
|
+
receipt shape/build binding; the agent must obtain genuine evidence. Local `status` reports source
|
|
127
|
+
freshness and recorded save state; it cannot know whether Studio changed since the last readback.
|
|
128
|
+
Reinspect before claiming the current Studio project is unchanged or saved. A new local package
|
|
129
|
+
or accepted build invalidates applicability of older save evidence.
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Studio connection, source updates, recovery and saving
|
|
2
|
+
|
|
3
|
+
Discover the connected tools rather than pinning an observed ABI. Typical operations are
|
|
4
|
+
`list_roblox_studios`, `get_studio_state`, hierarchy/source inspection, `multi_edit`, `execute_luau`,
|
|
5
|
+
Play controls, console/input, and captures. Every call targets the selected `studio_id`.
|
|
6
|
+
Studio MCP and Open Cloud authorization are independent. No helper starts its own MCP client.
|
|
7
|
+
|
|
8
|
+
Use the connected server's schemas and editor-aware reads. Do not assume Script.Source reflects
|
|
9
|
+
an unsaved open buffer. The generated updater requires `ScriptEditorService:GetEditorSource` and
|
|
10
|
+
`UpdateSourceAsync`; unavailable permissions stop it, not trigger a stale-Source fallback.
|
|
11
|
+
Do not paste an entire game into many improvised edits when a reviewed local package is available.
|
|
12
|
+
|
|
13
|
+
## Inspect and plan
|
|
14
|
+
|
|
15
|
+
After preparing the local source package:
|
|
16
|
+
|
|
17
|
+
```text
|
|
18
|
+
node "<bundle>/build-studio-update.mjs" inspect --project <port> --out <new-inspect.luau>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Read that generated code, then execute it through the connected MCP executor in the explicitly
|
|
22
|
+
selected **Edit** context. Save the returned JSON string's contents as `<live.json>`; unwrap the
|
|
23
|
+
tool envelope without editing values. Inspection reads the candidate paths plus managed scripts,
|
|
24
|
+
identity, build and pending-run markers. Duplicate names/IDs and occupied non-script paths fail.
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
node "<bundle>/port-state.mjs" prepare --project <port> --live <live.json> --run <new-run-dir>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
For the first explicitly selected unbound target add `--bind-target`. This installs the port's
|
|
31
|
+
managed UUIDs on apply, not a cloud identity. It never clears the template. For existing unowned
|
|
32
|
+
scripts, `--adopt` is allowed only after an explicit ownership decision and exact source/class/path/
|
|
33
|
+
enabled matches. First copy their actual editor content into the local package; do not use adoption
|
|
34
|
+
to overwrite a different game. Keep source contract/deviations separate from this ownership baseline.
|
|
35
|
+
|
|
36
|
+
Review `plan.json`: target/owner, source/package hashes, conflicts, desired values, changed scripts,
|
|
37
|
+
and preserved customizations. No uploads occur. A conflict result exits 2 and produces no applicable
|
|
38
|
+
update. Existing baseline omissions, missing managed objects, foreign path occupants, and divergent
|
|
39
|
+
edits are actionable conflicts. Locate a renamed object by stable ID rather than recreating it.
|
|
40
|
+
|
|
41
|
+
The plan stores complete affected script text locally. Protect it appropriately. Before altering
|
|
42
|
+
native models, terrain, assets or global settings, prepare separate complete backups and scoped
|
|
43
|
+
expected-value operations; the source helper deliberately does not pretend to cover that content.
|
|
44
|
+
|
|
45
|
+
## Apply and read back
|
|
46
|
+
|
|
47
|
+
```text
|
|
48
|
+
node "<bundle>/build-studio-update.mjs" apply --project <port> --run <run-dir> --out <new-apply.luau>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Review and execute that generated Luau in the same selected Edit target. It rechecks target,
|
|
52
|
+
source and hierarchy expectations, stages Script/LocalScript activation, checks editor buffers
|
|
53
|
+
inside update callbacks, and commits the build marker only after all managed sources match.
|
|
54
|
+
Save the returned snapshot as `<after.json>`:
|
|
55
|
+
|
|
56
|
+
```text
|
|
57
|
+
node "<bundle>/port-state.mjs" accept --project <port> --run <run-dir> --live <after.json>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
This advances only the source baseline. It does not run gameplay tests or save the place. Native
|
|
61
|
+
assets/settings must already satisfy the separate scene/asset plan before calling the whole port
|
|
62
|
+
ready. Whole-place atomicity is not provided by Studio MCP or a per-script update callback.
|
|
63
|
+
|
|
64
|
+
## Interrupted operations
|
|
65
|
+
|
|
66
|
+
Keep the same run after a timeout. Generate a fresh inspection with `inspect --project <port>
|
|
67
|
+
--run <run-dir> --out <new-inspect.luau>` so recovery reads the durable plan even when local
|
|
68
|
+
conversion inputs are missing or invalid. Inspect its pending marker and
|
|
69
|
+
actual source state, and either replay the existing apply or compensate. A replay accepts only
|
|
70
|
+
expected pre-state or this run's identifiable intermediate writes. An unrelated edit stops it.
|
|
71
|
+
Do not prepare a new baseline from a half-applied game.
|
|
72
|
+
|
|
73
|
+
Rollback records `rollback:<run-id>` in `BitmagicPendingRun` before restoring anything. While that
|
|
74
|
+
marker remains, new plans and forward replay are blocked; continue the same rollback after resolving
|
|
75
|
+
its conflicts. The marker clears only after successful recovery. Per-script recovery markers let
|
|
76
|
+
that rollback resume a source write interrupted before its ownership/path bookkeeping finished.
|
|
77
|
+
|
|
78
|
+
```text
|
|
79
|
+
node "<bundle>/build-studio-update.mjs" rollback --project <port> --run <run-dir> --out <new-rollback.luau>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Review and execute the generated recovery only for this run. It restores source/ownership/path/
|
|
83
|
+
enabled state conditionally; later human edits are preserved and reported. New scripts are removed
|
|
84
|
+
only if still attributable to this run, with expected content and no new children. Additional
|
|
85
|
+
attributes, tags, or changes to Archivable, RunContext or LinkedSource preserve the script and
|
|
86
|
+
report a conflict. Empty Folders created by the run also require their recorded original path,
|
|
87
|
+
unchanged attributes/tags and default Archivable state before removal. Missing folder creation
|
|
88
|
+
metadata or unreadable properties require manual recovery. It never deletes imported cloud assets.
|
|
89
|
+
Keep the returned recovery snapshot and resolve any `rollback-incomplete` entries before continuing.
|
|
90
|
+
|
|
91
|
+
After a successful rollback, restore the matching local accepted baseline from the run's durable
|
|
92
|
+
backup using the returned receipt:
|
|
93
|
+
|
|
94
|
+
```text
|
|
95
|
+
node "<bundle>/port-state.mjs" accept-rollback --project <port> --run <run-dir> --receipt <rollback.json>
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Never leave the local baseline naming the
|
|
99
|
+
reverted build. Cloud versions, purchases and account-data migrations are outside this recovery.
|
|
100
|
+
|
|
101
|
+
## Saving
|
|
102
|
+
|
|
103
|
+
After the complete verified game is in Edit state, discover whether a supported MCP operation can
|
|
104
|
+
save the complete place. Confirm its semantics: local file or non-published draft, selected
|
|
105
|
+
destination, no experience publication. Use it when available within the authorized port workflow.
|
|
106
|
+
Reuse an established save destination; ask when one is missing or it would overwrite unrelated work.
|
|
107
|
+
Confirm the result against the installed build and record actual evidence with `record-save`.
|
|
108
|
+
|
|
109
|
+
Do not infer save privilege from `execute_luau`, invent a save tool, request publishing credentials,
|
|
110
|
+
or call undocumented privileged APIs. If no supported save exists, finish with “ready in Studio;
|
|
111
|
+
save required.” On failure/ambiguous response preserve the project, inspect before retrying, and
|
|
112
|
+
report failed/unconfirmed. Source-only Rojo output and script backups are not complete-place saves.
|
|
113
|
+
|
|
114
|
+
Leave publishing, visibility and release management to the creator in Studio in every case.
|
|
115
|
+
|
|
116
|
+
- [Studio MCP](https://create.roblox.com/docs/studio/mcp).
|
|
117
|
+
- [ScriptEditorService](https://create.roblox.com/docs/reference/engine/classes/ScriptEditorService).
|
|
118
|
+
- [Instance attributes, tags and Archivable](https://create.roblox.com/docs/reference/engine/classes/Instance).
|
|
119
|
+
- [BaseScript properties](https://create.roblox.com/docs/reference/engine/classes/BaseScript).
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Verify the installed Roblox port
|
|
2
|
+
|
|
3
|
+
Bind evidence to source/package hashes, live overrides, asset versions, native settings, tool
|
|
4
|
+
versions, and the installed `BitmagicBuildId` attribute. Inspect Edit state before each relevant
|
|
5
|
+
test, then use a fresh Play context. A cached Edit-mode `require` can return old code.
|
|
6
|
+
|
|
7
|
+
Maintain independent results for:
|
|
8
|
+
|
|
9
|
+
| Check | Evidence |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| Source/data | Luau syntax/type checks and complete tables/configurations. |
|
|
12
|
+
| Rules | Fixed inputs and expected outputs/event sequences; defined numeric tolerances. |
|
|
13
|
+
| Native integration | Fresh correct Client/Server contexts, build marker, clean console, working dependencies. |
|
|
14
|
+
| Visual/input | Valid observed views/UI interactions at recorded desktop/touch viewports. |
|
|
15
|
+
| Performance | Named hardware, quality, resolution, foreground state, duration and representative load. |
|
|
16
|
+
| Saved | Confirmed complete-project save receipt/file evidence for this build, if supported. |
|
|
17
|
+
|
|
18
|
+
An earlier row does not imply later rows passed. Test stubs are not Roblox rendering, input,
|
|
19
|
+
replication, asset moderation or audio. Do not enable unsafe runtime `loadstring` to run tests.
|
|
20
|
+
Explicit owner inspection can provide manual evidence. Missing checks remain unverified, not green.
|
|
21
|
+
|
|
22
|
+
## Minimum player-journey contract
|
|
23
|
+
|
|
24
|
+
Derive concrete cases from the source inventory, including later progression:
|
|
25
|
+
|
|
26
|
+
- Title, briefing, map/level selection, build/play, combat, results, return, defeat/retry and replay.
|
|
27
|
+
- Every tutorial stage, legal/illegal actions, affordability/recovery, unlock and start gating.
|
|
28
|
+
- Rule tables, specialized enemies/abilities, damage boundaries, rewards and single payouts.
|
|
29
|
+
- Repeated full cycles: no duplicate input handlers, stale UI/audio/camera ownership or accumulating instances.
|
|
30
|
+
- Persistence across sessions/rejoining when required; session carry-over alone is not persistence.
|
|
31
|
+
- Required icons, mesh identities/materials, correct scale/grounding, pivots/sockets and animation timing.
|
|
32
|
+
- Feedback that communicates mechanics: splash radius, piercing corridor, attacks and meaningful impact.
|
|
33
|
+
- Desktop, portrait phone and short landscape: safe areas, text, scrolling, touch confirmation,
|
|
34
|
+
placement visibility, selected controls, pause/music and controller focus where supported.
|
|
35
|
+
- Existing target-only commerce/customization stays intact; inspect cancellation/unknown ownership,
|
|
36
|
+
receipt deduplication and rejoin behavior if those integrations are part of the port.
|
|
37
|
+
|
|
38
|
+
Use public UI for a short smoke journey. Test hooks may accelerate long scenarios, but label them
|
|
39
|
+
and exclude temporary cheats/fixtures from the final Edit project. Poll a bounded ready/build marker
|
|
40
|
+
instead of arbitrary sleeps. Stop bounded retries and preserve the last error when evidence fails.
|
|
41
|
+
|
|
42
|
+
Do not assume JavaScript PRNG streams match Roblox Random. Specify shared deterministic decisions
|
|
43
|
+
or compare exported fixtures. Counts of simulated actors differ from the number actually rendered.
|
|
44
|
+
Report sampling and visible role representation; reducing density requires a disclosed decision.
|
|
45
|
+
|
|
46
|
+
Profile cold load, mass spawn, collision/contact, peak VFX/audio, boss cases, and repeated returns.
|
|
47
|
+
Record frame-time distributions, simulation and presentation CPU costs, memory/instance/connection
|
|
48
|
+
growth, asset stalls and relevant traffic separately. A headless rules benchmark is not FPS.
|
|
49
|
+
Background-throttled Studio measurements cannot certify foreground or mobile performance.
|
|
50
|
+
|
|
51
|
+
## Captures and handoff
|
|
52
|
+
|
|
53
|
+
Check that a screenshot is the intended build/view and includes the GUI under test. Reject blank,
|
|
54
|
+
magenta, locked-screen, wrong-session or missing-overlay evidence. When MCP capture cannot observe
|
|
55
|
+
the UI, give the creator a precise view/action to inspect rather than claiming it passed.
|
|
56
|
+
|
|
57
|
+
After tests, return task-started Play to Edit and ensure all required content lives there. Remove
|
|
58
|
+
temporary fixtures, disable test hooks, and stop task-owned servers/children/ports. Leave unrelated
|
|
59
|
+
processes and Studio open. Save through a supported MCP operation if available and report its status.
|
|
60
|
+
|
|
61
|
+
Completion is a complete game ready in Studio with required checks/deviations accounted for.
|
|
62
|
+
Published-client testing, cloud release and public availability are the creator's subsequent
|
|
63
|
+
workflow. Never turn “not published yet” into a reason to add publishing automation.
|