@series-inc/stowkit-cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/dist/app/blob-store.d.ts +9 -0
  2. package/dist/app/blob-store.js +42 -0
  3. package/dist/app/disk-project.d.ts +84 -0
  4. package/dist/app/disk-project.js +70 -0
  5. package/dist/app/process-cache.d.ts +10 -0
  6. package/dist/app/process-cache.js +126 -0
  7. package/dist/app/state.d.ts +38 -0
  8. package/dist/app/state.js +16 -0
  9. package/dist/app/stowmat-io.d.ts +6 -0
  10. package/dist/app/stowmat-io.js +48 -0
  11. package/dist/app/stowmeta-io.d.ts +14 -0
  12. package/dist/app/stowmeta-io.js +207 -0
  13. package/dist/cleanup.d.ts +3 -0
  14. package/dist/cleanup.js +72 -0
  15. package/dist/cli.d.ts +2 -0
  16. package/dist/cli.js +148 -0
  17. package/dist/core/binary.d.ts +41 -0
  18. package/dist/core/binary.js +118 -0
  19. package/dist/core/constants.d.ts +64 -0
  20. package/dist/core/constants.js +65 -0
  21. package/dist/core/path.d.ts +3 -0
  22. package/dist/core/path.js +27 -0
  23. package/dist/core/types.d.ts +204 -0
  24. package/dist/core/types.js +76 -0
  25. package/dist/encoders/aac-encoder.d.ts +12 -0
  26. package/dist/encoders/aac-encoder.js +179 -0
  27. package/dist/encoders/basis-encoder.d.ts +15 -0
  28. package/dist/encoders/basis-encoder.js +116 -0
  29. package/dist/encoders/draco-encoder.d.ts +11 -0
  30. package/dist/encoders/draco-encoder.js +155 -0
  31. package/dist/encoders/fbx-loader.d.ts +4 -0
  32. package/dist/encoders/fbx-loader.js +540 -0
  33. package/dist/encoders/image-decoder.d.ts +13 -0
  34. package/dist/encoders/image-decoder.js +33 -0
  35. package/dist/encoders/interfaces.d.ts +105 -0
  36. package/dist/encoders/interfaces.js +1 -0
  37. package/dist/encoders/skinned-mesh-builder.d.ts +7 -0
  38. package/dist/encoders/skinned-mesh-builder.js +135 -0
  39. package/dist/format/metadata.d.ts +18 -0
  40. package/dist/format/metadata.js +381 -0
  41. package/dist/format/packer.d.ts +8 -0
  42. package/dist/format/packer.js +87 -0
  43. package/dist/index.d.ts +28 -0
  44. package/dist/index.js +35 -0
  45. package/dist/init.d.ts +1 -0
  46. package/dist/init.js +73 -0
  47. package/dist/node-fs.d.ts +22 -0
  48. package/dist/node-fs.js +148 -0
  49. package/dist/orchestrator.d.ts +20 -0
  50. package/dist/orchestrator.js +301 -0
  51. package/dist/pipeline.d.ts +23 -0
  52. package/dist/pipeline.js +354 -0
  53. package/dist/server.d.ts +9 -0
  54. package/dist/server.js +859 -0
  55. package/package.json +35 -0
  56. package/skill.md +211 -0
@@ -0,0 +1,3 @@
1
+ export declare function cleanupProject(projectDir: string, opts?: {
2
+ verbose?: boolean;
3
+ }): Promise<void>;
@@ -0,0 +1,72 @@
1
+ import * as fs from 'node:fs/promises';
2
+ import * as path from 'node:path';
3
+ import { readProjectConfig, scanDirectory } from './node-fs.js';
4
+ export async function cleanupProject(projectDir, opts) {
5
+ const verbose = opts?.verbose ?? false;
6
+ const config = await readProjectConfig(projectDir);
7
+ const scan = await scanDirectory(config.srcArtDir);
8
+ // Build set of source files that exist
9
+ const sourceFiles = new Set(scan.sourceFiles.map(f => f.relativePath));
10
+ const matFiles = new Set(scan.matFiles.map(f => f.relativePath));
11
+ let deletedCaches = 0;
12
+ let deletedMetas = 0;
13
+ let freedBytes = 0;
14
+ // Walk all files looking for orphaned .stowcache and .stowmeta
15
+ await walkForOrphans(config.srcArtDir, '', sourceFiles, matFiles, async (orphanPath, type, size) => {
16
+ const fullPath = path.join(config.srcArtDir, orphanPath);
17
+ await fs.unlink(fullPath);
18
+ freedBytes += size;
19
+ if (type === 'cache') {
20
+ deletedCaches++;
21
+ if (verbose)
22
+ console.log(` [delete] ${orphanPath} (orphaned cache)`);
23
+ }
24
+ else {
25
+ deletedMetas++;
26
+ if (verbose)
27
+ console.log(` [delete] ${orphanPath} (orphaned meta)`);
28
+ }
29
+ });
30
+ if (deletedCaches + deletedMetas === 0) {
31
+ console.log('No orphaned files found.');
32
+ }
33
+ else {
34
+ console.log(`Cleaned up ${deletedCaches} cache(s) and ${deletedMetas} meta(s) (${(freedBytes / 1024).toFixed(0)} KB freed)`);
35
+ }
36
+ }
37
+ async function walkForOrphans(basePath, prefix, sourceFiles, matFiles, onOrphan) {
38
+ const dirPath = prefix ? path.join(basePath, prefix) : basePath;
39
+ let entries;
40
+ try {
41
+ entries = await fs.readdir(dirPath, { withFileTypes: true });
42
+ }
43
+ catch {
44
+ return;
45
+ }
46
+ for (const entry of entries) {
47
+ const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
48
+ if (entry.isDirectory()) {
49
+ if (entry.name.startsWith('.'))
50
+ continue;
51
+ await walkForOrphans(basePath, relativePath, sourceFiles, matFiles, onOrphan);
52
+ }
53
+ else if (entry.isFile()) {
54
+ if (entry.name.endsWith('.stowcache')) {
55
+ // Cache orphan: source file doesn't exist
56
+ const sourceId = relativePath.replace(/\.stowcache$/, '');
57
+ if (!sourceFiles.has(sourceId) && !matFiles.has(sourceId)) {
58
+ const stat = await fs.stat(path.join(basePath, relativePath));
59
+ await onOrphan(relativePath, 'cache', stat.size);
60
+ }
61
+ }
62
+ else if (entry.name.endsWith('.stowmeta')) {
63
+ // Meta orphan: neither source file nor mat file exists
64
+ const sourceId = relativePath.replace(/\.stowmeta$/, '');
65
+ if (!sourceFiles.has(sourceId) && !matFiles.has(sourceId)) {
66
+ const stat = await fs.stat(path.join(basePath, relativePath));
67
+ await onOrphan(relativePath, 'meta', stat.size);
68
+ }
69
+ }
70
+ }
71
+ }
72
+ }
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,148 @@
1
+ #!/usr/bin/env node
2
+ import * as path from 'node:path';
3
+ import { existsSync } from 'node:fs';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { createRequire } from 'node:module';
6
+ import { fullBuild, scanProject, showStatus } from './orchestrator.js';
7
+ import { startServer } from './server.js';
8
+ import { initProject } from './init.js';
9
+ import { cleanupProject } from './cleanup.js';
10
+ const args = process.argv.slice(2);
11
+ const thisDir = path.dirname(fileURLToPath(import.meta.url));
12
+ const req = createRequire(import.meta.url);
13
+ function printUsage() {
14
+ console.log(`
15
+ Usage:
16
+ stowkit init [dir] Initialize a StowKit project
17
+ stowkit build [dir] Full build: scan + process + pack
18
+ stowkit scan [dir] Detect new assets, generate .stowmeta defaults
19
+ stowkit process [dir] Compress assets (respects cache)
20
+ stowkit status [dir] Show project summary, stale asset count
21
+ stowkit clean [dir] Delete orphaned .stowcache and .stowmeta files
22
+ stowkit packer [dir] Open the packer GUI
23
+ stowkit editor [dir] Open the level editor
24
+ stowkit serve [dir] Start API server only (no GUI)
25
+
26
+ Options:
27
+ --force Ignore cache and reprocess everything
28
+ --verbose Detailed output
29
+ --port Server port (default 3210)
30
+ --help Show this help message
31
+ `.trim());
32
+ }
33
+ function resolveAppDir(packageName, monorepoFolder) {
34
+ const candidates = [
35
+ path.resolve(thisDir, `../../${monorepoFolder}/dist`), // monorepo dev
36
+ ];
37
+ try {
38
+ const pkgJson = req.resolve(`${packageName}/package.json`);
39
+ candidates.push(path.join(path.dirname(pkgJson), 'dist'));
40
+ }
41
+ catch { /* not installed */ }
42
+ for (const dir of candidates) {
43
+ if (existsSync(path.join(dir, 'index.html')))
44
+ return dir;
45
+ }
46
+ return null;
47
+ }
48
+ function openBrowser(url) {
49
+ import('node:child_process').then(({ exec }) => {
50
+ const cmd = process.platform === 'win32' ? 'start' : process.platform === 'darwin' ? 'open' : 'xdg-open';
51
+ exec(`${cmd} ${url}`);
52
+ });
53
+ }
54
+ async function main() {
55
+ if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
56
+ printUsage();
57
+ process.exit(0);
58
+ }
59
+ const command = args[0];
60
+ const projectDir = args.find(a => !a.startsWith('-') && a !== command) ?? '.';
61
+ const force = args.includes('--force');
62
+ const verbose = args.includes('--verbose') || args.includes('-v');
63
+ const portIdx = args.indexOf('--port');
64
+ const port = portIdx >= 0 ? parseInt(args[portIdx + 1]) : 3210;
65
+ const opts = { force, verbose };
66
+ try {
67
+ switch (command) {
68
+ case 'init':
69
+ await initProject(projectDir);
70
+ break;
71
+ case 'build':
72
+ await fullBuild(projectDir, opts);
73
+ break;
74
+ case 'scan': {
75
+ const report = await scanProject(projectDir, { verbose });
76
+ console.log(`Project: ${report.projectName}`);
77
+ console.log(`Source files: ${report.sourceFiles}`);
78
+ console.log(`Meta files: ${report.metaFiles}`);
79
+ console.log(`Material files: ${report.matFiles}`);
80
+ if (report.newFiles.length > 0) {
81
+ console.log(`New assets detected (${report.newFiles.length}):`);
82
+ for (const f of report.newFiles)
83
+ console.log(` ${f}`);
84
+ }
85
+ else {
86
+ console.log('No new assets detected.');
87
+ }
88
+ break;
89
+ }
90
+ case 'process':
91
+ await fullBuild(projectDir, { ...opts, force });
92
+ break;
93
+ case 'status':
94
+ await showStatus(projectDir);
95
+ break;
96
+ case 'clean':
97
+ await cleanupProject(projectDir, { verbose });
98
+ break;
99
+ case 'packer': {
100
+ const packerDir = resolveAppDir('@series-inc/stowkit-packer-gui', 'stowkit-packer-gui');
101
+ if (!packerDir) {
102
+ console.error('Packer GUI not found. Install it: npm install @series-inc/stowkit-packer-gui');
103
+ process.exit(1);
104
+ }
105
+ const resolvedProject = path.resolve(projectDir);
106
+ await startServer({ port, projectDir: resolvedProject, staticApps: { '/': packerDir } });
107
+ console.log(`\n Packer: http://localhost:${port}\n`);
108
+ openBrowser(`http://localhost:${port}`);
109
+ break;
110
+ }
111
+ case 'editor': {
112
+ const editorDir = resolveAppDir('@series-inc/stowkit-editor', 'stowkit-editor');
113
+ if (!editorDir) {
114
+ console.error('Editor not found. Install it: npm install @series-inc/stowkit-editor');
115
+ process.exit(1);
116
+ }
117
+ const resolvedProject = path.resolve(projectDir);
118
+ const packerPort = port + 1;
119
+ // Start editor server
120
+ await startServer({ port, projectDir: resolvedProject, staticApps: { '/': editorDir } });
121
+ // Start packer on next port if installed
122
+ const packerDir = resolveAppDir('@series-inc/stowkit-packer-gui', 'stowkit-packer-gui');
123
+ if (packerDir) {
124
+ await startServer({ port: packerPort, projectDir: resolvedProject, staticApps: { '/': packerDir } });
125
+ console.log(`\n Editor: http://localhost:${port}`);
126
+ console.log(` Packer: http://localhost:${packerPort}\n`);
127
+ }
128
+ else {
129
+ console.log(`\n Editor: http://localhost:${port}\n`);
130
+ }
131
+ openBrowser(`http://localhost:${port}`);
132
+ break;
133
+ }
134
+ case 'serve':
135
+ await startServer({ port, projectDir: projectDir !== '.' ? path.resolve(projectDir) : undefined });
136
+ break;
137
+ default:
138
+ console.error(`Unknown command: ${command}`);
139
+ printUsage();
140
+ process.exit(1);
141
+ }
142
+ }
143
+ catch (err) {
144
+ console.error(err instanceof Error ? err.message : String(err));
145
+ process.exit(1);
146
+ }
147
+ }
148
+ main();
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Sequential binary reader over an ArrayBuffer.
3
+ * All multi-byte reads use little-endian byte order (matching .stow format).
4
+ */
5
+ export declare class BinaryReader {
6
+ private readonly view;
7
+ private pos;
8
+ constructor(source: ArrayBuffer | Uint8Array, offset?: number);
9
+ readUint32(): number;
10
+ readInt32(): number;
11
+ readUint64(): bigint;
12
+ readUint64AsNumber(): number;
13
+ readFloat32(): number;
14
+ readFixedString(byteLength: number): string;
15
+ readBytes(length: number): Uint8Array;
16
+ seek(offset: number): void;
17
+ tell(): number;
18
+ get remaining(): number;
19
+ }
20
+ /**
21
+ * Sequential binary writer into a pre-allocated ArrayBuffer.
22
+ * All multi-byte writes use little-endian byte order.
23
+ */
24
+ export declare class BinaryWriter {
25
+ private readonly buffer;
26
+ private readonly view;
27
+ private readonly bytes;
28
+ private pos;
29
+ constructor(size: number);
30
+ writeUint32(value: number): void;
31
+ writeInt32(value: number): void;
32
+ writeUint64(value: bigint): void;
33
+ writeUint64FromNumber(value: number): void;
34
+ writeFloat32(value: number): void;
35
+ writeFixedString(value: string, byteLength: number): void;
36
+ writeBytes(data: Uint8Array): void;
37
+ seek(offset: number): void;
38
+ tell(): number;
39
+ getBuffer(): ArrayBuffer;
40
+ getUint8Array(): Uint8Array;
41
+ }
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Sequential binary reader over an ArrayBuffer.
3
+ * All multi-byte reads use little-endian byte order (matching .stow format).
4
+ */
5
+ export class BinaryReader {
6
+ view;
7
+ pos;
8
+ constructor(source, offset = 0) {
9
+ if (source instanceof Uint8Array) {
10
+ this.view = new DataView(source.buffer, source.byteOffset, source.byteLength);
11
+ }
12
+ else {
13
+ this.view = new DataView(source);
14
+ }
15
+ this.pos = offset;
16
+ }
17
+ readUint32() {
18
+ const val = this.view.getUint32(this.pos, true);
19
+ this.pos += 4;
20
+ return val;
21
+ }
22
+ readInt32() {
23
+ const val = this.view.getInt32(this.pos, true);
24
+ this.pos += 4;
25
+ return val;
26
+ }
27
+ readUint64() {
28
+ const val = this.view.getBigUint64(this.pos, true);
29
+ this.pos += 8;
30
+ return val;
31
+ }
32
+ readUint64AsNumber() {
33
+ return Number(this.readUint64());
34
+ }
35
+ readFloat32() {
36
+ const val = this.view.getFloat32(this.pos, true);
37
+ this.pos += 4;
38
+ return val;
39
+ }
40
+ readFixedString(byteLength) {
41
+ const bytes = new Uint8Array(this.view.buffer, this.view.byteOffset + this.pos, byteLength);
42
+ this.pos += byteLength;
43
+ const nullIdx = bytes.indexOf(0);
44
+ return new TextDecoder().decode(bytes.subarray(0, nullIdx >= 0 ? nullIdx : byteLength));
45
+ }
46
+ readBytes(length) {
47
+ const bytes = new Uint8Array(this.view.buffer, this.view.byteOffset + this.pos, length);
48
+ this.pos += length;
49
+ return bytes.slice();
50
+ }
51
+ seek(offset) {
52
+ this.pos = offset;
53
+ }
54
+ tell() {
55
+ return this.pos;
56
+ }
57
+ get remaining() {
58
+ return this.view.byteLength - this.pos;
59
+ }
60
+ }
61
+ /**
62
+ * Sequential binary writer into a pre-allocated ArrayBuffer.
63
+ * All multi-byte writes use little-endian byte order.
64
+ */
65
+ export class BinaryWriter {
66
+ buffer;
67
+ view;
68
+ bytes;
69
+ pos;
70
+ constructor(size) {
71
+ this.buffer = new ArrayBuffer(size);
72
+ this.view = new DataView(this.buffer);
73
+ this.bytes = new Uint8Array(this.buffer);
74
+ this.pos = 0;
75
+ }
76
+ writeUint32(value) {
77
+ this.view.setUint32(this.pos, value, true);
78
+ this.pos += 4;
79
+ }
80
+ writeInt32(value) {
81
+ this.view.setInt32(this.pos, value, true);
82
+ this.pos += 4;
83
+ }
84
+ writeUint64(value) {
85
+ this.view.setBigUint64(this.pos, value, true);
86
+ this.pos += 8;
87
+ }
88
+ writeUint64FromNumber(value) {
89
+ this.view.setBigUint64(this.pos, BigInt(value), true);
90
+ this.pos += 8;
91
+ }
92
+ writeFloat32(value) {
93
+ this.view.setFloat32(this.pos, value, true);
94
+ this.pos += 4;
95
+ }
96
+ writeFixedString(value, byteLength) {
97
+ const encoded = new TextEncoder().encode(value);
98
+ this.bytes.fill(0, this.pos, this.pos + byteLength);
99
+ this.bytes.set(encoded.subarray(0, Math.min(encoded.length, byteLength - 1)), this.pos);
100
+ this.pos += byteLength;
101
+ }
102
+ writeBytes(data) {
103
+ this.bytes.set(data, this.pos);
104
+ this.pos += data.length;
105
+ }
106
+ seek(offset) {
107
+ this.pos = offset;
108
+ }
109
+ tell() {
110
+ return this.pos;
111
+ }
112
+ getBuffer() {
113
+ return this.buffer;
114
+ }
115
+ getUint8Array() {
116
+ return this.bytes;
117
+ }
118
+ }
@@ -0,0 +1,64 @@
1
+ /** Magic number: 'STKT' as little-endian uint32 (0x544B5453) */
2
+ export declare const STOW_MAGIC = 1414222931;
3
+ /** Current format version */
4
+ export declare const STOW_VERSION = 1;
5
+ /** Size of the FileHeader on disk (bytes) */
6
+ export declare const FILE_HEADER_SIZE = 20;
7
+ /** Size of a single AssetDirectoryEntry on disk (bytes) */
8
+ export declare const DIRECTORY_ENTRY_SIZE = 40;
9
+ /** Alignment boundary for data and metadata blobs (bytes) */
10
+ export declare const DATA_ALIGNMENT = 16;
11
+ /** Maximum canonical path length (characters) */
12
+ export declare const MAX_PATH_LENGTH = 512;
13
+ /** Size of the string_id field in metadata structs (bytes) */
14
+ export declare const STRING_ID_SIZE = 128;
15
+ /** Size of TextureMetadata on disk (bytes) */
16
+ export declare const TEXTURE_METADATA_SIZE = 144;
17
+ /** Size of AudioMetadata on disk (bytes) */
18
+ export declare const AUDIO_METADATA_SIZE = 140;
19
+ /** Size of MeshGeometryInfo on disk (bytes) */
20
+ export declare const MESH_GEOMETRY_INFO_SIZE = 40;
21
+ /** Size of SceneNode on disk (bytes) */
22
+ export declare const SCENE_NODE_SIZE = 116;
23
+ /** Size of MaterialData fixed portion on disk (bytes) */
24
+ export declare const MATERIAL_DATA_FIXED_SIZE = 196;
25
+ /** Size of MaterialPropertyValue on disk (bytes) */
26
+ export declare const MATERIAL_PROPERTY_VALUE_SIZE = 144;
27
+ /** Size of MeshMetadata fixed portion on disk (bytes) */
28
+ export declare const MESH_METADATA_FIXED_SIZE = 140;
29
+ /** Node name field size */
30
+ export declare const NODE_NAME_SIZE = 64;
31
+ /** Material name field size */
32
+ export declare const MATERIAL_NAME_SIZE = 64;
33
+ /** Material schema_id field size */
34
+ export declare const MATERIAL_SCHEMA_ID_SIZE = 128;
35
+ /** MaterialPropertyValue field_name / texture_id size */
36
+ export declare const MATERIAL_FIELD_NAME_SIZE = 64;
37
+ /** Material schema_name field size */
38
+ export declare const MATERIAL_SCHEMA_NAME_SIZE = 64;
39
+ /** MaterialSchemaField default_texture_id size */
40
+ export declare const MATERIAL_SCHEMA_DEFAULT_TEXTURE_ID_SIZE = 128;
41
+ /** Size of MaterialSchemaMetadata fixed portion on disk (bytes) */
42
+ export declare const MATERIAL_SCHEMA_METADATA_FIXED_SIZE = 196;
43
+ /** Size of MaterialSchemaField on disk (bytes) */
44
+ export declare const MATERIAL_SCHEMA_FIELD_SIZE = 216;
45
+ /** Bone name field size */
46
+ export declare const BONE_NAME_SIZE = 64;
47
+ /** Size of SkinnedMeshGeometryInfo on disk (bytes) */
48
+ export declare const SKINNED_MESH_GEOMETRY_INFO_SIZE = 72;
49
+ /** Size of SkinnedMeshMetadata fixed portion on disk (bytes) */
50
+ export declare const SKINNED_MESH_METADATA_FIXED_SIZE = 144;
51
+ /** Size of Bone on disk (bytes): name[64] + parent_index(4) + offset_matrix[64] */
52
+ export declare const BONE_SIZE = 132;
53
+ /** Size of VertexWeights on disk (bytes): bone_indices[16] + weights[16] */
54
+ export declare const VERTEX_WEIGHTS_SIZE = 32;
55
+ /** Interleaved vertex size for skinned meshes: pos[3] + normal[3] + uv[2] = 8 floats */
56
+ export declare const SKINNED_VERTEX_STRIDE = 32;
57
+ /** Size of AnimationTrackDescriptor on disk (bytes) */
58
+ export declare const ANIMATION_TRACK_DESCRIPTOR_SIZE = 88;
59
+ /** Size of AnimationClipMetadata fixed portion on disk (bytes) */
60
+ export declare const ANIMATION_CLIP_METADATA_FIXED_SIZE = 268;
61
+ /** Track name field size in AnimationTrackDescriptor */
62
+ export declare const TRACK_NAME_SIZE = 64;
63
+ /** Metadata version discriminator for v2 animation clips */
64
+ export declare const ANIMATION_METADATA_VERSION = 2;
@@ -0,0 +1,65 @@
1
+ /** Magic number: 'STKT' as little-endian uint32 (0x544B5453) */
2
+ export const STOW_MAGIC = 0x544B5453;
3
+ /** Current format version */
4
+ export const STOW_VERSION = 1;
5
+ /** Size of the FileHeader on disk (bytes) */
6
+ export const FILE_HEADER_SIZE = 20;
7
+ /** Size of a single AssetDirectoryEntry on disk (bytes) */
8
+ export const DIRECTORY_ENTRY_SIZE = 40;
9
+ /** Alignment boundary for data and metadata blobs (bytes) */
10
+ export const DATA_ALIGNMENT = 16;
11
+ /** Maximum canonical path length (characters) */
12
+ export const MAX_PATH_LENGTH = 512;
13
+ /** Size of the string_id field in metadata structs (bytes) */
14
+ export const STRING_ID_SIZE = 128;
15
+ /** Size of TextureMetadata on disk (bytes) */
16
+ export const TEXTURE_METADATA_SIZE = 144;
17
+ /** Size of AudioMetadata on disk (bytes) */
18
+ export const AUDIO_METADATA_SIZE = 140;
19
+ /** Size of MeshGeometryInfo on disk (bytes) */
20
+ export const MESH_GEOMETRY_INFO_SIZE = 40;
21
+ /** Size of SceneNode on disk (bytes) */
22
+ export const SCENE_NODE_SIZE = 116;
23
+ /** Size of MaterialData fixed portion on disk (bytes) */
24
+ export const MATERIAL_DATA_FIXED_SIZE = 196;
25
+ /** Size of MaterialPropertyValue on disk (bytes) */
26
+ export const MATERIAL_PROPERTY_VALUE_SIZE = 144;
27
+ /** Size of MeshMetadata fixed portion on disk (bytes) */
28
+ export const MESH_METADATA_FIXED_SIZE = 140;
29
+ /** Node name field size */
30
+ export const NODE_NAME_SIZE = 64;
31
+ /** Material name field size */
32
+ export const MATERIAL_NAME_SIZE = 64;
33
+ /** Material schema_id field size */
34
+ export const MATERIAL_SCHEMA_ID_SIZE = 128;
35
+ /** MaterialPropertyValue field_name / texture_id size */
36
+ export const MATERIAL_FIELD_NAME_SIZE = 64;
37
+ /** Material schema_name field size */
38
+ export const MATERIAL_SCHEMA_NAME_SIZE = 64;
39
+ /** MaterialSchemaField default_texture_id size */
40
+ export const MATERIAL_SCHEMA_DEFAULT_TEXTURE_ID_SIZE = 128;
41
+ /** Size of MaterialSchemaMetadata fixed portion on disk (bytes) */
42
+ export const MATERIAL_SCHEMA_METADATA_FIXED_SIZE = 196;
43
+ /** Size of MaterialSchemaField on disk (bytes) */
44
+ export const MATERIAL_SCHEMA_FIELD_SIZE = 216;
45
+ /** Bone name field size */
46
+ export const BONE_NAME_SIZE = 64;
47
+ /** Size of SkinnedMeshGeometryInfo on disk (bytes) */
48
+ export const SKINNED_MESH_GEOMETRY_INFO_SIZE = 72;
49
+ /** Size of SkinnedMeshMetadata fixed portion on disk (bytes) */
50
+ export const SKINNED_MESH_METADATA_FIXED_SIZE = 144;
51
+ /** Size of Bone on disk (bytes): name[64] + parent_index(4) + offset_matrix[64] */
52
+ export const BONE_SIZE = 132;
53
+ /** Size of VertexWeights on disk (bytes): bone_indices[16] + weights[16] */
54
+ export const VERTEX_WEIGHTS_SIZE = 32;
55
+ /** Interleaved vertex size for skinned meshes: pos[3] + normal[3] + uv[2] = 8 floats */
56
+ export const SKINNED_VERTEX_STRIDE = 32;
57
+ // ─── Animation V2 Constants ─────────────────────────────────────────────────
58
+ /** Size of AnimationTrackDescriptor on disk (bytes) */
59
+ export const ANIMATION_TRACK_DESCRIPTOR_SIZE = 88;
60
+ /** Size of AnimationClipMetadata fixed portion on disk (bytes) */
61
+ export const ANIMATION_CLIP_METADATA_FIXED_SIZE = 268;
62
+ /** Track name field size in AnimationTrackDescriptor */
63
+ export const TRACK_NAME_SIZE = 64;
64
+ /** Metadata version discriminator for v2 animation clips */
65
+ export const ANIMATION_METADATA_VERSION = 2;
@@ -0,0 +1,3 @@
1
+ export declare function normalizePath(path: string): string;
2
+ export declare function validatePath(path: string): string | null;
3
+ export declare function djb2Hash(input: string): bigint;
@@ -0,0 +1,27 @@
1
+ import { MAX_PATH_LENGTH } from './constants.js';
2
+ export function normalizePath(path) {
3
+ let result = path.replace(/\\/g, '/');
4
+ result = result.replace(/\/+/g, '/');
5
+ result = result.toLowerCase();
6
+ if (result.length > 1 && result.endsWith('/')) {
7
+ result = result.slice(0, -1);
8
+ }
9
+ return result;
10
+ }
11
+ export function validatePath(path) {
12
+ if (!path || path.length === 0)
13
+ return 'Path is empty';
14
+ if (path.length > MAX_PATH_LENGTH)
15
+ return `Path exceeds ${MAX_PATH_LENGTH} characters`;
16
+ return null;
17
+ }
18
+ export function djb2Hash(input) {
19
+ const normalized = normalizePath(input);
20
+ let hash = 5381n;
21
+ for (let i = 0; i < normalized.length; i++) {
22
+ const c = BigInt(normalized.charCodeAt(i));
23
+ hash = ((hash << 5n) + hash) + c;
24
+ hash = hash & 0xffffffffffffffffn;
25
+ }
26
+ return hash;
27
+ }