@helix3/helix-cli 0.1.14-helix3.205 → 0.1.14-helix3.207

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.
@@ -0,0 +1,60 @@
1
+ import { type GlbSummary } from "../glb";
2
+ /**
3
+ * WHAT THE PLATFORM WILL SAY ABOUT THIS AVATAR, SAID HERE INSTEAD.
4
+ *
5
+ * `publishItem` uploads the GLB byte-for-byte and the backend verifies it on arrival. That is the correct
6
+ * division of labour — the server must never trust a client's word about the file — but it means every
7
+ * rejection costs an upload, and the creator learns their character is a material over the cap after the round
8
+ * trip rather than before it. The failures are all statically checkable from the file, so this checks them.
9
+ *
10
+ * The numbers are the BACKEND's own defaults (`AVATAR_MAX_*` in its config), duplicated deliberately and
11
+ * labelled as such: a client-side preflight that guesses looser limits than the server is worse than no
12
+ * preflight, because it produces confident green output for a file that will be refused. When the server's
13
+ * numbers move, these move with them — and being wrong here costs a round trip, exactly what it costs today.
14
+ *
15
+ * The manifest checks are the other half, and they have no server counterpart at all. A character can pass
16
+ * every numeric limit and still arrive with no physics, no face and no first-person head declaration, which is
17
+ * a silently diminished avatar rather than a rejected one — the worst failure mode this pipeline has, and the
18
+ * one it has already shipped twice.
19
+ */
20
+ /** The backend's `AVATAR_MAX_*` defaults. Mirrored, not derived: this process cannot see that config. */
21
+ export declare const AVATAR_LIMITS: {
22
+ readonly maxTriangles: 60000;
23
+ readonly maxTextureEdge: 2048;
24
+ readonly maxFileBytes: number;
25
+ readonly maxMaterials: 8;
26
+ };
27
+ /** The wearable ceilings, roughly a third of an avatar's, because several are worn at once. */
28
+ export declare const WEARABLE_LIMITS: {
29
+ readonly maxTriangles: 20000;
30
+ readonly maxTextureEdge: 2048;
31
+ readonly maxFileBytes: number;
32
+ readonly maxMaterials: 4;
33
+ };
34
+ export type PreflightSeverity = 'error' | 'warning';
35
+ export interface PreflightFinding {
36
+ severity: PreflightSeverity;
37
+ /** Stable code so a caller can filter without matching prose. */
38
+ code: string;
39
+ message: string;
40
+ }
41
+ export interface AvatarPreflightResult {
42
+ ok: boolean;
43
+ findings: PreflightFinding[];
44
+ summary: GlbSummary;
45
+ /** What the character declares beyond geometry, for the report. */
46
+ manifests: {
47
+ dynamics: number | null;
48
+ faceChannels: number | null;
49
+ firstPersonHide: number | null;
50
+ };
51
+ }
52
+ /**
53
+ * Check a character GLB against the publish gate without touching the network.
54
+ *
55
+ * `kind` picks the ceiling set: an avatar and a wearable are the same file format with different budgets,
56
+ * because several wearables are worn on top of one avatar.
57
+ */
58
+ export declare function preflightAvatar(buffer: Buffer, kind?: 'avatar' | 'wearable'): AvatarPreflightResult;
59
+ /** One line per finding, plus the capability line, for the CLI's own output. */
60
+ export declare function formatPreflight(result: AvatarPreflightResult): string;
@@ -0,0 +1,144 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WEARABLE_LIMITS = exports.AVATAR_LIMITS = void 0;
4
+ exports.preflightAvatar = preflightAvatar;
5
+ exports.formatPreflight = formatPreflight;
6
+ const glb_1 = require("../glb");
7
+ /**
8
+ * WHAT THE PLATFORM WILL SAY ABOUT THIS AVATAR, SAID HERE INSTEAD.
9
+ *
10
+ * `publishItem` uploads the GLB byte-for-byte and the backend verifies it on arrival. That is the correct
11
+ * division of labour — the server must never trust a client's word about the file — but it means every
12
+ * rejection costs an upload, and the creator learns their character is a material over the cap after the round
13
+ * trip rather than before it. The failures are all statically checkable from the file, so this checks them.
14
+ *
15
+ * The numbers are the BACKEND's own defaults (`AVATAR_MAX_*` in its config), duplicated deliberately and
16
+ * labelled as such: a client-side preflight that guesses looser limits than the server is worse than no
17
+ * preflight, because it produces confident green output for a file that will be refused. When the server's
18
+ * numbers move, these move with them — and being wrong here costs a round trip, exactly what it costs today.
19
+ *
20
+ * The manifest checks are the other half, and they have no server counterpart at all. A character can pass
21
+ * every numeric limit and still arrive with no physics, no face and no first-person head declaration, which is
22
+ * a silently diminished avatar rather than a rejected one — the worst failure mode this pipeline has, and the
23
+ * one it has already shipped twice.
24
+ */
25
+ /** The backend's `AVATAR_MAX_*` defaults. Mirrored, not derived: this process cannot see that config. */
26
+ exports.AVATAR_LIMITS = {
27
+ maxTriangles: 60_000,
28
+ maxTextureEdge: 2048,
29
+ maxFileBytes: 10 * 1024 * 1024,
30
+ maxMaterials: 8,
31
+ };
32
+ /** The wearable ceilings, roughly a third of an avatar's, because several are worn at once. */
33
+ exports.WEARABLE_LIMITS = {
34
+ maxTriangles: 20_000,
35
+ maxTextureEdge: 2048,
36
+ maxFileBytes: 6 * 1024 * 1024,
37
+ maxMaterials: 4,
38
+ };
39
+ /**
40
+ * Core joints the runtime needs to embed a body at all.
41
+ *
42
+ * Deliberately the SHORT list — the handful whose absence means the avatar cannot be driven — rather than all
43
+ * 68. A preflight that reimplements the server's full skeleton check will drift from it; this one catches the
44
+ * case where the file is not a HELIX character at all.
45
+ */
46
+ const REQUIRED_JOINTS = ['root', 'pelvis', 'spine_01', 'head', 'hand_l', 'hand_r', 'foot_l', 'foot_r'];
47
+ /**
48
+ * Read the scene extras straight out of the GLB's JSON chunk.
49
+ *
50
+ * Parsed here rather than through `@gltf-transform` because a preflight must not fail on a file the reader
51
+ * cannot fully load: an unknown extension, a Draco stream without its decoder, a KTX2 texture. The one thing
52
+ * this needs is the JSON chunk, and every valid GLB has one.
53
+ */
54
+ function readSceneExtras(buffer) {
55
+ if (buffer.byteLength < 20)
56
+ return {};
57
+ const jsonLength = buffer.readUInt32LE(12);
58
+ if (jsonLength <= 0 || 20 + jsonLength > buffer.byteLength)
59
+ return {};
60
+ try {
61
+ const json = JSON.parse(buffer.subarray(20, 20 + jsonLength).toString('utf8'));
62
+ return json.scenes?.[0]?.extras ?? {};
63
+ }
64
+ catch {
65
+ return {};
66
+ }
67
+ }
68
+ /**
69
+ * Check a character GLB against the publish gate without touching the network.
70
+ *
71
+ * `kind` picks the ceiling set: an avatar and a wearable are the same file format with different budgets,
72
+ * because several wearables are worn on top of one avatar.
73
+ */
74
+ function preflightAvatar(buffer, kind = 'avatar') {
75
+ const limits = kind === 'avatar' ? exports.AVATAR_LIMITS : exports.WEARABLE_LIMITS;
76
+ const summary = (0, glb_1.summarizeGlb)(buffer);
77
+ const extras = readSceneExtras(buffer);
78
+ const findings = [];
79
+ const fail = (code, message) => { findings.push({ severity: 'error', code, message }); };
80
+ const warn = (code, message) => { findings.push({ severity: 'warning', code, message }); };
81
+ if (summary.triangleCount > limits.maxTriangles) {
82
+ fail('triangles', `${summary.triangleCount.toLocaleString()} triangles exceeds the ${limits.maxTriangles.toLocaleString()} ${kind} cap`);
83
+ }
84
+ if (summary.materialCount > limits.maxMaterials) {
85
+ fail('materials', `${summary.materialCount} materials exceeds the ${limits.maxMaterials}-material ${kind} cap — `
86
+ + 'atlas them at import (this is the limit a converted VRM fails first, and by the widest margin)');
87
+ }
88
+ if (summary.maxTextureEdge > limits.maxTextureEdge) {
89
+ fail('texture-edge', `largest texture edge ${summary.maxTextureEdge}px exceeds the ${limits.maxTextureEdge}px cap`);
90
+ }
91
+ if (summary.sizeBytes > limits.maxFileBytes) {
92
+ const mb = (n) => `${(n / 1024 / 1024).toFixed(1)} MB`;
93
+ fail('file-size', `${mb(summary.sizeBytes)} exceeds the ${mb(limits.maxFileBytes)} ${kind} cap`);
94
+ }
95
+ if (kind === 'avatar') {
96
+ if (summary.skinCount === 0) {
97
+ fail('no-skin', 'an avatar must carry a skinned mesh; this file has none');
98
+ }
99
+ const joints = new Set(summary.boneNames);
100
+ const missing = REQUIRED_JOINTS.filter((joint) => !joints.has(joint));
101
+ if (missing.length > 0) {
102
+ fail('skeleton', `missing core helix-humanoid joints: ${missing.join(', ')} — the runtime cannot drive this body`);
103
+ }
104
+ // A cm-scale rig binds pristinely and renders correctly at rest, then explodes the moment a clip plays.
105
+ if (summary.skeletonSpanY !== null && summary.skeletonSpanY > 10) {
106
+ fail('rig-scale', `skeleton spans ${summary.skeletonSpanY.toFixed(1)}m — the rig is not in metres`);
107
+ }
108
+ }
109
+ const dynamics = Array.isArray(extras.helixDynamics?.chains) ? extras.helixDynamics.chains.length : null;
110
+ const faceChannels = extras.helixFace?.channels ? Object.keys(extras.helixFace.channels).length : null;
111
+ const firstPersonHide = Array.isArray(extras.helixAvatar?.firstPersonHide)
112
+ ? extras.helixAvatar.firstPersonHide.length
113
+ : null;
114
+ if (kind === 'avatar') {
115
+ // Warnings, not errors: a character with no hair does not need physics, and a stylised avatar may have no
116
+ // visemes. Each one is a capability the platform WILL look for at runtime, so silence about them is how an
117
+ // avatar ships diminished and nobody notices until someone speaks to it.
118
+ if (dynamics === null)
119
+ warn('no-dynamics', 'no helixDynamics — hair, tails and cloth will not move');
120
+ if (faceChannels === null)
121
+ warn('no-face', 'no helixFace — this character cannot lip-sync or blink');
122
+ if (firstPersonHide === null) {
123
+ warn('no-first-person-hide', 'no helixAvatar.firstPersonHide — first person will render this head across the camera unless a mesh '
124
+ + 'happens to be named FaceMesh');
125
+ }
126
+ }
127
+ return {
128
+ ok: findings.every((finding) => finding.severity !== 'error'),
129
+ findings,
130
+ summary,
131
+ manifests: { dynamics, faceChannels, firstPersonHide },
132
+ };
133
+ }
134
+ /** One line per finding, plus the capability line, for the CLI's own output. */
135
+ function formatPreflight(result) {
136
+ const lines = result.findings.map((f) => `${f.severity === 'error' ? '✗' : '⚠'} ${f.message}`);
137
+ const { dynamics, faceChannels, firstPersonHide } = result.manifests;
138
+ lines.push(` ${result.summary.triangleCount.toLocaleString()} tris · ${result.summary.materialCount} materials · `
139
+ + `${result.summary.maxTextureEdge}px · ${(result.summary.sizeBytes / 1024 / 1024).toFixed(1)} MB · `
140
+ + `${result.summary.boneNames.length} joints`);
141
+ lines.push(` physics ${dynamics ?? 0} chains · face ${faceChannels ?? 0} channels · first-person ${firstPersonHide ?? 0} surfaces`);
142
+ return lines.join('\n');
143
+ }
144
+ //# sourceMappingURL=avatarPreflight.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"avatarPreflight.js","sourceRoot":"","sources":["../../src/character/avatarPreflight.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -3832,7 +3832,7 @@ continuum
3832
3832
  const creds = requireAuth();
3833
3833
  const backend = await continuumStagedPackageVersionSupport(creds.apiUrl);
3834
3834
  console.log(JSON.stringify({
3835
- contract: 'helix.creator.publication-capabilities/1',
3835
+ contract: 'helix.creator.continuum-publication-capabilities/1',
3836
3836
  apiOrigin: creds.apiUrl,
3837
3837
  existingObjectSource: true,
3838
3838
  sealedNonCurrentStaging: true,