@bitmagic/cli 0.1.61-dev.9 → 0.1.62-dev.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.
- package/README.md +27 -3
- package/assets/unity-import/README.md +13 -0
- package/assets/unity-import/blender_build.py +462 -0
- package/assets/unity-import/build_parts.py +263 -0
- package/assets/unity-import/convert_assets.py +193 -0
- package/assets/unity-import/fbx_ascii.py +156 -0
- package/assets/unity-import/fbx_axis.py +174 -0
- package/assets/unity-import/fbx_bounds.py +202 -0
- package/assets/unity-import/import_config.py +86 -0
- package/assets/unity-import/level_patches.example.json +23 -0
- package/assets/unity-import/mesh_walls.py +78 -0
- package/assets/unity-import/render_topdown.py +33 -0
- package/assets/unity-import/unity-import.example.json +27 -0
- package/assets/unity-import/unity_assets.py +114 -0
- package/assets/unity-import/unity_colliders2d.py +127 -0
- package/assets/unity-import/unity_extract.py +774 -0
- package/assets/unity-import/unity_markers.py +249 -0
- package/assets/unity-import/unity_renderers.py +279 -0
- package/assets/unity-import/write_arena_data.py +198 -0
- package/dist/cli.d.ts +12 -0
- package/dist/commands/import.d.ts +4 -0
- package/dist/commands/import.js +5 -0
- package/dist/commands/import.js.map +1 -1
- package/dist/commands/init.d.ts +4 -0
- package/dist/commands/init.js +20 -0
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/upgrade.d.ts +4 -0
- package/dist/commands/upgrade.js +31 -5
- package/dist/commands/upgrade.js.map +1 -1
- package/dist/editor/server.js +3 -3
- package/dist/editor/shell-page.js +16 -1
- package/dist/editor/shell-page.js.map +1 -1
- package/dist/project/context.d.ts +9 -0
- package/dist/project/context.js +4 -1
- package/dist/project/context.js.map +1 -1
- package/dist/scaffold/agent-targets.d.ts +126 -0
- package/dist/scaffold/agent-targets.js +180 -0
- package/dist/scaffold/agent-targets.js.map +1 -0
- package/dist/scaffold/agents-md.d.ts +4 -1
- package/dist/scaffold/agents-md.js +6 -2
- package/dist/scaffold/agents-md.js.map +1 -1
- package/dist/scaffold/project-files.d.ts +40 -5
- package/dist/scaffold/project-files.js +67 -25
- package/dist/scaffold/project-files.js.map +1 -1
- package/dist/scaffold/project.d.ts +42 -15
- package/dist/scaffold/project.js +100 -38
- package/dist/scaffold/project.js.map +1 -1
- package/dist/scaffold/reference-guidance.d.ts +1 -1
- package/dist/scaffold/reference-guidance.js +2 -1
- package/dist/scaffold/reference-guidance.js.map +1 -1
- package/dist/scaffold/skill-files.d.ts +32 -9
- package/dist/scaffold/skill-files.js +71 -15
- package/dist/scaffold/skill-files.js.map +1 -1
- package/dist/scaffold/unity-skill-files.d.ts +30 -0
- package/dist/scaffold/unity-skill-files.js +474 -0
- package/dist/scaffold/unity-skill-files.js.map +1 -0
- package/dist/scaffold/upgrade-project.d.ts +31 -11
- package/dist/scaffold/upgrade-project.js +35 -23
- package/dist/scaffold/upgrade-project.js.map +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `converting-unity-levels` skill: a Unity scene + prefabs + FBX + colliders into one GLB and
|
|
3
|
+
* collider data for a Bitmagic game. Distilled from a 2D arena-shooter port, where every rule in
|
|
4
|
+
* it was paid for with a level that looked plausible and was wrong.
|
|
5
|
+
*
|
|
6
|
+
* Same contract as `skill-files.ts`: pure functions returning file contents, registered in
|
|
7
|
+
* `SHIPPED_SKILLS` (project.ts), so every skills directory the project holds receives a copy. The
|
|
8
|
+
* skill is prose plus four references; the Python and Blender scripts it drives ship in the
|
|
9
|
+
* package's `assets/unity-import/` (see `files` in package.json) rather than being rendered into
|
|
10
|
+
* every project, since most games never port a Unity level and the scripts are large. Nothing
|
|
11
|
+
* game-specific is in those scripts: each project describes itself in `tools/unity-import.json`,
|
|
12
|
+
* which the skill's step 0 walks through.
|
|
13
|
+
*
|
|
14
|
+
* Scope, stated in the skill itself: the visual pipeline is general; collider extraction is
|
|
15
|
+
* general; the emitted level data is shaped for 2D-on-a-plane games, and a 3D game builds its
|
|
16
|
+
* physics from the raw collider JSON instead.
|
|
17
|
+
*
|
|
18
|
+
* Generated from the markdown in the origin project's `.claude/skills/converting-unity-levels/`;
|
|
19
|
+
* backticks and `${` are escaped for the template literal, and `project-files.test.ts` checks the
|
|
20
|
+
* rendered frontmatter.
|
|
21
|
+
*/
|
|
22
|
+
export function renderUnityLevelsSkill() {
|
|
23
|
+
return `---
|
|
24
|
+
name: converting-unity-levels
|
|
25
|
+
description: Convert a Unity level — a .unity scene, its prefabs, FBX models, materials and 2D/3D colliders — into a Bitmagic engine level with matching visuals (one GLB) and matching physics (collider data). Use this whenever a creator wants a Unity game or Unity scene ported, replicated, "converted to Bitmagic", "brought over", "made to look like the original", or asks to reuse original Unity assets, prefabs or level blocks — even if they only mention one symptom (pieces rotated 90 degrees, wrong scale, walls the ship flies through, a base that cannot be exited, an empty corner). Also use it when a converted level looks wrong in any of those ways, or after an engine upgrade changes how a ported level lights or collides.
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
# Converting a Unity level to Bitmagic
|
|
29
|
+
|
|
30
|
+
A Unity level is three things that Unity keeps apart and the port must reunite: **placement** (the
|
|
31
|
+
scene and prefab files), **art** (FBX models plus \`.mat\` files and textures), and **collision**
|
|
32
|
+
(colliders on prefabs, which may be 2D or 3D). The tools in \`scripts/\` read Unity's YAML directly,
|
|
33
|
+
rebuild the art in Blender into one GLB, and emit the collision as data the game builds physics
|
|
34
|
+
from. Every step below exists because skipping it produced a level that looked plausible and was
|
|
35
|
+
wrong — the references say which symptom each step prevents.
|
|
36
|
+
|
|
37
|
+
## What this covers, and what it does not
|
|
38
|
+
|
|
39
|
+
- **Visuals are general.** Scene and prefab reading, the override rules, FBX geometry, materials
|
|
40
|
+
and textures, and the Blender rebuild into one GLB apply to any Unity project, 2D or 3D.
|
|
41
|
+
- **Collision extraction is general; the emitted level data is 2D-on-a-plane.**
|
|
42
|
+
\`unity_colliders2d.py\` and \`unity_extract.py\` read every 2D and 3D collider Unity has
|
|
43
|
+
(BoxCollider2D, CircleCollider2D, BoxCollider, SphereCollider, MeshCollider). \`build_parts.py\`
|
|
44
|
+
and \`write_arena_data.py\` then reduce them to a gameplay plane — walls, floors, backdrop,
|
|
45
|
+
foreground, bases — which is the right shape for a top-down or side-on game and the wrong shape
|
|
46
|
+
for a 3D one.
|
|
47
|
+
- **For a 3D game**, stop after \`unity_extract.py\`: \`tools/colliders.json\` holds every collider
|
|
48
|
+
with its world transform, size, centre and mesh, and the GLB holds the art. Build the game's
|
|
49
|
+
physics from those directly (Rapier boxes and trimeshes from the same data), and skip the plane
|
|
50
|
+
emitters. That consumer does not exist yet; write it for the game at hand and it becomes the
|
|
51
|
+
next thing this skill ships.
|
|
52
|
+
- **The receiving game code is not part of the skill.** \`ArenaData.ts\` is a data contract; the
|
|
53
|
+
code that builds physics bodies, occluders and stand-ins from it lives in the game. The origin
|
|
54
|
+
game's \`Arena.ts\` is a worked example of that consumer, not a library.
|
|
55
|
+
|
|
56
|
+
Treat this as a **measured** conversion: after every stage, count things and render things, and
|
|
57
|
+
compare with the Unity editor. A symmetry score or a passing typecheck is not evidence; a top-down
|
|
58
|
+
render next to the creator's Unity screenshot is.
|
|
59
|
+
|
|
60
|
+
## 0. Install the tools and describe the project
|
|
61
|
+
|
|
62
|
+
The conversion scripts ship inside the Bitmagic CLI package, under \`assets/unity-import/\`. Copy
|
|
63
|
+
them into \`tools/\` of the game project once:
|
|
64
|
+
|
|
65
|
+
\`\`\`
|
|
66
|
+
CLI_ROOT="$(cd "$(dirname "$(readlink -f "$(which bitmagic)")")/.." && pwd)"
|
|
67
|
+
mkdir -p tools && cp "$CLI_ROOT"/assets/unity-import/*.py "$CLI_ROOT"/assets/unity-import/*.json tools/
|
|
68
|
+
\`\`\`
|
|
69
|
+
|
|
70
|
+
They need Python 3, Blender 4.x
|
|
71
|
+
(\`/Applications/Blender.app/Contents/MacOS/Blender\` on macOS) and the Unity project on disk.
|
|
72
|
+
|
|
73
|
+
Everything that belongs to a particular game lives in one file, \`tools/unity-import.json\`. Copy the
|
|
74
|
+
example and fill it in — the scripts read it and never need editing:
|
|
75
|
+
|
|
76
|
+
| Key | What it is |
|
|
77
|
+
|---|---|
|
|
78
|
+
| \`unityRoot\` | the Unity tree holding \`Assets/\` and \`ProjectSettings/\` (see step 1 — a repo can hold two) |
|
|
79
|
+
| \`scene\`, \`levelName\` | the scene file relative to \`unityRoot\`, and the level name the game data is written under |
|
|
80
|
+
| \`planeZ\`, \`planeHalf\` | the gameplay plane's Unity z and the half-thickness a collider must cross to count (the ship's radius) |
|
|
81
|
+
| \`arenaLimit\` | instances farther than this from the origin are stray copies parked outside the level |
|
|
82
|
+
| \`glb\`, \`assetName\`, \`outputTs\` | where the GLB is built, the asset name it is uploaded under, where the level data is emitted |
|
|
83
|
+
| \`markerScripts\`, \`markerPrefabs\` | the MonoBehaviour scripts and prefab files that mark spawns, bases and pickups in this game |
|
|
84
|
+
| \`badTags\` | prefab tags whose colliders are never walls (decoration that would seal a room) |
|
|
85
|
+
| \`wallFamilies\` | render-mesh name families that must be solid although they carry no 2D collider |
|
|
86
|
+
| \`prefabs\`, \`axisOverride\` | prefabs for non-scene conversions; a per-pack axis override for experiments |
|
|
87
|
+
|
|
88
|
+
\`UNITY_IMPORT_CONFIG\` points at a different file; \`UNITY_ROOT\`, \`UNITY_PLANE_Z\` and
|
|
89
|
+
\`UNITY_AXIS_OVERRIDE\` override single values for an experiment. The scripts refuse to start with
|
|
90
|
+
an \`unityRoot\` that has no \`Assets/\`, and reject unknown keys, so a typo fails early.
|
|
91
|
+
|
|
92
|
+
## 1. Find the right scene, and the right project tree
|
|
93
|
+
|
|
94
|
+
Before extracting anything, answer three questions; on the game this skill was built from, each
|
|
95
|
+
wrong answer cost a full round of work:
|
|
96
|
+
|
|
97
|
+
1. **Which tree does the creator's editor open?** A repo can hold two Unity trees (a modern
|
|
98
|
+
port and a legacy one) with different \`TagManager\` layer numbering. Unity Hub's recent-projects
|
|
99
|
+
list (\`~/Library/Application Support/UnityHub/projects-v1.json\`) says which path was opened;
|
|
100
|
+
\`ProjectSettings/EditorBuildSettings.asset\` lists the scenes in the build.
|
|
101
|
+
2. **Which scene?** Ask, or take the one the creator screenshots. A "Room for 10" in one tree and
|
|
102
|
+
a "Room for 9" in the other were different games entirely.
|
|
103
|
+
3. **Does the scene match the creator's memory?** Instances can be parked off-map, root-inactive,
|
|
104
|
+
or under a parent group with an offset (a \`WallsLeft\` group at x = −161). Run the census in
|
|
105
|
+
step 3 before believing either the creator or the file.
|
|
106
|
+
|
|
107
|
+
## 2. Decide the physics model first
|
|
108
|
+
|
|
109
|
+
Open the player prefab. If it carries \`Rigidbody2D\` and \`BoxCollider2D\`/\`CircleCollider2D\`, the
|
|
110
|
+
game uses Unity **2D physics**, and the only walls that exist are the 2D colliders on the level —
|
|
111
|
+
the 3D \`MeshCollider\`s on the art are for raycasts and shadows and never touch a 2D body. Extract
|
|
112
|
+
with \`unity_colliders2d.py\` and stop guessing from geometry. If the player is a \`Rigidbody\`, use
|
|
113
|
+
the 3D path (\`unity_extract.py\` + \`build_parts.py\`, which slices MeshColliders at the plane).
|
|
114
|
+
Mixing the two produced sealed bases and invisible walls; see \`references/physics-and-shadows.md\`.
|
|
115
|
+
|
|
116
|
+
## 3. Extract and census
|
|
117
|
+
|
|
118
|
+
\`\`\`
|
|
119
|
+
python3 tools/unity_renderers.py "<scene>.unity" # visible geometry with world transforms
|
|
120
|
+
python3 tools/unity_colliders2d.py "<scene>.unity" > tools/colliders2d.json # 2D games
|
|
121
|
+
python3 tools/unity_extract.py "<scene>.unity" > tools/colliders.json # 3D colliders
|
|
122
|
+
python3 tools/unity_markers.py "<scene>.unity" > tools/markers.json # spawns, bases
|
|
123
|
+
\`\`\`
|
|
124
|
+
|
|
125
|
+
Then census before converting: renderers and colliders **per prefab name**, positions of the big
|
|
126
|
+
structural pieces, anything beyond the arena limit, and which scene instances are switched off.
|
|
127
|
+
The walkers already honour what bit the origin port — nested prefab expansion, \`m_Layer\`,
|
|
128
|
+
\`m_IsActive\` and \`m_Enabled\` overrides (including on objects inside nested prefabs), parent
|
|
129
|
+
groups with offsets, LOD-named objects without an LODGroup. \`references/unity-scene-traps.md\`
|
|
130
|
+
lists each trap, the symptom it caused, and how the walkers handle it, so you can recognise a new one.
|
|
131
|
+
|
|
132
|
+
## 4. Build the GLB
|
|
133
|
+
|
|
134
|
+
\`\`\`
|
|
135
|
+
python3 tools/convert_assets.py "scene:<Scene Name>" # ~15 min; run in the background
|
|
136
|
+
\`\`\`
|
|
137
|
+
|
|
138
|
+
It writes \`build/glb/<scene>.glb\`, one draw call per material, textures capped and JPEG-compressed.
|
|
139
|
+
The FBX rules that took longest to get right — geometric transforms, Unity's X negation, unit
|
|
140
|
+
scale, multi-mesh files, ASCII FBX — are in \`references/fbx-conversion.md\`. When a piece looks
|
|
141
|
+
rotated or scaled wrong, read that file before touching axis permutations: the fix was never a
|
|
142
|
+
permutation.
|
|
143
|
+
|
|
144
|
+
Immediately render it and look:
|
|
145
|
+
|
|
146
|
+
\`\`\`
|
|
147
|
+
blender -b --python tools/render_topdown.py -- build/glb/<scene>.glb .bitmagic/topdown.png
|
|
148
|
+
\`\`\`
|
|
149
|
+
|
|
150
|
+
Put the render next to the creator's Unity screenshot. Crosses, corners, perimeter, statue — if
|
|
151
|
+
any differ, fix them now, not after the physics is built on top.
|
|
152
|
+
|
|
153
|
+
## 5. Emit collision and gameplay data
|
|
154
|
+
|
|
155
|
+
\`\`\`
|
|
156
|
+
python3 tools/write_arena_data.py "<Scene Name>" # -> src/work/ArenaData.ts
|
|
157
|
+
bitmagic check
|
|
158
|
+
\`\`\`
|
|
159
|
+
|
|
160
|
+
For geometry that must be solid but has no collider in Unity (a decorative perimeter ring a 2D
|
|
161
|
+
ship would fly through), slice the render mesh at the play plane instead of inventing boxes:
|
|
162
|
+
|
|
163
|
+
\`\`\`
|
|
164
|
+
blender -b --python tools/mesh_walls.py -- build/glb/<scene>.glb tools/render_walls.json
|
|
165
|
+
\`\`\`
|
|
166
|
+
|
|
167
|
+
Then run the **reachability flood** (in \`references/verification.md\`): every spawn must reach
|
|
168
|
+
the same connected region. A level that seals when a wall is "fixed" is telling you the scene
|
|
169
|
+
differs from what you think — investigate, do not patch.
|
|
170
|
+
|
|
171
|
+
## 6. Upload, verify, confirm on screen
|
|
172
|
+
|
|
173
|
+
\`\`\`
|
|
174
|
+
bitmagic assets add build/glb/<scene>.glb --name <asset-name> --keep-glb --asset-id <id>
|
|
175
|
+
bitmagic verify && grep -q "\\[YourGame\\] loaded" .bitmagic/verify/console.log
|
|
176
|
+
\`\`\`
|
|
177
|
+
|
|
178
|
+
\`verify\` passing means the game boots. It does not mean the walls are where the art is, the
|
|
179
|
+
crosses cast solid shadows, or the statue is the right size. Drive the running game in the
|
|
180
|
+
browser pane, get past the pre-match menus, teleport the ship next to the thing in question, and
|
|
181
|
+
look — \`references/verification.md\` has the exact moves, and the two traps that once let three
|
|
182
|
+
"fixes" be presented that had not reached the browser.
|
|
183
|
+
|
|
184
|
+
## 7. Iterate with patches, not rewrites
|
|
185
|
+
|
|
186
|
+
Genuine gameplay edits (move a corner block so it overlaps the wall, remove a stray instance) go in
|
|
187
|
+
\`tools/level_patches.json\` and are applied identically by the visual and collider walkers. Every
|
|
188
|
+
entry that file ever held before the corner-block one turned out to be a walker bug in disguise:
|
|
189
|
+
when a patch is needed to make Unity's own scene coherent, suspect the extraction first.
|
|
190
|
+
|
|
191
|
+
## Where the deep material is
|
|
192
|
+
|
|
193
|
+
| Read when… | File |
|
|
194
|
+
|---|---|
|
|
195
|
+
| a piece is missing, doubled, parked, rotated by a parent, or "not in Unity" | \`references/unity-scene-traps.md\` |
|
|
196
|
+
| pieces are rotated, mirrored, wrongly scaled, or a multi-mesh FBX picks the wrong mesh | \`references/fbx-conversion.md\` |
|
|
197
|
+
| walls do not collide, a base seals, shadows leak or floors cast, lights vanish after an upgrade | \`references/physics-and-shadows.md\` |
|
|
198
|
+
| deciding whether a change is real: census, render, flood, browser drive, stale-server checks | \`references/verification.md\` |
|
|
199
|
+
`;
|
|
200
|
+
}
|
|
201
|
+
/** `<skills dir>/converting-unity-levels/references/unity-scene-traps.md` */
|
|
202
|
+
export function renderUnityLevelsTrapsReference() {
|
|
203
|
+
return `# Reading Unity scenes and prefabs: the traps, by symptom
|
|
204
|
+
|
|
205
|
+
Unity's YAML is readable without Unity, but the *meaning* of a scene is spread across prefab
|
|
206
|
+
files, \`.meta\` files, \`ProjectSettings/TagManager.asset\` and per-instance override lists. Every
|
|
207
|
+
entry below was found by a wrong level during the port this skill comes from, in the order the
|
|
208
|
+
walkers now handle them.
|
|
209
|
+
|
|
210
|
+
## Symptom → cause → what the walker does
|
|
211
|
+
|
|
212
|
+
| Symptom in the port | Cause in the Unity data | Handling |
|
|
213
|
+
|---|---|---|
|
|
214
|
+
| Walls treated as decoration, or decoration treated as walls | Layer numbers guessed by majority; the repo held two project trees with different \`TagManager\` numbering | \`build_parts.layer_index(name)\` resolves \`Terrain\`, \`Shadows\`, … by NAME from the tree in \`UTOPOS_UNITY_ROOT\` |
|
|
215
|
+
| A prefab arrives with 6 of its 56 colliders, or a Towers prefab arrives empty | Nested prefab instances (class \`1001\`) show up in their parent's \`m_Children\` only as stripped stubs the transform walk cannot follow | Every expanded file is swept for its own \`1001\` records (\`expand_file\` / \`RendererWalk.expand\`); never guard on \`(path, transformId)\` — that blocked the 2nd..nth instance of the same prefab |
|
|
216
|
+
| 167 "walls" that seal a base shut | The same geometry exists twice: the visible copy and a low-poly stand-in on the SHADOWS layer (18), placed with the same transform; the stand-in's layer is a per-instance \`m_Layer\` override | \`go_layer(docs, goid, mods)\` honours the override; layer 18 is excluded from visuals and from physics |
|
|
217
|
+
| Tower blocks in front of every base exit; a ring of bars that seals the arena when completed | Every \`Towers\` and \`Long Side Wall\` instance is **root-inactive** in the scene (\`m_IsActive: 0\` on the prefab root), so Unity never shows them | \`go_active(docs, goid, mods)\` honours \`m_IsActive\` overrides; \`prefab()\` skips an instance whose root is off |
|
|
218
|
+
| A component behaves as if enabled when Unity has it off (or vice versa) | \`m_Enabled\` overrides target the component; when the component lives inside a *nested* prefab, the target id is \`(nestedObjectFileID ^ nestedInstanceFileID) & 0x7FFFFFFFFFFFFFFF\` in the outer file's namespace | \`instance_mods()\` re-keys outer overrides into the nested file's ids as it descends — verified on the Barrier: its override hit a 3D MeshCollider, leaving the 2D strip live |
|
|
219
|
+
| A whole wall "parked" at x = −161, or spawners at (50, 1171, 2004) | Scene-level instances sit under parent groups with their own transforms (\`WallsLeft\`, \`Spawners\`); the placement is local to the group | Compose the parent chain (\`expand\` walks \`m_Father\` up to the root, applying each group's TRS) — and then *believe* the composed position: \`WallsLeft\` really is parked off-map |
|
|
220
|
+
| Corners and the middle of every side empty while Unity shows them | 16 objects named \`…_L2\` placed by hand under plain groups (\`TunnelTop\`, \`CornerTower*\`); the Sci-Fi Base pack ships one FBX per LOD level | \`_is_lod_child\` only drops a LOD-named object when its parent carries an LODGroup (class 205) |
|
|
221
|
+
| A statue 141 units long, or the wrong statue | A multi-mesh FBX (\`GatheredStatues.FBX\`, 8 meshes); the MeshFilter's \`fileID\` (\`43000xx\`) maps to a mesh name through the FBX's \`.meta\` table | \`mesh_index_for(fbx, fileId)\` reads the \`.meta\`; the reader returns meshes in file order |
|
|
222
|
+
| The "corner element" that is not in the extracted data | The perimeter ring and corner towers are **plain scene GameObjects**, not prefab instances | Both walkers walk scene-native objects too; a census by prefab name alone never lists them — census renderers by mesh family as well |
|
|
223
|
+
| A sealed inner arena after adding the one bar that "should" be there | The scene as checked out really has only three bars; the fourth was never placed | Treat a level that only becomes coherent with a patch as evidence that the extraction, not the scene, is wrong — see the flood in \`verification.md\` |
|
|
224
|
+
|
|
225
|
+
## The census (run it before converting anything)
|
|
226
|
+
|
|
227
|
+
For the scene and every prefab it places, print:
|
|
228
|
+
|
|
229
|
+
- instances per prefab name, with world positions of the structural ones (bars, towers, crosses,
|
|
230
|
+
bases), and anything beyond the arena limit;
|
|
231
|
+
- top-level groups and their transforms (a non-zero group transform moves everything under it);
|
|
232
|
+
- objects that are inactive (\`m_IsActive: 0\`) and instances that override \`m_IsActive\` /
|
|
233
|
+
\`m_Enabled\` / \`m_Layer\`, with what the override targets;
|
|
234
|
+
- renderers by mesh family (the \`_L2\`, \`Tunnel\`, \`Staircase\`, \`Floor\` stems), with counts and
|
|
235
|
+
extents — this is how the missing corners were found;
|
|
236
|
+
- the collider inventory of the key prefabs: kind (Box2D/Circle2D/Box3D/Mesh3D), layer, active,
|
|
237
|
+
enabled, trigger.
|
|
238
|
+
|
|
239
|
+
Compare those counts with the built GLB (Blender can list objects by name and extents) and with
|
|
240
|
+
the creator's Unity screenshot. Counts that agree and a picture that does not means a transform
|
|
241
|
+
bug; counts that disagree means a walker bug. Both are cheaper to find at this stage than after the
|
|
242
|
+
physics is layered on top.
|
|
243
|
+
|
|
244
|
+
## Which tree, which scene
|
|
245
|
+
|
|
246
|
+
\`ProjectSettings/EditorBuildSettings.asset\` lists the scenes a tree builds; Unity Hub's
|
|
247
|
+
\`projects-v1.json\` lists the paths the creator actually opened. \`Library/\` is created when Unity
|
|
248
|
+
opens a project but is often deleted or ignored, so its absence proves nothing. When a pulled
|
|
249
|
+
commit changes nothing in the scene yet the creator's screenshot differs, the screenshot is of
|
|
250
|
+
another scene or tree — or of this scene with the overrides above honoured.
|
|
251
|
+
`;
|
|
252
|
+
}
|
|
253
|
+
/** `<skills dir>/converting-unity-levels/references/fbx-conversion.md` */
|
|
254
|
+
export function renderUnityLevelsFbxReference() {
|
|
255
|
+
return `# Rebuilding Unity art from FBX: the rules that produce Unity's mesh
|
|
256
|
+
|
|
257
|
+
Unity does not render the FBX as authored. It applies an import pipeline, and the GLB must
|
|
258
|
+
reproduce that pipeline, not the file. These are the rules, each learned from a piece that was
|
|
259
|
+
"rotated 90 degrees", mirrored, split in half, or a quarter of its size.
|
|
260
|
+
|
|
261
|
+
## Coordinates
|
|
262
|
+
|
|
263
|
+
- Unity is left-handed, Y up, camera looking down +Z for this 2D-on-a-plane genre. Blender's FBX
|
|
264
|
+
importer maps Unity (x, y, z) to Blender (x, z, y); glTF export from Blender gives the engine
|
|
265
|
+
(ux, uy, −uz). \`Arena.ts\` uses that depth convention, so "Unity z" reads as \`−z\` in the game.
|
|
266
|
+
- **Unity negates X on FBX import** (right-handed to left-handed). The reader applies
|
|
267
|
+
\`(−x, y, z)\` to every vertex (\`UNITY_NEGATE_X\`), and \`blender_build\` uses signs \`(−1, 1, 1)\`.
|
|
268
|
+
This was the residual mirror that split the round end caps: the \`Top_1_H\` tips only join into a
|
|
269
|
+
half-round with X negated. Do not apply it twice — the collider path once did (reader plus
|
|
270
|
+
\`apply_permutation\`) and every collider landed mirrored.
|
|
271
|
+
- Unity's own glTF exporters (UnityGLTF, glTFast) do the same with scale (−1, 1, 1) and
|
|
272
|
+
quaternion (x, −y, −z, w); that is the cross-check when in doubt.
|
|
273
|
+
|
|
274
|
+
## Geometric transforms (the 90-degree bug)
|
|
275
|
+
|
|
276
|
+
3ds Max exports put a **geometric transform** on the Model node — \`GeometricRotation\`,
|
|
277
|
+
\`GeometricTranslation\`, \`GeometricScaling\` — that applies to the mesh vertices only, not to
|
|
278
|
+
children. Unity bakes it into the mesh asset. The Euler order is XYZ, composed as Rz·Ry·Rx, and
|
|
279
|
+
was verified against Unity's own BoxCollider boxes (21 of 30 off-centre boxes match only with that
|
|
280
|
+
order). \`Wall_7\` reproduces Unity's box exactly: lo (−1, −0.003, −0.042), hi (1, 2.597, 0.042).
|
|
281
|
+
|
|
282
|
+
Blender's importer folds the geometric transform into the **object** matrix. \`bake_to_blender\`
|
|
283
|
+
works from the raw vertices and applies the geometric matrix itself, so it must take only the unit
|
|
284
|
+
scale from the object — and divide \`GeometricScaling\` back out of \`matrix_world.to_scale()\`,
|
|
285
|
+
otherwise that scaling is applied twice. That is exactly why the centre statue (GeometricScaling
|
|
286
|
+
0.28) came out at a quarter of its size while the Pack pieces (GeometricScaling 1) hid the bug.
|
|
287
|
+
|
|
288
|
+
## Axis permutations: do not derive them
|
|
289
|
+
|
|
290
|
+
An earlier version derived a per-pack axis permutation from collider sizes or from a symmetry
|
|
291
|
+
score of the assembled level. Both are blind to a uniform rotation or mirror of every piece, so
|
|
292
|
+
they happily chose a "best" permutation that rotated everything 90 degrees. The real causes were
|
|
293
|
+
the two rules above. \`fbx_axis.py\` now returns the identity with X negated
|
|
294
|
+
(\`DEFAULT_TRANSFORM = ((0,1,2), (−1,1,1))\`); \`UTOPOS_AXIS_OVERRIDE="Pack=cycYZX:-1,1,1"\` exists
|
|
295
|
+
for a pack that genuinely needs one, and the test of "needs one" is a Unity BoxCollider box that
|
|
296
|
+
does not match — never a picture that "looks more symmetric".
|
|
297
|
+
|
|
298
|
+
## Units and scale
|
|
299
|
+
|
|
300
|
+
A mesh asset in Unity is: raw vertices × baked geometric transform × **file unit scale**
|
|
301
|
+
(\`useFileScale: 1\` in the \`.meta\` times the FBX \`UnitScaleFactor\` / 100 — \`GatheredStatues.FBX\` is
|
|
302
|
+
in inches, factor 2.54). Nothing from the node's own \`Lcl Scaling\` reaches a mesh asset; that
|
|
303
|
+
lands on the imported GameObject, which a hand-built prefab does not use. \`read_fbx\` returns the
|
|
304
|
+
unit factor alongside the meshes; check \`.meta\` \`globalScale\` too.
|
|
305
|
+
|
|
306
|
+
## Multi-mesh files and ASCII files
|
|
307
|
+
|
|
308
|
+
- A MeshFilter references a mesh by \`fileID\` (\`43000xx\`); the FBX \`.meta\` maps those to mesh
|
|
309
|
+
names in file order. \`mesh_index_for\` does the lookup — without it the walker picks the first
|
|
310
|
+
mesh, which is how a 141-unit statue appeared.
|
|
311
|
+
- The ship packs are ASCII FBX 6100 from Blender 2.71, which Blender no longer imports;
|
|
312
|
+
\`fbx_ascii.py\` reads the handful of arrays that matter (vertices, polygon index, one material).
|
|
313
|
+
|
|
314
|
+
## Materials and textures
|
|
315
|
+
|
|
316
|
+
The FBX's embedded texture paths point into the vendor's authoring tree. What Unity renders is the
|
|
317
|
+
Renderer's \`m_Materials\` GUID list → \`.mat\` → texture GUIDs; \`unity_assets.py\` resolves that
|
|
318
|
+
chain. In Blender: textures capped (512 colour / 256 other) and JPEG q80 (a 68 MB GLB became
|
|
319
|
+
0.33 MB); emissive only when the material enables it (\`_EMISSION\` / \`_UseEmissiveIntensity\`),
|
|
320
|
+
preferring \`_EmissiveColor\` over \`_EmissionColor\` — otherwise the whole arena whites out; metalness
|
|
321
|
+
capped at 0.45 because the arena is lit by the ship's lamp alone and full metal renders black.
|
|
322
|
+
|
|
323
|
+
## Checking a converted piece
|
|
324
|
+
|
|
325
|
+
1. Its Unity BoxCollider box (from the prefab) against the mesh bounds the reader produces — the
|
|
326
|
+
only objective test of orientation and scale.
|
|
327
|
+
2. One piece with a known shape (an end cap, a cross bar) rendered from above next to the Unity
|
|
328
|
+
editor view.
|
|
329
|
+
3. Family extents in the finished GLB (Blender lists objects by name) against the census.
|
|
330
|
+
`;
|
|
331
|
+
}
|
|
332
|
+
/** `<skills dir>/converting-unity-levels/references/physics-and-shadows.md` */
|
|
333
|
+
export function renderUnityLevelsPhysicsReference() {
|
|
334
|
+
return `# Physics, walls and line-of-sight shadows
|
|
335
|
+
|
|
336
|
+
## Decide 2D or 3D from the player, not from the art
|
|
337
|
+
|
|
338
|
+
Open the player prefab. In the game this skill was built from, the player was a \`Rigidbody2D\` with
|
|
339
|
+
two \`BoxCollider2D\` and a \`CircleCollider2D\` (radius 0.8). Unity's 2D physics only ever collides
|
|
340
|
+
2D colliders with 2D colliders, so for such a game the playable walls are the small
|
|
341
|
+
designer-authored set of \`BoxCollider2D\` / \`CircleCollider2D\` on the level prefabs (there: tower
|
|
342
|
+
blocks, cross bars at 45° with circle tips, base door posts, barrier strips). The 3D
|
|
343
|
+
\`MeshCollider\`s on the art serve the land-check sphere-cast and shadows and never touch the ship.
|
|
344
|
+
Building physics from the 3D colliders (box-approximated, then triangle-rasterised) produced sealed
|
|
345
|
+
bases and walls of air; switching to the 2D set fixed both in one step and made every wall nameable.
|
|
346
|
+
|
|
347
|
+
\`unity_colliders2d.py\` emits \`{kind: box, cx, cy, hw, hh, rot}\` and \`{kind: circle, cx, cy, r}\`
|
|
348
|
+
with layer, trigger flag and prefab tag. \`write_arena_data.py\` turns them into \`ARENA_BOXES\` /
|
|
349
|
+
\`ARENA_CIRCLES\`; \`Arena.buildWalls\` makes a fixed body per collider (\`cuboid(hw, hh)\` with
|
|
350
|
+
\`setRotation(rot)\`, \`ball(r)\`), and an axis-aligned bound per collider for the flood and the
|
|
351
|
+
wall-hit log. Respect the Physics2D collision matrix (players hit Default and Terrain, not Shadows)
|
|
352
|
+
and skip triggers.
|
|
353
|
+
|
|
354
|
+
## Rapier needs to be told to report contacts
|
|
355
|
+
|
|
356
|
+
\`PhysicsWorld2D.createCollider\` does not enable collision events. Without
|
|
357
|
+
\`.setActiveEvents(R.ActiveEvents.COLLISION_EVENTS)\` on the ship's collider, no wall-hit callback
|
|
358
|
+
ever fires — wall damage and ship-ship damage silently never worked. Keep \`setCcdEnabled(true)\`
|
|
359
|
+
too; a fast ship tunnels through a 0.8-thick bar otherwise.
|
|
360
|
+
|
|
361
|
+
## Geometry that must be solid but has no collider
|
|
362
|
+
|
|
363
|
+
The perimeter tunnel ring is plain scene decoration a 2D ship flies through. Rather than invent
|
|
364
|
+
boxes, \`mesh_walls.py\` slices the *render* mesh at the play plane inside Blender: sample every
|
|
365
|
+
triangle crossing a thin slab (half-depth 0.45 — wide enough for the walls, narrow enough to miss
|
|
366
|
+
the tunnel floor at depth 0, which otherwise fills the corridor solid), rasterise at 0.5 units,
|
|
367
|
+
close one-cell holes, merge greedily into rectangles. \`write_arena_data.load_2d\` appends the result
|
|
368
|
+
as axis-aligned boxes. Re-run it after every GLB rebuild that moves that geometry, and keep the
|
|
369
|
+
\`FAMILIES\` tuple to what genuinely needs it.
|
|
370
|
+
|
|
371
|
+
## Line-of-sight shadows: casters are the collision set
|
|
372
|
+
|
|
373
|
+
The original lit the arena from the player and projected a shadow-camera render back as a
|
|
374
|
+
visibility mask. The port does it with two shadow-casting point lights on the ship (a warm lantern
|
|
375
|
+
with falloff, a cool decay-0 fill whose shadow map *is* the mask). What casts matters more than
|
|
376
|
+
what receives:
|
|
377
|
+
|
|
378
|
+
- Let the **collision set** cast, not the art. \`Arena.buildOccluders\` extrudes every 2D collider
|
|
379
|
+
and every sliced wall through the plane (z −8 … +8) into one merged mesh with a material that
|
|
380
|
+
writes neither colour nor depth, \`castShadow = true\`; every art mesh has \`castShadow = false\`.
|
|
381
|
+
This fixed two complaints at once: floor decks a hair behind the plane no longer darken the map,
|
|
382
|
+
and a cross built of many small pieces no longer leaks light between them.
|
|
383
|
+
- Consequence to state explicitly: anything that is not a collider no longer occludes. In the
|
|
384
|
+
origin game the base walls were not colliders (only their door posts were), so a ship inside a
|
|
385
|
+
base lit the level around it. Unity's own SHADOWS layer held only the cross stand-ins, so this is closer to the
|
|
386
|
+
original than art-casting was — but it is a design decision the creator should make.
|
|
387
|
+
- Visibility map 512 once the casters are a few hundred blocks; the six cube faces are cheap then.
|
|
388
|
+
|
|
389
|
+
## Lighting configuration, and what an engine upgrade does to it
|
|
390
|
+
|
|
391
|
+
- The engine reads scene lighting from \`worldProfileData.lightingConfig\` (\`sunIntensity\`,
|
|
392
|
+
\`sunColor\`, \`environmentIntensity\`, \`skyboxIntensity\`, \`ambientFloor\`). A block under any other
|
|
393
|
+
key is silently ignored and the sun runs at its daylight default of 5.0, colour-compensated —
|
|
394
|
+
bright enough to wash the shadow mask out entirely.
|
|
395
|
+
- The game's own \`dimSceneLighting\` then scales every non-ship light by 0.35, sun included, so the
|
|
396
|
+
effective sun is \`5 / luminance(sunColor) × sunIntensity × 0.35\`.
|
|
397
|
+
- After the upgrade from engine 3.1094 to 3.1122 the same point light read roughly **eight times
|
|
398
|
+
weaker** (three.js itself unchanged). The fill at 2.6 no longer reached the arena; 22 restored
|
|
399
|
+
the mask, 35 blew the deck out. Re-measure after every engine upgrade before trusting the
|
|
400
|
+
constants in \`VisibilityLight.ts\` — and measure in the running game, not by reasoning about it
|
|
401
|
+
(see \`verification.md\`).
|
|
402
|
+
`;
|
|
403
|
+
}
|
|
404
|
+
/** `<skills dir>/converting-unity-levels/references/verification.md` */
|
|
405
|
+
export function renderUnityLevelsVerificationReference() {
|
|
406
|
+
return `# Verifying a conversion: what counts as evidence
|
|
407
|
+
|
|
408
|
+
The order of trust, highest first: a gameplay frame you took yourself → a top-down render next to
|
|
409
|
+
the Unity editor → counts and extents from the data → \`bitmagic verify\` → a typecheck. During the
|
|
410
|
+
port this skill comes from, three "fixes" were presented on the strength of the bottom three and
|
|
411
|
+
none had reached the creator's screen.
|
|
412
|
+
|
|
413
|
+
## Data checks
|
|
414
|
+
|
|
415
|
+
- **Census** (see \`unity-scene-traps.md\`): per-prefab instance counts, group transforms, inactive
|
|
416
|
+
and overridden instances, renderer families with extents.
|
|
417
|
+
- **Collider map**: draw every solid 2D collider (oriented boxes, circles, sliced walls) and the
|
|
418
|
+
spawns as a PNG; a ring with a missing side or a strip in the middle is obvious at a glance.
|
|
419
|
+
- **Reachability flood**: rasterise the wall bounds (plus the arena boundary) at 0.25 units,
|
|
420
|
+
inflate by the ship radius, flood-fill from every spawn, and report the reachable fraction per
|
|
421
|
+
spawn. Every spawn must reach the same region; a spawn "IN WALL" or a 31 % / 69 % split means a
|
|
422
|
+
sealed arena. A level that only becomes coherent after adding or removing a wall is telling you
|
|
423
|
+
the scene differs from your reading — go back to the census.
|
|
424
|
+
- **GLB inventory**: Blender lists objects by name family with extents (\`Tunnel 52 where Unity has
|
|
425
|
+
56\`, \`Staircase 0\`) — the fastest way to see what the visual walker dropped.
|
|
426
|
+
|
|
427
|
+
## Renders
|
|
428
|
+
|
|
429
|
+
\`\`\`
|
|
430
|
+
blender -b --python tools/render_topdown.py -- build/glb/<scene>.glb .bitmagic/topdown.png
|
|
431
|
+
\`\`\`
|
|
432
|
+
|
|
433
|
+
Orthographic, whole arena; crop a corner or the centre when the question is local. Put it beside
|
|
434
|
+
the creator's Unity screenshot; that comparison found the missing left bar, the empty corners and
|
|
435
|
+
the tiny statue. Billboard textures may read mirrored in the render — cosmetic.
|
|
436
|
+
|
|
437
|
+
## In the running game (the browser pane)
|
|
438
|
+
|
|
439
|
+
Open \`http://localhost:<gamePort>/\` (from \`.bitmagic/dev.json\`). Then:
|
|
440
|
+
|
|
441
|
+
- The pre-match menus must be clicked through: game mode, opponents, Launch. At 800×450 the
|
|
442
|
+
buttons sat at (400, 264), (352, 302), (400, 325); take a screenshot first — coordinate clicks
|
|
443
|
+
need one — and re-check positions if the HUD changes.
|
|
444
|
+
- Shader warm-up after a cold reload takes 2–3 minutes in the pane. \`computer.wait\` caps at
|
|
445
|
+
10 s; chain several.
|
|
446
|
+
- The engine is reachable as \`window.gameTemplate.getGameEngine()\`: \`.scene\` (traverse for lights
|
|
447
|
+
and meshes), \`.renderer\` (shadow map state), \`.directionalLight\`, \`.currentLightingConfig\`,
|
|
448
|
+
\`.genreModule\` (this game's module: \`.player.body.setTranslation({x, y}, true)\` teleports the
|
|
449
|
+
ship next to whatever needs looking at; \`.started\` / \`.phase\` say whether the match runs).
|
|
450
|
+
- A/B toggles beat reasoning: switch \`castShadow\` off on the ship lights and screenshot; set an
|
|
451
|
+
intensity to 100 and screenshot. Two frames settled in minutes what probes could not.
|
|
452
|
+
|
|
453
|
+
## Two ways a change fails to reach the screen
|
|
454
|
+
|
|
455
|
+
1. **The dev server serves a stale module.** \`bitmagic dev\` serves \`/dist/src/work/*.js\` (tsc
|
|
456
|
+
watch output). It kept serving a 4,579-byte copy of \`VisibilityLight.js\` after the file on disk
|
|
457
|
+
was 5,132 bytes with new constants — even to \`fetch(..., {cache: 'reload'})\`, so forced page
|
|
458
|
+
reloads and \`bitmagic reload\` still ran old code. Check with
|
|
459
|
+
\`curl localhost:<port>/dist/src/work/<File>.js | grep <constant>\`; if stale, restart the dev
|
|
460
|
+
server (\`kill $(jq .pid .bitmagic/dev.json)\`, then \`bitmagic dev --port … --editor-port …\` in
|
|
461
|
+
the background) and force-navigate. Never touch a port another project's dev server owns.
|
|
462
|
+
2. **Verify passed on fallback geometry.** During a network drop the arena GLB failed to fetch and
|
|
463
|
+
the game kept procedural stand-ins; verify still passed. Grep the verify console for
|
|
464
|
+
\`arena model failed to load\` and for your own \`[Game] loaded\` line
|
|
465
|
+
(\`grep -q "\\[YourGame\\] loaded" .bitmagic/verify/console.log\`) before trusting a screenshot.
|
|
466
|
+
|
|
467
|
+
## Presenting
|
|
468
|
+
|
|
469
|
+
Say what was measured, with the number, and what was only inferred. If the creator is the judge
|
|
470
|
+
of a look (brightness, size, "large"), give them the single knob and its range rather than a
|
|
471
|
+
value dressed up as a restoration.
|
|
472
|
+
`;
|
|
473
|
+
}
|
|
474
|
+
//# sourceMappingURL=unity-skill-files.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unity-skill-files.js","sourceRoot":"","sources":["../../src/scaffold/unity-skill-files.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,MAAM,UAAU,sBAAsB;IACpC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgLR,CAAC;AACF,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,+BAA+B;IAC7C,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgDR,CAAC;AACF,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,6BAA6B;IAC3C,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2ER,CAAC;AACF,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,iCAAiC;IAC/C,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoER,CAAC;AACF,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,sCAAsC;IACpD,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkER,CAAC;AACF,CAAC"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type AgentId } from './agent-targets.js';
|
|
1
2
|
import { type AgentsMdOutcome } from './agents-md.js';
|
|
2
3
|
import { type RepointedAsset } from './legacy-default-assets.js';
|
|
3
4
|
export interface UpgradeOptions {
|
|
@@ -13,6 +14,13 @@ export interface UpgradeOptions {
|
|
|
13
14
|
* answer in a committed file.
|
|
14
15
|
*/
|
|
15
16
|
environmentToPin?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Agents to ADD to the project's recorded set, from `--agent`. Unioned with whatever
|
|
19
|
+
* `bitmagic.json` already says and persisted, because "I also use Kiro" is a fact about the
|
|
20
|
+
* project rather than an aim for one command — the opposite of `--env`, which is deliberately not
|
|
21
|
+
* persisted. Nothing is ever removed: see `agent-targets.ts`.
|
|
22
|
+
*/
|
|
23
|
+
addAgents?: readonly AgentId[];
|
|
16
24
|
}
|
|
17
25
|
export interface UpgradeResult {
|
|
18
26
|
engineVersion: string;
|
|
@@ -33,6 +41,20 @@ export interface UpgradeResult {
|
|
|
33
41
|
* never silent.
|
|
34
42
|
*/
|
|
35
43
|
environmentPinned?: string;
|
|
44
|
+
/** The agent set this run wrote for, after unioning the recorded set with `--agent`. */
|
|
45
|
+
agents: AgentId[];
|
|
46
|
+
/**
|
|
47
|
+
* The agents just added to bitmagic.json, when any were — including the backfill a project
|
|
48
|
+
* scaffolded before the field existed receives. Reported for the same reason
|
|
49
|
+
* `environmentPinned` is: a permanent write to a committed file is never silent.
|
|
50
|
+
*/
|
|
51
|
+
agentsAdded: AgentId[];
|
|
52
|
+
/**
|
|
53
|
+
* Values in the project's `agents` field this CLI does not recognise — a hand edit, or a newer
|
|
54
|
+
* CLI's. Reported and otherwise ignored, exactly as an unrecognised `environment` is: refusing to
|
|
55
|
+
* upgrade would be a strange way to report a typo.
|
|
56
|
+
*/
|
|
57
|
+
agentsUnknown: string[];
|
|
36
58
|
/**
|
|
37
59
|
* The platform-owned default assets in `src/work/world.json` that were repointed from their
|
|
38
60
|
* legacy JSON files to the VXL3 ones. Empty when there was nothing to repoint. This is the one
|
|
@@ -72,17 +94,15 @@ export interface UpgradeResult {
|
|
|
72
94
|
* ever deleted, so a skill we stop shipping stays behind rather than being cleaned up — a stale
|
|
73
95
|
* file being much cheaper than deleting one a creator wrote.
|
|
74
96
|
*
|
|
75
|
-
* Mirrors scaffoldProject's copy behaviour (same VENDORED_DIRS loop, same
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
97
|
+
* Mirrors scaffoldProject's copy behaviour (same VENDORED_DIRS loop, same
|
|
98
|
+
* `assertEngineBundleComplete` check), except it clears engine/ first: unlike a brand new
|
|
99
|
+
* project's empty target, an existing engine/ may hold files a since-removed VENDORED_DIRS entry
|
|
100
|
+
* left behind, or a leftover from a bad manual edit, and cpSync alone would merge over those
|
|
101
|
+
* rather than remove them.
|
|
80
102
|
*
|
|
81
|
-
* That "clear first" step is
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
* project's engine/, already rm'd — the same ordering would demolish it and fail partway through
|
|
86
|
-
* repopulating it, and a project not under version control has no way to recover that.
|
|
103
|
+
* That "clear first" step is what makes the up-front validation load-bearing here rather than
|
|
104
|
+
* merely tidy: the target is a live project's engine/, already rm'd, so a check inside the copy
|
|
105
|
+
* loop would demolish it and fail partway through repopulating it — and a project not under
|
|
106
|
+
* version control has no way to recover that.
|
|
87
107
|
*/
|
|
88
108
|
export declare function upgradeProject(options: UpgradeOptions): UpgradeResult;
|