@multitapio/multitap 0.0.13 → 0.0.14

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 (46) hide show
  1. package/dist/codegen/config.d.ts +5 -2
  2. package/dist/codegen/config.d.ts.map +1 -1
  3. package/dist/codegen/rust.d.ts +3 -6
  4. package/dist/codegen/rust.d.ts.map +1 -1
  5. package/dist/codegen/typescript.d.ts +3 -6
  6. package/dist/codegen/typescript.d.ts.map +1 -1
  7. package/dist/diagnostics.js +404 -8
  8. package/dist/executor.d.ts +27 -0
  9. package/dist/executor.d.ts.map +1 -1
  10. package/dist/init-data.d.ts +25 -0
  11. package/dist/init-data.d.ts.map +1 -0
  12. package/dist/lib.js +610 -9
  13. package/dist/schema.d.ts +68 -0
  14. package/dist/schema.d.ts.map +1 -1
  15. package/dist/session.d.ts +5 -0
  16. package/dist/session.d.ts.map +1 -1
  17. package/dist/test-session.d.ts +4 -0
  18. package/dist/test-session.d.ts.map +1 -1
  19. package/dist/types/codegen/config.d.ts +5 -2
  20. package/dist/types/codegen/config.d.ts.map +1 -1
  21. package/dist/types/codegen/rust.d.ts +3 -6
  22. package/dist/types/codegen/rust.d.ts.map +1 -1
  23. package/dist/types/codegen/typescript.d.ts +3 -6
  24. package/dist/types/codegen/typescript.d.ts.map +1 -1
  25. package/dist/types/executor.d.ts +27 -0
  26. package/dist/types/executor.d.ts.map +1 -1
  27. package/dist/types/init-data.d.ts +25 -0
  28. package/dist/types/init-data.d.ts.map +1 -0
  29. package/dist/types/schema.d.ts +68 -0
  30. package/dist/types/schema.d.ts.map +1 -1
  31. package/dist/types/session.d.ts +5 -0
  32. package/dist/types/session.d.ts.map +1 -1
  33. package/dist/types/test-session.d.ts +4 -0
  34. package/dist/types/test-session.d.ts.map +1 -1
  35. package/dist/types/vite/codegen-runner.d.ts +2 -1
  36. package/dist/types/vite/codegen-runner.d.ts.map +1 -1
  37. package/dist/types/vite/module-builder.d.ts +2 -1
  38. package/dist/types/vite/module-builder.d.ts.map +1 -1
  39. package/dist/types/vite/plugin.d.ts.map +1 -1
  40. package/dist/vite/codegen-runner.d.ts +2 -1
  41. package/dist/vite/codegen-runner.d.ts.map +1 -1
  42. package/dist/vite/index.js +1403 -692
  43. package/dist/vite/module-builder.d.ts +2 -1
  44. package/dist/vite/module-builder.d.ts.map +1 -1
  45. package/dist/vite/plugin.d.ts.map +1 -1
  46. package/package.json +2 -2
@@ -137,8 +137,10 @@ function topologicalSort(plugins) {
137
137
  function mergeConfigs(graph) {
138
138
  const componentOwners = /* @__PURE__ */ new Map();
139
139
  const eventOwners = /* @__PURE__ */ new Map();
140
+ const dataOwners = /* @__PURE__ */ new Map();
140
141
  const mergedComponents = [];
141
142
  const mergedEvents = [];
143
+ const mergedData = [];
142
144
  for (const pluginPath of graph.order) {
143
145
  const node = graph.plugins.get(pluginPath);
144
146
  for (const comp of node.config.state.components ?? []) {
@@ -177,6 +179,25 @@ function mergeConfigs(graph) {
177
179
  eventOwners.set(event.name, graph.root.dir);
178
180
  mergedEvents.push(event);
179
181
  }
182
+ for (const pluginPath of graph.order) {
183
+ const node = graph.plugins.get(pluginPath);
184
+ for (const data of node.config.data ?? []) {
185
+ if (dataOwners.has(data.name)) {
186
+ const owner = dataOwners.get(data.name);
187
+ throw new Error(`Data item "${data.name}" defined in both ${owner} and ${pluginPath}`);
188
+ }
189
+ dataOwners.set(data.name, pluginPath);
190
+ mergedData.push(data);
191
+ }
192
+ }
193
+ for (const data of graph.root.config.data ?? []) {
194
+ if (dataOwners.has(data.name)) {
195
+ const owner = dataOwners.get(data.name);
196
+ throw new Error(`Data item "${data.name}" defined in both ${owner} and root config`);
197
+ }
198
+ dataOwners.set(data.name, graph.root.dir);
199
+ mergedData.push(data);
200
+ }
180
201
  const mergedState = {
181
202
  maxEntities: graph.root.config.state.maxEntities,
182
203
  components: mergedComponents
@@ -200,10 +221,14 @@ function mergeConfigs(graph) {
200
221
  if (graph.root.config.fixtures !== void 0) {
201
222
  mergedConfig.fixtures = graph.root.config.fixtures;
202
223
  }
224
+ if (mergedData.length > 0) {
225
+ mergedConfig.data = mergedData;
226
+ }
203
227
  return {
204
228
  config: mergedConfig,
205
229
  pluginOrder: graph.order,
206
- componentOwners
230
+ componentOwners,
231
+ dataOwners
207
232
  };
208
233
  }
209
234
 
@@ -425,118 +450,796 @@ function getElementSize(type) {
425
450
  }
426
451
  }
427
452
 
428
- // src/codegen/typescript.ts
429
- function tsType(field) {
430
- if (field.arrayElementType) {
431
- return tsTypeMapping(field.arrayElementType);
453
+ // src/schema.ts
454
+ var HEADER_SIZE = 44;
455
+ var TYPE_SIZES = {
456
+ bool: 1,
457
+ uint8: 1,
458
+ int8: 1,
459
+ uint16: 2,
460
+ int16: 2,
461
+ uint32: 4,
462
+ int32: 4,
463
+ entityRef: 4,
464
+ f32: 4,
465
+ vec2: 8,
466
+ vec3: 12,
467
+ quat: 16,
468
+ enum: 1
469
+ };
470
+ var INPUT_TYPE_SIZES = {
471
+ bool: 1,
472
+ uint8: 1,
473
+ int8: 1,
474
+ uint16: 2,
475
+ int16: 2,
476
+ uint32: 4,
477
+ int32: 4,
478
+ f32: 4,
479
+ flags8: 1,
480
+ flags16: 2,
481
+ flags32: 4,
482
+ vec2: 8,
483
+ vec3: 12,
484
+ quat: 16
485
+ };
486
+ var PRIMITIVE_TYPES = /* @__PURE__ */ new Set([
487
+ "bool",
488
+ "uint8",
489
+ "int8",
490
+ "uint16",
491
+ "int16",
492
+ "uint32",
493
+ "int32",
494
+ "entityRef",
495
+ "f32"
496
+ ]);
497
+ var INPUT_PRIMITIVE_SCALAR_TYPES = /* @__PURE__ */ new Set([
498
+ "bool",
499
+ "uint8",
500
+ "int8",
501
+ "uint16",
502
+ "int16",
503
+ "uint32",
504
+ "int32",
505
+ "f32"
506
+ ]);
507
+ var ENUM_VARIANT_NAME_RE = /^[A-Za-z][A-Za-z0-9_]*$/;
508
+ function parseArrayType(type) {
509
+ const match = type.match(/^(\w+)\[(\d+)\]$/);
510
+ if (!match || !match[1] || !match[2]) return null;
511
+ const elementType = match[1];
512
+ const length = parseInt(match[2], 10);
513
+ if (!PRIMITIVE_TYPES.has(elementType)) return null;
514
+ return { elementType, length };
515
+ }
516
+ function parseInputArrayType(type) {
517
+ const match = type.match(/^(\w+)\[(\d+)\]$/);
518
+ if (!match || !match[1] || !match[2]) return null;
519
+ const elementType = match[1];
520
+ const length = parseInt(match[2], 10);
521
+ if (!INPUT_PRIMITIVE_SCALAR_TYPES.has(elementType)) {
522
+ throw new Error(
523
+ `Invalid array element type '${elementType}': arrays only support primitive scalar types (bool, uint8, int8, uint16, int16, uint32, int32, f32)`
524
+ );
432
525
  }
433
- if (field.type === "vec2" || field.type === "vec3" || field.type === "quat") {
434
- return "number";
526
+ return { elementType, length };
527
+ }
528
+ function getTypeSize(type) {
529
+ const arrayInfo = parseArrayType(type);
530
+ if (arrayInfo) {
531
+ const elementSize = TYPE_SIZES[arrayInfo.elementType];
532
+ if (elementSize === void 0) {
533
+ throw new Error(`Unknown element type: ${arrayInfo.elementType}`);
534
+ }
535
+ return elementSize * arrayInfo.length;
435
536
  }
436
- if (field.type === "enum") {
437
- return "number";
537
+ const size = TYPE_SIZES[type];
538
+ if (size === void 0) {
539
+ throw new Error(`Unknown type: ${type}`);
438
540
  }
439
- return tsTypeMapping(field.type);
541
+ return size;
440
542
  }
441
- function tsTypeMapping(typ) {
442
- switch (typ) {
443
- case "bool":
444
- return "boolean";
445
- case "uint8":
446
- case "int8":
447
- case "uint16":
448
- case "int16":
449
- case "uint32":
450
- case "int32":
451
- case "f32":
452
- case "entityRef":
453
- return "number";
454
- default:
455
- throw new Error(`Unknown type '${typ}' in TypeScript code generation`);
543
+ function getInputTypeSize(type) {
544
+ const arrayInfo = parseInputArrayType(type);
545
+ if (arrayInfo) {
546
+ const elementSize = INPUT_TYPE_SIZES[arrayInfo.elementType];
547
+ if (elementSize === void 0) {
548
+ throw new Error(`Unknown element type: ${arrayInfo.elementType}`);
549
+ }
550
+ return elementSize * arrayInfo.length;
551
+ }
552
+ const size = INPUT_TYPE_SIZES[type];
553
+ if (size === void 0) {
554
+ throw new Error(`Unknown type: ${type}`);
456
555
  }
556
+ return size;
457
557
  }
458
- function tsInputType(typ) {
459
- switch (typ) {
460
- case "bool":
461
- return "boolean";
462
- case "uint8":
463
- case "int8":
464
- case "uint16":
465
- case "int16":
466
- case "uint32":
467
- case "int32":
468
- case "f32":
469
- case "flags8":
470
- case "flags16":
471
- case "flags32":
472
- case "vec2":
473
- case "vec3":
474
- case "quat":
475
- return "number";
476
- default:
477
- throw new Error(`Unknown input type '${typ}' in TypeScript code generation`);
558
+ function getValidatedTypeSize(type, componentName, fieldName) {
559
+ try {
560
+ return getTypeSize(type);
561
+ } catch (error) {
562
+ const location = fieldName ? `${componentName}.${fieldName}` : componentName;
563
+ const message = error instanceof Error ? error.message : String(error);
564
+ throw new Error(`Invalid type '${type}' for '${location}': ${message}`);
478
565
  }
479
566
  }
480
- function tsLoadFn(field) {
481
- let typ = field.type;
482
- if (field.arrayElementType) {
483
- typ = field.arrayElementType;
567
+ function validateEnumVariants(rawVariants, subject) {
568
+ const variants = rawVariants ?? [];
569
+ if (!Array.isArray(variants)) {
570
+ throw new Error(`enum ${subject} must have at least one variant`);
484
571
  }
485
- if (typ === "vec2" || typ === "vec3" || typ === "quat") {
486
- typ = "f32";
572
+ if (variants.length === 0) {
573
+ throw new Error(`enum ${subject} must have at least one variant`);
487
574
  }
488
- if (typ === "enum") {
489
- typ = "uint8";
575
+ if (variants.length > 256) {
576
+ throw new Error(`enum ${subject} has ${variants.length} variants, maximum is 256`);
490
577
  }
491
- return tsLoadFnForType(typ);
492
- }
493
- function tsLoadFnForType(typ) {
494
- switch (typ) {
495
- case "bool":
496
- case "uint8":
497
- return "getUint8";
498
- case "int8":
499
- return "getInt8";
500
- case "uint16":
501
- return "getUint16";
502
- case "int16":
503
- return "getInt16";
504
- case "uint32":
505
- case "entityRef":
506
- return "getUint32";
507
- case "int32":
508
- return "getInt32";
509
- case "f32":
510
- return "getFloat32";
511
- default:
512
- throw new Error(`Unknown type '${typ}' in TypeScript load function generation`);
578
+ const seen = /* @__PURE__ */ new Set();
579
+ const seenNormalized = /* @__PURE__ */ new Map();
580
+ for (const v of variants) {
581
+ if (typeof v !== "string" || v.trim().length === 0) {
582
+ throw new Error(`enum ${subject} has empty variant name`);
583
+ }
584
+ if (!ENUM_VARIANT_NAME_RE.test(v)) {
585
+ throw new Error(
586
+ `enum ${subject} has invalid variant name ${JSON.stringify(v)} (must match ${ENUM_VARIANT_NAME_RE})`
587
+ );
588
+ }
589
+ if (seen.has(v)) {
590
+ throw new Error(`enum ${subject} has duplicate variant '${v}'`);
591
+ }
592
+ seen.add(v);
593
+ const normalized = toUpperSnake2(v);
594
+ const prev = seenNormalized.get(normalized);
595
+ if (prev !== void 0) {
596
+ throw new Error(
597
+ `enum ${subject} has variants ${JSON.stringify(prev)} and ${JSON.stringify(v)} which collide as ${JSON.stringify(normalized)}`
598
+ );
599
+ }
600
+ seenNormalized.set(normalized, v);
513
601
  }
602
+ return variants;
514
603
  }
515
- function tsStoreFn(field) {
516
- let typ = field.type;
517
- if (field.arrayElementType) {
518
- typ = field.arrayElementType;
604
+ function toUpperSnake2(s) {
605
+ let result = "";
606
+ for (let i = 0; i < s.length; i++) {
607
+ const c = s[i];
608
+ if (i > 0 && c >= "A" && c <= "Z") {
609
+ result += "_";
610
+ }
611
+ result += c.toUpperCase();
519
612
  }
520
- if (typ === "vec2" || typ === "vec3" || typ === "quat") {
521
- typ = "f32";
613
+ return result;
614
+ }
615
+ function compileStateSchema(schema) {
616
+ const { maxEntities, components: componentDefs, events: eventDefs } = schema;
617
+ if (maxEntities <= 0 || maxEntities > 65535) {
618
+ throw new Error(`maxEntities must be between 1 and 65535, got ${maxEntities}`);
522
619
  }
523
- if (typ === "enum") {
524
- typ = "uint8";
620
+ if (!Array.isArray(componentDefs)) {
621
+ throw new Error("schema.components must be an array to preserve deterministic ordering");
525
622
  }
526
- return tsStoreFnForType(typ);
527
- }
528
- function tsStoreFnForType(typ) {
529
- switch (typ) {
530
- case "bool":
531
- case "uint8":
532
- return "setUint8";
533
- case "int8":
534
- return "setInt8";
535
- case "uint16":
536
- return "setUint16";
537
- case "int16":
538
- return "setInt16";
539
- case "uint32":
623
+ const numEntityComponents = componentDefs.filter((componentDef) => {
624
+ const singletonFlag = componentDef.singleton;
625
+ return singletonFlag !== true;
626
+ }).length;
627
+ const bitmaskSize = Math.ceil(numEntityComponents / 8);
628
+ const entityRecordSize = 2 + bitmaskSize;
629
+ const entityTableOffset = HEADER_SIZE;
630
+ const entityTableSize = maxEntities * entityRecordSize;
631
+ const freeStackOffset = entityTableOffset + entityTableSize;
632
+ const freeStackSize = 2 + maxEntities * 2;
633
+ const componentStorageOffset = freeStackOffset + freeStackSize;
634
+ const components = [];
635
+ const entityComponents = [];
636
+ const singletonComponents = [];
637
+ const componentByName = /* @__PURE__ */ new Map();
638
+ const seenComponentNames = /* @__PURE__ */ new Set();
639
+ let storageOffset = componentStorageOffset;
640
+ const pendingSingletons = [];
641
+ let bitIndex = 0;
642
+ for (let i = 0; i < componentDefs.length; i++) {
643
+ const componentDef = componentDefs[i];
644
+ if (componentDef === void 0 || componentDef === null || typeof componentDef !== "object") {
645
+ throw new Error(`Component at index ${i} must be an object`);
646
+ }
647
+ const rawName = componentDef.name;
648
+ if (typeof rawName !== "string" || rawName.trim().length === 0) {
649
+ throw new Error(`Component at index ${i} must have a non-empty string 'name'`);
650
+ }
651
+ const name = rawName;
652
+ if (seenComponentNames.has(name)) {
653
+ throw new Error(`Duplicate component name '${name}' in schema`);
654
+ }
655
+ seenComponentNames.add(name);
656
+ const rawType = componentDef.type;
657
+ if (typeof rawType !== "string" || rawType.trim().length === 0) {
658
+ throw new Error(`Component '${name}' must have a string 'type'`);
659
+ }
660
+ const componentType = rawType;
661
+ const fields = [];
662
+ let componentSize = 0;
663
+ let isTag = false;
664
+ let isBlob = false;
665
+ let blobMaxSize;
666
+ const isSingleton = componentDef.singleton === true;
667
+ if (componentType === "tag") {
668
+ isTag = true;
669
+ if (isSingleton) {
670
+ throw new Error(`Component '${name}' cannot be a singleton tag`);
671
+ }
672
+ } else if (componentType === "compound") {
673
+ const rawFields = componentDef.fields;
674
+ if (!Array.isArray(rawFields)) {
675
+ throw new Error(`Component '${name}' with type 'compound' must declare a 'fields' array`);
676
+ }
677
+ const seenFieldNames = /* @__PURE__ */ new Set();
678
+ let fieldOffset = 0;
679
+ for (let fieldIndex = 0; fieldIndex < rawFields.length; fieldIndex++) {
680
+ const fieldDef = rawFields[fieldIndex];
681
+ if (fieldDef === void 0 || fieldDef === null || typeof fieldDef !== "object") {
682
+ throw new Error(`Component '${name}' has an invalid field at index ${fieldIndex}`);
683
+ }
684
+ const rawFieldName = fieldDef.name;
685
+ if (typeof rawFieldName !== "string" || rawFieldName.trim().length === 0) {
686
+ throw new Error(`Component '${name}' has a field with a missing or empty 'name'`);
687
+ }
688
+ if (seenFieldNames.has(rawFieldName)) {
689
+ throw new Error(`Component '${name}' has duplicate field '${rawFieldName}'`);
690
+ }
691
+ seenFieldNames.add(rawFieldName);
692
+ const rawFieldType = fieldDef.type;
693
+ if (typeof rawFieldType !== "string" || rawFieldType.trim().length === 0) {
694
+ throw new Error(`Field '${name}.${rawFieldName}' must have a string 'type'`);
695
+ }
696
+ const size = getValidatedTypeSize(rawFieldType, name, rawFieldName);
697
+ const arrayInfo = parseArrayType(rawFieldType);
698
+ const compiledField = {
699
+ name: rawFieldName,
700
+ type: rawFieldType,
701
+ size,
702
+ offset: fieldOffset
703
+ };
704
+ if (arrayInfo) {
705
+ compiledField.arrayLength = arrayInfo.length;
706
+ compiledField.arrayElementType = arrayInfo.elementType;
707
+ }
708
+ if (rawFieldType === "enum") {
709
+ compiledField.variants = validateEnumVariants(
710
+ fieldDef.variants,
711
+ `field '${name}.${rawFieldName}'`
712
+ );
713
+ }
714
+ fields.push(compiledField);
715
+ fieldOffset += size;
716
+ componentSize += size;
717
+ }
718
+ } else if (componentType === "blob") {
719
+ if (!isSingleton) {
720
+ throw new Error(`Component '${name}' with type 'blob' must be a singleton`);
721
+ }
722
+ const maxSize = componentDef.maxSize;
723
+ if (typeof maxSize !== "number" || maxSize <= 0) {
724
+ throw new Error(`Component '${name}' with type 'blob' requires positive 'maxSize'`);
725
+ }
726
+ componentSize = 4 + maxSize;
727
+ isBlob = true;
728
+ blobMaxSize = maxSize;
729
+ } else {
730
+ const fieldType = componentType;
731
+ const size = getValidatedTypeSize(fieldType, name);
732
+ const arrayInfo = parseArrayType(fieldType);
733
+ const compiledField = {
734
+ name: "",
735
+ type: fieldType,
736
+ size,
737
+ offset: 0
738
+ };
739
+ if (arrayInfo) {
740
+ compiledField.arrayLength = arrayInfo.length;
741
+ compiledField.arrayElementType = arrayInfo.elementType;
742
+ }
743
+ if (fieldType === "enum") {
744
+ compiledField.variants = validateEnumVariants(
745
+ componentDef.variants,
746
+ `component '${name}'`
747
+ );
748
+ }
749
+ fields.push(compiledField);
750
+ componentSize = size;
751
+ }
752
+ const compiled = {
753
+ name,
754
+ index: isSingleton ? -1 : bitIndex,
755
+ isTag,
756
+ isSingleton,
757
+ size: componentSize,
758
+ storageOffset: 0,
759
+ fields
760
+ };
761
+ if (isBlob) {
762
+ compiled.isBlob = true;
763
+ compiled.blobMaxSize = blobMaxSize;
764
+ }
765
+ components.push(compiled);
766
+ componentByName.set(name, compiled);
767
+ if (isSingleton) {
768
+ pendingSingletons.push(compiled);
769
+ singletonComponents.push(compiled);
770
+ } else {
771
+ if (!isTag && componentSize > 0) {
772
+ compiled.storageOffset = storageOffset;
773
+ storageOffset += maxEntities * componentSize;
774
+ } else {
775
+ compiled.storageOffset = 0;
776
+ }
777
+ entityComponents.push(compiled);
778
+ bitIndex += 1;
779
+ }
780
+ }
781
+ const singletonStorageOffset = storageOffset;
782
+ let singletonCursor = singletonStorageOffset;
783
+ for (const singleton of pendingSingletons) {
784
+ singleton.storageOffset = singletonCursor;
785
+ singletonCursor += singleton.size;
786
+ }
787
+ const singletonStorageSize = singletonCursor - singletonStorageOffset;
788
+ let totalSize = singletonCursor;
789
+ let events;
790
+ let eventByName;
791
+ let eventStorageOffset;
792
+ let eventStorageSize;
793
+ if (eventDefs && eventDefs.length > 0) {
794
+ events = [];
795
+ eventByName = /* @__PURE__ */ new Map();
796
+ eventStorageOffset = totalSize;
797
+ let eventCursor = eventStorageOffset;
798
+ const seenEventNames = /* @__PURE__ */ new Set();
799
+ for (let i = 0; i < eventDefs.length; i++) {
800
+ const eventDef = eventDefs[i];
801
+ if (typeof eventDef.name !== "string" || eventDef.name.trim().length === 0) {
802
+ throw new Error(`Event at index ${i} must have a non-empty string 'name'`);
803
+ }
804
+ if (seenEventNames.has(eventDef.name)) {
805
+ throw new Error(`Duplicate event name '${eventDef.name}' in schema`);
806
+ }
807
+ seenEventNames.add(eventDef.name);
808
+ if (seenComponentNames.has(eventDef.name)) {
809
+ throw new Error(`Event name '${eventDef.name}' conflicts with component name`);
810
+ }
811
+ if (!Array.isArray(eventDef.fields) || eventDef.fields.length === 0) {
812
+ throw new Error(`Event '${eventDef.name}' must have at least one field`);
813
+ }
814
+ const maxEvents = eventDef.maxEvents && eventDef.maxEvents > 0 ? eventDef.maxEvents : 64;
815
+ const eventFields = [];
816
+ let fieldOffset = 0;
817
+ const seenFieldNames = /* @__PURE__ */ new Set();
818
+ for (let fi = 0; fi < eventDef.fields.length; fi++) {
819
+ const fieldDef = eventDef.fields[fi];
820
+ if (typeof fieldDef.name !== "string" || fieldDef.name.trim().length === 0) {
821
+ throw new Error(`Event '${eventDef.name}' has a field with a missing or empty 'name' at index ${fi}`);
822
+ }
823
+ if (seenFieldNames.has(fieldDef.name)) {
824
+ throw new Error(`Event '${eventDef.name}' has duplicate field '${fieldDef.name}'`);
825
+ }
826
+ seenFieldNames.add(fieldDef.name);
827
+ if (typeof fieldDef.type !== "string" || fieldDef.type.trim().length === 0) {
828
+ throw new Error(`Event field '${eventDef.name}.${fieldDef.name}' must have a string 'type'`);
829
+ }
830
+ const arrayInfo = parseArrayType(fieldDef.type);
831
+ if (arrayInfo) {
832
+ throw new Error(
833
+ `Event field '${eventDef.name}.${fieldDef.name}' has array type '${fieldDef.type}'; events do not support array types`
834
+ );
835
+ }
836
+ const size = getValidatedTypeSize(fieldDef.type, eventDef.name, fieldDef.name);
837
+ const compiledField = {
838
+ name: fieldDef.name,
839
+ type: fieldDef.type,
840
+ size,
841
+ offset: fieldOffset
842
+ };
843
+ if (fieldDef.type === "enum" && fieldDef.variants) {
844
+ compiledField.variants = validateEnumVariants(fieldDef.variants, `event field '${eventDef.name}.${fieldDef.name}'`);
845
+ }
846
+ eventFields.push(compiledField);
847
+ fieldOffset += size;
848
+ }
849
+ const recordSize = fieldOffset;
850
+ const bufferSize = 2 + maxEvents * recordSize;
851
+ const compiledEvent = {
852
+ name: eventDef.name,
853
+ index: i,
854
+ maxEvents,
855
+ recordSize,
856
+ storageOffset: eventCursor,
857
+ bufferSize,
858
+ fields: eventFields
859
+ };
860
+ events.push(compiledEvent);
861
+ eventByName.set(eventDef.name, compiledEvent);
862
+ eventCursor += bufferSize;
863
+ }
864
+ eventStorageSize = eventCursor - eventStorageOffset;
865
+ totalSize = eventCursor;
866
+ }
867
+ return {
868
+ maxEntities,
869
+ components,
870
+ entityComponents,
871
+ singletonComponents,
872
+ componentByName,
873
+ definition: schema,
874
+ events,
875
+ eventByName,
876
+ headerOffset: 0,
877
+ headerSize: HEADER_SIZE,
878
+ entityTableOffset,
879
+ entityRecordSize,
880
+ freeStackOffset,
881
+ freeStackSize,
882
+ componentStorageOffset,
883
+ singletonStorageOffset,
884
+ singletonStorageSize,
885
+ eventStorageOffset,
886
+ eventStorageSize,
887
+ totalSize,
888
+ bitmaskSize
889
+ };
890
+ }
891
+ var INIT_DATA_MAGIC = 1297369412;
892
+ var INIT_DATA_HEADER_SIZE = 8;
893
+ var MAX_INIT_DATA_ARRAY_LENGTH = 65536;
894
+ function getInitDataTypeSize(type) {
895
+ const quatArrayMatch = type.match(/^quat\[(\d+)\]$/);
896
+ if (quatArrayMatch) {
897
+ throw new Error(`Quat arrays are not supported in init data. Use individual quat fields or a different data structure.`);
898
+ }
899
+ const vec3ArrayMatch = type.match(/^vec3\[(\d+)\]$/);
900
+ if (vec3ArrayMatch?.[1]) {
901
+ const length = parseInt(vec3ArrayMatch[1], 10);
902
+ if (length > MAX_INIT_DATA_ARRAY_LENGTH) {
903
+ throw new Error(`Array length ${length} exceeds maximum ${MAX_INIT_DATA_ARRAY_LENGTH}`);
904
+ }
905
+ return 12 * length;
906
+ }
907
+ const vec2ArrayMatch = type.match(/^vec2\[(\d+)\]$/);
908
+ if (vec2ArrayMatch?.[1]) {
909
+ const length = parseInt(vec2ArrayMatch[1], 10);
910
+ if (length > MAX_INIT_DATA_ARRAY_LENGTH) {
911
+ throw new Error(`Array length ${length} exceeds maximum ${MAX_INIT_DATA_ARRAY_LENGTH}`);
912
+ }
913
+ return 8 * length;
914
+ }
915
+ const arrayInfo = parseArrayType(type);
916
+ if (arrayInfo) {
917
+ if (arrayInfo.length > MAX_INIT_DATA_ARRAY_LENGTH) {
918
+ throw new Error(`Array length ${arrayInfo.length} exceeds maximum ${MAX_INIT_DATA_ARRAY_LENGTH}`);
919
+ }
920
+ const elementSize = TYPE_SIZES[arrayInfo.elementType];
921
+ if (elementSize === void 0) {
922
+ throw new Error(`Unknown element type: ${arrayInfo.elementType}`);
923
+ }
924
+ return elementSize * arrayInfo.length;
925
+ }
926
+ const size = TYPE_SIZES[type];
927
+ if (size === void 0) {
928
+ throw new Error(`Unknown type: ${type}`);
929
+ }
930
+ return size;
931
+ }
932
+ function parseVec3ArrayType(type) {
933
+ const match = type.match(/^vec3\[(\d+)\]$/);
934
+ if (!match?.[1]) return null;
935
+ return { elementType: "vec3", length: parseInt(match[1], 10) };
936
+ }
937
+ function parseVec2ArrayType(type) {
938
+ const match = type.match(/^vec2\[(\d+)\]$/);
939
+ if (!match?.[1]) return null;
940
+ return { elementType: "vec2", length: parseInt(match[1], 10) };
941
+ }
942
+ function compileInitDataSchema(items) {
943
+ const compiledItems = [];
944
+ const itemByName = /* @__PURE__ */ new Map();
945
+ const seenNames = /* @__PURE__ */ new Set();
946
+ let offset = INIT_DATA_HEADER_SIZE;
947
+ for (let i = 0; i < items.length; i++) {
948
+ const item = items[i];
949
+ if (!item) continue;
950
+ if (typeof item.name !== "string" || item.name.trim().length === 0) {
951
+ throw new Error(`Init data item at index ${i} must have a non-empty string 'name'`);
952
+ }
953
+ if (seenNames.has(item.name)) {
954
+ throw new Error(`Duplicate init data item name '${item.name}'`);
955
+ }
956
+ seenNames.add(item.name);
957
+ if (item.type !== "compound" && item.type !== "blob") {
958
+ throw new Error(`Init data item '${item.name}' must have type 'compound' or 'blob'`);
959
+ }
960
+ let itemSize = 0;
961
+ const fields = [];
962
+ if (item.type === "blob") {
963
+ if (typeof item.maxSize !== "number" || item.maxSize <= 0) {
964
+ throw new Error(`Init data item '${item.name}' with type 'blob' requires positive 'maxSize'`);
965
+ }
966
+ itemSize = 4 + item.maxSize;
967
+ const compiled = {
968
+ name: item.name,
969
+ type: "blob",
970
+ size: itemSize,
971
+ storageOffset: offset,
972
+ fields: [],
973
+ isBlob: true,
974
+ blobMaxSize: item.maxSize
975
+ };
976
+ compiledItems.push(compiled);
977
+ itemByName.set(item.name, compiled);
978
+ } else {
979
+ if (!Array.isArray(item.fields) || item.fields.length === 0) {
980
+ throw new Error(`Init data item '${item.name}' with type 'compound' must have non-empty 'fields' array`);
981
+ }
982
+ const seenFieldNames = /* @__PURE__ */ new Set();
983
+ let fieldOffset = 0;
984
+ for (let fi = 0; fi < item.fields.length; fi++) {
985
+ const field = item.fields[fi];
986
+ if (!field) continue;
987
+ if (typeof field.name !== "string" || field.name.trim().length === 0) {
988
+ throw new Error(`Init data item '${item.name}' has field with missing name at index ${fi}`);
989
+ }
990
+ if (seenFieldNames.has(field.name)) {
991
+ throw new Error(`Init data item '${item.name}' has duplicate field '${field.name}'`);
992
+ }
993
+ seenFieldNames.add(field.name);
994
+ if (typeof field.type !== "string" || field.type.trim().length === 0) {
995
+ throw new Error(`Init data field '${item.name}.${field.name}' must have a string 'type'`);
996
+ }
997
+ let fieldSize;
998
+ try {
999
+ fieldSize = getInitDataTypeSize(field.type);
1000
+ } catch (error) {
1001
+ const message = error instanceof Error ? error.message : String(error);
1002
+ throw new Error(`Invalid type '${field.type}' for init data field '${item.name}.${field.name}': ${message}`);
1003
+ }
1004
+ const compiledField = {
1005
+ name: field.name,
1006
+ type: field.type,
1007
+ size: fieldSize,
1008
+ offset: fieldOffset
1009
+ };
1010
+ const vec3Array = parseVec3ArrayType(field.type);
1011
+ if (vec3Array) {
1012
+ compiledField.arrayLength = vec3Array.length;
1013
+ compiledField.arrayElementType = "f32";
1014
+ } else {
1015
+ const vec2Array = parseVec2ArrayType(field.type);
1016
+ if (vec2Array) {
1017
+ compiledField.arrayLength = vec2Array.length;
1018
+ compiledField.arrayElementType = "f32";
1019
+ } else {
1020
+ const primArray = parseArrayType(field.type);
1021
+ if (primArray) {
1022
+ compiledField.arrayLength = primArray.length;
1023
+ compiledField.arrayElementType = primArray.elementType;
1024
+ }
1025
+ }
1026
+ }
1027
+ fields.push(compiledField);
1028
+ fieldOffset += fieldSize;
1029
+ itemSize += fieldSize;
1030
+ }
1031
+ const compiled = {
1032
+ name: item.name,
1033
+ type: "compound",
1034
+ size: itemSize,
1035
+ storageOffset: offset,
1036
+ fields
1037
+ };
1038
+ compiledItems.push(compiled);
1039
+ itemByName.set(item.name, compiled);
1040
+ }
1041
+ offset += itemSize;
1042
+ }
1043
+ return {
1044
+ items: compiledItems,
1045
+ itemByName,
1046
+ headerSize: INIT_DATA_HEADER_SIZE,
1047
+ totalSize: offset
1048
+ };
1049
+ }
1050
+ function compileInputSchema(schema) {
1051
+ const controls = [];
1052
+ const commands = [];
1053
+ const controlByName = /* @__PURE__ */ new Map();
1054
+ const commandByName = /* @__PURE__ */ new Map();
1055
+ const seenNames = /* @__PURE__ */ new Set();
1056
+ let index = 0;
1057
+ for (let i = 0; i < schema.controls.length; i++) {
1058
+ const controlDef = schema.controls[i];
1059
+ if (typeof controlDef.name !== "string" || controlDef.name.trim().length === 0) {
1060
+ throw new Error(`Control at index ${i} must have a non-empty string 'name'`);
1061
+ }
1062
+ if (seenNames.has(controlDef.name)) {
1063
+ throw new Error(`Duplicate control/command name '${controlDef.name}' in schema`);
1064
+ }
1065
+ seenNames.add(controlDef.name);
1066
+ if (typeof controlDef.type !== "string" || controlDef.type.trim().length === 0) {
1067
+ throw new Error(`Control '${controlDef.name}' must have a string 'type'`);
1068
+ }
1069
+ const size = getInputTypeSize(controlDef.type);
1070
+ const compiled = {
1071
+ name: controlDef.name,
1072
+ index: index++,
1073
+ type: controlDef.type,
1074
+ size,
1075
+ options: controlDef.options,
1076
+ hint: controlDef.hint,
1077
+ retain: controlDef.retain === "always" ? "always" : "tick"
1078
+ };
1079
+ controls.push(compiled);
1080
+ controlByName.set(controlDef.name, compiled);
1081
+ if (index > 255) {
1082
+ throw new Error(`Too many controls/commands: index ${index} exceeds u8 limit (255)`);
1083
+ }
1084
+ }
1085
+ for (let i = 0; i < schema.commands.length; i++) {
1086
+ const commandDef = schema.commands[i];
1087
+ if (typeof commandDef.name !== "string" || commandDef.name.trim().length === 0) {
1088
+ throw new Error(`Command at index ${i} must have a non-empty string 'name'`);
1089
+ }
1090
+ if (seenNames.has(commandDef.name)) {
1091
+ throw new Error(`Duplicate control/command name '${commandDef.name}' in schema`);
1092
+ }
1093
+ seenNames.add(commandDef.name);
1094
+ const args = [];
1095
+ let offset = 0;
1096
+ for (let ai = 0; ai < commandDef.args.length; ai++) {
1097
+ const argDef = commandDef.args[ai];
1098
+ if (typeof argDef.name !== "string" || argDef.name.trim().length === 0) {
1099
+ throw new Error(`Command '${commandDef.name}' has an argument with missing 'name' at index ${ai}`);
1100
+ }
1101
+ const size = getInputTypeSize(argDef.type);
1102
+ const arrayInfo = parseInputArrayType(argDef.type);
1103
+ const arg = {
1104
+ name: argDef.name,
1105
+ type: argDef.type,
1106
+ size,
1107
+ offset
1108
+ };
1109
+ if (arrayInfo) {
1110
+ arg.arrayLength = arrayInfo.length;
1111
+ arg.arrayElementType = arrayInfo.elementType;
1112
+ }
1113
+ args.push(arg);
1114
+ offset += size;
1115
+ }
1116
+ const compiled = {
1117
+ name: commandDef.name,
1118
+ index: index++,
1119
+ args,
1120
+ totalSize: offset
1121
+ };
1122
+ commands.push(compiled);
1123
+ commandByName.set(commandDef.name, compiled);
1124
+ if (index > 255) {
1125
+ throw new Error(`Too many controls/commands: index ${index} exceeds u8 limit (255)`);
1126
+ }
1127
+ }
1128
+ return { controls, commands, controlByName, commandByName };
1129
+ }
1130
+
1131
+ // src/codegen/typescript.ts
1132
+ function tsType(field) {
1133
+ if (field.arrayElementType) {
1134
+ return tsTypeMapping(field.arrayElementType);
1135
+ }
1136
+ if (field.type === "vec2" || field.type === "vec3" || field.type === "quat") {
1137
+ return "number";
1138
+ }
1139
+ if (field.type === "enum") {
1140
+ return "number";
1141
+ }
1142
+ return tsTypeMapping(field.type);
1143
+ }
1144
+ function tsTypeMapping(typ) {
1145
+ switch (typ) {
1146
+ case "bool":
1147
+ return "boolean";
1148
+ case "uint8":
1149
+ case "int8":
1150
+ case "uint16":
1151
+ case "int16":
1152
+ case "uint32":
1153
+ case "int32":
1154
+ case "f32":
1155
+ case "entityRef":
1156
+ return "number";
1157
+ default:
1158
+ throw new Error(`Unknown type '${typ}' in TypeScript code generation`);
1159
+ }
1160
+ }
1161
+ function tsInputType(typ) {
1162
+ switch (typ) {
1163
+ case "bool":
1164
+ return "boolean";
1165
+ case "uint8":
1166
+ case "int8":
1167
+ case "uint16":
1168
+ case "int16":
1169
+ case "uint32":
1170
+ case "int32":
1171
+ case "f32":
1172
+ case "flags8":
1173
+ case "flags16":
1174
+ case "flags32":
1175
+ case "vec2":
1176
+ case "vec3":
1177
+ case "quat":
1178
+ return "number";
1179
+ default:
1180
+ throw new Error(`Unknown input type '${typ}' in TypeScript code generation`);
1181
+ }
1182
+ }
1183
+ function tsLoadFn(field) {
1184
+ let typ = field.type;
1185
+ if (field.arrayElementType) {
1186
+ typ = field.arrayElementType;
1187
+ }
1188
+ if (typ === "vec2" || typ === "vec3" || typ === "quat") {
1189
+ typ = "f32";
1190
+ }
1191
+ if (typ === "enum") {
1192
+ typ = "uint8";
1193
+ }
1194
+ return tsLoadFnForType(typ);
1195
+ }
1196
+ function tsLoadFnForType(typ) {
1197
+ switch (typ) {
1198
+ case "bool":
1199
+ case "uint8":
1200
+ return "getUint8";
1201
+ case "int8":
1202
+ return "getInt8";
1203
+ case "uint16":
1204
+ return "getUint16";
1205
+ case "int16":
1206
+ return "getInt16";
1207
+ case "uint32":
1208
+ case "entityRef":
1209
+ return "getUint32";
1210
+ case "int32":
1211
+ return "getInt32";
1212
+ case "f32":
1213
+ return "getFloat32";
1214
+ default:
1215
+ throw new Error(`Unknown type '${typ}' in TypeScript load function generation`);
1216
+ }
1217
+ }
1218
+ function tsStoreFn(field) {
1219
+ let typ = field.type;
1220
+ if (field.arrayElementType) {
1221
+ typ = field.arrayElementType;
1222
+ }
1223
+ if (typ === "vec2" || typ === "vec3" || typ === "quat") {
1224
+ typ = "f32";
1225
+ }
1226
+ if (typ === "enum") {
1227
+ typ = "uint8";
1228
+ }
1229
+ return tsStoreFnForType(typ);
1230
+ }
1231
+ function tsStoreFnForType(typ) {
1232
+ switch (typ) {
1233
+ case "bool":
1234
+ case "uint8":
1235
+ return "setUint8";
1236
+ case "int8":
1237
+ return "setInt8";
1238
+ case "uint16":
1239
+ return "setUint16";
1240
+ case "int16":
1241
+ return "setInt16";
1242
+ case "uint32":
540
1243
  case "entityRef":
541
1244
  return "setUint32";
542
1245
  case "int32":
@@ -1938,40 +2641,278 @@ export function get${pascal}${argPascal}YFromPayload(payload: Uint8Array): numbe
1938
2641
  if (offset < 0) {
1939
2642
  return 0;
1940
2643
  }
1941
- return readF32(payload, offset + ${arg.offset} + 4);
2644
+ return readF32(payload, offset + ${arg.offset} + 4);
2645
+ }
2646
+
2647
+ /** Get ${cmd.name}.${arg.name}.z (f32), or 0 if command not present. */
2648
+ export function get${pascal}${argPascal}ZFromPayload(payload: Uint8Array): number {
2649
+ const offset = findControl(payload, ${cmd.index});
2650
+ if (offset < 0) {
2651
+ return 0;
2652
+ }
2653
+ return readF32(payload, offset + ${arg.offset} + 8);
2654
+ }
2655
+
2656
+ /** Get ${cmd.name}.${arg.name}.w (f32), or 1 if command not present (identity quaternion). */
2657
+ export function get${pascal}${argPascal}WFromPayload(payload: Uint8Array): number {
2658
+ const offset = findControl(payload, ${cmd.index});
2659
+ if (offset < 0) {
2660
+ return 1;
2661
+ }
2662
+ return readF32(payload, offset + ${arg.offset} + 12);
2663
+ }
2664
+ `;
2665
+ } else {
2666
+ const readFn = arg.type === "f32" ? "readF32" : arg.type === "uint8" ? "readU8" : arg.type === "int8" ? "readI8" : arg.type === "uint16" ? "readU16" : arg.type === "int16" ? "readI16" : arg.type === "uint32" ? "readU32" : arg.type === "int32" ? "readI32" : "readU8";
2667
+ out += `
2668
+ /** Get ${cmd.name}.${arg.name} value, or 0 if command not present. */
2669
+ export function get${pascal}${argPascal}FromPayload(payload: Uint8Array): ${tsInputType(arg.type)} {
2670
+ const offset = findControl(payload, ${cmd.index});
2671
+ if (offset < 0) {
2672
+ return 0;
2673
+ }
2674
+ return ${readFn}(payload, offset + ${arg.offset});
2675
+ }
2676
+ `;
2677
+ }
2678
+ }
2679
+ return out;
2680
+ }
2681
+ function generateInitDataHeader(schema) {
2682
+ return `// =============================================================================
2683
+ // Init Data Bindings
2684
+ // =============================================================================
2685
+
2686
+ // Init data is read-only constant data set once per session.
2687
+ // The data is shared by all clients and hashed into session identification.
2688
+
2689
+ /** Init data buffer magic number */
2690
+ export const INIT_DATA_MAGIC = 0x${INIT_DATA_MAGIC.toString(16)};
2691
+
2692
+ /** Init data header size in bytes */
2693
+ export const INIT_DATA_HEADER_SIZE = ${INIT_DATA_HEADER_SIZE};
2694
+
2695
+ /** Total init data buffer size in bytes */
2696
+ export const INIT_DATA_TOTAL_SIZE = ${schema.totalSize};
2697
+
2698
+ /** Internal DataView for init data - set via setInitData() */
2699
+ let _initDataView: DataView | null = null;
2700
+
2701
+ /** Set init data buffer. Called by executor during initialization. */
2702
+ export function setInitData(data: Uint8Array): void {
2703
+ if (data.length !== INIT_DATA_TOTAL_SIZE) {
2704
+ throw new Error(\`Init data size mismatch: expected \${INIT_DATA_TOTAL_SIZE}, got \${data.length}\`);
2705
+ }
2706
+ _initDataView = new DataView(data.buffer, data.byteOffset, data.byteLength);
2707
+
2708
+ // Verify magic number
2709
+ const magic = _initDataView.getUint32(0, true);
2710
+ if (magic !== INIT_DATA_MAGIC) {
2711
+ throw new Error(\`Invalid init data magic: expected 0x\${INIT_DATA_MAGIC.toString(16)}, got 0x\${magic.toString(16)}\`);
2712
+ }
2713
+ }
2714
+
2715
+ /** Check if init data has been set */
2716
+ export function hasInitData(): boolean {
2717
+ return _initDataView !== null;
2718
+ }
2719
+
2720
+ /** Get the init data DataView (throws if not set) */
2721
+ function getInitDataView(): DataView {
2722
+ if (!_initDataView) {
2723
+ throw new Error('Init data not set. Call setInitData() first.');
2724
+ }
2725
+ return _initDataView;
2726
+ }
2727
+ `;
2728
+ }
2729
+ function generateInitDataItemOffsets(schema) {
2730
+ let out = `// =============================================================================
2731
+ // Init Data Item Offsets
2732
+ // =============================================================================
2733
+
2734
+ `;
2735
+ for (const item of schema.items) {
2736
+ const UPPER = toUpperSnake(item.name);
2737
+ out += `const INIT_${UPPER}_OFFSET = ${item.storageOffset};
2738
+ `;
2739
+ if (item.isBlob) {
2740
+ out += `export const INIT_${UPPER}_MAX_SIZE = ${item.blobMaxSize};
2741
+ `;
2742
+ }
2743
+ }
2744
+ return out;
2745
+ }
2746
+ function generateInitDataAccessor(field, itemName, itemOffset) {
2747
+ const fieldPascal = toPascalCase(field.name);
2748
+ const funcName = `get${toPascalCase(itemName)}${fieldPascal}`;
2749
+ const offset = itemOffset + field.offset;
2750
+ const vec3Array = parseVec3ArrayType(field.type);
2751
+ if (vec3Array) {
2752
+ const count = vec3Array.length;
2753
+ return `
2754
+ /** Get ${itemName}.${field.name}[index].x (max index: ${count - 1}) */
2755
+ export function ${funcName}X(index: number): number {
2756
+ if (index < 0 || index >= ${count}) return 0;
2757
+ return getInitDataView().getFloat32(${offset} + index * 12, true);
2758
+ }
2759
+
2760
+ /** Get ${itemName}.${field.name}[index].y (max index: ${count - 1}) */
2761
+ export function ${funcName}Y(index: number): number {
2762
+ if (index < 0 || index >= ${count}) return 0;
2763
+ return getInitDataView().getFloat32(${offset} + index * 12 + 4, true);
2764
+ }
2765
+
2766
+ /** Get ${itemName}.${field.name}[index].z (max index: ${count - 1}) */
2767
+ export function ${funcName}Z(index: number): number {
2768
+ if (index < 0 || index >= ${count}) return 0;
2769
+ return getInitDataView().getFloat32(${offset} + index * 12 + 8, true);
2770
+ }
2771
+ `;
2772
+ }
2773
+ const vec2Array = parseVec2ArrayType(field.type);
2774
+ if (vec2Array) {
2775
+ const count = vec2Array.length;
2776
+ return `
2777
+ /** Get ${itemName}.${field.name}[index].x (max index: ${count - 1}) */
2778
+ export function ${funcName}X(index: number): number {
2779
+ if (index < 0 || index >= ${count}) return 0;
2780
+ return getInitDataView().getFloat32(${offset} + index * 8, true);
2781
+ }
2782
+
2783
+ /** Get ${itemName}.${field.name}[index].y (max index: ${count - 1}) */
2784
+ export function ${funcName}Y(index: number): number {
2785
+ if (index < 0 || index >= ${count}) return 0;
2786
+ return getInitDataView().getFloat32(${offset} + index * 8 + 4, true);
2787
+ }
2788
+ `;
2789
+ }
2790
+ if (field.type === "vec3") {
2791
+ return `
2792
+ /** Get ${itemName}.${field.name}.x */
2793
+ export function ${funcName}X(): number {
2794
+ return getInitDataView().getFloat32(${offset}, true);
2795
+ }
2796
+
2797
+ /** Get ${itemName}.${field.name}.y */
2798
+ export function ${funcName}Y(): number {
2799
+ return getInitDataView().getFloat32(${offset + 4}, true);
2800
+ }
2801
+
2802
+ /** Get ${itemName}.${field.name}.z */
2803
+ export function ${funcName}Z(): number {
2804
+ return getInitDataView().getFloat32(${offset + 8}, true);
2805
+ }
2806
+ `;
2807
+ }
2808
+ if (field.type === "vec2") {
2809
+ return `
2810
+ /** Get ${itemName}.${field.name}.x */
2811
+ export function ${funcName}X(): number {
2812
+ return getInitDataView().getFloat32(${offset}, true);
2813
+ }
2814
+
2815
+ /** Get ${itemName}.${field.name}.y */
2816
+ export function ${funcName}Y(): number {
2817
+ return getInitDataView().getFloat32(${offset + 4}, true);
2818
+ }
2819
+ `;
2820
+ }
2821
+ if (field.type === "quat") {
2822
+ return `
2823
+ /** Get ${itemName}.${field.name}.x */
2824
+ export function ${funcName}X(): number {
2825
+ return getInitDataView().getFloat32(${offset}, true);
1942
2826
  }
1943
2827
 
1944
- /** Get ${cmd.name}.${arg.name}.z (f32), or 0 if command not present. */
1945
- export function get${pascal}${argPascal}ZFromPayload(payload: Uint8Array): number {
1946
- const offset = findControl(payload, ${cmd.index});
1947
- if (offset < 0) {
1948
- return 0;
1949
- }
1950
- return readF32(payload, offset + ${arg.offset} + 8);
2828
+ /** Get ${itemName}.${field.name}.y */
2829
+ export function ${funcName}Y(): number {
2830
+ return getInitDataView().getFloat32(${offset + 4}, true);
1951
2831
  }
1952
2832
 
1953
- /** Get ${cmd.name}.${arg.name}.w (f32), or 1 if command not present (identity quaternion). */
1954
- export function get${pascal}${argPascal}WFromPayload(payload: Uint8Array): number {
1955
- const offset = findControl(payload, ${cmd.index});
1956
- if (offset < 0) {
1957
- return 1;
2833
+ /** Get ${itemName}.${field.name}.z */
2834
+ export function ${funcName}Z(): number {
2835
+ return getInitDataView().getFloat32(${offset + 8}, true);
2836
+ }
2837
+
2838
+ /** Get ${itemName}.${field.name}.w */
2839
+ export function ${funcName}W(): number {
2840
+ return getInitDataView().getFloat32(${offset + 12}, true);
2841
+ }
2842
+ `;
1958
2843
  }
1959
- return readF32(payload, offset + ${arg.offset} + 12);
2844
+ if (field.arrayLength !== void 0 && field.arrayElementType) {
2845
+ const loadFn3 = tsLoadFnForType(field.arrayElementType);
2846
+ const elemSize = getElementSize(field.arrayElementType);
2847
+ const needsLE2 = needsEndian(field.arrayElementType);
2848
+ const tsTyp2 = tsTypeMapping(field.arrayElementType);
2849
+ const leArg2 = needsLE2 ? ", true" : "";
2850
+ const defaultVal = tsTyp2 === "boolean" ? "false" : "0";
2851
+ const count = field.arrayLength;
2852
+ return `
2853
+ /** Get ${itemName}.${field.name}[index] (max index: ${count - 1}) */
2854
+ export function ${funcName}(index: number): ${tsTyp2} {
2855
+ if (index < 0 || index >= ${count}) return ${defaultVal};
2856
+ return getInitDataView().${loadFn3}(${offset} + index * ${elemSize}${leArg2});
1960
2857
  }
1961
2858
  `;
1962
- } else {
1963
- const readFn = arg.type === "f32" ? "readF32" : arg.type === "uint8" ? "readU8" : arg.type === "int8" ? "readI8" : arg.type === "uint16" ? "readU16" : arg.type === "int16" ? "readI16" : arg.type === "uint32" ? "readU32" : arg.type === "int32" ? "readI32" : "readU8";
1964
- out += `
1965
- /** Get ${cmd.name}.${arg.name} value, or 0 if command not present. */
1966
- export function get${pascal}${argPascal}FromPayload(payload: Uint8Array): ${tsInputType(arg.type)} {
1967
- const offset = findControl(payload, ${cmd.index});
1968
- if (offset < 0) {
1969
- return 0;
1970
2859
  }
1971
- return ${readFn}(payload, offset + ${arg.offset});
2860
+ const loadFn2 = tsLoadFnForType(field.type);
2861
+ const needsLE = needsEndian(field.type);
2862
+ const tsTyp = tsTypeMapping(field.type);
2863
+ const leArg = needsLE ? ", true" : "";
2864
+ const retExpr = field.type === "bool" ? `getInitDataView().${loadFn2}(${offset}) !== 0` : `getInitDataView().${loadFn2}(${offset}${leArg})`;
2865
+ return `
2866
+ /** Get ${itemName}.${field.name} */
2867
+ export function ${funcName}(): ${tsTyp} {
2868
+ return ${retExpr};
1972
2869
  }
1973
2870
  `;
1974
- }
2871
+ }
2872
+ function generateInitDataItemAccessors(item) {
2873
+ if (item.isBlob) {
2874
+ const UPPER = toUpperSnake(item.name);
2875
+ const funcName = `get${toPascalCase(item.name)}`;
2876
+ return `
2877
+ // ---------- ${item.name} (blob) ----------
2878
+
2879
+ /** Get current length of ${item.name} blob */
2880
+ export function ${funcName}Length(): number {
2881
+ return getInitDataView().getUint32(INIT_${UPPER}_OFFSET, true);
2882
+ }
2883
+
2884
+ /**
2885
+ * Get ${item.name} blob data as a Uint8Array view.
2886
+ *
2887
+ * WARNING: This returns a view into the init data buffer, not a copy.
2888
+ * Init data is read-only - do NOT mutate the returned array.
2889
+ * Modifying it will corrupt shared init data used by all game logic.
2890
+ */
2891
+ export function ${funcName}(): Uint8Array {
2892
+ const view = getInitDataView();
2893
+ const length = view.getUint32(INIT_${UPPER}_OFFSET, true);
2894
+ return new Uint8Array(view.buffer, view.byteOffset + INIT_${UPPER}_OFFSET + 4, length);
2895
+ }
2896
+ `;
2897
+ }
2898
+ let out = `
2899
+ // ---------- ${item.name} ----------
2900
+ `;
2901
+ for (const field of item.fields) {
2902
+ out += generateInitDataAccessor(field, item.name, item.storageOffset);
2903
+ }
2904
+ return out;
2905
+ }
2906
+ function generateInitData(schema) {
2907
+ if (schema.items.length === 0) {
2908
+ return "";
2909
+ }
2910
+ let out = generateInitDataHeader(schema);
2911
+ out += "\n";
2912
+ out += generateInitDataItemOffsets(schema);
2913
+ out += "\n";
2914
+ for (const item of schema.items) {
2915
+ out += generateInitDataItemAccessors(item);
1975
2916
  }
1976
2917
  return out;
1977
2918
  }
@@ -2059,8 +3000,18 @@ var TypeScriptGenerator = class {
2059
3000
  }
2060
3001
  return out;
2061
3002
  }
2062
- generateGame(state, input) {
2063
- return this.generateState(state) + "\n" + this.generateInput(input);
3003
+ generateInitData(schema) {
3004
+ if (!schema || schema.items.length === 0) {
3005
+ return "";
3006
+ }
3007
+ return generateInitData(schema);
3008
+ }
3009
+ generateGame(state, input, initData) {
3010
+ let out = this.generateState(state) + "\n" + this.generateInput(input);
3011
+ if (initData && initData.items.length > 0) {
3012
+ out += "\n" + this.generateInitData(initData);
3013
+ }
3014
+ return out;
2064
3015
  }
2065
3016
  };
2066
3017
 
@@ -3420,611 +4371,357 @@ function generateGameAllocator() {
3420
4371
  static ALLOCATOR: talc::TalckWasm = unsafe { talc::TalckWasm::new_global() };
3421
4372
  `;
3422
4373
  }
3423
- var RustGenerator = class {
3424
- language = "rust";
3425
- opts;
3426
- maxParticipants;
3427
- constructor(opts = {}) {
3428
- this.opts = opts;
3429
- this.maxParticipants = opts.maxParticipants ?? 8;
3430
- }
3431
- generateState(schema) {
3432
- let out = generateStateHeader2(schema, this.maxParticipants);
3433
- out += generateComponentOffsets2(schema);
3434
- out += generateMemoryHelpers();
3435
- out += generateInternalHelpers();
3436
- out += generateIterationHelpers2(schema);
3437
- out += generateQueryHelpers2();
3438
- out += generateLifecycle2(schema);
3439
- out += `
3440
- // =============================================================================
3441
- // Component Operations
4374
+ function generateRustInitDataHeader(schema) {
4375
+ return `// =============================================================================
4376
+ // Init Data Bindings
3442
4377
  // =============================================================================
4378
+
4379
+ // Init data is read-only constant data set once per session.
4380
+ // The data is shared by all clients and hashed into session identification.
4381
+ //
4382
+ // SAFETY: Init data accessors must only be called after set_init_data_ptr() has
4383
+ // been called. The host runtime guarantees this is done before apply() is called.
4384
+ // Calling accessors before initialization will result in null pointer access.
4385
+ //
4386
+ // To explicitly check if init data is available, use has_init_data().
4387
+ // This is not normally needed since the runtime guarantees initialization order.
4388
+
4389
+ /// Init data buffer magic number
4390
+ pub const INIT_DATA_MAGIC: u32 = 0x${INIT_DATA_MAGIC.toString(16)};
4391
+
4392
+ /// Init data header size in bytes
4393
+ pub const INIT_DATA_HEADER_SIZE: u32 = ${INIT_DATA_HEADER_SIZE};
4394
+
4395
+ /// Total init data buffer size in bytes
4396
+ pub const INIT_DATA_TOTAL_SIZE: u32 = ${schema.totalSize};
4397
+
4398
+ /// Static pointer to init data buffer - set via set_init_data_ptr()
4399
+ ///
4400
+ /// SAFETY: This pointer is initialized to null and must be set via set_init_data_ptr()
4401
+ /// before any init data accessors are called. The runtime guarantees this happens
4402
+ /// before apply() is invoked.
4403
+ static mut INIT_DATA_PTR: *const u8 = core::ptr::null();
4404
+
4405
+ /// Set init data pointer. Called by host during WASM initialization.
4406
+ ///
4407
+ /// # Safety
4408
+ /// The provided pointer must point to a valid init data buffer of size INIT_DATA_TOTAL_SIZE.
4409
+ /// This function must be called exactly once before any init data accessors are used.
4410
+ ///
4411
+ /// # Panics (debug builds only)
4412
+ /// Panics if called more than once, indicating a bug in the runtime.
4413
+ #[no_mangle]
4414
+ pub unsafe extern "C" fn set_init_data_ptr(ptr: *const u8) {
4415
+ debug_assert!(INIT_DATA_PTR.is_null(), "set_init_data_ptr called more than once");
4416
+ INIT_DATA_PTR = ptr;
4417
+ }
4418
+
4419
+ /// Check if init data pointer has been set.
4420
+ ///
4421
+ /// Returns true if init data is available, false otherwise.
4422
+ /// This can be used to guard against accessing init data before initialization,
4423
+ /// though this is not normally needed since the runtime guarantees initialization order.
4424
+ #[inline]
4425
+ pub fn has_init_data() -> bool {
4426
+ unsafe { !INIT_DATA_PTR.is_null() }
4427
+ }
3443
4428
  `;
3444
- for (const comp of schema.entityComponents) {
3445
- out += generateComponentAccessors2(comp);
3446
- }
3447
- if (schema.singletonComponents.length > 0) {
3448
- out += `
4429
+ }
4430
+ function generateRustInitDataItemOffsets(schema) {
4431
+ let out = `
3449
4432
  // =============================================================================
3450
- // Singleton Components
4433
+ // Init Data Item Offsets
3451
4434
  // =============================================================================
4435
+
3452
4436
  `;
3453
- for (const comp of schema.singletonComponents) {
3454
- if (comp.isBlob) {
3455
- out += generateBlobAccessors2(comp);
3456
- } else {
3457
- out += generateSingletonAccessors2(comp);
3458
- }
3459
- }
3460
- }
3461
- if (schema.events && schema.events.length > 0) {
3462
- out += `
3463
- // =============================================================================
3464
- // Events
3465
- // =============================================================================
4437
+ for (const item of schema.items) {
4438
+ const UPPER = toUpperSnake(item.name);
4439
+ out += `const INIT_${UPPER}_OFFSET: u32 = ${item.storageOffset};
3466
4440
  `;
3467
- for (const event of schema.events) {
3468
- out += generateEventAccessors2(event);
3469
- }
3470
- }
3471
- return out;
3472
- }
3473
- generateInput(schema) {
3474
- validateInputSchemaForCodegen2(schema);
3475
- let out = generateInputHeader2();
3476
- if (schema.controls.length > 0) {
3477
- out += generateControlConstants2(schema);
3478
- out += generateFlagConstants2(schema);
3479
- out += `
3480
- // =============================================================================
3481
- // Control Accessors (read from Player component)
3482
- // =============================================================================
4441
+ if (item.isBlob) {
4442
+ out += `pub const INIT_${UPPER}_MAX_SIZE: u32 = ${item.blobMaxSize};
3483
4443
  `;
3484
- for (const ctrl of schema.controls) {
3485
- out += generateControlAccessors2(ctrl);
3486
- }
3487
- }
3488
- if (schema.commands.length > 0) {
3489
- out += generateCommandConstants(schema);
3490
4444
  }
3491
- return out;
3492
4445
  }
3493
- generateGame(state, input) {
3494
- let out = `// Combined Rust module: state accessors + input helpers + transition
3495
- //
3496
- // Generated code - do not edit manually.
4446
+ return out;
4447
+ }
4448
+ function generateRustInitDataAccessor(field, itemName, itemOffset) {
4449
+ const ITEM_LOWER = toLowerSnake(itemName);
4450
+ const fieldLower = toLowerSnake(field.name);
4451
+ const funcName = `get_${ITEM_LOWER}_${fieldLower}`;
4452
+ const offset = itemOffset + field.offset;
4453
+ const vec3Array = parseVec3ArrayType(field.type);
4454
+ if (vec3Array) {
4455
+ const count = vec3Array.length;
4456
+ return `
4457
+ /// Get ${itemName}.${field.name}[index].x
4458
+ #[inline]
4459
+ pub unsafe fn ${funcName}_x(index: i32) -> f32 {
4460
+ if (index as u32) >= ${count} { return 0.0; }
4461
+ load_f32(INIT_DATA_PTR.add((${offset} + (index as u32) * 12) as usize))
4462
+ }
3497
4463
 
3498
- // --- State accessors --------------------------------------------------------
3499
- `;
3500
- out += this.generateState(state);
3501
- out += `
3502
- // --- Input helpers ---------------------------------------------------------
4464
+ /// Get ${itemName}.${field.name}[index].y
4465
+ #[inline]
4466
+ pub unsafe fn ${funcName}_y(index: i32) -> f32 {
4467
+ if (index as u32) >= ${count} { return 0.0; }
4468
+ load_f32(INIT_DATA_PTR.add((${offset} + (index as u32) * 12 + 4) as usize))
4469
+ }
4470
+
4471
+ /// Get ${itemName}.${field.name}[index].z
4472
+ #[inline]
4473
+ pub unsafe fn ${funcName}_z(index: i32) -> f32 {
4474
+ if (index as u32) >= ${count} { return 0.0; }
4475
+ load_f32(INIT_DATA_PTR.add((${offset} + (index as u32) * 12 + 8) as usize))
4476
+ }
3503
4477
  `;
3504
- out += this.generateInput(input);
3505
- out += generateGameAllocator();
3506
- return out;
3507
4478
  }
3508
- };
4479
+ const vec2Array = parseVec2ArrayType(field.type);
4480
+ if (vec2Array) {
4481
+ const count = vec2Array.length;
4482
+ return `
4483
+ /// Get ${itemName}.${field.name}[index].x
4484
+ #[inline]
4485
+ pub unsafe fn ${funcName}_x(index: i32) -> f32 {
4486
+ if (index as u32) >= ${count} { return 0.0; }
4487
+ load_f32(INIT_DATA_PTR.add((${offset} + (index as u32) * 8) as usize))
4488
+ }
3509
4489
 
3510
- // src/schema.ts
3511
- var HEADER_SIZE = 44;
3512
- var TYPE_SIZES = {
3513
- bool: 1,
3514
- uint8: 1,
3515
- int8: 1,
3516
- uint16: 2,
3517
- int16: 2,
3518
- uint32: 4,
3519
- int32: 4,
3520
- entityRef: 4,
3521
- f32: 4,
3522
- vec2: 8,
3523
- vec3: 12,
3524
- quat: 16,
3525
- enum: 1
3526
- };
3527
- var INPUT_TYPE_SIZES = {
3528
- bool: 1,
3529
- uint8: 1,
3530
- int8: 1,
3531
- uint16: 2,
3532
- int16: 2,
3533
- uint32: 4,
3534
- int32: 4,
3535
- f32: 4,
3536
- flags8: 1,
3537
- flags16: 2,
3538
- flags32: 4,
3539
- vec2: 8,
3540
- vec3: 12,
3541
- quat: 16
3542
- };
3543
- var PRIMITIVE_TYPES = /* @__PURE__ */ new Set([
3544
- "bool",
3545
- "uint8",
3546
- "int8",
3547
- "uint16",
3548
- "int16",
3549
- "uint32",
3550
- "int32",
3551
- "entityRef",
3552
- "f32"
3553
- ]);
3554
- var INPUT_PRIMITIVE_SCALAR_TYPES = /* @__PURE__ */ new Set([
3555
- "bool",
3556
- "uint8",
3557
- "int8",
3558
- "uint16",
3559
- "int16",
3560
- "uint32",
3561
- "int32",
3562
- "f32"
3563
- ]);
3564
- var ENUM_VARIANT_NAME_RE = /^[A-Za-z][A-Za-z0-9_]*$/;
3565
- function parseArrayType2(type) {
3566
- const match = type.match(/^(\w+)\[(\d+)\]$/);
3567
- if (!match || !match[1] || !match[2]) return null;
3568
- const elementType = match[1];
3569
- const length = parseInt(match[2], 10);
3570
- if (!PRIMITIVE_TYPES.has(elementType)) return null;
3571
- return { elementType, length };
4490
+ /// Get ${itemName}.${field.name}[index].y
4491
+ #[inline]
4492
+ pub unsafe fn ${funcName}_y(index: i32) -> f32 {
4493
+ if (index as u32) >= ${count} { return 0.0; }
4494
+ load_f32(INIT_DATA_PTR.add((${offset} + (index as u32) * 8 + 4) as usize))
3572
4495
  }
3573
- function parseInputArrayType(type) {
3574
- const match = type.match(/^(\w+)\[(\d+)\]$/);
3575
- if (!match || !match[1] || !match[2]) return null;
3576
- const elementType = match[1];
3577
- const length = parseInt(match[2], 10);
3578
- if (!INPUT_PRIMITIVE_SCALAR_TYPES.has(elementType)) {
3579
- throw new Error(
3580
- `Invalid array element type '${elementType}': arrays only support primitive scalar types (bool, uint8, int8, uint16, int16, uint32, int32, f32)`
3581
- );
4496
+ `;
3582
4497
  }
3583
- return { elementType, length };
4498
+ if (field.type === "vec3") {
4499
+ return `
4500
+ /// Get ${itemName}.${field.name}.x
4501
+ #[inline]
4502
+ pub unsafe fn ${funcName}_x() -> f32 {
4503
+ load_f32(INIT_DATA_PTR.add(${offset}))
3584
4504
  }
3585
- function getTypeSize(type) {
3586
- const arrayInfo = parseArrayType2(type);
3587
- if (arrayInfo) {
3588
- const elementSize = TYPE_SIZES[arrayInfo.elementType];
3589
- if (elementSize === void 0) {
3590
- throw new Error(`Unknown element type: ${arrayInfo.elementType}`);
3591
- }
3592
- return elementSize * arrayInfo.length;
3593
- }
3594
- const size = TYPE_SIZES[type];
3595
- if (size === void 0) {
3596
- throw new Error(`Unknown type: ${type}`);
3597
- }
3598
- return size;
4505
+
4506
+ /// Get ${itemName}.${field.name}.y
4507
+ #[inline]
4508
+ pub unsafe fn ${funcName}_y() -> f32 {
4509
+ load_f32(INIT_DATA_PTR.add(${offset + 4}))
3599
4510
  }
3600
- function getInputTypeSize(type) {
3601
- const arrayInfo = parseInputArrayType(type);
3602
- if (arrayInfo) {
3603
- const elementSize = INPUT_TYPE_SIZES[arrayInfo.elementType];
3604
- if (elementSize === void 0) {
3605
- throw new Error(`Unknown element type: ${arrayInfo.elementType}`);
3606
- }
3607
- return elementSize * arrayInfo.length;
3608
- }
3609
- const size = INPUT_TYPE_SIZES[type];
3610
- if (size === void 0) {
3611
- throw new Error(`Unknown type: ${type}`);
4511
+
4512
+ /// Get ${itemName}.${field.name}.z
4513
+ #[inline]
4514
+ pub unsafe fn ${funcName}_z() -> f32 {
4515
+ load_f32(INIT_DATA_PTR.add(${offset + 8}))
4516
+ }
4517
+ `;
3612
4518
  }
3613
- return size;
4519
+ if (field.type === "vec2") {
4520
+ return `
4521
+ /// Get ${itemName}.${field.name}.x
4522
+ #[inline]
4523
+ pub unsafe fn ${funcName}_x() -> f32 {
4524
+ load_f32(INIT_DATA_PTR.add(${offset}))
3614
4525
  }
3615
- function getValidatedTypeSize(type, componentName, fieldName) {
3616
- try {
3617
- return getTypeSize(type);
3618
- } catch (error) {
3619
- const location = fieldName ? `${componentName}.${fieldName}` : componentName;
3620
- const message = error instanceof Error ? error.message : String(error);
3621
- throw new Error(`Invalid type '${type}' for '${location}': ${message}`);
4526
+
4527
+ /// Get ${itemName}.${field.name}.y
4528
+ #[inline]
4529
+ pub unsafe fn ${funcName}_y() -> f32 {
4530
+ load_f32(INIT_DATA_PTR.add(${offset + 4}))
4531
+ }
4532
+ `;
3622
4533
  }
4534
+ if (field.type === "quat") {
4535
+ return `
4536
+ /// Get ${itemName}.${field.name}.x
4537
+ #[inline]
4538
+ pub unsafe fn ${funcName}_x() -> f32 {
4539
+ load_f32(INIT_DATA_PTR.add(${offset}))
3623
4540
  }
3624
- function validateEnumVariants(rawVariants, subject) {
3625
- const variants = rawVariants ?? [];
3626
- if (!Array.isArray(variants)) {
3627
- throw new Error(`enum ${subject} must have at least one variant`);
4541
+
4542
+ /// Get ${itemName}.${field.name}.y
4543
+ #[inline]
4544
+ pub unsafe fn ${funcName}_y() -> f32 {
4545
+ load_f32(INIT_DATA_PTR.add(${offset + 4}))
4546
+ }
4547
+
4548
+ /// Get ${itemName}.${field.name}.z
4549
+ #[inline]
4550
+ pub unsafe fn ${funcName}_z() -> f32 {
4551
+ load_f32(INIT_DATA_PTR.add(${offset + 8}))
4552
+ }
4553
+
4554
+ /// Get ${itemName}.${field.name}.w
4555
+ #[inline]
4556
+ pub unsafe fn ${funcName}_w() -> f32 {
4557
+ load_f32(INIT_DATA_PTR.add(${offset + 12}))
4558
+ }
4559
+ `;
3628
4560
  }
3629
- if (variants.length === 0) {
3630
- throw new Error(`enum ${subject} must have at least one variant`);
4561
+ if (field.arrayLength !== void 0 && field.arrayElementType) {
4562
+ const loadFunc2 = loadFnForType(field.arrayElementType);
4563
+ const elemSize = getElementSize(field.arrayElementType);
4564
+ const rustTyp2 = rustTypeMapping(field.arrayElementType);
4565
+ const defaultVal = rustTyp2 === "bool" ? "false" : `0${rustTyp2 === "f32" ? ".0" : ""}`;
4566
+ const count = field.arrayLength;
4567
+ return `
4568
+ /// Get ${itemName}.${field.name}[index]
4569
+ #[inline]
4570
+ pub unsafe fn ${funcName}(index: i32) -> ${rustTyp2} {
4571
+ if (index as u32) >= ${count} { return ${defaultVal}; }
4572
+ ${loadFunc2}(INIT_DATA_PTR.add((${offset} + (index as u32) * ${elemSize}) as usize))
4573
+ }
4574
+ `;
3631
4575
  }
3632
- if (variants.length > 256) {
3633
- throw new Error(`enum ${subject} has ${variants.length} variants, maximum is 256`);
4576
+ const loadFunc = loadFnForType(field.type);
4577
+ const rustTyp = rustTypeMapping(field.type);
4578
+ return `
4579
+ /// Get ${itemName}.${field.name}
4580
+ #[inline]
4581
+ pub unsafe fn ${funcName}() -> ${rustTyp} {
4582
+ ${loadFunc}(INIT_DATA_PTR.add(${offset}))
4583
+ }
4584
+ `;
4585
+ }
4586
+ function generateRustInitDataItemAccessors(item) {
4587
+ if (item.isBlob) {
4588
+ const UPPER = toUpperSnake(item.name);
4589
+ const funcNameLower = toLowerSnake(item.name);
4590
+ return `
4591
+ // ---------- ${item.name} (blob) ----------
4592
+
4593
+ /// Get current length of ${item.name} blob
4594
+ #[inline]
4595
+ pub unsafe fn get_${funcNameLower}_len() -> u32 {
4596
+ load_u32(INIT_DATA_PTR.add(INIT_${UPPER}_OFFSET as usize))
4597
+ }
4598
+
4599
+ /// Get pointer to ${item.name} blob data
4600
+ #[inline]
4601
+ pub unsafe fn get_${funcNameLower}_ptr() -> *const u8 {
4602
+ INIT_DATA_PTR.add(INIT_${UPPER}_OFFSET as usize + 4)
4603
+ }
4604
+ `;
3634
4605
  }
3635
- const seen = /* @__PURE__ */ new Set();
3636
- const seenNormalized = /* @__PURE__ */ new Map();
3637
- for (const v of variants) {
3638
- if (typeof v !== "string" || v.trim().length === 0) {
3639
- throw new Error(`enum ${subject} has empty variant name`);
3640
- }
3641
- if (!ENUM_VARIANT_NAME_RE.test(v)) {
3642
- throw new Error(
3643
- `enum ${subject} has invalid variant name ${JSON.stringify(v)} (must match ${ENUM_VARIANT_NAME_RE})`
3644
- );
3645
- }
3646
- if (seen.has(v)) {
3647
- throw new Error(`enum ${subject} has duplicate variant '${v}'`);
3648
- }
3649
- seen.add(v);
3650
- const normalized = toUpperSnake2(v);
3651
- const prev = seenNormalized.get(normalized);
3652
- if (prev !== void 0) {
3653
- throw new Error(
3654
- `enum ${subject} has variants ${JSON.stringify(prev)} and ${JSON.stringify(v)} which collide as ${JSON.stringify(normalized)}`
3655
- );
3656
- }
3657
- seenNormalized.set(normalized, v);
4606
+ let out = `
4607
+ // ---------- ${item.name} ----------
4608
+ `;
4609
+ for (const field of item.fields) {
4610
+ out += generateRustInitDataAccessor(field, item.name, item.storageOffset);
3658
4611
  }
3659
- return variants;
4612
+ return out;
3660
4613
  }
3661
- function toUpperSnake2(s) {
3662
- let result = "";
3663
- for (let i = 0; i < s.length; i++) {
3664
- const c = s[i];
3665
- if (i > 0 && c >= "A" && c <= "Z") {
3666
- result += "_";
3667
- }
3668
- result += c.toUpperCase();
4614
+ function generateRustInitData(schema) {
4615
+ if (schema.items.length === 0) {
4616
+ return "";
3669
4617
  }
3670
- return result;
3671
- }
3672
- function compileStateSchema(schema) {
3673
- const { maxEntities, components: componentDefs, events: eventDefs } = schema;
3674
- if (maxEntities <= 0 || maxEntities > 65535) {
3675
- throw new Error(`maxEntities must be between 1 and 65535, got ${maxEntities}`);
4618
+ let out = generateRustInitDataHeader(schema);
4619
+ out += "\n";
4620
+ out += generateRustInitDataItemOffsets(schema);
4621
+ out += "\n";
4622
+ for (const item of schema.items) {
4623
+ out += generateRustInitDataItemAccessors(item);
3676
4624
  }
3677
- if (!Array.isArray(componentDefs)) {
3678
- throw new Error("schema.components must be an array to preserve deterministic ordering");
4625
+ return out;
4626
+ }
4627
+ var RustGenerator = class {
4628
+ language = "rust";
4629
+ opts;
4630
+ maxParticipants;
4631
+ constructor(opts = {}) {
4632
+ this.opts = opts;
4633
+ this.maxParticipants = opts.maxParticipants ?? 8;
3679
4634
  }
3680
- const numEntityComponents = componentDefs.filter((componentDef) => {
3681
- const singletonFlag = componentDef.singleton;
3682
- return singletonFlag !== true;
3683
- }).length;
3684
- const bitmaskSize = Math.ceil(numEntityComponents / 8);
3685
- const entityRecordSize = 2 + bitmaskSize;
3686
- const entityTableOffset = HEADER_SIZE;
3687
- const entityTableSize = maxEntities * entityRecordSize;
3688
- const freeStackOffset = entityTableOffset + entityTableSize;
3689
- const freeStackSize = 2 + maxEntities * 2;
3690
- const componentStorageOffset = freeStackOffset + freeStackSize;
3691
- const components = [];
3692
- const entityComponents = [];
3693
- const singletonComponents = [];
3694
- const componentByName = /* @__PURE__ */ new Map();
3695
- const seenComponentNames = /* @__PURE__ */ new Set();
3696
- let storageOffset = componentStorageOffset;
3697
- const pendingSingletons = [];
3698
- let bitIndex = 0;
3699
- for (let i = 0; i < componentDefs.length; i++) {
3700
- const componentDef = componentDefs[i];
3701
- if (componentDef === void 0 || componentDef === null || typeof componentDef !== "object") {
3702
- throw new Error(`Component at index ${i} must be an object`);
3703
- }
3704
- const rawName = componentDef.name;
3705
- if (typeof rawName !== "string" || rawName.trim().length === 0) {
3706
- throw new Error(`Component at index ${i} must have a non-empty string 'name'`);
3707
- }
3708
- const name = rawName;
3709
- if (seenComponentNames.has(name)) {
3710
- throw new Error(`Duplicate component name '${name}' in schema`);
3711
- }
3712
- seenComponentNames.add(name);
3713
- const rawType = componentDef.type;
3714
- if (typeof rawType !== "string" || rawType.trim().length === 0) {
3715
- throw new Error(`Component '${name}' must have a string 'type'`);
3716
- }
3717
- const componentType = rawType;
3718
- const fields = [];
3719
- let componentSize = 0;
3720
- let isTag = false;
3721
- let isBlob = false;
3722
- let blobMaxSize;
3723
- const isSingleton = componentDef.singleton === true;
3724
- if (componentType === "tag") {
3725
- isTag = true;
3726
- if (isSingleton) {
3727
- throw new Error(`Component '${name}' cannot be a singleton tag`);
3728
- }
3729
- } else if (componentType === "compound") {
3730
- const rawFields = componentDef.fields;
3731
- if (!Array.isArray(rawFields)) {
3732
- throw new Error(`Component '${name}' with type 'compound' must declare a 'fields' array`);
3733
- }
3734
- const seenFieldNames = /* @__PURE__ */ new Set();
3735
- let fieldOffset = 0;
3736
- for (let fieldIndex = 0; fieldIndex < rawFields.length; fieldIndex++) {
3737
- const fieldDef = rawFields[fieldIndex];
3738
- if (fieldDef === void 0 || fieldDef === null || typeof fieldDef !== "object") {
3739
- throw new Error(`Component '${name}' has an invalid field at index ${fieldIndex}`);
3740
- }
3741
- const rawFieldName = fieldDef.name;
3742
- if (typeof rawFieldName !== "string" || rawFieldName.trim().length === 0) {
3743
- throw new Error(`Component '${name}' has a field with a missing or empty 'name'`);
3744
- }
3745
- if (seenFieldNames.has(rawFieldName)) {
3746
- throw new Error(`Component '${name}' has duplicate field '${rawFieldName}'`);
3747
- }
3748
- seenFieldNames.add(rawFieldName);
3749
- const rawFieldType = fieldDef.type;
3750
- if (typeof rawFieldType !== "string" || rawFieldType.trim().length === 0) {
3751
- throw new Error(`Field '${name}.${rawFieldName}' must have a string 'type'`);
3752
- }
3753
- const size = getValidatedTypeSize(rawFieldType, name, rawFieldName);
3754
- const arrayInfo = parseArrayType2(rawFieldType);
3755
- const compiledField = {
3756
- name: rawFieldName,
3757
- type: rawFieldType,
3758
- size,
3759
- offset: fieldOffset
3760
- };
3761
- if (arrayInfo) {
3762
- compiledField.arrayLength = arrayInfo.length;
3763
- compiledField.arrayElementType = arrayInfo.elementType;
3764
- }
3765
- if (rawFieldType === "enum") {
3766
- compiledField.variants = validateEnumVariants(
3767
- fieldDef.variants,
3768
- `field '${name}.${rawFieldName}'`
3769
- );
3770
- }
3771
- fields.push(compiledField);
3772
- fieldOffset += size;
3773
- componentSize += size;
3774
- }
3775
- } else if (componentType === "blob") {
3776
- if (!isSingleton) {
3777
- throw new Error(`Component '${name}' with type 'blob' must be a singleton`);
3778
- }
3779
- const maxSize = componentDef.maxSize;
3780
- if (typeof maxSize !== "number" || maxSize <= 0) {
3781
- throw new Error(`Component '${name}' with type 'blob' requires positive 'maxSize'`);
3782
- }
3783
- componentSize = 4 + maxSize;
3784
- isBlob = true;
3785
- blobMaxSize = maxSize;
3786
- } else {
3787
- const fieldType = componentType;
3788
- const size = getValidatedTypeSize(fieldType, name);
3789
- const arrayInfo = parseArrayType2(fieldType);
3790
- const compiledField = {
3791
- name: "",
3792
- type: fieldType,
3793
- size,
3794
- offset: 0
3795
- };
3796
- if (arrayInfo) {
3797
- compiledField.arrayLength = arrayInfo.length;
3798
- compiledField.arrayElementType = arrayInfo.elementType;
3799
- }
3800
- if (fieldType === "enum") {
3801
- compiledField.variants = validateEnumVariants(
3802
- componentDef.variants,
3803
- `component '${name}'`
3804
- );
3805
- }
3806
- fields.push(compiledField);
3807
- componentSize = size;
3808
- }
3809
- const compiled = {
3810
- name,
3811
- index: isSingleton ? -1 : bitIndex,
3812
- isTag,
3813
- isSingleton,
3814
- size: componentSize,
3815
- storageOffset: 0,
3816
- fields
3817
- };
3818
- if (isBlob) {
3819
- compiled.isBlob = true;
3820
- compiled.blobMaxSize = blobMaxSize;
4635
+ generateState(schema) {
4636
+ let out = generateStateHeader2(schema, this.maxParticipants);
4637
+ out += generateComponentOffsets2(schema);
4638
+ out += generateMemoryHelpers();
4639
+ out += generateInternalHelpers();
4640
+ out += generateIterationHelpers2(schema);
4641
+ out += generateQueryHelpers2();
4642
+ out += generateLifecycle2(schema);
4643
+ out += `
4644
+ // =============================================================================
4645
+ // Component Operations
4646
+ // =============================================================================
4647
+ `;
4648
+ for (const comp of schema.entityComponents) {
4649
+ out += generateComponentAccessors2(comp);
3821
4650
  }
3822
- components.push(compiled);
3823
- componentByName.set(name, compiled);
3824
- if (isSingleton) {
3825
- pendingSingletons.push(compiled);
3826
- singletonComponents.push(compiled);
3827
- } else {
3828
- if (!isTag && componentSize > 0) {
3829
- compiled.storageOffset = storageOffset;
3830
- storageOffset += maxEntities * componentSize;
3831
- } else {
3832
- compiled.storageOffset = 0;
4651
+ if (schema.singletonComponents.length > 0) {
4652
+ out += `
4653
+ // =============================================================================
4654
+ // Singleton Components
4655
+ // =============================================================================
4656
+ `;
4657
+ for (const comp of schema.singletonComponents) {
4658
+ if (comp.isBlob) {
4659
+ out += generateBlobAccessors2(comp);
4660
+ } else {
4661
+ out += generateSingletonAccessors2(comp);
4662
+ }
3833
4663
  }
3834
- entityComponents.push(compiled);
3835
- bitIndex += 1;
3836
4664
  }
3837
- }
3838
- const singletonStorageOffset = storageOffset;
3839
- let singletonCursor = singletonStorageOffset;
3840
- for (const singleton of pendingSingletons) {
3841
- singleton.storageOffset = singletonCursor;
3842
- singletonCursor += singleton.size;
3843
- }
3844
- const singletonStorageSize = singletonCursor - singletonStorageOffset;
3845
- let totalSize = singletonCursor;
3846
- let events;
3847
- let eventByName;
3848
- let eventStorageOffset;
3849
- let eventStorageSize;
3850
- if (eventDefs && eventDefs.length > 0) {
3851
- events = [];
3852
- eventByName = /* @__PURE__ */ new Map();
3853
- eventStorageOffset = totalSize;
3854
- let eventCursor = eventStorageOffset;
3855
- const seenEventNames = /* @__PURE__ */ new Set();
3856
- for (let i = 0; i < eventDefs.length; i++) {
3857
- const eventDef = eventDefs[i];
3858
- if (typeof eventDef.name !== "string" || eventDef.name.trim().length === 0) {
3859
- throw new Error(`Event at index ${i} must have a non-empty string 'name'`);
3860
- }
3861
- if (seenEventNames.has(eventDef.name)) {
3862
- throw new Error(`Duplicate event name '${eventDef.name}' in schema`);
3863
- }
3864
- seenEventNames.add(eventDef.name);
3865
- if (seenComponentNames.has(eventDef.name)) {
3866
- throw new Error(`Event name '${eventDef.name}' conflicts with component name`);
3867
- }
3868
- if (!Array.isArray(eventDef.fields) || eventDef.fields.length === 0) {
3869
- throw new Error(`Event '${eventDef.name}' must have at least one field`);
3870
- }
3871
- const maxEvents = eventDef.maxEvents && eventDef.maxEvents > 0 ? eventDef.maxEvents : 64;
3872
- const eventFields = [];
3873
- let fieldOffset = 0;
3874
- const seenFieldNames = /* @__PURE__ */ new Set();
3875
- for (let fi = 0; fi < eventDef.fields.length; fi++) {
3876
- const fieldDef = eventDef.fields[fi];
3877
- if (typeof fieldDef.name !== "string" || fieldDef.name.trim().length === 0) {
3878
- throw new Error(`Event '${eventDef.name}' has a field with a missing or empty 'name' at index ${fi}`);
3879
- }
3880
- if (seenFieldNames.has(fieldDef.name)) {
3881
- throw new Error(`Event '${eventDef.name}' has duplicate field '${fieldDef.name}'`);
3882
- }
3883
- seenFieldNames.add(fieldDef.name);
3884
- if (typeof fieldDef.type !== "string" || fieldDef.type.trim().length === 0) {
3885
- throw new Error(`Event field '${eventDef.name}.${fieldDef.name}' must have a string 'type'`);
3886
- }
3887
- const arrayInfo = parseArrayType2(fieldDef.type);
3888
- if (arrayInfo) {
3889
- throw new Error(
3890
- `Event field '${eventDef.name}.${fieldDef.name}' has array type '${fieldDef.type}'; events do not support array types`
3891
- );
3892
- }
3893
- const size = getValidatedTypeSize(fieldDef.type, eventDef.name, fieldDef.name);
3894
- const compiledField = {
3895
- name: fieldDef.name,
3896
- type: fieldDef.type,
3897
- size,
3898
- offset: fieldOffset
3899
- };
3900
- if (fieldDef.type === "enum" && fieldDef.variants) {
3901
- compiledField.variants = validateEnumVariants(fieldDef.variants, `event field '${eventDef.name}.${fieldDef.name}'`);
3902
- }
3903
- eventFields.push(compiledField);
3904
- fieldOffset += size;
4665
+ if (schema.events && schema.events.length > 0) {
4666
+ out += `
4667
+ // =============================================================================
4668
+ // Events
4669
+ // =============================================================================
4670
+ `;
4671
+ for (const event of schema.events) {
4672
+ out += generateEventAccessors2(event);
3905
4673
  }
3906
- const recordSize = fieldOffset;
3907
- const bufferSize = 2 + maxEvents * recordSize;
3908
- const compiledEvent = {
3909
- name: eventDef.name,
3910
- index: i,
3911
- maxEvents,
3912
- recordSize,
3913
- storageOffset: eventCursor,
3914
- bufferSize,
3915
- fields: eventFields
3916
- };
3917
- events.push(compiledEvent);
3918
- eventByName.set(eventDef.name, compiledEvent);
3919
- eventCursor += bufferSize;
3920
4674
  }
3921
- eventStorageSize = eventCursor - eventStorageOffset;
3922
- totalSize = eventCursor;
4675
+ return out;
3923
4676
  }
3924
- return {
3925
- maxEntities,
3926
- components,
3927
- entityComponents,
3928
- singletonComponents,
3929
- componentByName,
3930
- definition: schema,
3931
- events,
3932
- eventByName,
3933
- headerOffset: 0,
3934
- headerSize: HEADER_SIZE,
3935
- entityTableOffset,
3936
- entityRecordSize,
3937
- freeStackOffset,
3938
- freeStackSize,
3939
- componentStorageOffset,
3940
- singletonStorageOffset,
3941
- singletonStorageSize,
3942
- eventStorageOffset,
3943
- eventStorageSize,
3944
- totalSize,
3945
- bitmaskSize
3946
- };
3947
- }
3948
- function compileInputSchema(schema) {
3949
- const controls = [];
3950
- const commands = [];
3951
- const controlByName = /* @__PURE__ */ new Map();
3952
- const commandByName = /* @__PURE__ */ new Map();
3953
- const seenNames = /* @__PURE__ */ new Set();
3954
- let index = 0;
3955
- for (let i = 0; i < schema.controls.length; i++) {
3956
- const controlDef = schema.controls[i];
3957
- if (typeof controlDef.name !== "string" || controlDef.name.trim().length === 0) {
3958
- throw new Error(`Control at index ${i} must have a non-empty string 'name'`);
3959
- }
3960
- if (seenNames.has(controlDef.name)) {
3961
- throw new Error(`Duplicate control/command name '${controlDef.name}' in schema`);
3962
- }
3963
- seenNames.add(controlDef.name);
3964
- if (typeof controlDef.type !== "string" || controlDef.type.trim().length === 0) {
3965
- throw new Error(`Control '${controlDef.name}' must have a string 'type'`);
4677
+ generateInput(schema) {
4678
+ validateInputSchemaForCodegen2(schema);
4679
+ let out = generateInputHeader2();
4680
+ if (schema.controls.length > 0) {
4681
+ out += generateControlConstants2(schema);
4682
+ out += generateFlagConstants2(schema);
4683
+ out += `
4684
+ // =============================================================================
4685
+ // Control Accessors (read from Player component)
4686
+ // =============================================================================
4687
+ `;
4688
+ for (const ctrl of schema.controls) {
4689
+ out += generateControlAccessors2(ctrl);
4690
+ }
3966
4691
  }
3967
- const size = getInputTypeSize(controlDef.type);
3968
- const compiled = {
3969
- name: controlDef.name,
3970
- index: index++,
3971
- type: controlDef.type,
3972
- size,
3973
- options: controlDef.options,
3974
- hint: controlDef.hint,
3975
- retain: controlDef.retain === "always" ? "always" : "tick"
3976
- };
3977
- controls.push(compiled);
3978
- controlByName.set(controlDef.name, compiled);
3979
- if (index > 255) {
3980
- throw new Error(`Too many controls/commands: index ${index} exceeds u8 limit (255)`);
4692
+ if (schema.commands.length > 0) {
4693
+ out += generateCommandConstants(schema);
3981
4694
  }
4695
+ return out;
3982
4696
  }
3983
- for (let i = 0; i < schema.commands.length; i++) {
3984
- const commandDef = schema.commands[i];
3985
- if (typeof commandDef.name !== "string" || commandDef.name.trim().length === 0) {
3986
- throw new Error(`Command at index ${i} must have a non-empty string 'name'`);
3987
- }
3988
- if (seenNames.has(commandDef.name)) {
3989
- throw new Error(`Duplicate control/command name '${commandDef.name}' in schema`);
3990
- }
3991
- seenNames.add(commandDef.name);
3992
- const args = [];
3993
- let offset = 0;
3994
- for (let ai = 0; ai < commandDef.args.length; ai++) {
3995
- const argDef = commandDef.args[ai];
3996
- if (typeof argDef.name !== "string" || argDef.name.trim().length === 0) {
3997
- throw new Error(`Command '${commandDef.name}' has an argument with missing 'name' at index ${ai}`);
3998
- }
3999
- const size = getInputTypeSize(argDef.type);
4000
- const arrayInfo = parseInputArrayType(argDef.type);
4001
- const arg = {
4002
- name: argDef.name,
4003
- type: argDef.type,
4004
- size,
4005
- offset
4006
- };
4007
- if (arrayInfo) {
4008
- arg.arrayLength = arrayInfo.length;
4009
- arg.arrayElementType = arrayInfo.elementType;
4010
- }
4011
- args.push(arg);
4012
- offset += size;
4697
+ generateInitData(schema) {
4698
+ if (!schema || schema.items.length === 0) {
4699
+ return "";
4013
4700
  }
4014
- const compiled = {
4015
- name: commandDef.name,
4016
- index: index++,
4017
- args,
4018
- totalSize: offset
4019
- };
4020
- commands.push(compiled);
4021
- commandByName.set(commandDef.name, compiled);
4022
- if (index > 255) {
4023
- throw new Error(`Too many controls/commands: index ${index} exceeds u8 limit (255)`);
4701
+ return generateRustInitData(schema);
4702
+ }
4703
+ generateGame(state, input, initData) {
4704
+ let out = `// Combined Rust module: state accessors + input helpers + transition
4705
+ //
4706
+ // Generated code - do not edit manually.
4707
+
4708
+ // --- State accessors --------------------------------------------------------
4709
+ `;
4710
+ out += this.generateState(state);
4711
+ out += `
4712
+ // --- Input helpers ---------------------------------------------------------
4713
+ `;
4714
+ out += this.generateInput(input);
4715
+ if (initData && initData.items.length > 0) {
4716
+ out += `
4717
+ // --- Init data accessors ---------------------------------------------------
4718
+ `;
4719
+ out += this.generateInitData(initData);
4024
4720
  }
4721
+ out += generateGameAllocator();
4722
+ return out;
4025
4723
  }
4026
- return { controls, commands, controlByName, commandByName };
4027
- }
4724
+ };
4028
4725
 
4029
4726
  // src/vite/codegen-runner.ts
4030
4727
  async function runCodegen(configPath, options) {
@@ -4038,11 +4735,15 @@ async function runCodegen(configPath, options) {
4038
4735
  const compiledInput = compileInputSchema(merged.config.input);
4039
4736
  const preparedState = prepareStateSchema(merged.config.state, compiledInput);
4040
4737
  const compiledState = compileStateSchema(preparedState);
4738
+ const compiledInitData = merged.config.data && merged.config.data.length > 0 ? compileInitDataSchema(merged.config.data) : void 0;
4041
4739
  if (verbose) {
4042
4740
  console.log(`[multitap] State buffer size: ${compiledState.totalSize} bytes`);
4741
+ if (compiledInitData) {
4742
+ console.log(`[multitap] Init data buffer size: ${compiledInitData.totalSize} bytes`);
4743
+ }
4043
4744
  }
4044
4745
  const tsGen = new TypeScriptGenerator({ maxParticipants: merged.config.maxParticipants });
4045
- const tsCode = tsGen.generateGame(compiledState, compiledInput);
4746
+ const tsCode = tsGen.generateGame(compiledState, compiledInput, compiledInitData);
4046
4747
  await mkdir(generatedDir, { recursive: true });
4047
4748
  const tsPath = join2(generatedDir, "state.ts");
4048
4749
  await writeFile(tsPath, tsCode, "utf-8");
@@ -4050,7 +4751,7 @@ async function runCodegen(configPath, options) {
4050
4751
  console.log(`[multitap] Generated ${tsPath}`);
4051
4752
  }
4052
4753
  const rustGen = new RustGenerator({ maxParticipants: merged.config.maxParticipants });
4053
- const rustCode = rustGen.generateGame(compiledState, compiledInput);
4754
+ const rustCode = rustGen.generateGame(compiledState, compiledInput, compiledInitData);
4054
4755
  for (const pluginPath of merged.pluginOrder) {
4055
4756
  const rustDir = join2(pluginPath, "src");
4056
4757
  await mkdir(rustDir, { recursive: true });
@@ -4063,6 +4764,7 @@ async function runCodegen(configPath, options) {
4063
4764
  return {
4064
4765
  compiledState,
4065
4766
  compiledInput,
4767
+ compiledInitData,
4066
4768
  mergedConfig: merged,
4067
4769
  graph
4068
4770
  };
@@ -4186,8 +4888,15 @@ function prepareStateSchemaForSerialization(schema) {
4186
4888
  bitmaskSize: schema.bitmaskSize
4187
4889
  };
4188
4890
  }
4891
+ function prepareInitDataSchemaForSerialization(schema) {
4892
+ return {
4893
+ items: schema.items,
4894
+ headerSize: schema.headerSize,
4895
+ totalSize: schema.totalSize
4896
+ };
4897
+ }
4189
4898
  function buildVirtualModule(options) {
4190
- const { config, plugins, compiledState } = options;
4899
+ const { config, plugins, compiledState, compiledInitData } = options;
4191
4900
  const lines = [];
4192
4901
  lines.push("function base64ToUint8Array(b64) {");
4193
4902
  lines.push(" const binary = atob(b64);");
@@ -4206,7 +4915,8 @@ function buildVirtualModule(options) {
4206
4915
  lines.push("");
4207
4916
  const stateJSON = JSON.stringify(prepareStateSchemaForSerialization(compiledState));
4208
4917
  const inputJSON = JSON.stringify(config.input);
4209
- lines.push(`const schema = { state: ${stateJSON}, input: ${inputJSON} };`);
4918
+ const initDataJSON = compiledInitData ? JSON.stringify(prepareInitDataSchemaForSerialization(compiledInitData)) : "undefined";
4919
+ lines.push(`const schema = { state: ${stateJSON}, input: ${inputJSON}, initData: ${initDataJSON} };`);
4210
4920
  lines.push("");
4211
4921
  lines.push("export default {");
4212
4922
  lines.push(` appID: ${JSON.stringify(config.appID)},`);
@@ -4275,7 +4985,8 @@ function multitapPlugin(options = {}) {
4275
4985
  const moduleCode = buildVirtualModule({
4276
4986
  config: codegenResult.mergedConfig.config,
4277
4987
  plugins,
4278
- compiledState: codegenResult.compiledState
4988
+ compiledState: codegenResult.compiledState,
4989
+ compiledInitData: codegenResult.compiledInitData
4279
4990
  });
4280
4991
  if (verbose) {
4281
4992
  console.log(`[multitap] Build complete`);