@helix3/helix-cli 0.1.15-helix3.227 → 0.1.15-helix3.229
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/dist/character/avatarPreflight.d.ts +36 -7
- package/dist/character/avatarPreflight.js +108 -2
- package/dist/character/avatarPreflight.js.map +1 -1
- package/dist/glb/draw-calls.d.ts +60 -0
- package/dist/glb/draw-calls.js +160 -0
- package/dist/glb/draw-calls.js.map +1 -0
- package/dist/glb/index.d.ts +1 -0
- package/dist/glb/index.js +3 -1
- package/dist/glb/index.js.map +1 -1
- package/dist/lib.js +25 -2
- package/dist/lib.js.map +1 -1
- package/dist/sceneElements/continuum.d.ts +7 -1
- package/dist/sceneElements/continuum.js +73 -4
- package/dist/sceneElements/continuum.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/plugins/beamng-vehicle/bin/__pycache__/beamng_abs.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/beamng_expression.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/beamng_jbeam.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/beamng_materials.cpython-312.pyc +0 -0
|
@@ -31,6 +31,41 @@ export declare const WEARABLE_LIMITS: {
|
|
|
31
31
|
readonly maxFileBytes: number;
|
|
32
32
|
readonly maxMaterials: 4;
|
|
33
33
|
};
|
|
34
|
+
/** The backend's `VEHICLE_MAX_*` defaults. A vehicle is not furniture and is not judged as such. */
|
|
35
|
+
export declare const VEHICLE_LIMITS: {
|
|
36
|
+
readonly maxTriangles: 90000;
|
|
37
|
+
readonly maxTextureEdge: 2048;
|
|
38
|
+
readonly maxFileBytes: number;
|
|
39
|
+
readonly maxMaterials: 48;
|
|
40
|
+
};
|
|
41
|
+
/** The backend's `WEAPON_MAX_*` defaults. Held at arm's length in first person; not furniture either. */
|
|
42
|
+
export declare const WEAPON_LIMITS: {
|
|
43
|
+
readonly maxTriangles: 30000;
|
|
44
|
+
readonly maxTextureEdge: 2048;
|
|
45
|
+
readonly maxFileBytes: number;
|
|
46
|
+
readonly maxMaterials: 12;
|
|
47
|
+
};
|
|
48
|
+
/** The backend's `HOME_ITEM_MAX_*` defaults. */
|
|
49
|
+
export declare const HOME_ITEM_LIMITS: {
|
|
50
|
+
readonly maxTriangles: 15000;
|
|
51
|
+
readonly maxTextureEdge: 2048;
|
|
52
|
+
readonly maxFileBytes: number;
|
|
53
|
+
readonly maxMaterials: 6;
|
|
54
|
+
};
|
|
55
|
+
/** Kinds this preflight can judge. A kind absent here is left entirely to the server. */
|
|
56
|
+
export type PreflightKind = 'avatar' | 'wearable' | 'add_on' | 'home_item' | 'vehicle' | 'weapon';
|
|
57
|
+
/**
|
|
58
|
+
* THE DRAW-CALL WALL, PER KIND — the backend's `*_MAX_DRAW_CALLS` defaults.
|
|
59
|
+
*
|
|
60
|
+
* A draw call is one glTF mesh primitive, counted once per node that places it.
|
|
61
|
+
* It is NOT the material count, which is only a lower bound: the published
|
|
62
|
+
* reference vehicle bound 43 materials across 188 primitives and so cleared a
|
|
63
|
+
* 48-material ceiling by 11 while submitting 188 batches.
|
|
64
|
+
*
|
|
65
|
+
* Design targets, which this does not enforce but the report prints, are half
|
|
66
|
+
* of each: character 6, wearable 2, add-on 4, item 8, vehicle 12, weapon 4.
|
|
67
|
+
*/
|
|
68
|
+
export declare const DRAW_CALL_LIMITS: Record<PreflightKind, number>;
|
|
34
69
|
export type PreflightSeverity = 'error' | 'warning';
|
|
35
70
|
export interface PreflightFinding {
|
|
36
71
|
severity: PreflightSeverity;
|
|
@@ -49,12 +84,6 @@ export interface AvatarPreflightResult {
|
|
|
49
84
|
firstPersonHide: number | null;
|
|
50
85
|
};
|
|
51
86
|
}
|
|
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;
|
|
87
|
+
export declare function preflightAvatar(buffer: Buffer, kind?: PreflightKind): AvatarPreflightResult;
|
|
59
88
|
/** One line per finding, plus the capability line, for the CLI's own output. */
|
|
60
89
|
export declare function formatPreflight(result: AvatarPreflightResult): string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.WEARABLE_LIMITS = exports.AVATAR_LIMITS = void 0;
|
|
3
|
+
exports.DRAW_CALL_LIMITS = exports.HOME_ITEM_LIMITS = exports.WEAPON_LIMITS = exports.VEHICLE_LIMITS = exports.WEARABLE_LIMITS = exports.AVATAR_LIMITS = void 0;
|
|
4
4
|
exports.preflightAvatar = preflightAvatar;
|
|
5
5
|
exports.formatPreflight = formatPreflight;
|
|
6
6
|
const glb_1 = require("../glb");
|
|
@@ -36,6 +36,56 @@ exports.WEARABLE_LIMITS = {
|
|
|
36
36
|
maxFileBytes: 6 * 1024 * 1024,
|
|
37
37
|
maxMaterials: 4,
|
|
38
38
|
};
|
|
39
|
+
/** The backend's `VEHICLE_MAX_*` defaults. A vehicle is not furniture and is not judged as such. */
|
|
40
|
+
exports.VEHICLE_LIMITS = {
|
|
41
|
+
maxTriangles: 90_000,
|
|
42
|
+
maxTextureEdge: 2048,
|
|
43
|
+
maxFileBytes: 16 * 1024 * 1024,
|
|
44
|
+
maxMaterials: 48,
|
|
45
|
+
};
|
|
46
|
+
/** The backend's `WEAPON_MAX_*` defaults. Held at arm's length in first person; not furniture either. */
|
|
47
|
+
exports.WEAPON_LIMITS = {
|
|
48
|
+
maxTriangles: 30_000,
|
|
49
|
+
maxTextureEdge: 2048,
|
|
50
|
+
maxFileBytes: 8 * 1024 * 1024,
|
|
51
|
+
maxMaterials: 12,
|
|
52
|
+
};
|
|
53
|
+
/** The backend's `HOME_ITEM_MAX_*` defaults. */
|
|
54
|
+
exports.HOME_ITEM_LIMITS = {
|
|
55
|
+
maxTriangles: 15_000,
|
|
56
|
+
maxTextureEdge: 2048,
|
|
57
|
+
maxFileBytes: 8 * 1024 * 1024,
|
|
58
|
+
maxMaterials: 6,
|
|
59
|
+
};
|
|
60
|
+
const LIMITS_BY_KIND = {
|
|
61
|
+
avatar: exports.AVATAR_LIMITS,
|
|
62
|
+
wearable: exports.WEARABLE_LIMITS,
|
|
63
|
+
// An add-on is judged against the wearable ceilings server-side: several are
|
|
64
|
+
// mounted at once onto a host already paying its own budget.
|
|
65
|
+
add_on: exports.WEARABLE_LIMITS,
|
|
66
|
+
home_item: exports.HOME_ITEM_LIMITS,
|
|
67
|
+
vehicle: exports.VEHICLE_LIMITS,
|
|
68
|
+
weapon: exports.WEAPON_LIMITS,
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* THE DRAW-CALL WALL, PER KIND — the backend's `*_MAX_DRAW_CALLS` defaults.
|
|
72
|
+
*
|
|
73
|
+
* A draw call is one glTF mesh primitive, counted once per node that places it.
|
|
74
|
+
* It is NOT the material count, which is only a lower bound: the published
|
|
75
|
+
* reference vehicle bound 43 materials across 188 primitives and so cleared a
|
|
76
|
+
* 48-material ceiling by 11 while submitting 188 batches.
|
|
77
|
+
*
|
|
78
|
+
* Design targets, which this does not enforce but the report prints, are half
|
|
79
|
+
* of each: character 6, wearable 2, add-on 4, item 8, vehicle 12, weapon 4.
|
|
80
|
+
*/
|
|
81
|
+
exports.DRAW_CALL_LIMITS = {
|
|
82
|
+
avatar: 12,
|
|
83
|
+
wearable: 4,
|
|
84
|
+
add_on: 8,
|
|
85
|
+
home_item: 16,
|
|
86
|
+
vehicle: 24,
|
|
87
|
+
weapon: 8,
|
|
88
|
+
};
|
|
39
89
|
/**
|
|
40
90
|
* Core joints the runtime needs to embed a body at all.
|
|
41
91
|
*
|
|
@@ -71,8 +121,59 @@ function readSceneExtras(buffer) {
|
|
|
71
121
|
* `kind` picks the ceiling set: an avatar and a wearable are the same file format with different budgets,
|
|
72
122
|
* because several wearables are worn on top of one avatar.
|
|
73
123
|
*/
|
|
124
|
+
/**
|
|
125
|
+
* Which parts must stay separate primitives, per kind.
|
|
126
|
+
*
|
|
127
|
+
* "Merge your meshes" is correct advice right up until it destroys the thing
|
|
128
|
+
* that made the item interactive. The runtime resolves articulated parts BY
|
|
129
|
+
* NODE NAME — `getObjectByName` for a vehicle's articulators and lamps, socket
|
|
130
|
+
* source names for its wheels — so joining them does not just merge geometry,
|
|
131
|
+
* it deletes the handle the runtime steers by. Naming the specific parts per
|
|
132
|
+
* kind is the difference between advice a creator can follow safely and advice
|
|
133
|
+
* that breaks their car the first time someone opens a door.
|
|
134
|
+
*/
|
|
135
|
+
const ARTICULATED_PARTS = {
|
|
136
|
+
avatar: 'the head, hands and anything a face or IK target drives',
|
|
137
|
+
wearable: 'anything a bone or blendshape drives',
|
|
138
|
+
add_on: 'anything mounted on a socket or driven by the host',
|
|
139
|
+
home_item: 'lids, drawers, doors and anything an interaction opens',
|
|
140
|
+
vehicle: 'wheels, doors, hood, boot, steering wheel and lamps',
|
|
141
|
+
weapon: 'the magazine, bolt, trigger and optic',
|
|
142
|
+
};
|
|
143
|
+
/**
|
|
144
|
+
* The local rejection text — the same facts the server will state, minus the round trip.
|
|
145
|
+
*
|
|
146
|
+
* It has to carry more than "too many draw calls" because the creator cannot see draw calls in any tool they
|
|
147
|
+
* own. So: what the number is, what a draw call IS, which materials are responsible by name, how much of the
|
|
148
|
+
* overage is free to fix, and which of the two fixes each part needs. The split matters — telling someone to
|
|
149
|
+
* atlas textures when their problem is unmerged geometry sends them down a much harder road than they need.
|
|
150
|
+
*/
|
|
151
|
+
function describeDrawCallOverage(breakdown, limit, kind) {
|
|
152
|
+
const lines = [
|
|
153
|
+
`${breakdown.drawCalls.toLocaleString()} draw calls exceeds the ${limit} ${kind.replace('_', ' ')} cap `
|
|
154
|
+
+ '(one draw call = one glTF mesh primitive, counted once per node that places it — not one per material, '
|
|
155
|
+
+ 'and not one per object in your DCC)',
|
|
156
|
+
];
|
|
157
|
+
if (breakdown.offenders.length > 0) {
|
|
158
|
+
lines.push(' heaviest materials: '
|
|
159
|
+
+ breakdown.offenders.map((o) => `${o.material} x${o.drawCalls}`).join(', '));
|
|
160
|
+
}
|
|
161
|
+
if (breakdown.mergeable > 0) {
|
|
162
|
+
lines.push(` ${breakdown.mergeable.toLocaleString()} of these are FREE to remove: the geometry binds only `
|
|
163
|
+
+ `${breakdown.boundMaterials} distinct material(s), so parts sharing one can be joined into a single `
|
|
164
|
+
+ 'primitive with no visual change (Blender: select them, Ctrl+J, then Merge by Distance). Do NOT join '
|
|
165
|
+
+ `parts the runtime moves separately — ${ARTICULATED_PARTS[kind]} — they are resolved by node name.`);
|
|
166
|
+
}
|
|
167
|
+
if (breakdown.boundMaterials > limit) {
|
|
168
|
+
lines.push(` merging alone cannot reach the cap: ${breakdown.boundMaterials} distinct materials is a floor of `
|
|
169
|
+
+ `${breakdown.boundMaterials} draws. Atlas the set down to ${limit} or fewer first.`);
|
|
170
|
+
}
|
|
171
|
+
lines.push(' if this item is already published, the server allows a grace window for updates — pass --allow-oversize '
|
|
172
|
+
+ 'to send it and let the server decide.');
|
|
173
|
+
return lines.join('\n');
|
|
174
|
+
}
|
|
74
175
|
function preflightAvatar(buffer, kind = 'avatar') {
|
|
75
|
-
const limits = kind
|
|
176
|
+
const limits = LIMITS_BY_KIND[kind] ?? exports.HOME_ITEM_LIMITS;
|
|
76
177
|
const summary = (0, glb_1.summarizeGlb)(buffer);
|
|
77
178
|
const extras = readSceneExtras(buffer);
|
|
78
179
|
const findings = [];
|
|
@@ -88,6 +189,11 @@ function preflightAvatar(buffer, kind = 'avatar') {
|
|
|
88
189
|
if (summary.maxTextureEdge > limits.maxTextureEdge) {
|
|
89
190
|
fail('texture-edge', `largest texture edge ${summary.maxTextureEdge}px exceeds the ${limits.maxTextureEdge}px cap`);
|
|
90
191
|
}
|
|
192
|
+
const drawCalls = (0, glb_1.measureDrawCalls)(buffer);
|
|
193
|
+
const drawCallLimit = exports.DRAW_CALL_LIMITS[kind] ?? exports.DRAW_CALL_LIMITS.home_item;
|
|
194
|
+
if (!drawCalls.unreadable && drawCalls.drawCalls > drawCallLimit) {
|
|
195
|
+
fail('draw-calls', describeDrawCallOverage(drawCalls, drawCallLimit, kind));
|
|
196
|
+
}
|
|
91
197
|
if (summary.sizeBytes > limits.maxFileBytes) {
|
|
92
198
|
const mb = (n) => `${(n / 1024 / 1024).toFixed(1)} MB`;
|
|
93
199
|
fail('file-size', `${mb(summary.sizeBytes)} exceeds the ${mb(limits.maxFileBytes)} ${kind} cap`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"avatarPreflight.js","sourceRoot":"","sources":["../../src/character/avatarPreflight.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"avatarPreflight.js","sourceRoot":"","sources":["../../src/character/avatarPreflight.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DRAW CALLS — what the renderer will submit for this GLB, measured locally.
|
|
3
|
+
*
|
|
4
|
+
* WHY A CREATOR NEEDS THIS BEFORE UPLOADING. Draw calls are the one publish ceiling that no DCC tool displays.
|
|
5
|
+
* Blender shows objects, materials and triangles; a glTF viewer shows nodes and meshes. None of them shows the
|
|
6
|
+
* number the platform actually judges, so a creator can look at a "clean" file in every tool they own and still
|
|
7
|
+
* be refused. Learning that after a 9 MB upload is the round trip this preflight exists to remove.
|
|
8
|
+
*
|
|
9
|
+
* WHY THIS IS A SECOND IMPLEMENTATION, DELIBERATELY. The authoritative copy is `asset-verification.ts` in
|
|
10
|
+
* helix-backend-api, mirrored into universal-item-validator-worker and held there by the `glb-parser-sync` CI
|
|
11
|
+
* check. It cannot import this module, and this module cannot be the one it calls: both of those hosts consume
|
|
12
|
+
* this package from npm, so a measurement added here reaches them only after a release of
|
|
13
|
+
* `@hypersoniclabs/helix-cli` and a lockfile bump in two repositories — which would mean the server could not
|
|
14
|
+
* enforce the budget in the same change that defines it. The server gate is the one that counts; this one
|
|
15
|
+
* exists to be EARLY, and it is pinned to the same numbers by `draw-calls.spec.ts`.
|
|
16
|
+
*
|
|
17
|
+
* If the two ever disagree, the server is right and this is the bug. The body below is a verbatim port; keep
|
|
18
|
+
* it that way.
|
|
19
|
+
*/
|
|
20
|
+
/** One material's share of the submissions, for naming worst offenders. */
|
|
21
|
+
export interface DrawCallOffender {
|
|
22
|
+
material: string;
|
|
23
|
+
drawCalls: number;
|
|
24
|
+
triangles: number;
|
|
25
|
+
}
|
|
26
|
+
export interface DrawCallBreakdown {
|
|
27
|
+
/** GPU submissions: mesh primitives, once per node that instantiates the mesh. */
|
|
28
|
+
drawCalls: number;
|
|
29
|
+
/**
|
|
30
|
+
* Distinct materials bound by drawn geometry. This is the FLOOR `drawCalls` can reach by merging geometry
|
|
31
|
+
* alone — below it you must atlas.
|
|
32
|
+
*/
|
|
33
|
+
boundMaterials: number;
|
|
34
|
+
/**
|
|
35
|
+
* `drawCalls - boundMaterials`: submissions removable by merging geometry that already shares a material, at
|
|
36
|
+
* zero visual cost. The single most useful number to hand a creator, because it is the part of the overage
|
|
37
|
+
* that costs them nothing to fix.
|
|
38
|
+
*/
|
|
39
|
+
mergeable: number;
|
|
40
|
+
/** Materials bound by the most primitives, worst first, capped at 5. */
|
|
41
|
+
offenders: DrawCallOffender[];
|
|
42
|
+
/** True when the JSON chunk could not be read; callers must not gate on a guess. */
|
|
43
|
+
unreadable: boolean;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Count what the renderer will submit for this GLB.
|
|
47
|
+
*
|
|
48
|
+
* Instancing rules, stated because they are the whole difference between this
|
|
49
|
+
* number and a naive primitive count:
|
|
50
|
+
* - A mesh referenced by N nodes is drawn N times. Four wheel nodes sharing one
|
|
51
|
+
* wheel mesh are four draws, and a creator who "deduplicated" them has saved
|
|
52
|
+
* bytes, not batches.
|
|
53
|
+
* - A mesh referenced by NO node is not in the scene graph and is not drawn. It
|
|
54
|
+
* is still shipped bytes, which the file-size ceiling already judges.
|
|
55
|
+
* - `EXT_mesh_gpu_instancing` collapses its node's copies into one submission,
|
|
56
|
+
* which is exactly what per-node counting already yields.
|
|
57
|
+
* - Points and lines still submit; they are counted as draws and carry no
|
|
58
|
+
* triangles.
|
|
59
|
+
*/
|
|
60
|
+
export declare function measureDrawCalls(buffer: Buffer): DrawCallBreakdown;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* DRAW CALLS — what the renderer will submit for this GLB, measured locally.
|
|
4
|
+
*
|
|
5
|
+
* WHY A CREATOR NEEDS THIS BEFORE UPLOADING. Draw calls are the one publish ceiling that no DCC tool displays.
|
|
6
|
+
* Blender shows objects, materials and triangles; a glTF viewer shows nodes and meshes. None of them shows the
|
|
7
|
+
* number the platform actually judges, so a creator can look at a "clean" file in every tool they own and still
|
|
8
|
+
* be refused. Learning that after a 9 MB upload is the round trip this preflight exists to remove.
|
|
9
|
+
*
|
|
10
|
+
* WHY THIS IS A SECOND IMPLEMENTATION, DELIBERATELY. The authoritative copy is `asset-verification.ts` in
|
|
11
|
+
* helix-backend-api, mirrored into universal-item-validator-worker and held there by the `glb-parser-sync` CI
|
|
12
|
+
* check. It cannot import this module, and this module cannot be the one it calls: both of those hosts consume
|
|
13
|
+
* this package from npm, so a measurement added here reaches them only after a release of
|
|
14
|
+
* `@hypersoniclabs/helix-cli` and a lockfile bump in two repositories — which would mean the server could not
|
|
15
|
+
* enforce the budget in the same change that defines it. The server gate is the one that counts; this one
|
|
16
|
+
* exists to be EARLY, and it is pinned to the same numbers by `draw-calls.spec.ts`.
|
|
17
|
+
*
|
|
18
|
+
* If the two ever disagree, the server is right and this is the bug. The body below is a verbatim port; keep
|
|
19
|
+
* it that way.
|
|
20
|
+
*/
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
exports.measureDrawCalls = measureDrawCalls;
|
|
23
|
+
const GLB_MAGIC = 0x46546c67;
|
|
24
|
+
const GLB_CHUNK_JSON = 0x4e4f534a;
|
|
25
|
+
/** The glTF JSON chunk, or null when the buffer is not a readable GLB. */
|
|
26
|
+
function readGlbJson(buffer) {
|
|
27
|
+
try {
|
|
28
|
+
if (buffer.length < 20 || buffer.readUInt32LE(0) !== GLB_MAGIC)
|
|
29
|
+
return null;
|
|
30
|
+
const declared = buffer.readUInt32LE(8);
|
|
31
|
+
const end = Math.min(Number.isFinite(declared) && declared > 0 ? declared : buffer.length, buffer.length);
|
|
32
|
+
let offset = 12;
|
|
33
|
+
while (offset + 8 <= end) {
|
|
34
|
+
const length = buffer.readUInt32LE(offset);
|
|
35
|
+
const type = buffer.readUInt32LE(offset + 4);
|
|
36
|
+
const start = offset + 8;
|
|
37
|
+
if (length < 0 || start + length > buffer.length)
|
|
38
|
+
return null;
|
|
39
|
+
if (type === GLB_CHUNK_JSON) {
|
|
40
|
+
const parsed = JSON.parse(buffer.subarray(start, start + length).toString('utf8'));
|
|
41
|
+
return parsed !== null && typeof parsed === 'object'
|
|
42
|
+
? parsed
|
|
43
|
+
: null;
|
|
44
|
+
}
|
|
45
|
+
offset = start + length + ((4 - (length % 4)) % 4);
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** Triangles a primitive contributes, per instance. Non-triangle modes contribute none. */
|
|
54
|
+
function primitiveTriangles(primitive, accessors) {
|
|
55
|
+
const mode = typeof primitive.mode === 'number' ? primitive.mode : 4;
|
|
56
|
+
let vertices = 0;
|
|
57
|
+
if (typeof primitive.indices === 'number') {
|
|
58
|
+
vertices = accessors[primitive.indices]?.count ?? 0;
|
|
59
|
+
}
|
|
60
|
+
else if (typeof primitive.attributes?.POSITION === 'number') {
|
|
61
|
+
vertices = accessors[primitive.attributes.POSITION]?.count ?? 0;
|
|
62
|
+
}
|
|
63
|
+
if (mode === 4)
|
|
64
|
+
return Math.floor(vertices / 3);
|
|
65
|
+
if (mode === 5 || mode === 6)
|
|
66
|
+
return Math.max(0, vertices - 2);
|
|
67
|
+
return 0;
|
|
68
|
+
}
|
|
69
|
+
/** A primitive's material, named for the creator rather than indexed. */
|
|
70
|
+
function primitiveMaterialName(primitive, materials) {
|
|
71
|
+
const index = primitive.material;
|
|
72
|
+
if (typeof index !== 'number')
|
|
73
|
+
return '(no material)';
|
|
74
|
+
const name = materials[index]?.name;
|
|
75
|
+
return typeof name === 'string' && name.length > 0
|
|
76
|
+
? name
|
|
77
|
+
: `material[${index}]`;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Count what the renderer will submit for this GLB.
|
|
81
|
+
*
|
|
82
|
+
* Instancing rules, stated because they are the whole difference between this
|
|
83
|
+
* number and a naive primitive count:
|
|
84
|
+
* - A mesh referenced by N nodes is drawn N times. Four wheel nodes sharing one
|
|
85
|
+
* wheel mesh are four draws, and a creator who "deduplicated" them has saved
|
|
86
|
+
* bytes, not batches.
|
|
87
|
+
* - A mesh referenced by NO node is not in the scene graph and is not drawn. It
|
|
88
|
+
* is still shipped bytes, which the file-size ceiling already judges.
|
|
89
|
+
* - `EXT_mesh_gpu_instancing` collapses its node's copies into one submission,
|
|
90
|
+
* which is exactly what per-node counting already yields.
|
|
91
|
+
* - Points and lines still submit; they are counted as draws and carry no
|
|
92
|
+
* triangles.
|
|
93
|
+
*/
|
|
94
|
+
function measureDrawCalls(buffer) {
|
|
95
|
+
const empty = {
|
|
96
|
+
drawCalls: 0,
|
|
97
|
+
boundMaterials: 0,
|
|
98
|
+
mergeable: 0,
|
|
99
|
+
offenders: [],
|
|
100
|
+
unreadable: true,
|
|
101
|
+
};
|
|
102
|
+
const json = readGlbJson(buffer);
|
|
103
|
+
if (!json)
|
|
104
|
+
return empty;
|
|
105
|
+
const meshes = Array.isArray(json.meshes) ? json.meshes : [];
|
|
106
|
+
const nodes = Array.isArray(json.nodes) ? json.nodes : [];
|
|
107
|
+
const materials = Array.isArray(json.materials)
|
|
108
|
+
? json.materials
|
|
109
|
+
: [];
|
|
110
|
+
const accessors = Array.isArray(json.accessors)
|
|
111
|
+
? json.accessors
|
|
112
|
+
: [];
|
|
113
|
+
if (meshes.length === 0)
|
|
114
|
+
return { ...empty, unreadable: false };
|
|
115
|
+
const instantiations = new Array(meshes.length).fill(0);
|
|
116
|
+
let referenced = 0;
|
|
117
|
+
for (const node of nodes) {
|
|
118
|
+
const index = node?.mesh;
|
|
119
|
+
if (typeof index === 'number' && index >= 0 && index < meshes.length) {
|
|
120
|
+
instantiations[index] += 1;
|
|
121
|
+
referenced += 1;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// A GLB whose nodes reference nothing is either authored without a scene
|
|
125
|
+
// graph or written by a tool we do not model. Judging it as zero draws would
|
|
126
|
+
// wave through the very thing this gate exists to catch, so fall back to the
|
|
127
|
+
// authored primitive count — the count a renderer reaches if anything ever
|
|
128
|
+
// puts these meshes in a scene.
|
|
129
|
+
const fallback = referenced === 0;
|
|
130
|
+
const byMaterial = new Map();
|
|
131
|
+
let drawCalls = 0;
|
|
132
|
+
for (let m = 0; m < meshes.length; m++) {
|
|
133
|
+
const copies = fallback ? 1 : (instantiations[m] ?? 0);
|
|
134
|
+
if (copies === 0)
|
|
135
|
+
continue;
|
|
136
|
+
const primitives = Array.isArray(meshes[m]?.primitives)
|
|
137
|
+
? meshes[m].primitives
|
|
138
|
+
: [];
|
|
139
|
+
for (const primitive of primitives) {
|
|
140
|
+
drawCalls += copies;
|
|
141
|
+
const name = primitiveMaterialName(primitive, materials);
|
|
142
|
+
const bucket = byMaterial.get(name) ?? { drawCalls: 0, triangles: 0 };
|
|
143
|
+
bucket.drawCalls += copies;
|
|
144
|
+
bucket.triangles += primitiveTriangles(primitive, accessors) * copies;
|
|
145
|
+
byMaterial.set(name, bucket);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const offenders = [...byMaterial.entries()]
|
|
149
|
+
.map(([material, counts]) => ({ material, ...counts }))
|
|
150
|
+
.sort((a, b) => b.drawCalls - a.drawCalls || b.triangles - a.triangles)
|
|
151
|
+
.slice(0, 5);
|
|
152
|
+
return {
|
|
153
|
+
drawCalls,
|
|
154
|
+
boundMaterials: byMaterial.size,
|
|
155
|
+
mergeable: Math.max(0, drawCalls - byMaterial.size),
|
|
156
|
+
offenders,
|
|
157
|
+
unreadable: false,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=draw-calls.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"draw-calls.js","sourceRoot":"","sources":["../../src/glb/draw-calls.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;GAkBG;;AA2IH,4CAwEC;AAtLD,MAAM,SAAS,GAAG,UAAU,CAAC;AAC7B,MAAM,cAAc,GAAG,UAAU,CAAC;AAiClC,0EAA0E;AAC1E,SAAS,WAAW,CAAC,MAAc;IACjC,IAAI,CAAC;QACH,IAAI,MAAM,CAAC,MAAM,GAAG,EAAE,IAAI,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC;QAC5E,MAAM,QAAQ,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACxC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAClB,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,EACpE,MAAM,CAAC,MAAM,CACd,CAAC;QACF,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,OAAO,MAAM,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC;YACzB,MAAM,MAAM,GAAG,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;YAC3C,MAAM,IAAI,GAAG,MAAM,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;YAC7C,MAAM,KAAK,GAAG,MAAM,GAAG,CAAC,CAAC;YACzB,IAAI,MAAM,GAAG,CAAC,IAAI,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC,MAAM;gBAAE,OAAO,IAAI,CAAC;YAC9D,IAAI,IAAI,KAAK,cAAc,EAAE,CAAC;gBAC5B,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAChC,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CACxD,CAAC;gBACF,OAAO,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ;oBAClD,CAAC,CAAE,MAAuB;oBAC1B,CAAC,CAAC,IAAI,CAAC;YACX,CAAC;YACD,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACrD,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,2FAA2F;AAC3F,SAAS,kBAAkB,CACzB,SAAwB,EACxB,SAAyB;IAEzB,MAAM,IAAI,GAAG,OAAO,SAAS,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IACrE,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,IAAI,OAAO,SAAS,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;QAC1C,QAAQ,GAAG,SAAS,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,KAAK,IAAI,CAAC,CAAC;IACtD,CAAC;SAAM,IAAI,OAAO,SAAS,CAAC,UAAU,EAAE,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAC9D,QAAQ,GAAG,SAAS,CAAC,SAAS,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,KAAK,IAAI,CAAC,CAAC;IAClE,CAAC;IACD,IAAI,IAAI,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC;IAChD,IAAI,IAAI,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,GAAG,CAAC,CAAC,CAAC;IAC/D,OAAO,CAAC,CAAC;AACX,CAAC;AAED,yEAAyE;AACzE,SAAS,qBAAqB,CAC5B,SAAwB,EACxB,SAAyB;IAEzB,MAAM,KAAK,GAAG,SAAS,CAAC,QAAQ,CAAC;IACjC,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,eAAe,CAAC;IACtD,MAAM,IAAI,GAAG,SAAS,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC;IACpC,OAAO,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;QAChD,CAAC,CAAC,IAAI;QACN,CAAC,CAAC,YAAY,KAAK,GAAG,CAAC;AAC3B,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,SAAgB,gBAAgB,CAAC,MAAc;IAC7C,MAAM,KAAK,GAAsB;QAC/B,SAAS,EAAE,CAAC;QACZ,cAAc,EAAE,CAAC;QACjB,SAAS,EAAE,CAAC;QACZ,SAAS,EAAE,EAAE;QACb,UAAU,EAAE,IAAI;KACjB,CAAC;IAEF,MAAM,IAAI,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;IACjC,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IAExB,MAAM,MAAM,GAAe,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IACzE,MAAM,KAAK,GAAe,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IACtE,MAAM,SAAS,GAAmB,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC;QAC7D,CAAC,CAAC,IAAI,CAAC,SAAS;QAChB,CAAC,CAAC,EAAE,CAAC;IACP,MAAM,SAAS,GAAmB,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC;QAC7D,CAAC,CAAC,IAAI,CAAC,SAAS;QAChB,CAAC,CAAC,EAAE,CAAC;IACP,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,GAAG,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC;IAEhE,MAAM,cAAc,GAAG,IAAI,KAAK,CAAS,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAChE,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,KAAK,GAAG,IAAI,EAAE,IAAI,CAAC;QACzB,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,IAAI,CAAC,IAAI,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC;YACrE,cAAc,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAC3B,UAAU,IAAI,CAAC,CAAC;QAClB,CAAC;IACH,CAAC;IACD,yEAAyE;IACzE,6EAA6E;IAC7E,6EAA6E;IAC7E,2EAA2E;IAC3E,gCAAgC;IAChC,MAAM,QAAQ,GAAG,UAAU,KAAK,CAAC,CAAC;IAElC,MAAM,UAAU,GAAG,IAAI,GAAG,EAGvB,CAAC;IACJ,IAAI,SAAS,GAAG,CAAC,CAAC;IAElB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,MAAM,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QACvD,IAAI,MAAM,KAAK,CAAC;YAAE,SAAS;QAC3B,MAAM,UAAU,GAAoB,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC;YACtE,CAAC,CAAE,MAAM,CAAC,CAAC,CAAC,CAAC,UAA8B;YAC3C,CAAC,CAAC,EAAE,CAAC;QACP,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;YACnC,SAAS,IAAI,MAAM,CAAC;YACpB,MAAM,IAAI,GAAG,qBAAqB,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;YACzD,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC;YACtE,MAAM,CAAC,SAAS,IAAI,MAAM,CAAC;YAC3B,MAAM,CAAC,SAAS,IAAI,kBAAkB,CAAC,SAAS,EAAE,SAAS,CAAC,GAAG,MAAM,CAAC;YACtE,UAAU,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAC/B,CAAC;IACH,CAAC;IAED,MAAM,SAAS,GAAG,CAAC,GAAG,UAAU,CAAC,OAAO,EAAE,CAAC;SACxC,GAAG,CAAC,CAAC,CAAC,QAAQ,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC;SACtD,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,SAAS,CAAC;SACtE,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAEf,OAAO;QACL,SAAS;QACT,cAAc,EAAE,UAAU,CAAC,IAAI;QAC/B,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,SAAS,GAAG,UAAU,CAAC,IAAI,CAAC;QACnD,SAAS;QACT,UAAU,EAAE,KAAK;KAClB,CAAC;AACJ,CAAC"}
|
package/dist/glb/index.d.ts
CHANGED
|
@@ -8,3 +8,4 @@
|
|
|
8
8
|
* their host config/logger coupling is inverted into injected arguments.
|
|
9
9
|
*/
|
|
10
10
|
export { GlbParseError, summarizeGlb, type GlbBoundingBox, type GlbSummary, type GlbTextureInfo, } from './glb-summary';
|
|
11
|
+
export { measureDrawCalls, type DrawCallBreakdown, type DrawCallOffender, } from './draw-calls';
|
package/dist/glb/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.summarizeGlb = exports.GlbParseError = void 0;
|
|
3
|
+
exports.measureDrawCalls = exports.summarizeGlb = exports.GlbParseError = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* `@hypersoniclabs/helix-cli/glb` — shared GLB pipeline modules.
|
|
6
6
|
*
|
|
@@ -13,4 +13,6 @@ exports.summarizeGlb = exports.GlbParseError = void 0;
|
|
|
13
13
|
var glb_summary_1 = require("./glb-summary");
|
|
14
14
|
Object.defineProperty(exports, "GlbParseError", { enumerable: true, get: function () { return glb_summary_1.GlbParseError; } });
|
|
15
15
|
Object.defineProperty(exports, "summarizeGlb", { enumerable: true, get: function () { return glb_summary_1.summarizeGlb; } });
|
|
16
|
+
var draw_calls_1 = require("./draw-calls");
|
|
17
|
+
Object.defineProperty(exports, "measureDrawCalls", { enumerable: true, get: function () { return draw_calls_1.measureDrawCalls; } });
|
|
16
18
|
//# sourceMappingURL=index.js.map
|
package/dist/glb/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/glb/index.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;GAQG;AACH,6CAMuB;AALrB,4GAAA,aAAa,OAAA;AACb,2GAAA,YAAY,OAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/glb/index.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;GAQG;AACH,6CAMuB;AALrB,4GAAA,aAAa,OAAA;AACb,2GAAA,YAAY,OAAA;AAMd,2CAIsB;AAHpB,8GAAA,gBAAgB,OAAA"}
|
package/dist/lib.js
CHANGED
|
@@ -116,6 +116,23 @@ Object.defineProperty(exports, "PLACEMENT_REQUEST_FILENAME", { enumerable: true,
|
|
|
116
116
|
const bundle_1 = require("./bundle");
|
|
117
117
|
const package_1 = require("./package");
|
|
118
118
|
const avatarPreflight_1 = require("./character/avatarPreflight");
|
|
119
|
+
/**
|
|
120
|
+
* The item kinds this process can judge before the upload.
|
|
121
|
+
*
|
|
122
|
+
* A kind absent here is not unchecked — the server checks every kind. It is
|
|
123
|
+
* only unchecked EARLY, which costs a round trip. Adding a kind here means
|
|
124
|
+
* mirroring its server ceilings in `avatarPreflight.ts`, and a mirror that
|
|
125
|
+
* guesses LOOSER numbers than the server is worse than no mirror at all: it
|
|
126
|
+
* prints confident green for a file that will be refused.
|
|
127
|
+
*/
|
|
128
|
+
const PREFLIGHT_KINDS = new Set([
|
|
129
|
+
'avatar',
|
|
130
|
+
'wearable',
|
|
131
|
+
'add_on',
|
|
132
|
+
'home_item',
|
|
133
|
+
'vehicle',
|
|
134
|
+
'weapon',
|
|
135
|
+
]);
|
|
119
136
|
const ability_1 = require("./ability");
|
|
120
137
|
const item_1 = require("./item");
|
|
121
138
|
const achievement_1 = require("./achievement");
|
|
@@ -863,10 +880,16 @@ function planItemPublish(opts) {
|
|
|
863
880
|
if (sizeWarning)
|
|
864
881
|
warnings.push(sizeWarning);
|
|
865
882
|
// ── The publish gate, checked here instead of after the upload ──────────────────────────────────────────
|
|
866
|
-
// Every
|
|
883
|
+
// Every rejection the backend issues for these kinds is statically decidable from the file. Uploading first
|
|
867
884
|
// and learning second costs the creator a round trip to be told a number they could have been told offline —
|
|
868
885
|
// and `--dry-run` exists precisely so the answer arrives before the network does.
|
|
869
|
-
|
|
886
|
+
//
|
|
887
|
+
// This used to cover avatars and wearables only, which is why a vehicle learned about its ceilings the
|
|
888
|
+
// expensive way: the one kind whose files are big enough for the round trip to actually hurt was the one
|
|
889
|
+
// kind not checked. The draw-call budget makes that worse if left alone — draw calls are invisible in every
|
|
890
|
+
// authoring tool a creator owns, so without a local check there is no way to see the number before the
|
|
891
|
+
// server states it.
|
|
892
|
+
if (PREFLIGHT_KINDS.has(opts.kind)) {
|
|
870
893
|
// A preflight that cannot READ the file must not block the publish. `assertLooksLikeGlb` above already
|
|
871
894
|
// rejects what is not a GLB; past that point an unparseable variant — an extension this summariser does
|
|
872
895
|
// not know, a container it cannot walk — is the SERVER's call to make, and refusing locally would turn a
|