@hypersoniclabs/helix-mcp 0.2.4 → 0.2.12
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 +81 -11
- package/dist/continuumCanary.d.ts +17 -0
- package/dist/continuumCanary.js +17 -0
- package/dist/continuumCanary.js.map +1 -0
- package/dist/server.d.ts +14 -1
- package/dist/server.js +4058 -162
- package/dist/server.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/vehicleTools.d.ts +86 -0
- package/dist/vehicleTools.js +229 -0
- package/dist/vehicleTools.js.map +1 -0
- package/docs/avatar-face.md +115 -0
- package/docs/bridge.md +98 -0
- package/docs/bring-your-world.md +117 -0
- package/docs/catalog.md +69 -1
- package/docs/character-animation.md +442 -0
- package/docs/character-attachments.md +166 -0
- package/docs/character-world.md +785 -130
- package/docs/continuum.md +153 -0
- package/docs/items.md +73 -0
- package/docs/lighting-world.md +667 -0
- package/docs/locomotion-clip-spec.md +294 -0
- package/docs/manifest.md +31 -6
- package/docs/multiplayer-logic.md +460 -17
- package/docs/multiplayer-templates/chrono-orchard.md +36 -22
- package/docs/multiplayer-templates/collect-a-thon.md +28 -38
- package/docs/multiplayer-templates/collections.md +24 -24
- package/docs/multiplayer-templates/hangout.md +124 -111
- package/docs/multiplayer-templates/npc-wave.md +310 -0
- package/docs/multiplayer-templates/obby.md +13 -16
- package/docs/multiplayer-templates/persistent-progress.md +218 -0
- package/docs/multiplayer-templates/physics-bumper.md +27 -9
- package/docs/multiplayer-templates/physics-football.md +22 -8
- package/docs/multiplayer-templates/relic-bearers.md +12 -15
- package/docs/multiplayer-templates/server-motion.md +16 -19
- package/docs/multiplayer-templates/shooter-range.md +275 -0
- package/docs/multiplayer-templates/team-control.md +28 -15
- package/docs/multiplayer-templates/turn-arena.md +31 -22
- package/docs/multiplayer-templates/voice-radio.md +166 -0
- package/docs/multiplayer-templates/wave-survival.md +7 -8
- package/docs/multiplayer-templates/world-shop.md +240 -0
- package/docs/multiplayer-world.md +222 -129
- package/docs/npc-world.md +623 -0
- package/docs/publishing.md +108 -28
- package/docs/purchases.md +223 -0
- package/docs/scene-performance.md +64 -0
- package/docs/screenshots.md +140 -0
- package/docs/sdk.md +324 -5
- package/docs/shooter-worlds.md +537 -0
- package/docs/terrain.md +173 -0
- package/docs/upgrades.md +324 -0
- package/docs/vehicles.md +727 -0
- package/docs/world-inspect.md +156 -0
- package/docs/world-look.md +241 -0
- package/docs/world-recipe.md +65 -6
- package/package.json +15 -4
- package/skills/README.md +91 -0
- package/skills/helix-assets/SKILL.md +491 -0
- package/skills/helix-assets/references/asset-sources.md +143 -0
- package/skills/helix-assets/references/vault-api.md +105 -0
- package/skills/helix-avatar-qa/SKILL.md +85 -0
- package/skills/helix-avatars/SKILL.md +206 -0
- package/skills/helix-avatars/references/contract.md +166 -0
- package/skills/helix-avatars/references/dynamics.md +367 -0
- package/skills/helix-avatars/references/face.md +50 -0
- package/skills/helix-avatars/references/publish.md +76 -0
- package/skills/helix-avatars/references/qa.md +251 -0
- package/skills/helix-avatars/references/rigging.md +88 -0
- package/skills/helix-avatars/references/source-generated.md +190 -0
- package/skills/helix-avatars/references/source-model.md +90 -0
- package/skills/helix-avatars/references/source-rigid.md +90 -0
- package/skills/helix-avatars/references/source-vrm.md +61 -0
- package/skills/helix-gauntlet/SKILL.md +128 -0
- package/skills/helix-multiplayer/SKILL.md +150 -0
- package/skills/helix-multiplayer/references/dsl-capability-map.md +107 -0
- package/skills/helix-vehicles/SKILL.md +218 -0
- package/skills/helix-vehicles/references/addons.md +212 -0
- package/skills/helix-vehicles/references/appearance.md +339 -0
- package/skills/helix-vehicles/references/audio-import.md +138 -0
- package/skills/helix-vehicles/references/audio.md +580 -0
- package/skills/helix-vehicles/references/cabin.md +225 -0
- package/skills/helix-vehicles/references/host-manifest.md +174 -0
- package/skills/helix-vehicles/references/physics-from-specs.md +463 -0
- package/skills/helix-vehicles/references/publish.md +214 -0
- package/skills/helix-vehicles/references/qa.md +177 -0
- package/skills/helix-vehicles/references/reference-package.json +3481 -0
- package/skills/helix-vehicles/references/reference-package.md +69 -0
- package/skills/helix-vehicles/references/source-beamng.md +167 -0
- package/skills/helix-vehicles/references/source-concept.md +38 -0
- package/skills/helix-vehicles/references/source-model.md +100 -0
- package/skills/helix-vehicles/references/source-scratch.md +60 -0
- package/skills/helix-world-build/SKILL.md +376 -0
- package/skills/helix-world-build/references/config-gates.md +104 -0
- package/skills/helix-world-director/SKILL.md +210 -0
- package/skills/helix-world-qa/SKILL.md +371 -0
- package/skills/helix-world-qa/references/perf-budgets.md +240 -0
- package/skills/helix-world-qa/references/perf-handle.md +125 -0
- package/skills/helix-world-qa/references/visual-scorecard.md +85 -0
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# Avatar QA: what to measure, what to look at
|
|
2
|
+
|
|
3
|
+
**The rule:** nothing ships until you have looked at the avatar **moving** in the real HELIX runtime,
|
|
4
|
+
close up. Every gate passed Cream Hoodie 1.0.0, and in that version:
|
|
5
|
+
- the chest dented with arm swing;
|
|
6
|
+
- a hem edge stretched 16 cm into a spike at the hip.
|
|
7
|
+
|
|
8
|
+
Every gate also passed a dozen Dreamer drafts that rendered flat white. A rest-pose render proves
|
|
9
|
+
almost nothing: **a T-pose or A-pose hides wrong skin indices**, because every bone sits at its bind.
|
|
10
|
+
|
|
11
|
+
Run every browser headless, on your own profile directory, with `--use-mock-keychain
|
|
12
|
+
--password-store=basic`. Record the GPU adapter you rendered with.
|
|
13
|
+
|
|
14
|
+
## 1. Structure (offline, before upload)
|
|
15
|
+
|
|
16
|
+
Read the final GLB with `@gltf-transform/core` and assert each of these. Every one is a defect that
|
|
17
|
+
has already shipped.
|
|
18
|
+
|
|
19
|
+
| Check | Pass |
|
|
20
|
+
| --- | --- |
|
|
21
|
+
| Scenes vs skins | One skin per LOD scene, each scene self-contained. Normally 3 skins and 6 meshes. |
|
|
22
|
+
| Joint names | Identical in every LOD skin, including every auxiliary joint. |
|
|
23
|
+
| Weighted joint names per region | Head vertices on `head`/`neck_*`, hands on `hand_*`, no foot or thigh weight above the pelvis, no arm weight on the central torso. Read the **names**, not the indices. |
|
|
24
|
+
| Influences | ≤ 4 per vertex, summing to 1 ± 1e-3. No influence from a bone more than 0.3 m from the vertex. |
|
|
25
|
+
| Emissive | No material has an `emissiveFactor` above 0 without an `emissiveTexture`. |
|
|
26
|
+
| Metallic | No `metallicFactor` of 1 without a metallic map; glTF's default turns untextured parts black chrome. |
|
|
27
|
+
| Manifests | `helixDynamics`, `helixFace` and `helixAvatar` are identical on every scene. `helixDynamics` obeys every rule in `dynamics`. |
|
|
28
|
+
| Budgets | The Character Package's avatar validation passes: ≤ 60k triangles and ≤ 12 draws across all LODs, ≤ 10 MiB, ≤ 8 materials, ≤ 2048 px. |
|
|
29
|
+
| Short body | Height ≥ 1.35 m, or (pelvis − foot) / 0.877 < 0.65. |
|
|
30
|
+
|
|
31
|
+
## 2. Static render (offline)
|
|
32
|
+
|
|
33
|
+
Render in headless three.js:
|
|
34
|
+
- `GLTFLoader` with `KTX2Loader` and the basis transcoder;
|
|
35
|
+
- a neutral environment;
|
|
36
|
+
- a ground plane at Y = 0.
|
|
37
|
+
|
|
38
|
+
Look at:
|
|
39
|
+
- a turnaround (front, back, both sides, both three-quarters);
|
|
40
|
+
- face, hands and feet close-ups;
|
|
41
|
+
- LOD1 and LOD2 at their switch distances (8 m and 15 m), checking that no part goes missing and
|
|
42
|
+
nothing becomes a shard or a smear.
|
|
43
|
+
|
|
44
|
+
Compare against the reference images at matched angles.
|
|
45
|
+
|
|
46
|
+
## 3. Motion against a control body (offline, no GPU)
|
|
47
|
+
|
|
48
|
+
This is the check that catches rig bugs, and the one an agent most often skips. Run it before the
|
|
49
|
+
upload: once a version is published it cannot be deleted.
|
|
50
|
+
|
|
51
|
+
The control is the stock body on the same skeleton, played through the platform's own clips and the
|
|
52
|
+
engine's own dynamics solver. All three come from a throwaway character world:
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
helix init /tmp/avatar-harness --kind character && cd /tmp/avatar-harness
|
|
56
|
+
npm install && helix install # the engine module lands in public/helix_modules/humanoid-character/
|
|
57
|
+
npm i --no-save @gltf-transform/core @gltf-transform/extensions
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`helix.lock.json` then names the asset pack: `systems[0].assetBaseUrl` for `humanoid-character`.
|
|
61
|
+
Download these into one directory:
|
|
62
|
+
- `<assetBaseUrl>/body-f-default.web.glb` and `<assetBaseUrl>/body-m-default.web.glb`, the control
|
|
63
|
+
bodies;
|
|
64
|
+
- `<assetBaseUrl>/anims/clip-meta.json`;
|
|
65
|
+
- `<assetBaseUrl>/anims/locomotion.idle.glb`, `locomotion.walk.glb` and `locomotion.run.glb`.
|
|
66
|
+
|
|
67
|
+
Use a browser user agent; a bare request can be refused. Then run this probe from the harness
|
|
68
|
+
directory, with the avatar, the control, and the clip directory as arguments:
|
|
69
|
+
|
|
70
|
+
```js
|
|
71
|
+
// node probe.mjs <avatar.web.glb> <control.web.glb> <clipDir> [framesOut.json] (run inside the harness world)
|
|
72
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
73
|
+
import * as THREE from 'three';
|
|
74
|
+
import { NodeIO } from '@gltf-transform/core';
|
|
75
|
+
import { ALL_EXTENSIONS } from '@gltf-transform/extensions';
|
|
76
|
+
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
|
|
77
|
+
const { DynamicsLayer } = await import('./public/helix_modules/humanoid-character/index.js');
|
|
78
|
+
|
|
79
|
+
async function load(path) { // Node cannot decode KTX2: drop textures, keep skins + extras
|
|
80
|
+
const io = new NodeIO().registerExtensions(ALL_EXTENSIONS);
|
|
81
|
+
const doc = await io.readBinary(new Uint8Array(readFileSync(path)));
|
|
82
|
+
for (const t of doc.getRoot().listTextures()) t.dispose();
|
|
83
|
+
for (const e of doc.getRoot().listExtensionsUsed()) if (/texture|basisu|webp/i.test(e.extensionName)) e.dispose();
|
|
84
|
+
const glb = await io.writeBinary(doc);
|
|
85
|
+
return new GLTFLoader().parseAsync(glb.buffer.slice(glb.byteOffset, glb.byteOffset + glb.byteLength), '');
|
|
86
|
+
}
|
|
87
|
+
const [avatarPath, controlPath, clipDir, framesOut] = process.argv.slice(2);
|
|
88
|
+
const avatar = (await load(avatarPath)).scenes[0], control = (await load(controlPath)).scenes[0]; // LOD0
|
|
89
|
+
const LIMBS = ['spine_03', 'head', ...['upperarm', 'lowerarm', 'hand', 'thigh', 'calf', 'foot'].flatMap((b) => [`${b}_l`, `${b}_r`])];
|
|
90
|
+
const bone = (root, n) => { let b = null; root.traverse((o) => { if (!b && o.isBone && o.name === n) b = o; }); return b; };
|
|
91
|
+
const canonical = new Set(); control.traverse((o) => o.isBone && canonical.add(o.name));
|
|
92
|
+
const meta = JSON.parse(readFileSync(`${clipDir}/clip-meta.json`, 'utf8'));
|
|
93
|
+
const report = {}, frames = {};
|
|
94
|
+
for (const name of ['locomotion.idle', 'locomotion.walk', 'locomotion.run']) {
|
|
95
|
+
const clip = (await load(`${clipDir}/${name}.glb`)).animations[0];
|
|
96
|
+
const speed = meta.find((m) => m.name === name)?.strideSpeed ?? 0, dt = 1 / 60;
|
|
97
|
+
const mixers = [avatar, control].map((m) => { const x = new THREE.AnimationMixer(m); x.clipAction(clip).play(); return x; });
|
|
98
|
+
avatar.position.set(0, 0, 0);
|
|
99
|
+
const dyn = new DynamicsLayer(avatar, { isLocal: true });
|
|
100
|
+
const aux = []; avatar.traverse((o) => { if (o.isBone && !canonical.has(o.name)) aux.push(o); });
|
|
101
|
+
// Each aux bone's aim, in its own local frame: toward its child bone, or the chain's endpoint.
|
|
102
|
+
const endpoints = new Map();
|
|
103
|
+
for (const sc of [avatar]) for (const ch of sc.userData.helixDynamics?.chains ?? []) {
|
|
104
|
+
let b = bone(avatar, ch.root); while (b && b.children.filter((c) => c.isBone).length === 1) b = b.children.find((c) => c.isBone);
|
|
105
|
+
if (b && ch.endpoint) endpoints.set(b, new THREE.Vector3(...ch.endpoint));
|
|
106
|
+
}
|
|
107
|
+
const aim = new Map(aux.map((b) => [b, (b.children.find((c) => c.isBone)?.position.clone() ?? endpoints.get(b) ?? new THREE.Vector3(-1, 0, 0)).normalize()]));
|
|
108
|
+
const rest = new Map(aux.map((b) => [b, b.quaternion.clone()]));
|
|
109
|
+
let worstAt = null;
|
|
110
|
+
const skinned = []; avatar.traverse((o) => o.isSkinnedMesh && skinned.push(o));
|
|
111
|
+
const edges = skinned.map((m) => { // unique edges with their bind lengths
|
|
112
|
+
const idx = m.geometry.index.array, pos = m.geometry.attributes.position, seen = new Set(), out = [];
|
|
113
|
+
for (let t = 0; t < idx.length; t += 3) for (const [a, b] of [[idx[t], idx[t + 1]], [idx[t + 1], idx[t + 2]], [idx[t + 2], idx[t]]]) {
|
|
114
|
+
const k = Math.min(a, b) * 1e7 + Math.max(a, b); if (seen.has(k)) continue; seen.add(k);
|
|
115
|
+
out.push([a, b, new THREE.Vector3().fromBufferAttribute(pos, a).distanceTo(new THREE.Vector3().fromBufferAttribute(pos, b))]);
|
|
116
|
+
}
|
|
117
|
+
return out;
|
|
118
|
+
});
|
|
119
|
+
let limb = 0, stretch = 0; const swing = {};
|
|
120
|
+
for (let i = 0; i < Math.round((2 * clip.duration) / dt); i++) {
|
|
121
|
+
mixers.forEach((x) => x.update(dt));
|
|
122
|
+
avatar.position.z += speed * dt; // carry the body forward so springs feel the anchor's velocity
|
|
123
|
+
avatar.updateMatrixWorld(true); control.updateMatrixWorld(true);
|
|
124
|
+
dyn.update({ dt, cameraDistance: null, gate: 1 }, 1);
|
|
125
|
+
avatar.updateMatrixWorld(true);
|
|
126
|
+
for (const n of LIMBS) {
|
|
127
|
+
const a = bone(avatar, n), c = bone(control, n); if (!a || !c) continue;
|
|
128
|
+
limb = Math.max(limb, THREE.MathUtils.radToDeg(a.getWorldQuaternion(new THREE.Quaternion()).angleTo(c.getWorldQuaternion(new THREE.Quaternion()))));
|
|
129
|
+
}
|
|
130
|
+
if (i * dt < 1) continue; // skip the spawn warm-up
|
|
131
|
+
for (const b of aux) swing[b.name] = Math.max(swing[b.name] ?? 0, +THREE.MathUtils.radToDeg(aim.get(b).clone().applyQuaternion(b.quaternion).angleTo(aim.get(b).clone().applyQuaternion(rest.get(b)))).toFixed(1));
|
|
132
|
+
if (framesOut && i % 6 === 0) (frames[name] ??= []).push(Object.fromEntries(aux.concat(LIMBS.map((n) => bone(avatar, n)).filter(Boolean))
|
|
133
|
+
.map((b) => [b.name, b.quaternion.toArray().map((x) => +x.toFixed(5))])));
|
|
134
|
+
if (i % 15) continue; // 4 Hz: worst edge stretch of the skinned surface
|
|
135
|
+
skinned.forEach((m, k) => {
|
|
136
|
+
const cache = new Map(), at = (v) => cache.get(v) ?? cache.set(v, m.getVertexPosition(v, new THREE.Vector3())).get(v);
|
|
137
|
+
for (const [a, b, len] of edges[k]) {
|
|
138
|
+
const d = at(a).distanceTo(at(b)) - len;
|
|
139
|
+
if (d > stretch) { stretch = d; worstAt = { mesh: m.name, vertex: a, bindY: +m.geometry.attributes.position.getY(a).toFixed(2), bindX: +m.geometry.attributes.position.getX(a).toFixed(2) }; }
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
report[name] = { limbDegVsControl: +limb.toFixed(2), stretchCm: +(stretch * 100).toFixed(1), worstEdgeAt: worstAt, dynamics: { active: dyn.active, rejected: dyn.rejected, ...dyn.cost }, swingDeg: swing };
|
|
144
|
+
}
|
|
145
|
+
if (framesOut) writeFileSync(framesOut, JSON.stringify(frames)); // local quaternions every 0.1 s, for rendering
|
|
146
|
+
console.log(JSON.stringify(report, null, 2));
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Read the report for each clip:
|
|
150
|
+
|
|
151
|
+
| Field | Meaning | Pass |
|
|
152
|
+
| --- | --- | --- |
|
|
153
|
+
| `limbDegVsControl` | Worst world-rotation difference of any limb bone against the control. | ≤ 2°. The stock body against itself reads 0.01 / 0.48 / 0.8° (idle / walk / run). That is the noise floor. More than 5° means a rest-frame or bone-map error. |
|
|
154
|
+
| `stretchCm`, `worstEdgeAt` | Worst growth of any mesh edge over its bind length, and where: the mesh, vertex and bind x/y. | Close to the control. Stock Base Female reads 1.3 / 2.1 / 4.4 cm. Loose cloth stretches more: Cream Hoodie's armpit and sleeve cloth shipped at 5.8 cm walking and 8.9 cm running, by the lane's own probe. **10 cm or more:** look up where it is. If it sits between two body parts (inner thighs, cuff to hip, chest to arm), it is a weight bridge, so fix the weights (`source-generated` step 5). If it is cloth stretching over a joint, render that frame and judge it. |
|
|
155
|
+
| `dynamics.active`, `rejected` | Whether the engine accepted the manifest. | `active: true` and `rejected: []`. A rejected manifest means **no physics anywhere**. |
|
|
156
|
+
| `swingDeg` | Peak deflection of each auxiliary bone's aim (toward its child, or its chain's `endpoint`) from rest, after warm-up. | Inside its cone, and enough to read. Restrained hood hair read 6–9°; visible long hair 10–25° walking. A value pinned at the cone limit while walking means too little inertia. Near 0° walking means too stiff to notice. |
|
|
157
|
+
|
|
158
|
+
The probe cannot see two things: a strand passing through the body or collar, and the look of it.
|
|
159
|
+
**Render the frames it solved.**
|
|
160
|
+
1. Pass a fourth argument, for example `frames.json`. The probe writes every auxiliary and limb
|
|
161
|
+
bone's local quaternion every 0.1 s, per clip.
|
|
162
|
+
2. In a **headless** three.js page (`GLTFLoader` with `KTX2Loader` and the basis transcoder), load the
|
|
163
|
+
avatar's LOD0 scene.
|
|
164
|
+
3. For each frame, copy those quaternions onto the bones by name, then render a side, back and
|
|
165
|
+
three-quarter view of the head and shoulders.
|
|
166
|
+
4. Look at the running frames for hair passing through the hood, collar or shoulders.
|
|
167
|
+
|
|
168
|
+
The live surfaces in step 5 are the final word.
|
|
169
|
+
|
|
170
|
+
## 4. Platform visual QA
|
|
171
|
+
|
|
172
|
+
Run `visual_qa_item_hosted({ itemId })` (or `visual_qa_item` with a QA sign-in). It must pass, with
|
|
173
|
+
no override. What each assert measures, and how to fix a failure, is in
|
|
174
|
+
`read_skill({ name: "helix-avatar-qa" })`.
|
|
175
|
+
|
|
176
|
+
The gate has two stages. Stage 1 is the Try Now walk in the world. Stage 2 (route `avatar@3`) is the
|
|
177
|
+
Marketplace item page's own walk, seen from the true sides and back: it blocks arms bound too close to
|
|
178
|
+
the body (the walk adducts ~24° from the clip's A-pose, so bind them near the standard 36°), forearms
|
|
179
|
+
buried in the torso, a surface that opens at the side as an arm swings, stray strands, and a hung
|
|
180
|
+
silhouette or leaning posture. A `not measured` result fails the run. Your own run of step 3 does not
|
|
181
|
+
replace it: the probe's controls measure rig noise, the gate measures what a buyer sees.
|
|
182
|
+
|
|
183
|
+
**Open the contact sheet anyway**, especially the `motion.preview.*` side and back frames. A pass says
|
|
184
|
+
the avatar renders, walks and does not tear in those frames. It says nothing about:
|
|
185
|
+
- dynamics;
|
|
186
|
+
- the face;
|
|
187
|
+
- first-person hide;
|
|
188
|
+
- how close the avatar is to the reference.
|
|
189
|
+
|
|
190
|
+
Cream Hoodie 1.0.0 passed an earlier version of the gate with a 9 cm hip spike, so keep measuring
|
|
191
|
+
`stretchCm` yourself. Cream Hoodie Anime Girl 1.0.6 passed `avatar@2` with arms bound at 13-15° that
|
|
192
|
+
walked inside the hoodie, a torn side and a rigid hair slab; that is what stage 2 now blocks, and the
|
|
193
|
+
hair slab is still the critic's call on the side frames, not a number.
|
|
194
|
+
|
|
195
|
+
## 5. Live close-ups, in both surfaces
|
|
196
|
+
|
|
197
|
+
Capture both. Each one uses a different code path.
|
|
198
|
+
|
|
199
|
+
- **The Marketplace item preview.** It runs every dynamics chain at 60 Hz with no budget, on the
|
|
200
|
+
WebGPU renderer.
|
|
201
|
+
- Headless macOS Chromium drew nothing for the earlier lanes: Tint "swizzle view instruction still
|
|
202
|
+
has usages after lowering". Cream Hoodie rendered headless on a real Metal adapter.
|
|
203
|
+
- Record the adapter. If you get no frames, render on a machine with a discrete GPU instead.
|
|
204
|
+
- **In-world, through Try Now.** Use a signed-in QA account in the test grounds.
|
|
205
|
+
- Check that the GLB sha256 the world downloaded matches your file.
|
|
206
|
+
- Record the ready time, distance walked, idle arm angle and sole range.
|
|
207
|
+
- Capture close-ups: face, hands, feet, and hair or tail at idle, at mid-stride walking, and
|
|
208
|
+
running.
|
|
209
|
+
- **Headless camera gotchas:**
|
|
210
|
+
- The camera starts behind the character, and pointer-lock orbit does not work headless. Walk
|
|
211
|
+
toward the camera for front shots, or re-aim the world camera from a render hook.
|
|
212
|
+
- Take walk frames at ≥ 92 % of the stride's own maximum foot separation. A mid-hop frame decided
|
|
213
|
+
one critic round.
|
|
214
|
+
- Record the engine version the world resolved while you test (the served `…/runtime/^0.3/…` redirect and the
|
|
215
|
+
bundle's `VERSION`). Worlds pick up the live `^0.3` range, so an engine release mid-run can change the result —
|
|
216
|
+
compare runs by that recorded version. Do NOT exact-pin helix.json to freeze it: publish refuses exact engine
|
|
217
|
+
pins. For a one-off local comparison use the visual-QA engine override (`HELIX_VQA_ENGINE_RUNTIME=<version>`).
|
|
218
|
+
|
|
219
|
+
## 6. Blind critic against the reference
|
|
220
|
+
|
|
221
|
+
The bar is the real reference: photographs of the real robot, actor or prop, the source game's own
|
|
222
|
+
renders, or the concept art.
|
|
223
|
+
|
|
224
|
+
1. Render a fixed set of 11–12 frozen frames per candidate, deterministic so that two runs diff to
|
|
225
|
+
0. Include a turnaround, face, hands, feet and the dressed close-ups, plus a walk frame with
|
|
226
|
+
dynamics running.
|
|
227
|
+
2. Give a fresh critic agent that never saw the build an X/Y pair with the labels shuffled. Keep the
|
|
228
|
+
key outside the packet.
|
|
229
|
+
3. The critic must **pick a side before scoring**, with no ties.
|
|
230
|
+
4. Keep the incumbent unless the challenger wins head-to-head. Always re-anchor on the true
|
|
231
|
+
incumbent: one lane paired two losers by mistake.
|
|
232
|
+
|
|
233
|
+
Every avatar lane exited on a **plateau** of 6–7/10, not a win. Generated sources plateau lower.
|
|
234
|
+
Report which exit you took.
|
|
235
|
+
|
|
236
|
+
## What eyes caught that numbers missed
|
|
237
|
+
|
|
238
|
+
- A flat white avatar: an emissive factor left without its map.
|
|
239
|
+
- A smeared mouth and blocky hair: the importer's rebake.
|
|
240
|
+
- Forearms walking inside the hoodie, the side torn open as an arm swung back, back hair standing off
|
|
241
|
+
as a slab, a strand at the waist: seen only from the side in the Marketplace walk. Arms bound at
|
|
242
|
+
13-15°, fused sleeve/torso triangles cut into holes, hair weighted to arm and clavicle bones.
|
|
243
|
+
- A dented chest with arm swing, and a spike at the hip: auto-rig weights. The verifier, the bind
|
|
244
|
+
check and visual QA all passed it.
|
|
245
|
+
- A 60 cm chest spike, invisible in the game's own clips: chest vertices weighted to a foot.
|
|
246
|
+
- Feet sinking 2–5.7 cm on a short robot: the short-body gate skipped retargeting.
|
|
247
|
+
- Hair smearing into the hood while running, but fine while walking.
|
|
248
|
+
- Ears swinging 100°+ at a sprint: loose inertia and no limit.
|
|
249
|
+
- Chrome rendering near black: metallic 1.0 under the world sky.
|
|
250
|
+
- A normal map reading as hammered foil: ETC1S on the normal map.
|
|
251
|
+
- Hard-surface LODs tearing into shards: collapse decimation.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Rigging: getting any mesh onto `helix-humanoid@1`
|
|
2
|
+
|
|
3
|
+
The importer, `helix character import` (or MCP `import_character`), does the last step. It maps the
|
|
4
|
+
source skeleton onto the canonical 68 joints, conforms them to rest, builds LODs and KTX2, and splits
|
|
5
|
+
`FaceMesh`. Your job is to hand it a source it can map: a skinned mesh in the canonical frame, close
|
|
6
|
+
to the A-pose, with bones it can name.
|
|
7
|
+
|
|
8
|
+
## Bone maps
|
|
9
|
+
|
|
10
|
+
- `--map <json>` takes `{ "name", "version", "comment", "map": { "<sourceBone>": "<helixBone>" } }`.
|
|
11
|
+
- Without `--map`, the importer picks MetaHuman when it recognises it, or else the best of its
|
|
12
|
+
built-in Meshy, Mixamo and Mixamo-FBX maps. A Meshy or Dreamer rig needs no map.
|
|
13
|
+
- If you author the rig yourself (in Blender, from a CAD tree or a game rig), **name the bones with
|
|
14
|
+
the canonical helix names** and pass an identity map. Leave fingers and twists unmapped if you did
|
|
15
|
+
not build them. The importer then leaves them at rest, and fingers ride rigidly on `hand_*`.
|
|
16
|
+
- `--fit source` keeps the model's own proportions and embeds the canonical skeleton at its joints.
|
|
17
|
+
Every lane used it. `--fit canonical` re-poses the model to canonical proportions instead.
|
|
18
|
+
|
|
19
|
+
## The canonical rest, as data
|
|
20
|
+
|
|
21
|
+
To aim limbs at the A-pose, you need the canonical rest transforms: per bone, the parent, the
|
|
22
|
+
position and the rotation, at the 1.7 m canonical body's metre scale. Read them from a base avatar's
|
|
23
|
+
LOD0 skin: the stock `body-m-default.web.glb` or `body-f-default.web.glb`. Download them from the
|
|
24
|
+
asset pack of a throwaway character world; `qa` step 3 gives the commands.
|
|
25
|
+
|
|
26
|
+
Aim these at the canonical directions:
|
|
27
|
+
- **upper arm, forearm, thigh and calf:** by bone direction plus the elbow or knee hinge plane;
|
|
28
|
+
- **hands:** by direction plus the palm axis;
|
|
29
|
+
- **feet:** by **yaw only**, so the soles stay flat.
|
|
30
|
+
|
|
31
|
+
Then bake the pose into the mesh. If you skip this, `conformed.restAimed` does the aiming, and a
|
|
32
|
+
hard-surface part bound to a moved bone will shear.
|
|
33
|
+
|
|
34
|
+
## Pick the binding that matches the source
|
|
35
|
+
|
|
36
|
+
| Source | Binding | Lane |
|
|
37
|
+
| --- | --- | --- |
|
|
38
|
+
| Rigid mechanical parts (a robot, CAD, armour plates) | **Rigid 1:1.** Every part is weighted 1.0 to exactly one bone. Joints sit at the mechanical pivots. Parts spanning two bones are split, or become **pistons**: each half is rigid to its own bone, so they telescope. Thin cables keep blended weights. | Unitree G1, C-3PO, T-800 |
|
|
39
|
+
| A properly skinned game character on an Epic- or Valve-style skeleton | **Keep its weights.** Rename the canonical joints and **fold every helper joint** into its nearest canonical ancestor. Helper joints are twist helpers, `deform_*`, `Dyn_*` physics joints, `attach_*`, `*_end`, metacarpals. The game's weights survive. | Daft Punk (Fortnite), Iron Man (Valve biped, shoulder pads split 50/50 between clavicle and upper arm) |
|
|
40
|
+
| Skinned, but posed in the mesh, or carrying bad weights | **Aim to the A-pose, then bind per part:** rigid if one bone carries at least 90 % of the part, piston if it spans two bones, blended otherwise. | T-800 |
|
|
41
|
+
| Meshy or Dreamer auto-rig | **Keep the weights, then repair them.** The auto-rig bleeds arm weight into the torso and hips. See `source-generated`. | Cream Hoodie |
|
|
42
|
+
| VRM | Do not rig. The bridge reads the file's own humanoid map. | See `source-vrm` |
|
|
43
|
+
|
|
44
|
+
**Weight sanity.** Apply these on every non-trivial source:
|
|
45
|
+
- **Drop any influence from a bone whose segment (from the joint to its child) is more than 0.3 m
|
|
46
|
+
from the vertex.** Measure to the segment, not the joint head: a long thigh's own vertices sit
|
|
47
|
+
further than 0.3 m from its head. MK11's chest vertices weighted
|
|
48
|
+
to the right foot drew a 60 cm spike, and it was invisible in the game's own clips.
|
|
49
|
+
- Hard-surface plates dominated by one bone at 75 % or more get one constant blend. Otherwise they
|
|
50
|
+
bend like rubber.
|
|
51
|
+
|
|
52
|
+
## T-pose hides skin-index bugs
|
|
53
|
+
|
|
54
|
+
Wrong `JOINTS_0` indices look perfect at bind, because every joint is at its bind pose and the error
|
|
55
|
+
multiplies out to identity. They show only when a clip moves the bones. So read back, for each LOD:
|
|
56
|
+
- **the names** of the joints that carry weight on each region;
|
|
57
|
+
- whether any vertex is weighted to an implausible bone (a foot on the chest, a hand on the hip);
|
|
58
|
+
- the worst edge stretch over walk and run frames, compared against a base body playing the same clip
|
|
59
|
+
(see `qa`).
|
|
60
|
+
|
|
61
|
+
Most bad weights get past the platform gates:
|
|
62
|
+
- the backend verifier passed Cream Hoodie 1.0.0;
|
|
63
|
+
- the engine bind check passed it;
|
|
64
|
+
- visual QA passed it too, with a 9 cm spike at the hip while walking (16 cm running).
|
|
65
|
+
|
|
66
|
+
Route `avatar@3` now catches three rig faults in the Marketplace walk: **arms bound too near straight
|
|
67
|
+
down** (under 17°; standard 36°), **a surface that opens at the seams** as a limb swings, and **forearms
|
|
68
|
+
sunk in the torso**. Bind arms near the canonical A-pose, and never cut fused sleeve/torso triangles to
|
|
69
|
+
hide a bad weight; fix the weight (`source-generated` step 5). Spike and stretch faults still need your
|
|
70
|
+
own probe.
|
|
71
|
+
|
|
72
|
+
## Proportion edits
|
|
73
|
+
|
|
74
|
+
- Scale to the real height: Unitree G1 1.32 m, C-3PO 1.67 m, T-800 1.88 m, Iron Man 1.98 m.
|
|
75
|
+
- Widen girdles only from measured reference proportions, and move whole limb chains rigidly. T-800's
|
|
76
|
+
shoulders were widened 12 % and its pelvis 8 %.
|
|
77
|
+
- A 5 % head scale-up read as stocky and lost the critic round.
|
|
78
|
+
|
|
79
|
+
## Tooling
|
|
80
|
+
|
|
81
|
+
- Use headless Blender for posing, binding and baking.
|
|
82
|
+
- Use `@gltf-transform/core` for JSON and accessor surgery.
|
|
83
|
+
- Use `meshoptimizer` or the importer for LODs. **Hard-surface plates decimated with Blender's
|
|
84
|
+
collapse tear into shards.** Two fixes worked:
|
|
85
|
+
- per-region meshoptimizer ratios (T-800);
|
|
86
|
+
- voxel-remeshed closed shells (12 mm / 20 mm) for LOD1 and LOD2, baked the same way as LOD0
|
|
87
|
+
(Iron Man).
|
|
88
|
+
- Brackets that decimate badly can be replaced with convex hulls (Unitree G1).
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# Source: generated characters (Dreamer image-to-3D, or any Meshy-rigged GLB)
|
|
2
|
+
|
|
3
|
+
Generated characters are the fastest source and the most deceptive. Every gate passed the first
|
|
4
|
+
Cream Hoodie build while it:
|
|
5
|
+
- glowed flat white in drafts;
|
|
6
|
+
- had a smeared mouth;
|
|
7
|
+
- had a dented chest;
|
|
8
|
+
- had a 9 cm spike at the hip.
|
|
9
|
+
|
|
10
|
+
Plan for every step below. The template lane is Cream Hoodie Anime Girl (helix3, 2026-10-03), the
|
|
11
|
+
first avatar through the visual-QA avatar route with no override.
|
|
12
|
+
|
|
13
|
+
## 1. The input image
|
|
14
|
+
|
|
15
|
+
- Image-to-3D wants a clean **four-view A-pose turnaround**: front, back, left and right. A single
|
|
16
|
+
small illustration does not work; 214 × 395 px was too small. Make or edit a turnaround first.
|
|
17
|
+
- **Layout: 2×2, 1×4 or 4×1, in the order front, back, left, right** (row left to right, column top
|
|
18
|
+
to bottom, grid reading order), with **exactly one figure per view**. Dreamer finds each figure
|
|
19
|
+
against the background, cuts the sheet through the empty gutters between them, and **refuses the
|
|
20
|
+
upload before anything is created or charged** when it cannot: the error says how many figure
|
|
21
|
+
columns and rows it found (`found 3 figure column(s) …`) or which panel is empty or holds two
|
|
22
|
+
figures. A figure that touches or crosses its neighbour leaves no gutter, so keep a visible gap and
|
|
23
|
+
turn the two profiles to face opposite ways.
|
|
24
|
+
- **Make the figures stand out from the background.** Detection works from the colour distance to a
|
|
25
|
+
flat background; a cream outfit on light grey is only about 40 levels away, white on white is
|
|
26
|
+
invisible. Use a **mid or dark neutral grey** (or a clean flat colour unlike the clothes), no
|
|
27
|
+
gradients, no frames, no captions touching the figures. A clean sheet reads fine on light grey; a
|
|
28
|
+
pale one on a pale background does not.
|
|
29
|
+
- **Open the sheet and look at it before you generate:** four panels, one figure each, a visible gap
|
|
30
|
+
between them. Passing the layout check is not a promise the semantic gate agrees on which view is
|
|
31
|
+
which.
|
|
32
|
+
- Why this is written down: on 2026-10-06 a 1536×1024 four-in-a-row sheet of a cream figure on light
|
|
33
|
+
grey was read as a 2×2 grid, Meshy was given the front and back figures as one view and built a
|
|
34
|
+
two-figure mesh (123 LIX). The detector has been fixed; the same sheet is now read as 1×4. When you
|
|
35
|
+
cannot get a clean row, re-lay the figures yourself as a 2×2 on a darker grey (that is what worked).
|
|
36
|
+
- Lower legs, boots and the back that the source does not show are inferred. Say so in the item
|
|
37
|
+
description if they matter.
|
|
38
|
+
- **Generative variance is real.** A second generation from a corrected sheet lost to the first, 5.0
|
|
39
|
+
vs 7.0. Do not re-run hoping for luck: fix what you have.
|
|
40
|
+
|
|
41
|
+
## 2. Generate
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
generate_asset({ kind: "character", prompt, title, referenceSheetPath: "/abs/sheet.png", visibility: "private" })
|
|
45
|
+
# CLI: helix assets generate-reference sheet.png "<prompt>" --visibility private
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The server runs mesh, texture, rig, LOD, thumbnail and publication; 123 LIX in the lanes. Two
|
|
49
|
+
problems with the draft it publishes:
|
|
50
|
+
|
|
51
|
+
- **Visibility.** Pass `visibility: "private"` for a draft you will rebuild. Until 2026-10-03 the
|
|
52
|
+
server ignored it and listed drafts publicly. That is fixed, but confirm with `search_items` after
|
|
53
|
+
generation anyway. A raw draft that did go public gets `delist_item` before anyone buys it.
|
|
54
|
+
- **The draft is not your avatar.** The server imported it with the automatic rebake. Treat the draft
|
|
55
|
+
as a source.
|
|
56
|
+
- For a hero result, rebuild from the **raw rigged GLB** with the steps below.
|
|
57
|
+
- Dreamer does not keep the raw rig; a platform operator can re-fetch it by the job's rigging task.
|
|
58
|
+
- If you cannot get it, repair the draft in place, applying steps 4–6 to every LOD. The rebake
|
|
59
|
+
damage stays.
|
|
60
|
+
|
|
61
|
+
## 3. Strip the emissive before import
|
|
62
|
+
|
|
63
|
+
The raw material carries a **black emissive map with `emissiveFactor [1, 1, 1]`**.
|
|
64
|
+
|
|
65
|
+
- An importer that drops the map but keeps the factor renders the **whole avatar glowing flat
|
|
66
|
+
white**. This was a stop-ship.
|
|
67
|
+
- That is fixed in the importer: CLI `0.1.18-helix3.351` and later never keep an emissive factor
|
|
68
|
+
whose map was dropped, and strip flat-black emissive maps. The visual-QA gate's emissive wash-out
|
|
69
|
+
assert also fails it now.
|
|
70
|
+
- Strip both from the source anyway. It costs nothing, and it keeps the material honest for any tool
|
|
71
|
+
you run before the importer.
|
|
72
|
+
|
|
73
|
+
After import, check that no material keeps an `emissiveFactor` without an `emissiveTexture`.
|
|
74
|
+
|
|
75
|
+
## 4. Import without the rebake
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
helix character import raw.glb -o out --name <slug> --lod-tris 30000,15000,5000 --tex-size 2048 --no-bake --ktx-mode etc1s
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
- `import_character` passes `bake: false`, `ktxMode` and `lodTris`. The default Meshy bone map is
|
|
82
|
+
picked automatically.
|
|
83
|
+
- The source's micro-chart atlas **triggers the automatic weld, unwrap and rebake**. On Cream Hoodie
|
|
84
|
+
that smeared the mouth into the chin and turned hair strands into blocks. `--no-bake` keeps the
|
|
85
|
+
source UVs, and it beat the rebake 6.5 to 5.0 in the blind critic round.
|
|
86
|
+
- ETC1S at 2048 kept Cream Hoodie at 7.06 MB, normal map included. That is fine for soft cloth
|
|
87
|
+
and skin. The "hammered foil" ETC1S artefact (`source-model`) shows on glossy hard surfaces, so
|
|
88
|
+
judge it in close-ups.
|
|
89
|
+
- A generated source has no external URL or licence. Leave `--provenance` off, and record the Dreamer
|
|
90
|
+
job id in your ledger.
|
|
91
|
+
|
|
92
|
+
Texture retouching (colour grading cloth or irises, projecting a clean face) is done on the source
|
|
93
|
+
atlas before import. Gain-match it to the skin, and judge it in the critic loop. Colour-only tweaks
|
|
94
|
+
oscillated between "too warm" and "too cold".
|
|
95
|
+
|
|
96
|
+
## 5. Repair the auto-rig weights
|
|
97
|
+
|
|
98
|
+
Three Meshy defects only show in the Marketplace walk, seen from the side and back
|
|
99
|
+
(`avatar.bind_pose`, `avatar.preview.*`, see `helix-avatar-qa`):
|
|
100
|
+
- **Arms bound near straight down** (13-15° on the hoodie; standard is 36°). The walk adducts the arm
|
|
101
|
+
~24° from the clip's A-pose, so the forearms walk inside the body. Re-bind the arms near Base Female's
|
|
102
|
+
A-pose (the rework used +6° for oversized sleeves): a rotation-only re-bind of upperarm/lowerarm,
|
|
103
|
+
baked into POSITION/NORMAL/TANGENT, with IBMs and node rotations rewritten on every LOD.
|
|
104
|
+
- **Arm and clavicle weights in the hair.** Hair belongs to the head and the hair chains only. A big share
|
|
105
|
+
of the mesh on a few stiff joints reads as a rigid slab behind the back.
|
|
106
|
+
- **Neck and head bound off canonical**, so the head droops or pushes forward with the walk.
|
|
107
|
+
|
|
108
|
+
The auto-rig blends arm weight into the torso and fuses sleeve cuffs to the hips. Cream Hoodie's raw
|
|
109
|
+
rig had:
|
|
110
|
+
- 4,121 central-torso vertices with more than 10 % upper-arm weight, so the chest dented with every
|
|
111
|
+
arm swing;
|
|
112
|
+
- hem vertices weighted to the forearm and thigh, one at 0.51 / 0.32, which stretched an edge 9 cm
|
|
113
|
+
walking and 16 cm running. It showed as a thin line out of the hip in the Marketplace preview.
|
|
114
|
+
|
|
115
|
+
The fix that worked:
|
|
116
|
+
1. Change only vertices that carry **both** arm weight (upper arm, lower arm, hand, fingers) **and**
|
|
117
|
+
body weight (spine, pelvis, legs, clavicle).
|
|
118
|
+
2. Classify each one by **geometry**. It is arm surface if it lies within sleeve radius of its side's
|
|
119
|
+
arm bones (0.095 m for the upper arm, 0.08 m for the forearm and hand) **and** its normal faces
|
|
120
|
+
away from the arm axis. Otherwise it is body.
|
|
121
|
+
3. Classify per **welded vertex group**, not per vertex. Seam duplicates have different normals, so
|
|
122
|
+
classifying them separately leaves 0.5 / 0.5 blends. Then diffuse the arm share over the welded
|
|
123
|
+
mesh so the boundary does not crease, but **only above the hem**. Diffusing across the cuff-to-hip
|
|
124
|
+
contact spread the bridge and made the stretch worse: 17 / 28 cm. The cut-off was about
|
|
125
|
+
y = 1.2 m on a 1.75 m body; find yours by sweeping it against the probe.
|
|
126
|
+
4. Keep the old relative weights inside each group.
|
|
127
|
+
5. **Do not cut the fused sleeve/torso surface.** The 1.0.x lane cut the cuff-to-hip bridge below the armpit
|
|
128
|
+
(364 / 203 / 79 triangles per LOD). It was real cloth: the cut left holes, and the hoodie's side tore open as
|
|
129
|
+
an arm swung back in the Marketplace walk (`avatar.preview.surface_closed`, 36 cm). Keep every triangle and
|
|
130
|
+
fix the **weights** so the sleeve follows the arm and the hem the hip. Hard or geodesic arm/body splits and
|
|
131
|
+
cut-and-cap also tore the shared armpit and hem (10-23 cm); the rework that passed kept the surface whole,
|
|
132
|
+
re-bound the arms (above) and left a residual cuff/hem stretch of ~10 cm, hidden behind the arm.
|
|
133
|
+
6. Rewrite `JOINTS_0` and `WEIGHTS_0` **in place**, identically on every LOD. Leave `POSITION` alone.
|
|
134
|
+
|
|
135
|
+
**Hands fused to the thighs.** Meshy generates in an A-pose with the hands resting on the thighs, and
|
|
136
|
+
its mesher merges the two surfaces. A strip of triangles then runs from the inside of the hand and
|
|
137
|
+
forearm to the outside of the thigh, and the rigger spreads thigh weight over the hand and hand weight
|
|
138
|
+
over the thigh. Standing, it looks fine. In the walk the arm swings forward as the leg swings back, and
|
|
139
|
+
the strip tears into a sheet from the wrist to the knee, or into a "sleeve sheet" from the forearm.
|
|
140
|
+
- Measure it before trusting a render. Count triangles with one corner owned (>= 0.5 weight) by an arm
|
|
141
|
+
and another by a leg. Clean Meshy rigs have 0–8. Dreamer item 4400fe58 had 342: skin_intact read
|
|
142
|
+
4.5% stretched (limit 1.5%), and the critic saw a smeared sheet from the arm to the rear leg.
|
|
143
|
+
- The importer repairs it. `helix character import` cuts the bridge, closes both holes, and strips the
|
|
144
|
+
other limb's weight from each side, on the full mesh and again on every LOD. It is on by default
|
|
145
|
+
(`--limb-bridges repair`); `--limb-bridges report` logs what it would change and changes nothing.
|
|
146
|
+
It sides contact vertices by geometry, because the bled weights cannot tell the thigh under the hand
|
|
147
|
+
from the hand. It keeps the cut only when a two-stride stretch measure falls. It never cuts any other
|
|
148
|
+
pair, so skirts, pockets, capes and sleeves are left alone.
|
|
149
|
+
- A Dreamer draft built before the importer had this step keeps the defect. Re-fetch the raw rig and
|
|
150
|
+
re-import it. 4400fe58 re-imported this way fell from 4.5% to 0.09% edge stretch and passed visual
|
|
151
|
+
QA.
|
|
152
|
+
- The caps are flat patches under the arm, in one cloth colour. They are hidden in normal play. Look
|
|
153
|
+
at them close up anyway: a cap that folds or takes a seam texel reads as a dark shard at the hip.
|
|
154
|
+
|
|
155
|
+
**The legs fuse too.** Inner-thigh vertices often carry both `thigh_l` and `thigh_r` weight. That
|
|
156
|
+
gave 14 cm of stretch at a run after the arms were fixed. Snap each welded group to the leg on its
|
|
157
|
+
side of the mid-plane, and cut the crotch bridge triangles. Check the front of the crotch in a
|
|
158
|
+
render afterwards: the cut can leave a small notch.
|
|
159
|
+
|
|
160
|
+
Measure the result against a base body; see `qa`. Torso vertices with upper-arm weight fell from
|
|
161
|
+
4,121 to 169. Worst stretch fell from 9.1 to 5.8 cm walking and from 16.1 to 8.9 cm running, against
|
|
162
|
+
Base Female's 0 and 4.4 cm on the same probe.
|
|
163
|
+
|
|
164
|
+
## 6. Dynamics
|
|
165
|
+
|
|
166
|
+
A generated character is **one fused shell**: hair, hood, face and body are welded together. So:
|
|
167
|
+
- select hair by its baked colour per LOD, and blend weights through a diffused field;
|
|
168
|
+
- anchor back hair on the bone it rests on (`spine_05` on a hood);
|
|
169
|
+
- **drop front locks on hooded or high-collar characters**. They smeared into the collar while
|
|
170
|
+
running.
|
|
171
|
+
|
|
172
|
+
The full method and values are in `dynamics`.
|
|
173
|
+
|
|
174
|
+
## 7. What you cannot fix, so say so
|
|
175
|
+
|
|
176
|
+
- **Some outfits come out pale and glossy.** The material ships roughness around 0.45, so a
|
|
177
|
+
cream or white hoodie reads washed out and near-glowing under the engine's lights, against its own
|
|
178
|
+
albedo. The critic fails `avatar.textures` for it, and its verdict varies run to run. Check the
|
|
179
|
+
roughness the import kept before blaming the lights.
|
|
180
|
+
- **Thumbnails may come back flat-shaded.** The server thumbnail renderer may draw triangles
|
|
181
|
+
flat-shaded, which makes a smooth avatar look faceted in listings. That is still being checked.
|
|
182
|
+
Judge shading in the visual-QA frames, not in the thumbnail.
|
|
183
|
+
|
|
184
|
+
- The face carries no blink or viseme channels: `face: none`. The avatar is mute in voice chat. A
|
|
185
|
+
projected, calm closed-mouth face texture reads better than a smeared one.
|
|
186
|
+
- Generated texture flaws (smeared eye or brow texels, ribbed hair, melted laces, fringe texels)
|
|
187
|
+
survive every tuning pass. Both generated lanes exited their critic loop on a plateau of 5.8–7.0,
|
|
188
|
+
not a win. Report that honestly.
|
|
189
|
+
- Toes usually point about 25° off forward because of where the foot joint was placed. It is a
|
|
190
|
+
visual-QA **warning**, not a failure. Check the feet in the walk frames.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Source: a rigged game or downloaded character (rips, mods, Sketchfab, FBX/GLB)
|
|
2
|
+
|
|
3
|
+
Lanes: Daft Punk (Fortnite rips), T-800 (a Mortal Kombat 11 model from the SFM workshop), Iron Man
|
|
4
|
+
Mark III (a Garry's Mod player model).
|
|
5
|
+
|
|
6
|
+
## Acquire, and score the candidates
|
|
7
|
+
|
|
8
|
+
- **Prefer a skinned, textured source on an Epic- or Valve-style biped.** The deciding features are:
|
|
9
|
+
- real skinning;
|
|
10
|
+
- textures at 1–2k;
|
|
11
|
+
- the right variant (outfit, mark, era);
|
|
12
|
+
- enough triangles to decimate from: 100k–450k worked.
|
|
13
|
+
- **Reject** these:
|
|
14
|
+
- a low-poly console rip when a better source exists (Iron Man passed over a 10k PS3 rip);
|
|
15
|
+
- a model of the wrong variant;
|
|
16
|
+
- a model you cannot download.
|
|
17
|
+
- **Tools that worked:**
|
|
18
|
+
- Workshop items: `steamcmd +login anonymous +workshop_download_item <app> <id>`.
|
|
19
|
+
- Source-engine `.mdl`: Blender with SourceIO.
|
|
20
|
+
- Textures: `srctools` to decode VTF.
|
|
21
|
+
- A viewer-only web model: hook WebGL in a headless page and decode its buffers. This reproduced
|
|
22
|
+
C-3PO's 120,988 triangles exactly.
|
|
23
|
+
- **Record what is missing.** Daft Punk's sequin outfit was separate geometry the rip lacked, so the
|
|
24
|
+
leather outfit shipped. Do not invent parts.
|
|
25
|
+
|
|
26
|
+
## Pose and rig
|
|
27
|
+
|
|
28
|
+
Choose by what the source gives you. The full table is in `rigging`.
|
|
29
|
+
|
|
30
|
+
| Source | Approach |
|
|
31
|
+
| --- | --- |
|
|
32
|
+
| Clean game skinning on a mannequin-like skeleton (Fortnite) | Keep the weights. Rename the 68 canonical joints, and fold every helper (twist helpers, `deform_*`, `Dyn_*`, `attach_*`, metacarpals, `*_end`) into its nearest canonical ancestor. |
|
|
33
|
+
| A posed mesh with dubious weights (MK11) | Aim the limbs at the canonical A-pose, then bind **per part**. Drop influences from bones more than 0.3 m away. |
|
|
34
|
+
| A Valve biped (GMod) | Re-pose to the A-pose: aim each segment, roll the hands onto the canonical palm axis (60 % forearm twist, 40 % wrist), and **yaw the feet only**. Then build a fresh rig with the Valve weights renamed. Split shoulder pads 50/50 between clavicle and upper arm, and make plates rigid at a 75 % dominant share. |
|
|
35
|
+
|
|
36
|
+
Before import, lift the soles to just above Y = 0. T-800 used 1 cm and Iron Man 8 mm, measured
|
|
37
|
+
against a canonical idle.
|
|
38
|
+
|
|
39
|
+
## Materials
|
|
40
|
+
|
|
41
|
+
Convert the game material model to glTF PBR and keep it to **1–2 materials**, because draws are
|
|
42
|
+
paid once per LOD.
|
|
43
|
+
|
|
44
|
+
| Source maps | glTF |
|
|
45
|
+
| --- | --- |
|
|
46
|
+
| Diffuse | Base colour |
|
|
47
|
+
| Spec/gloss or a packed S map (R = spec, G = roughness, B = metallic) | ORM |
|
|
48
|
+
| DirectX-convention normals | **Flip green** to the OpenGL convention |
|
|
49
|
+
| Glowing parts: visors, eyes, reactors, displays | An emissive map. Paint animated displays the export lost, through a UV-to-surface probe. |
|
|
50
|
+
|
|
51
|
+
Lessons from the critic rounds:
|
|
52
|
+
- Fully metallic 1.0 chrome renders **near black** under the world sky. 0.9–0.92 beat 1.0.
|
|
53
|
+
- A slightly darker chrome lost its round. Keep the source's value unless the reference says
|
|
54
|
+
otherwise.
|
|
55
|
+
- **The normal map needs UASTC.** An ETC1S normal or base map reads as hammered foil under the
|
|
56
|
+
marketplace's room lighting. ETC1S is fine for ORM, emissive, and every LOD1/LOD2 map.
|
|
57
|
+
|
|
58
|
+
## Geometry and LODs
|
|
59
|
+
|
|
60
|
+
- Cull what can never be seen. An embree visibility pass kept 53 % of Iron Man's triangles.
|
|
61
|
+
- Close see-through gaps between plates with a dark undersuit shell. A near-black underlayer read as
|
|
62
|
+
holes in the critic round; gunmetal won.
|
|
63
|
+
- LOD0 can be a baked low-poly. Use Blender collapse with area-weighted normals, then a Cycles bake
|
|
64
|
+
of base colour, normal, ORM and emissive onto an xatlas atlas.
|
|
65
|
+
- **Hard-surface LOD1 and LOD2 tear into shards when decimated directly.** Use either:
|
|
66
|
+
- per-region meshoptimizer ratios (T-800: head 0.55, hands 0.6, feet 0.3, body 0.3); or
|
|
67
|
+
- voxel-remeshed closed shells at 12 mm and 20 mm, decimated and baked the same way (Iron Man).
|
|
68
|
+
- **The importer's own LODs scramble a many-chart atlas.** On Iron Man they kept only 49 % and 37 %
|
|
69
|
+
UV coherence. Fixes:
|
|
70
|
+
- pass `--no-bake`;
|
|
71
|
+
- pass `--lock-border` to stop seams opening;
|
|
72
|
+
- or build LOD1/LOD2 yourself and swap them into the imported file by joint name, then run the
|
|
73
|
+
importer's own finishing checks again (self-contained scenes, canonical geometry, split face).
|
|
74
|
+
|
|
75
|
+
## Import
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
helix character import src.glb --map identity-map.json --fit source -o imp --name <slug> \
|
|
79
|
+
--lod-tris 48000,8000,3900 --tex-size 2048 --ktx-mode etc1s --ktx-quality 255 --no-bake --lock-border
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`import_character` exposes these flags too: `map`, `fit`, `bake`, `lockBorder`, `ktxMode`. Stay under
|
|
83
|
+
60k triangles across all LODs, 12 draws and 10 MiB. Iron Man shipped at 9.75 MiB with a UASTC normal
|
|
84
|
+
map.
|
|
85
|
+
|
|
86
|
+
## A helmet or a mask
|
|
87
|
+
|
|
88
|
+
Keep a helmet as its own mesh if it needs its own material, and list it in
|
|
89
|
+
`helixAvatar.firstPersonHide` on **every scene**, for example `["HelmetFaceMesh", "FaceMesh"]`.
|
|
90
|
+
Otherwise the first-person camera looks out through the inside of the helmet.
|