@neocompose/cli 0.66.0 → 0.67.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.67.0] - 2026-10-04
4
+
5
+ - `float` is a 32-bit float everywhere, as in C#. Float literals, defaults, constructor arguments, and stored values round to the nearest 32-bit float, and pull prints the shortest decimal that reads back as that float, so `0.7` stays `0.7`. Previously push stored the 64-bit double, and Unity read a value that its own `float` could not hold.
6
+ - Float arithmetic rounds to 32 bits after every step, as do `Math.Sqrt`, `ToFloat`, vector constructors, and native function results declared `float`. `int` division truncates toward zero, so `7 / 2` is `3`. Previously it returned `3.5`.
7
+ - Unity codegen types NeoScript `float` members, parameters, and returns as C# `float` instead of `double`.
8
+ - Native functions with `List` or `Dictionary` parameters receive the collection. Previously generated code cast the script value straight to the C# collection type and threw `InvalidCastException`.
9
+ - Compiler revision 17 recompiles every stored body. The rollout also rounds every 64-bit float already stored in values, members, and constructors, including retained history.
10
+ - Push rejects a float, vector component, color channel, float bound, or float parameter default that is not a 32-bit float. Older CLIs that send 64-bit doubles fail validation instead of storing them.
11
+
3
12
  ## [0.66.0] - 2026-10-03
4
13
 
5
14
  - Add the 18 script runtime lifecycle interfaces from P98, such as `INeoOnLoad`, `INeoUnityUpdate`, and `INeoUnityOnTriggerEnter2D`. Every project gets them as protected system records, and pull writes them under `Interfaces/System/` with `@system(kind: .ScriptRuntime)`.
package/dist/neo.mjs CHANGED
@@ -16371,7 +16371,7 @@ var NEOSCRIPT_COMPILER_REVISION;
16371
16371
  var init_strict_ir = __esm({
16372
16372
  "../packages/neoscript-language/src/strict-ir.ts"() {
16373
16373
  "use strict";
16374
- NEOSCRIPT_COMPILER_REVISION = 16;
16374
+ NEOSCRIPT_COMPILER_REVISION = 17;
16375
16375
  }
16376
16376
  });
16377
16377
 
@@ -20116,7 +20116,10 @@ var init_strict_resolver = __esm({
20116
20116
  canonicalDecimal(expression.raw, expression.pos)
20117
20117
  );
20118
20118
  }
20119
- return literal({ kind: "primitive", name: "float" }, expression.value);
20119
+ return literal(
20120
+ { kind: "primitive", name: "float" },
20121
+ Math.fround(expression.value)
20122
+ );
20120
20123
  }
20121
20124
  case "litString":
20122
20125
  return literal({ kind: "primitive", name: "string" }, expression.value);
@@ -24750,7 +24753,9 @@ var init_strict_resolver = __esm({
24750
24753
  arithmetic: {
24751
24754
  type: operator,
24752
24755
  pointers: [left.pointer, right.pointer],
24753
- ...isPrimitive(result, "decimal") ? { decimal: true } : {}
24756
+ ...isPrimitive(result, "decimal") ? { decimal: true } : {},
24757
+ ...isPrimitive(result, "int") ? { numeric: "int" } : {},
24758
+ ...isPrimitive(result, "float") ? { numeric: "float" } : {}
24754
24759
  }
24755
24760
  }
24756
24761
  },
@@ -30109,7 +30114,7 @@ var init_registry = __esm({
30109
30114
  "../packages/manifest/schema-contract/registry.mjs"() {
30110
30115
  "use strict";
30111
30116
  PROJECT_SCHEMA_CONTRACT = Object.freeze({
30112
- cliVersion: "0.66.0",
30117
+ cliVersion: "0.67.0",
30113
30118
  projectFileUploadBatchSize: 32,
30114
30119
  documentRecords: {
30115
30120
  member: {
@@ -39430,6 +39435,47 @@ var init_project_manifest = __esm({
39430
39435
  }
39431
39436
  });
39432
39437
 
39438
+ // ../src/models/members/float32.ts
39439
+ function isFloat32(value) {
39440
+ return typeof value === "number" && Number.isFinite(value) && Math.fround(value) === value;
39441
+ }
39442
+ function unroundedFloatPath(data, path = "") {
39443
+ if (typeof data === "number") {
39444
+ if (Number.isInteger(data)) return null;
39445
+ return isFloat32(data) ? null : path;
39446
+ }
39447
+ if (typeof data !== "object" || data === null) return null;
39448
+ if (Array.isArray(data)) {
39449
+ for (let index = 0; index < data.length; index++) {
39450
+ const found = unroundedFloatPath(data[index], `${path}[${index}]`);
39451
+ if (found !== null) return found;
39452
+ }
39453
+ return null;
39454
+ }
39455
+ for (const [key, value] of Object.entries(data)) {
39456
+ const found = unroundedFloatPath(
39457
+ value,
39458
+ path === "" ? key : `${path}.${key}`
39459
+ );
39460
+ if (found !== null) return found;
39461
+ }
39462
+ return null;
39463
+ }
39464
+ function float32Text(value) {
39465
+ if (Number.isInteger(value)) return String(value);
39466
+ if (Math.fround(value) !== value) return String(value);
39467
+ for (let precision = 1; precision <= 9; precision++) {
39468
+ const text = String(Number(value.toPrecision(precision)));
39469
+ if (Math.fround(Number(text)) === value) return text;
39470
+ }
39471
+ return String(value);
39472
+ }
39473
+ var init_float32 = __esm({
39474
+ "../src/models/members/float32.ts"() {
39475
+ "use strict";
39476
+ }
39477
+ });
39478
+
39433
39479
  // src/project-source/source-format.ts
39434
39480
  function quoteNeoString(value) {
39435
39481
  return JSON.stringify(value);
@@ -39450,7 +39496,7 @@ function formatNeoNumber(value) {
39450
39496
  if (!Number.isFinite(value)) {
39451
39497
  throw new Error(`Cannot emit non-finite Neo number ${String(value)}.`);
39452
39498
  }
39453
- const source = String(value);
39499
+ const source = float32Text(value);
39454
39500
  const match = /^(?<sign>-?)(?<integer>\d+)(?:\.(?<fraction>\d+))?[eE](?<exponent>[+-]?\d+)$/u.exec(
39455
39501
  source
39456
39502
  );
@@ -39474,6 +39520,7 @@ function invariantCaseKey(value) {
39474
39520
  var init_source_format = __esm({
39475
39521
  "src/project-source/source-format.ts"() {
39476
39522
  "use strict";
39523
+ init_float32();
39477
39524
  init_machine_strings();
39478
39525
  }
39479
39526
  });
@@ -40701,7 +40748,7 @@ function hasValidParameterDefault(value) {
40701
40748
  case 2 /* Int */:
40702
40749
  return typeof payload === "number" && Number.isInteger(payload);
40703
40750
  case 4 /* Float */:
40704
- return typeof payload === "number" && Number.isFinite(payload);
40751
+ return isFloat32(payload);
40705
40752
  case 20 /* Decimal */:
40706
40753
  case 3 /* String */:
40707
40754
  case 8 /* Enum */:
@@ -40987,6 +41034,9 @@ function isNSArithmeticOperationInfo(value) {
40987
41034
  const v = value;
40988
41035
  if (typeof v !== "object" || v === null) return false;
40989
41036
  if (!isNSArithmeticOperationType(v.type)) return false;
41037
+ if (v.numeric !== void 0 && v.numeric !== "int" && v.numeric !== "float") {
41038
+ return false;
41039
+ }
40990
41040
  return isNSPointers(v.pointers);
40991
41041
  }
40992
41042
  function isNSArithmeticOperation(value) {
@@ -41557,6 +41607,7 @@ var init_neoscript_guards = __esm({
41557
41607
  "use strict";
41558
41608
  init_neoscript_compiler_revision();
41559
41609
  init_member_kind_enum();
41610
+ init_float32();
41560
41611
  init_structural_narrow();
41561
41612
  init_core_types();
41562
41613
  init_neoscript_types();
@@ -41953,10 +42004,14 @@ function isStructuredLeafFieldValue(type, value) {
41953
42004
  return typeof value === "string" && value.length > 0;
41954
42005
  }
41955
42006
  if (typeof value !== "number" || !Number.isFinite(value)) return false;
41956
- if (type === "float") return true;
42007
+ if (type === "float") return isFloat32(value);
41957
42008
  if (type === "int") return Number.isInteger(value);
41958
42009
  if (type === "sliceIndex") return Number.isInteger(value) && value >= 0;
41959
- return value >= 0 && value <= 1;
42010
+ return isFloat32(value) && value >= 0 && value <= 1;
42011
+ }
42012
+ function storedStructuredLeafFieldNumber(type, value) {
42013
+ const float = type === "float" || type === "unitFloat";
42014
+ return float ? Math.fround(value) : value;
41960
42015
  }
41961
42016
  function isStructuredLeafPartialValue(value) {
41962
42017
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
@@ -41975,22 +42030,43 @@ function structuredLeafPartialViolation(kind, value, legalKeysLabel) {
41975
42030
  return `has no field "${key}"; ${legalKeysLabel} accepts ${structuredLeafFieldKeys(kind).join(", ")}`;
41976
42031
  }
41977
42032
  if (isStructuredLeafFieldValue(field.type, fieldValue)) continue;
41978
- return `field "${key}" ${structuredLeafFieldViolation(field.type)}`;
42033
+ return `field "${key}" ${structuredLeafFieldViolation(field.type, fieldValue)}`;
41979
42034
  }
41980
42035
  return null;
41981
42036
  }
41982
- function structuredLeafFieldViolation(type) {
42037
+ function structuredLeafRecordFieldViolation(kind, value) {
42038
+ if (typeof value !== "object" || value === null) return null;
42039
+ if (Array.isArray(value)) return null;
42040
+ const entries = Object.entries(value);
42041
+ if (entries.length !== structuredLeafFields(kind).length) return null;
42042
+ let found = null;
42043
+ for (const [key, fieldValue] of entries) {
42044
+ const field = structuredLeafField(kind, key);
42045
+ if (field === null) return null;
42046
+ if (typeof fieldValue !== "number") return null;
42047
+ if (found !== null) continue;
42048
+ if (isStructuredLeafFieldValue(field.type, fieldValue)) continue;
42049
+ found = {
42050
+ key,
42051
+ value: fieldValue,
42052
+ violation: structuredLeafFieldViolation(field.type, fieldValue)
42053
+ };
42054
+ }
42055
+ return found;
42056
+ }
42057
+ function structuredLeafFieldViolation(type, value) {
41983
42058
  if (type === "fileId") return "must be a non-empty file id";
41984
- if (type === "float") return "must be a finite number";
41985
42059
  if (type === "int") return "must be a whole number";
41986
42060
  if (type === "sliceIndex") return "must be a whole number 0 or greater";
41987
- return "must be a number between 0 and 1";
42061
+ if (!isFloat32(value)) return "must be a 32-bit float";
42062
+ return "must be between 0 and 1";
41988
42063
  }
41989
42064
  var STRUCTURED_LEAF_FIELDS, STRUCTURED_LEAF_PARTIAL_KEY;
41990
42065
  var init_structured_leaf_fields = __esm({
41991
42066
  "../src/models/members/structured-leaf-fields.ts"() {
41992
42067
  "use strict";
41993
42068
  init_member_kind_enum();
42069
+ init_float32();
41994
42070
  STRUCTURED_LEAF_FIELDS = {
41995
42071
  [11 /* Sprite */]: [
41996
42072
  { name: "FileId", key: "fileId", type: "fileId" },
@@ -42143,9 +42219,9 @@ function isMemberString(value) {
42143
42219
  function isMemberFloatBase(value) {
42144
42220
  const v = asMemberBaseForKind(value, 4 /* Float */);
42145
42221
  if (v === null) return false;
42146
- if (v.minValue !== void 0 && v.minValue !== null && typeof v.minValue !== "number")
42222
+ if (v.minValue !== void 0 && v.minValue !== null && !isFloat32(v.minValue))
42147
42223
  return false;
42148
- if (v.maxValue !== void 0 && v.maxValue !== null && typeof v.maxValue !== "number")
42224
+ if (v.maxValue !== void 0 && v.maxValue !== null && !isFloat32(v.maxValue))
42149
42225
  return false;
42150
42226
  return v.decimalPoints === void 0 || v.decimalPoints === null || typeof v.decimalPoints === "number";
42151
42227
  }
@@ -42154,12 +42230,8 @@ function hasExactKeys(value, keys) {
42154
42230
  if (actual.length !== keys.length) return false;
42155
42231
  return keys.every((key) => Object.prototype.hasOwnProperty.call(value, key));
42156
42232
  }
42157
- function isFiniteNumberValue(value) {
42158
- return typeof value === "number" && Number.isFinite(value);
42159
- }
42160
42233
  function isVectorComponentValue(value, integer3) {
42161
- if (!isFiniteNumberValue(value)) return false;
42162
- if (!integer3) return true;
42234
+ if (!integer3) return isFloat32(value);
42163
42235
  return Number.isInteger(value);
42164
42236
  }
42165
42237
  function isVector2ValueWithComponents(value, integer3) {
@@ -42193,7 +42265,7 @@ function isVector3IntValue(value) {
42193
42265
  return isVector3ValueWithComponents(value, true);
42194
42266
  }
42195
42267
  function isColorComponentValue(value) {
42196
- if (!isFiniteNumberValue(value)) return false;
42268
+ if (!isFloat32(value)) return false;
42197
42269
  return value >= 0 && value <= 1;
42198
42270
  }
42199
42271
  function isColorValue(value) {
@@ -43159,6 +43231,7 @@ var init_member_kinds = __esm({
43159
43231
  init_structural_narrow();
43160
43232
  init_core_types();
43161
43233
  init_docs_text2();
43234
+ init_float32();
43162
43235
  init_schema_identifiers();
43163
43236
  init_neoscript_guards();
43164
43237
  init_neoscript_types();
@@ -49673,6 +49746,8 @@ function structuredLeafPartialMemberViolation(member, value, position) {
49673
49746
  return structuredLeafPartialViolation(kind, value, MemberKind[kind]);
49674
49747
  }
49675
49748
  function storedLeafShapeIssueMessage(issue, style) {
49749
+ if (issue.kind === "float") return "must store a 32-bit float.";
49750
+ if (issue.kind === "field") return storedLeafFieldIssueMessage(issue);
49676
49751
  if (issue.kind === "sprite" || issue.kind === "audio") {
49677
49752
  return `must store a valid ${issue.kind === "sprite" ? "Sprite" : "Audio"} value.`;
49678
49753
  }
@@ -49683,12 +49758,15 @@ function storedLeafShapeIssueMessage(issue, style) {
49683
49758
  }
49684
49759
  return issue.kind === "decimal" ? `${issue.violation}.` : `must store ${issue.kind === "color" ? "color " : ""}${shape}.`;
49685
49760
  }
49761
+ function storedLeafFieldIssueMessage(issue) {
49762
+ return `${MemberKind[issue.leaf]} field "${issue.key}" ${issue.violation}, not ${issue.value}.`;
49763
+ }
49686
49764
  function storedLeafExpectation(kind) {
49687
- if (kind === "vector2") return "{ x: number, y: number }";
49765
+ if (kind === "vector2") return "{ x: float, y: float }";
49688
49766
  if (kind === "vector2Int") return "{ x: integer, y: integer }";
49689
- if (kind === "vector3") return "{ x: number, y: number, z: number }";
49767
+ if (kind === "vector3") return "{ x: float, y: float, z: float }";
49690
49768
  if (kind === "vector3Int") return "{ x: integer, y: integer, z: integer }";
49691
- if (kind === "color") return "{ r, g, b, a } in [0, 1]";
49769
+ if (kind === "color") return "{ r, g, b, a } 32-bit floats in [0, 1]";
49692
49770
  return "canonical decimal string";
49693
49771
  }
49694
49772
  function* storedValueChildReferences(args) {
@@ -49734,16 +49812,19 @@ function* storedValueChildEntries(args) {
49734
49812
  }
49735
49813
  function storedLeafShapeIssue(member, value) {
49736
49814
  switch (member.kind) {
49815
+ // Null is an optional Float's absent value.
49816
+ case 4 /* Float */:
49817
+ return value === null || isFloat32(value) ? null : { kind: "float" };
49737
49818
  case 14 /* Vector2 */:
49738
- return isVector2Value(value) ? null : { kind: "vector2" };
49819
+ return isVector2Value(value) ? null : leafIssue(member.kind, "vector2", value);
49739
49820
  case 15 /* Vector2Int */:
49740
- return isVector2IntValue(value) ? null : { kind: "vector2Int" };
49821
+ return isVector2IntValue(value) ? null : leafIssue(member.kind, "vector2Int", value);
49741
49822
  case 16 /* Vector3 */:
49742
- return isVector3Value(value) ? null : { kind: "vector3" };
49823
+ return isVector3Value(value) ? null : leafIssue(member.kind, "vector3", value);
49743
49824
  case 17 /* Vector3Int */:
49744
- return isVector3IntValue(value) ? null : { kind: "vector3Int" };
49825
+ return isVector3IntValue(value) ? null : leafIssue(member.kind, "vector3Int", value);
49745
49826
  case 19 /* Color */:
49746
- return isColorValue(value) ? null : { kind: "color" };
49827
+ return isColorValue(value) ? null : leafIssue(member.kind, "color", value);
49747
49828
  case 20 /* Decimal */: {
49748
49829
  const violation = getDecimalStringViolation(value);
49749
49830
  return violation === null ? null : { kind: "decimal", violation };
@@ -49756,12 +49837,17 @@ function storedLeafShapeIssue(member, value) {
49756
49837
  return null;
49757
49838
  }
49758
49839
  }
49840
+ function leafIssue(leaf, shape, value) {
49841
+ const field = structuredLeafRecordFieldViolation(leaf, value);
49842
+ return field === null ? { kind: shape } : { kind: "field", leaf, ...field };
49843
+ }
49759
49844
  var ANIMATION_OVERRIDE_GRAPH_MEMBER_IDS;
49760
49845
  var init_stored_value_shape = __esm({
49761
49846
  "../src/models/members/stored-value-shape.ts"() {
49762
49847
  "use strict";
49763
49848
  init_world_system_classes();
49764
49849
  init_member_kinds();
49850
+ init_float32();
49765
49851
  init_structured_leaf_fields();
49766
49852
  ANIMATION_OVERRIDE_GRAPH_MEMBER_IDS = /* @__PURE__ */ new Set([
49767
49853
  WORLD_ANIMATION_FRAME_OVERRIDES_MEMBER_ID,
@@ -50846,16 +50932,24 @@ function validateProjectDocumentFileMemberValue(document, member, body, position
50846
50932
  return;
50847
50933
  }
50848
50934
  const shapeIssue = storedLeafShapeIssue(member, body.value);
50935
+ if (shapeIssue?.kind === "float") {
50936
+ throw new Error(
50937
+ `Float value for member "${member.name}" must be a 32-bit float, not ${String(body.value)}.`
50938
+ );
50939
+ }
50940
+ if (shapeIssue?.kind === "field") {
50941
+ throw new Error(
50942
+ `Member "${member.name}" ${storedLeafFieldIssueMessage(shapeIssue)}`
50943
+ );
50944
+ }
50849
50945
  if (shapeIssue?.kind === "vector2") {
50850
- throw new Error("Vector2 value must be { x: number, y: number }.");
50946
+ throw new Error("Vector2 value must be { x: float, y: float }.");
50851
50947
  }
50852
50948
  if (shapeIssue?.kind === "vector2Int") {
50853
50949
  throw new Error("Vector2Int value must be { x: integer, y: integer }.");
50854
50950
  }
50855
50951
  if (shapeIssue?.kind === "vector3") {
50856
- throw new Error(
50857
- "Vector3 value must be { x: number, y: number, z: number }."
50858
- );
50952
+ throw new Error("Vector3 value must be { x: float, y: float, z: float }.");
50859
50953
  }
50860
50954
  if (shapeIssue?.kind === "vector3Int") {
50861
50955
  throw new Error(
@@ -50864,7 +50958,7 @@ function validateProjectDocumentFileMemberValue(document, member, body, position
50864
50958
  }
50865
50959
  if (shapeIssue?.kind === "color") {
50866
50960
  throw new Error(
50867
- "Color value must be { r, g, b, a } with each component in [0, 1]."
50961
+ "Color value must be { r, g, b, a } with each component a 32-bit float in [0, 1]."
50868
50962
  );
50869
50963
  }
50870
50964
  if (shapeIssue?.kind === "decimal") {
@@ -51695,10 +51789,13 @@ function lowerStructuredLeafFieldValue(kind, label, field, expression, lowering)
51695
51789
  if (field.type === "fileId") {
51696
51790
  return lowerStructuredLeafFileId(kind, label, field, expression, lowering);
51697
51791
  }
51698
- const value = lowering.number(expression);
51792
+ const value = storedStructuredLeafFieldNumber(
51793
+ field.type,
51794
+ lowering.number(expression)
51795
+ );
51699
51796
  if (isStructuredLeafFieldValue(field.type, value)) return value;
51700
51797
  throw new Error(
51701
- `${label} field ${JSON.stringify(field.name)} ${structuredLeafFieldRequirement(field.type)}, not ${value}.`
51798
+ `${label} field ${JSON.stringify(field.name)} ${structuredLeafFieldViolation(field.type, value)}, not ${value}.`
51702
51799
  );
51703
51800
  }
51704
51801
  function lowerStructuredLeafFileId(kind, label, field, expression, lowering) {
@@ -51745,10 +51842,13 @@ function lowerStructuredLeafComponents(kind, label, expression, lowering) {
51745
51842
  if (argument2 === void 0) {
51746
51843
  throw new Error(`${label} is missing component ${field.name}.`);
51747
51844
  }
51748
- const component = lowering.number(argument2);
51845
+ const component = storedStructuredLeafFieldNumber(
51846
+ field.type,
51847
+ lowering.number(argument2)
51848
+ );
51749
51849
  if (!isStructuredLeafFieldValue(field.type, component)) {
51750
51850
  throw new Error(
51751
- `${label} component ${field.name} ${structuredLeafFieldRequirement(field.type)}, not ${component}.`
51851
+ `${label} component ${field.name} ${structuredLeafFieldViolation(field.type, component)}, not ${component}.`
51752
51852
  );
51753
51853
  }
51754
51854
  value[field.key] = component;
@@ -51778,7 +51878,7 @@ function structuredLeafPartialAssignments(kind, label, value, emit) {
51778
51878
  function structuredLeafFieldSource(label, field, value, emit) {
51779
51879
  if (!isStructuredLeafFieldValue(field.type, value)) {
51780
51880
  throw new Error(
51781
- `${label} stores field ${JSON.stringify(field.name)} as ${JSON.stringify(value)}, which ${structuredLeafFieldRequirement(field.type)}.`
51881
+ `${label} stores field ${JSON.stringify(field.name)} as ${JSON.stringify(value)}, which ${structuredLeafFieldViolation(field.type, value)}.`
51782
51882
  );
51783
51883
  }
51784
51884
  if (typeof value === "string") return emit.fileId(value);
@@ -51787,13 +51887,6 @@ function structuredLeafFieldSource(label, field, value, emit) {
51787
51887
  `${label} stores field ${JSON.stringify(field.name)} as ${JSON.stringify(value)}, which is neither a number nor a file id.`
51788
51888
  );
51789
51889
  }
51790
- function structuredLeafFieldRequirement(type) {
51791
- if (type === "fileId") return "must be a non-empty file id";
51792
- if (type === "float") return "must be a finite number";
51793
- if (type === "int") return "must be a whole number";
51794
- if (type === "sliceIndex") return "must be a whole number 0 or greater";
51795
- return "must be a number between 0 and 1";
51796
- }
51797
51890
  var STRUCTURED_LEAF_SOURCE_FORMS, STRUCTURED_LEAF_SOURCE_ENTRIES;
51798
51891
  var init_structured_leaf_source = __esm({
51799
51892
  "src/project-source/structured-leaf-source.ts"() {
@@ -54508,7 +54601,7 @@ function lowerParameterDefaultValue(context, expression, type, path) {
54508
54601
  `Parameter ${path} defaults to a number literal, but its type is ${type.kind}.`
54509
54602
  );
54510
54603
  }
54511
- return expression.value;
54604
+ return type.kind === "float" ? Math.fround(expression.value) : expression.value;
54512
54605
  case "litFloat":
54513
54606
  if (type.kind === "decimal") return expression.raw;
54514
54607
  if (type.kind !== "float") {
@@ -54516,7 +54609,7 @@ function lowerParameterDefaultValue(context, expression, type, path) {
54516
54609
  `Parameter ${path} defaults to a fractional literal, but its type is ${type.kind}.`
54517
54610
  );
54518
54611
  }
54519
- return expression.value;
54612
+ return Math.fround(expression.value);
54520
54613
  case "unary": {
54521
54614
  if (expression.op !== "-") {
54522
54615
  throw new Error(
@@ -55655,8 +55748,8 @@ function lowerFieldMember(context, ownerClass, declaration, id2, commonInput, ba
55655
55748
  };
55656
55749
  }
55657
55750
  const inheritedFloat = inherited?.kind === "float" ? inherited : void 0;
55658
- const numericMin = min === null ? null : Number(min);
55659
- const numericMax = max === null ? null : Number(max);
55751
+ const numericMin = min === null ? null : Math.fround(Number(min));
55752
+ const numericMax = max === null ? null : Math.fround(Number(max));
55660
55753
  return {
55661
55754
  ...common,
55662
55755
  kind: "float",
@@ -56615,9 +56708,8 @@ function lowerExpressionValue(context, expression, declaredExpected, ownerClass,
56615
56708
  case "litString":
56616
56709
  return expression.value;
56617
56710
  case "litInt":
56618
- return expected.name === "decimal" ? expression.raw : expression.value;
56619
56711
  case "litFloat":
56620
- return expected.name === "decimal" ? expression.raw : expression.value;
56712
+ return numberLiteralValue(expression, expected);
56621
56713
  case "unary": {
56622
56714
  if (expression.op !== "-") {
56623
56715
  throw new Error(
@@ -57474,6 +57566,11 @@ function positionalContextualEnumArgument(value) {
57474
57566
  const expression = parseAnnotationExpression(positional.expression);
57475
57567
  return expression.kind === "contextualEnum" ? expression.name : null;
57476
57568
  }
57569
+ function numberLiteralValue(expression, expected) {
57570
+ if (expected.name === "decimal") return expression.raw;
57571
+ if (expected.name === "float") return Math.fround(expression.value);
57572
+ return expression.value;
57573
+ }
57477
57574
  function numberTextArgument(value, name) {
57478
57575
  const raw = argument(value, name);
57479
57576
  if (raw === null) return null;
@@ -72485,7 +72582,8 @@ function evalPointer(pointer, scope, ctx, resultUsage = "value") {
72485
72582
  callSiteId: pointer.callSiteId,
72486
72583
  invokeDelegate: (delegate, delegateArgs = []) => invokeDelegateValue(delegate, delegateArgs, ctx, pointer.callSiteId)
72487
72584
  });
72488
- if (intercepted?.handled === true) return intercepted.value;
72585
+ if (intercepted?.handled === true)
72586
+ return hostCallResult(member, intercepted.value, ctx);
72489
72587
  if (member === null) {
72490
72588
  if (pointer.missingMemberFallback === "valueEquality") {
72491
72589
  return jsEqual(innerThis, suppliedArgs[0]);
@@ -72521,7 +72619,8 @@ function evalPointer(pointer, scope, ctx, resultUsage = "value") {
72521
72619
  pointer.callSiteId
72522
72620
  )
72523
72621
  });
72524
- if (hosted?.handled === true) return hosted.value;
72622
+ if (hosted?.handled === true)
72623
+ return hostCallResult(member, hosted.value, ctx);
72525
72624
  const receiverId = trackedRowForValueReference(innerThis, ctx)?.id;
72526
72625
  throw new NativeFunctionDelegateUnavailableError(
72527
72626
  `No native delegate handled Function '${member.name}' (member id '${member.id}'${nativeReceiverDescription(member, innerThis, receiverId)}). Web preview and other non-native evaluator hosts must configure a native handler or call interceptor before evaluation.`,
@@ -72812,7 +72911,8 @@ function invokeDelegateValue(value, args, ctx, callSiteId, ownerReceiver, result
72812
72911
  callSiteId,
72813
72912
  invokeDelegate: (delegate, delegateArgs = []) => invokeDelegateValue(delegate, delegateArgs, ctx, callSiteId)
72814
72913
  });
72815
- if (intercepted?.handled === true) return intercepted.value;
72914
+ if (intercepted?.handled === true)
72915
+ return hostCallResult(member, intercepted.value, ctx);
72816
72916
  state.delegateStack.push(frame);
72817
72917
  try {
72818
72918
  if (member.kind === 13 /* Function */) {
@@ -72827,7 +72927,8 @@ function invokeDelegateValue(value, args, ctx, callSiteId, ownerReceiver, result
72827
72927
  callSiteId,
72828
72928
  invokeDelegate: (delegate, delegateArgs = []) => invokeDelegateValue(delegate, delegateArgs, ctx, callSiteId)
72829
72929
  });
72830
- if (hosted?.handled === true) return hosted.value;
72930
+ if (hosted?.handled === true)
72931
+ return hostCallResult(member, hosted.value, ctx);
72831
72932
  const receiverId = trackedRowForValueReference(receiver, ctx)?.id;
72832
72933
  throw new NativeFunctionDelegateUnavailableError(
72833
72934
  `No native delegate handled Function '${member.name}' (member id '${member.id}'${nativeReceiverDescription(member, receiver, receiverId)}). Web preview and other non-native evaluator hosts must configure a native handler or call interceptor before evaluation.`,
@@ -73696,13 +73797,14 @@ function runtimeCollectionEntryMatchesType(value, typeInfo, ctx, seenCollections
73696
73797
  return resolved !== value && runtimeValueMatchesType(resolved, typeInfo, ctx, seenCollections);
73697
73798
  }
73698
73799
  function evalVectorConstructor(vectorType, componentPointers, scope, ctx) {
73699
- const values = componentPointers.map(
73700
- (pointer) => finiteNumberArg(
73800
+ const integer3 = vectorType === 15 /* Vector2Int */ || vectorType === 17 /* Vector3Int */;
73801
+ const values = componentPointers.map((pointer) => {
73802
+ const value = finiteNumberArg(
73701
73803
  evalPointer(pointer, scope, ctx),
73702
73804
  `${MemberKind[vectorType]} component`
73703
- )
73704
- );
73705
- const integer3 = vectorType === 15 /* Vector2Int */ || vectorType === 17 /* Vector3Int */;
73805
+ );
73806
+ return integer3 ? value : Math.fround(value);
73807
+ });
73706
73808
  if (integer3 && values.some((value) => !Number.isInteger(value))) {
73707
73809
  throw new NSGetterRuntimeError(
73708
73810
  `${MemberKind[vectorType]} components must be integers`
@@ -74593,11 +74695,17 @@ function evalOperation(operation, scope, ctx) {
74593
74695
  if (operation.type === "arithmetic" /* arithmetic */) {
74594
74696
  const op = operation.arithmetic;
74595
74697
  const operands = op.pointers.map((p) => evalPointer(p, scope, ctx));
74596
- return applyArithmetic(op.type, operands, op.decimal === true, ctx);
74698
+ return applyArithmetic(
74699
+ op.type,
74700
+ operands,
74701
+ op.decimal === true,
74702
+ ctx,
74703
+ op.numeric
74704
+ );
74597
74705
  }
74598
74706
  return evalBooleanExpression(operation.expression, scope, ctx);
74599
74707
  }
74600
- function applyArithmetic(op, operands, decimal2, ctx) {
74708
+ function applyArithmetic(op, operands, decimal2, ctx, numeric) {
74601
74709
  if (operands.length === 0) {
74602
74710
  throw new NSGetterRuntimeError("Arithmetic operation with no operands");
74603
74711
  }
@@ -74630,31 +74738,43 @@ function applyArithmetic(op, operands, decimal2, ctx) {
74630
74738
  `Arithmetic operand is not numeric: ${typeof o}`
74631
74739
  );
74632
74740
  });
74741
+ const round = numeric === "float" ? Math.fround : (value) => value;
74633
74742
  switch (op) {
74634
74743
  case "+" /* addition */:
74635
- return numbers.reduce((a, b) => a + b, 0);
74744
+ return numbers.reduce((a, b) => round(a + b), 0);
74636
74745
  case "-" /* subtraction */:
74637
- return numbers.reduce((a, b, i2) => i2 === 0 ? b : a - b);
74746
+ return numbers.reduce((a, b, i2) => i2 === 0 ? b : round(a - b));
74638
74747
  case "*" /* multiplication */:
74639
- return numbers.reduce((a, b) => a * b, 1);
74748
+ return numbers.reduce((a, b) => round(a * b), 1);
74640
74749
  case "/" /* division */:
74641
74750
  return numbers.reduce((a, b, i2) => {
74642
74751
  if (i2 === 0) return b;
74643
74752
  if (b === 0) throw new NSGetterRuntimeError("Division by zero");
74644
- return a / b;
74753
+ return numeric === "int" ? Math.trunc(a / b) : round(a / b);
74645
74754
  });
74646
74755
  case "%" /* remainder */:
74647
74756
  return numbers.reduce((a, b, i2) => {
74648
74757
  if (i2 === 0) return b;
74649
74758
  if (b === 0) throw new NSGetterRuntimeError("Modulo by zero");
74650
- return a % b;
74759
+ return round(a % b);
74651
74760
  });
74652
74761
  }
74653
74762
  }
74763
+ function hostCallResult(member, value, ctx) {
74764
+ if (typeof value !== "number" || member === null) return value;
74765
+ if (member.kind !== 13 /* Function */ && member.kind !== 23 /* NSFunction */) {
74766
+ return value;
74767
+ }
74768
+ const signature = resolveCallableSignature(member.id, member.kind, ctx);
74769
+ return signature?.returnTypeInfo.type === 4 /* Float */ ? Math.fround(value) : value;
74770
+ }
74654
74771
  function coerceAssignmentValue(value, targetTypeInfo, context) {
74655
74772
  if (targetTypeInfo.type === 8 /* Enum */ && typeof value === "string") {
74656
74773
  return [value];
74657
74774
  }
74775
+ if (targetTypeInfo.type === 4 /* Float */ && typeof value === "number") {
74776
+ return Math.fround(value);
74777
+ }
74658
74778
  return targetTypeInfo.type === 20 /* Decimal */ && typeof value === "number" ? coerceDecimalOperand(value, context) : value;
74659
74779
  }
74660
74780
  function coerceDecimalOperand(value, context) {
@@ -74804,7 +74924,7 @@ function evalMathOp(info, scope, ctx) {
74804
74924
  if (value < 0) return -1;
74805
74925
  return 0;
74806
74926
  case "sqrt":
74807
- return Math.sqrt(value);
74927
+ return Math.fround(Math.sqrt(value));
74808
74928
  }
74809
74929
  }
74810
74930
  function evalDecimalMathOp(info, fn, scope, ctx) {
@@ -75445,8 +75565,10 @@ function evalFunction(fn, scope, ctx) {
75445
75565
  );
75446
75566
  }
75447
75567
  case "toFloat":
75448
- return floatFromDecimalString(
75449
- coerceDecimalOperand(receiverRaw, "ToFloat")
75568
+ return Math.fround(
75569
+ floatFromDecimalString(
75570
+ coerceDecimalOperand(receiverRaw, "ToFloat")
75571
+ )
75450
75572
  );
75451
75573
  case "toDecimal": {
75452
75574
  if (typeof receiverRaw !== "number") {
@@ -75954,6 +76076,9 @@ function evaluateConstructorFields(descriptor, scope, ctx) {
75954
76076
  `constructor field '${validated.schemaKey}'`
75955
76077
  );
75956
76078
  }
76079
+ if (typeof value === "number" && validated.typeInfo.type === 4 /* Float */) {
76080
+ value = Math.fround(value);
76081
+ }
75957
76082
  if (value === null || value === void 0) {
75958
76083
  if (validated.requiredWithoutDefault) {
75959
76084
  throw new NSGetterRuntimeError(
@@ -76574,8 +76699,9 @@ function evaluateDeclaredConstructorArguments(info, record6, scope, ctx) {
76574
76699
  );
76575
76700
  }
76576
76701
  const value = byName.get(parameter4.name);
76577
- if (parameter4.type !== 20 /* Decimal */) return value;
76578
76702
  if (typeof value !== "number") return value;
76703
+ if (parameter4.type === 4 /* Float */) return Math.fround(value);
76704
+ if (parameter4.type !== 20 /* Decimal */) return value;
76579
76705
  return coerceDecimalOperand(
76580
76706
  value,
76581
76707
  `constructor argument '${parameter4.name}'`
@@ -89277,6 +89403,13 @@ function validateProjectRecordByKind(recordKind, data, options = {}) {
89277
89403
  if (recordKind === ProjectRecordKind.Member) {
89278
89404
  const memberGuard = options.members === "authored-or-compiled" ? isAnyMemberAuthoredOrCompiled : isAnyMember;
89279
89405
  if (memberGuard(data)) return;
89406
+ const unrounded = unroundedFloatPath(data);
89407
+ if (unrounded !== null) {
89408
+ const id2 = typeof data === "object" && data !== null && "id" in data ? String(data.id) : "";
89409
+ throw new Error(
89410
+ `Member "${id2}" field "${unrounded}" must be a 32-bit float.`
89411
+ );
89412
+ }
89280
89413
  }
89281
89414
  if (recordKind === ProjectRecordKind.Value) {
89282
89415
  if (isMemberValue(data)) {
@@ -89372,6 +89505,7 @@ var init_project_record_validation = __esm({
89372
89505
  init_localization_types();
89373
89506
  init_unity_import_settings();
89374
89507
  init_project_file_authoring_identifiers();
89508
+ init_float32();
89375
89509
  }
89376
89510
  });
89377
89511
 
@@ -97503,15 +97637,13 @@ function inspectStoredValueNodeInternal(options) {
97503
97637
  return terminal();
97504
97638
  }
97505
97639
  if (member.kind === 4 /* Float */) {
97506
- if (typeof row.value !== "number" || !Number.isFinite(row.value)) {
97507
- fail(`${path} must store a finite number.`);
97508
- }
97640
+ if (!isFloat32(row.value)) fail(`${path} must store a 32-bit float.`);
97509
97641
  assertNumberRange(member, row.value, path);
97510
97642
  return terminal();
97511
97643
  }
97512
- const leafIssue = storedLeafShapeIssue(member, row.value);
97513
- if (leafIssue !== null) {
97514
- fail(`${path} ${storedLeafShapeIssueMessage(leafIssue, "graph")}`);
97644
+ const leafIssue2 = storedLeafShapeIssue(member, row.value);
97645
+ if (leafIssue2 !== null) {
97646
+ fail(`${path} ${storedLeafShapeIssueMessage(leafIssue2, "graph")}`);
97515
97647
  }
97516
97648
  if (isMemberStringBase(member)) {
97517
97649
  if (typeof row.value !== "string") fail(`${path} must store a string.`);
@@ -97873,6 +98005,7 @@ var init_stored_value_graph_semantics = __esm({
97873
98005
  init_inheritance();
97874
98006
  init_generics();
97875
98007
  init_classes();
98008
+ init_float32();
97876
98009
  StoredValueSemanticError = class extends Error {
97877
98010
  name = "StoredValueSemanticError";
97878
98011
  };
@@ -104917,9 +105050,7 @@ function hasValidOpaqueParameterDefault(value) {
104917
105050
  if (value.type === 2) {
104918
105051
  return typeof payload === "number" && Number.isInteger(payload);
104919
105052
  }
104920
- if (value.type === 4) {
104921
- return typeof payload === "number" && Number.isFinite(payload);
104922
- }
105053
+ if (value.type === 4) return isFloat32(payload);
104923
105054
  if (value.type === 3 || value.type === 8 || value.type === 20) {
104924
105055
  return typeof payload === "string";
104925
105056
  }
@@ -106849,6 +106980,7 @@ var init_projectInterfaceValidation = __esm({
106849
106980
  init_classes();
106850
106981
  init_interface_types();
106851
106982
  init_member_kinds();
106983
+ init_float32();
106852
106984
  init_stored_value_shape();
106853
106985
  init_member_storage();
106854
106986
  NEO_OBJECT_WORLD_KIND = "object";
@@ -108267,7 +108399,7 @@ function csharpValueType(ctx, member, saved = false, env, genericBindingSaved =
108267
108399
  return isRequiredMember2(member) ? "bool" : "bool?";
108268
108400
  if (isMemberIntBase(member)) return isRequiredMember2(member) ? "int" : "int?";
108269
108401
  if (isMemberFloatBase(member))
108270
- return isRequiredMember2(member) ? "double" : "double?";
108402
+ return isRequiredMember2(member) ? "float" : "float?";
108271
108403
  if (isMemberDecimalBase(member)) {
108272
108404
  return isRequiredMember2(member) ? "decimal" : "decimal?";
108273
108405
  }
@@ -108482,7 +108614,7 @@ function csharpTypeInfoValueType(ctx, typeInfo, saved, genericEnv, genericBindin
108482
108614
  case 2 /* Int */:
108483
108615
  return optional2 ? "int?" : "int";
108484
108616
  case 4 /* Float */:
108485
- return optional2 ? "double?" : "double";
108617
+ return optional2 ? "float?" : "float";
108486
108618
  case 20 /* Decimal */:
108487
108619
  return optional2 ? "decimal?" : "decimal";
108488
108620
  case 3 /* String */:
@@ -124748,8 +124880,9 @@ function lowerValueBody(context, member, expression, base, source, environment,
124748
124880
  case "bool":
124749
124881
  return { ...next, value: requiredBoolean(expression) };
124750
124882
  case "int":
124751
- case "float":
124752
124883
  return { ...next, value: requiredNumber2(expression) };
124884
+ case "float":
124885
+ return { ...next, value: Math.fround(requiredNumber2(expression)) };
124753
124886
  case "decimal":
124754
124887
  return { ...next, value: requiredDecimal(expression) };
124755
124888
  case "string":
@@ -156644,7 +156777,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
156644
156777
  async function main() {
156645
156778
  const args = parseArgs(process.argv.slice(2));
156646
156779
  if (args.command === "--version") {
156647
- console.log("0.66.0");
156780
+ console.log("0.67.0");
156648
156781
  return;
156649
156782
  }
156650
156783
  if (args.command === null || args.command === "help" || args.command === "--help" || args.command === "-h") {
@@ -1755,7 +1755,7 @@ var TEST_GLOBALS = [
1755
1755
  ];
1756
1756
 
1757
1757
  // ../packages/neoscript-language/src/strict-ir.ts
1758
- var NEOSCRIPT_COMPILER_REVISION = 16;
1758
+ var NEOSCRIPT_COMPILER_REVISION = 17;
1759
1759
 
1760
1760
  // ../src/models/members/member-storage.ts
1761
1761
  function isMemberStorageKind(value) {
@@ -2167,6 +2167,11 @@ function isValidDocsText(value) {
2167
2167
  return textEncoder.encode(value).byteLength <= DOCS_TEXT_MAX_UTF8_BYTES;
2168
2168
  }
2169
2169
 
2170
+ // ../src/models/members/float32.ts
2171
+ function isFloat32(value) {
2172
+ return typeof value === "number" && Number.isFinite(value) && Math.fround(value) === value;
2173
+ }
2174
+
2170
2175
  // ../packages/neoscript-language/src/project-source-identifiers.ts
2171
2176
  var PROJECT_IDENTIFIER_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
2172
2177
  var PROJECT_RESERVED_NAMES = /* @__PURE__ */ new Set([
@@ -2507,7 +2512,7 @@ function hasValidParameterDefault(value) {
2507
2512
  case 2 /* Int */:
2508
2513
  return typeof payload === "number" && Number.isInteger(payload);
2509
2514
  case 4 /* Float */:
2510
- return typeof payload === "number" && Number.isFinite(payload);
2515
+ return isFloat32(payload);
2511
2516
  case 20 /* Decimal */:
2512
2517
  case 3 /* String */:
2513
2518
  case 8 /* Enum */:
@@ -2793,6 +2798,9 @@ function isNSArithmeticOperationInfo(value) {
2793
2798
  const v = value;
2794
2799
  if (typeof v !== "object" || v === null) return false;
2795
2800
  if (!isNSArithmeticOperationType(v.type)) return false;
2801
+ if (v.numeric !== void 0 && v.numeric !== "int" && v.numeric !== "float") {
2802
+ return false;
2803
+ }
2796
2804
  return isNSPointers(v.pointers);
2797
2805
  }
2798
2806
  function isNSArithmeticOperation(value) {
@@ -3703,6 +3711,9 @@ var STRUCTURED_LEAF_FIELDS = {
3703
3711
  function isStructuredLeafKind(kind) {
3704
3712
  return Object.prototype.hasOwnProperty.call(STRUCTURED_LEAF_FIELDS, kind);
3705
3713
  }
3714
+ function structuredLeafFields(kind) {
3715
+ return STRUCTURED_LEAF_FIELDS[kind];
3716
+ }
3706
3717
  function structuredLeafFieldKeys(kind) {
3707
3718
  return STRUCTURED_LEAF_FIELDS[kind].map((entry) => entry.key);
3708
3719
  }
@@ -3715,10 +3726,10 @@ function isStructuredLeafFieldValue(type, value) {
3715
3726
  return typeof value === "string" && value.length > 0;
3716
3727
  }
3717
3728
  if (typeof value !== "number" || !Number.isFinite(value)) return false;
3718
- if (type === "float") return true;
3729
+ if (type === "float") return isFloat32(value);
3719
3730
  if (type === "int") return Number.isInteger(value);
3720
3731
  if (type === "sliceIndex") return Number.isInteger(value) && value >= 0;
3721
- return value >= 0 && value <= 1;
3732
+ return isFloat32(value) && value >= 0 && value <= 1;
3722
3733
  }
3723
3734
  var STRUCTURED_LEAF_PARTIAL_KEY = "~partial";
3724
3735
  function isStructuredLeafPartialValue(value) {
@@ -3738,16 +3749,36 @@ function structuredLeafPartialViolation(kind, value, legalKeysLabel) {
3738
3749
  return `has no field "${key}"; ${legalKeysLabel} accepts ${structuredLeafFieldKeys(kind).join(", ")}`;
3739
3750
  }
3740
3751
  if (isStructuredLeafFieldValue(field.type, fieldValue)) continue;
3741
- return `field "${key}" ${structuredLeafFieldViolation(field.type)}`;
3752
+ return `field "${key}" ${structuredLeafFieldViolation(field.type, fieldValue)}`;
3742
3753
  }
3743
3754
  return null;
3744
3755
  }
3745
- function structuredLeafFieldViolation(type) {
3756
+ function structuredLeafRecordFieldViolation(kind, value) {
3757
+ if (typeof value !== "object" || value === null) return null;
3758
+ if (Array.isArray(value)) return null;
3759
+ const entries = Object.entries(value);
3760
+ if (entries.length !== structuredLeafFields(kind).length) return null;
3761
+ let found = null;
3762
+ for (const [key, fieldValue] of entries) {
3763
+ const field = structuredLeafField(kind, key);
3764
+ if (field === null) return null;
3765
+ if (typeof fieldValue !== "number") return null;
3766
+ if (found !== null) continue;
3767
+ if (isStructuredLeafFieldValue(field.type, fieldValue)) continue;
3768
+ found = {
3769
+ key,
3770
+ value: fieldValue,
3771
+ violation: structuredLeafFieldViolation(field.type, fieldValue)
3772
+ };
3773
+ }
3774
+ return found;
3775
+ }
3776
+ function structuredLeafFieldViolation(type, value) {
3746
3777
  if (type === "fileId") return "must be a non-empty file id";
3747
- if (type === "float") return "must be a finite number";
3748
3778
  if (type === "int") return "must be a whole number";
3749
3779
  if (type === "sliceIndex") return "must be a whole number 0 or greater";
3750
- return "must be a number between 0 and 1";
3780
+ if (!isFloat32(value)) return "must be a 32-bit float";
3781
+ return "must be between 0 and 1";
3751
3782
  }
3752
3783
 
3753
3784
  // ../src/models/members/member-kinds.ts
@@ -3894,9 +3925,9 @@ function isMemberString(value) {
3894
3925
  function isMemberFloatBase(value) {
3895
3926
  const v = asMemberBaseForKind(value, 4 /* Float */);
3896
3927
  if (v === null) return false;
3897
- if (v.minValue !== void 0 && v.minValue !== null && typeof v.minValue !== "number")
3928
+ if (v.minValue !== void 0 && v.minValue !== null && !isFloat32(v.minValue))
3898
3929
  return false;
3899
- if (v.maxValue !== void 0 && v.maxValue !== null && typeof v.maxValue !== "number")
3930
+ if (v.maxValue !== void 0 && v.maxValue !== null && !isFloat32(v.maxValue))
3900
3931
  return false;
3901
3932
  return v.decimalPoints === void 0 || v.decimalPoints === null || typeof v.decimalPoints === "number";
3902
3933
  }
@@ -3905,12 +3936,8 @@ function hasExactKeys(value, keys) {
3905
3936
  if (actual.length !== keys.length) return false;
3906
3937
  return keys.every((key) => Object.prototype.hasOwnProperty.call(value, key));
3907
3938
  }
3908
- function isFiniteNumberValue(value) {
3909
- return typeof value === "number" && Number.isFinite(value);
3910
- }
3911
3939
  function isVectorComponentValue(value, integer2) {
3912
- if (!isFiniteNumberValue(value)) return false;
3913
- if (!integer2) return true;
3940
+ if (!integer2) return isFloat32(value);
3914
3941
  return Number.isInteger(value);
3915
3942
  }
3916
3943
  function isVector2ValueWithComponents(value, integer2) {
@@ -3944,7 +3971,7 @@ function isVector3IntValue(value) {
3944
3971
  return isVector3ValueWithComponents(value, true);
3945
3972
  }
3946
3973
  function isColorComponentValue(value) {
3947
- if (!isFiniteNumberValue(value)) return false;
3974
+ if (!isFloat32(value)) return false;
3948
3975
  return value >= 0 && value <= 1;
3949
3976
  }
3950
3977
  function isColorValue(value) {
@@ -10038,6 +10065,9 @@ function structuredLeafPartialMemberViolation(member, value, position) {
10038
10065
  }
10039
10066
  return structuredLeafPartialViolation(kind, value, MemberKind[kind]);
10040
10067
  }
10068
+ function storedLeafFieldIssueMessage(issue) {
10069
+ return `${MemberKind[issue.leaf]} field "${issue.key}" ${issue.violation}, not ${issue.value}.`;
10070
+ }
10041
10071
  function* storedValueChildEntries(args) {
10042
10072
  const entryMemberId = args.member.entryMemberId;
10043
10073
  if (args.member.kind === 6 /* List */ && typeof entryMemberId === "string") {
@@ -10071,16 +10101,19 @@ function* storedValueChildEntries(args) {
10071
10101
  }
10072
10102
  function storedLeafShapeIssue(member, value) {
10073
10103
  switch (member.kind) {
10104
+ // Null is an optional Float's absent value.
10105
+ case 4 /* Float */:
10106
+ return value === null || isFloat32(value) ? null : { kind: "float" };
10074
10107
  case 14 /* Vector2 */:
10075
- return isVector2Value(value) ? null : { kind: "vector2" };
10108
+ return isVector2Value(value) ? null : leafIssue(member.kind, "vector2", value);
10076
10109
  case 15 /* Vector2Int */:
10077
- return isVector2IntValue(value) ? null : { kind: "vector2Int" };
10110
+ return isVector2IntValue(value) ? null : leafIssue(member.kind, "vector2Int", value);
10078
10111
  case 16 /* Vector3 */:
10079
- return isVector3Value(value) ? null : { kind: "vector3" };
10112
+ return isVector3Value(value) ? null : leafIssue(member.kind, "vector3", value);
10080
10113
  case 17 /* Vector3Int */:
10081
- return isVector3IntValue(value) ? null : { kind: "vector3Int" };
10114
+ return isVector3IntValue(value) ? null : leafIssue(member.kind, "vector3Int", value);
10082
10115
  case 19 /* Color */:
10083
- return isColorValue(value) ? null : { kind: "color" };
10116
+ return isColorValue(value) ? null : leafIssue(member.kind, "color", value);
10084
10117
  case 20 /* Decimal */: {
10085
10118
  const violation = getDecimalStringViolation(value);
10086
10119
  return violation === null ? null : { kind: "decimal", violation };
@@ -10093,6 +10126,10 @@ function storedLeafShapeIssue(member, value) {
10093
10126
  return null;
10094
10127
  }
10095
10128
  }
10129
+ function leafIssue(leaf, shape, value) {
10130
+ const field = structuredLeafRecordFieldViolation(leaf, value);
10131
+ return field === null ? { kind: shape } : { kind: "field", leaf, ...field };
10132
+ }
10096
10133
 
10097
10134
  // ../src/models/members/build-default-member-value.ts
10098
10135
  var DECLARATION_DEFAULT_POSITION = {
@@ -11174,16 +11211,24 @@ function validateProjectDocumentFileMemberValue(document2, member, body, positio
11174
11211
  return;
11175
11212
  }
11176
11213
  const shapeIssue = storedLeafShapeIssue(member, body.value);
11214
+ if (shapeIssue?.kind === "float") {
11215
+ throw new Error(
11216
+ `Float value for member "${member.name}" must be a 32-bit float, not ${String(body.value)}.`
11217
+ );
11218
+ }
11219
+ if (shapeIssue?.kind === "field") {
11220
+ throw new Error(
11221
+ `Member "${member.name}" ${storedLeafFieldIssueMessage(shapeIssue)}`
11222
+ );
11223
+ }
11177
11224
  if (shapeIssue?.kind === "vector2") {
11178
- throw new Error("Vector2 value must be { x: number, y: number }.");
11225
+ throw new Error("Vector2 value must be { x: float, y: float }.");
11179
11226
  }
11180
11227
  if (shapeIssue?.kind === "vector2Int") {
11181
11228
  throw new Error("Vector2Int value must be { x: integer, y: integer }.");
11182
11229
  }
11183
11230
  if (shapeIssue?.kind === "vector3") {
11184
- throw new Error(
11185
- "Vector3 value must be { x: number, y: number, z: number }."
11186
- );
11231
+ throw new Error("Vector3 value must be { x: float, y: float, z: float }.");
11187
11232
  }
11188
11233
  if (shapeIssue?.kind === "vector3Int") {
11189
11234
  throw new Error(
@@ -11192,7 +11237,7 @@ function validateProjectDocumentFileMemberValue(document2, member, body, positio
11192
11237
  }
11193
11238
  if (shapeIssue?.kind === "color") {
11194
11239
  throw new Error(
11195
- "Color value must be { r, g, b, a } with each component in [0, 1]."
11240
+ "Color value must be { r, g, b, a } with each component a 32-bit float in [0, 1]."
11196
11241
  );
11197
11242
  }
11198
11243
  if (shapeIssue?.kind === "decimal") {
@@ -17454,7 +17499,8 @@ function evalPointer(pointer, scope, ctx, resultUsage = "value") {
17454
17499
  callSiteId: pointer.callSiteId,
17455
17500
  invokeDelegate: (delegate, delegateArgs = []) => invokeDelegateValue(delegate, delegateArgs, ctx, pointer.callSiteId)
17456
17501
  });
17457
- if (intercepted?.handled === true) return intercepted.value;
17502
+ if (intercepted?.handled === true)
17503
+ return hostCallResult(member, intercepted.value, ctx);
17458
17504
  if (member === null) {
17459
17505
  if (pointer.missingMemberFallback === "valueEquality") {
17460
17506
  return jsEqual(innerThis, suppliedArgs[0]);
@@ -17490,7 +17536,8 @@ function evalPointer(pointer, scope, ctx, resultUsage = "value") {
17490
17536
  pointer.callSiteId
17491
17537
  )
17492
17538
  });
17493
- if (hosted?.handled === true) return hosted.value;
17539
+ if (hosted?.handled === true)
17540
+ return hostCallResult(member, hosted.value, ctx);
17494
17541
  const receiverId = trackedRowForValueReference(innerThis, ctx)?.id;
17495
17542
  throw new NativeFunctionDelegateUnavailableError(
17496
17543
  `No native delegate handled Function '${member.name}' (member id '${member.id}'${nativeReceiverDescription(member, innerThis, receiverId)}). Web preview and other non-native evaluator hosts must configure a native handler or call interceptor before evaluation.`,
@@ -17781,7 +17828,8 @@ function invokeDelegateValue(value, args, ctx, callSiteId, ownerReceiver, result
17781
17828
  callSiteId,
17782
17829
  invokeDelegate: (delegate, delegateArgs = []) => invokeDelegateValue(delegate, delegateArgs, ctx, callSiteId)
17783
17830
  });
17784
- if (intercepted?.handled === true) return intercepted.value;
17831
+ if (intercepted?.handled === true)
17832
+ return hostCallResult(member, intercepted.value, ctx);
17785
17833
  state.delegateStack.push(frame);
17786
17834
  try {
17787
17835
  if (member.kind === 13 /* Function */) {
@@ -17796,7 +17844,8 @@ function invokeDelegateValue(value, args, ctx, callSiteId, ownerReceiver, result
17796
17844
  callSiteId,
17797
17845
  invokeDelegate: (delegate, delegateArgs = []) => invokeDelegateValue(delegate, delegateArgs, ctx, callSiteId)
17798
17846
  });
17799
- if (hosted?.handled === true) return hosted.value;
17847
+ if (hosted?.handled === true)
17848
+ return hostCallResult(member, hosted.value, ctx);
17800
17849
  const receiverId = trackedRowForValueReference(receiver, ctx)?.id;
17801
17850
  throw new NativeFunctionDelegateUnavailableError(
17802
17851
  `No native delegate handled Function '${member.name}' (member id '${member.id}'${nativeReceiverDescription(member, receiver, receiverId)}). Web preview and other non-native evaluator hosts must configure a native handler or call interceptor before evaluation.`,
@@ -18665,13 +18714,14 @@ function runtimeCollectionEntryMatchesType(value, typeInfo, ctx, seenCollections
18665
18714
  return resolved !== value && runtimeValueMatchesType(resolved, typeInfo, ctx, seenCollections);
18666
18715
  }
18667
18716
  function evalVectorConstructor(vectorType, componentPointers, scope, ctx) {
18668
- const values = componentPointers.map(
18669
- (pointer) => finiteNumberArg(
18717
+ const integer2 = vectorType === 15 /* Vector2Int */ || vectorType === 17 /* Vector3Int */;
18718
+ const values = componentPointers.map((pointer) => {
18719
+ const value = finiteNumberArg(
18670
18720
  evalPointer(pointer, scope, ctx),
18671
18721
  `${MemberKind[vectorType]} component`
18672
- )
18673
- );
18674
- const integer2 = vectorType === 15 /* Vector2Int */ || vectorType === 17 /* Vector3Int */;
18722
+ );
18723
+ return integer2 ? value : Math.fround(value);
18724
+ });
18675
18725
  if (integer2 && values.some((value) => !Number.isInteger(value))) {
18676
18726
  throw new NSGetterRuntimeError(
18677
18727
  `${MemberKind[vectorType]} components must be integers`
@@ -19562,11 +19612,17 @@ function evalOperation(operation, scope, ctx) {
19562
19612
  if (operation.type === "arithmetic" /* arithmetic */) {
19563
19613
  const op = operation.arithmetic;
19564
19614
  const operands = op.pointers.map((p) => evalPointer(p, scope, ctx));
19565
- return applyArithmetic(op.type, operands, op.decimal === true, ctx);
19615
+ return applyArithmetic(
19616
+ op.type,
19617
+ operands,
19618
+ op.decimal === true,
19619
+ ctx,
19620
+ op.numeric
19621
+ );
19566
19622
  }
19567
19623
  return evalBooleanExpression(operation.expression, scope, ctx);
19568
19624
  }
19569
- function applyArithmetic(op, operands, decimal, ctx) {
19625
+ function applyArithmetic(op, operands, decimal, ctx, numeric) {
19570
19626
  if (operands.length === 0) {
19571
19627
  throw new NSGetterRuntimeError("Arithmetic operation with no operands");
19572
19628
  }
@@ -19599,31 +19655,43 @@ function applyArithmetic(op, operands, decimal, ctx) {
19599
19655
  `Arithmetic operand is not numeric: ${typeof o}`
19600
19656
  );
19601
19657
  });
19658
+ const round = numeric === "float" ? Math.fround : (value) => value;
19602
19659
  switch (op) {
19603
19660
  case "+" /* addition */:
19604
- return numbers.reduce((a, b) => a + b, 0);
19661
+ return numbers.reduce((a, b) => round(a + b), 0);
19605
19662
  case "-" /* subtraction */:
19606
- return numbers.reduce((a, b, i) => i === 0 ? b : a - b);
19663
+ return numbers.reduce((a, b, i) => i === 0 ? b : round(a - b));
19607
19664
  case "*" /* multiplication */:
19608
- return numbers.reduce((a, b) => a * b, 1);
19665
+ return numbers.reduce((a, b) => round(a * b), 1);
19609
19666
  case "/" /* division */:
19610
19667
  return numbers.reduce((a, b, i) => {
19611
19668
  if (i === 0) return b;
19612
19669
  if (b === 0) throw new NSGetterRuntimeError("Division by zero");
19613
- return a / b;
19670
+ return numeric === "int" ? Math.trunc(a / b) : round(a / b);
19614
19671
  });
19615
19672
  case "%" /* remainder */:
19616
19673
  return numbers.reduce((a, b, i) => {
19617
19674
  if (i === 0) return b;
19618
19675
  if (b === 0) throw new NSGetterRuntimeError("Modulo by zero");
19619
- return a % b;
19676
+ return round(a % b);
19620
19677
  });
19621
19678
  }
19622
19679
  }
19680
+ function hostCallResult(member, value, ctx) {
19681
+ if (typeof value !== "number" || member === null) return value;
19682
+ if (member.kind !== 13 /* Function */ && member.kind !== 23 /* NSFunction */) {
19683
+ return value;
19684
+ }
19685
+ const signature = resolveCallableSignature(member.id, member.kind, ctx);
19686
+ return signature?.returnTypeInfo.type === 4 /* Float */ ? Math.fround(value) : value;
19687
+ }
19623
19688
  function coerceAssignmentValue(value, targetTypeInfo, context) {
19624
19689
  if (targetTypeInfo.type === 8 /* Enum */ && typeof value === "string") {
19625
19690
  return [value];
19626
19691
  }
19692
+ if (targetTypeInfo.type === 4 /* Float */ && typeof value === "number") {
19693
+ return Math.fround(value);
19694
+ }
19627
19695
  return targetTypeInfo.type === 20 /* Decimal */ && typeof value === "number" ? coerceDecimalOperand(value, context) : value;
19628
19696
  }
19629
19697
  function coerceDecimalOperand(value, context) {
@@ -19786,7 +19854,7 @@ function evalMathOp(info, scope, ctx) {
19786
19854
  if (value < 0) return -1;
19787
19855
  return 0;
19788
19856
  case "sqrt":
19789
- return Math.sqrt(value);
19857
+ return Math.fround(Math.sqrt(value));
19790
19858
  }
19791
19859
  }
19792
19860
  function evalDecimalMathOp(info, fn, scope, ctx) {
@@ -20427,8 +20495,10 @@ function evalFunction(fn, scope, ctx) {
20427
20495
  );
20428
20496
  }
20429
20497
  case "toFloat":
20430
- return floatFromDecimalString(
20431
- coerceDecimalOperand(receiverRaw, "ToFloat")
20498
+ return Math.fround(
20499
+ floatFromDecimalString(
20500
+ coerceDecimalOperand(receiverRaw, "ToFloat")
20501
+ )
20432
20502
  );
20433
20503
  case "toDecimal": {
20434
20504
  if (typeof receiverRaw !== "number") {
@@ -20937,6 +21007,9 @@ function evaluateConstructorFields(descriptor, scope, ctx) {
20937
21007
  `constructor field '${validated.schemaKey}'`
20938
21008
  );
20939
21009
  }
21010
+ if (typeof value === "number" && validated.typeInfo.type === 4 /* Float */) {
21011
+ value = Math.fround(value);
21012
+ }
20940
21013
  if (value === null || value === void 0) {
20941
21014
  if (validated.requiredWithoutDefault) {
20942
21015
  throw new NSGetterRuntimeError(
@@ -21557,8 +21630,9 @@ function evaluateDeclaredConstructorArguments(info, record2, scope, ctx) {
21557
21630
  );
21558
21631
  }
21559
21632
  const value = byName.get(parameter3.name);
21560
- if (parameter3.type !== 20 /* Decimal */) return value;
21561
21633
  if (typeof value !== "number") return value;
21634
+ if (parameter3.type === 4 /* Float */) return Math.fround(value);
21635
+ if (parameter3.type !== 20 /* Decimal */) return value;
21562
21636
  return coerceDecimalOperand(
21563
21637
  value,
21564
21638
  `constructor argument '${parameter3.name}'`
@@ -32599,7 +32673,10 @@ var StrictNeoScriptResolver = class {
32599
32673
  canonicalDecimal(expression.raw, expression.pos)
32600
32674
  );
32601
32675
  }
32602
- return literal({ kind: "primitive", name: "float" }, expression.value);
32676
+ return literal(
32677
+ { kind: "primitive", name: "float" },
32678
+ Math.fround(expression.value)
32679
+ );
32603
32680
  }
32604
32681
  case "litString":
32605
32682
  return literal({ kind: "primitive", name: "string" }, expression.value);
@@ -37233,7 +37310,9 @@ var StrictNeoScriptResolver = class {
37233
37310
  arithmetic: {
37234
37311
  type: operator,
37235
37312
  pointers: [left.pointer, right.pointer],
37236
- ...isPrimitive(result, "decimal") ? { decimal: true } : {}
37313
+ ...isPrimitive(result, "decimal") ? { decimal: true } : {},
37314
+ ...isPrimitive(result, "int") ? { numeric: "int" } : {},
37315
+ ...isPrimitive(result, "float") ? { numeric: "float" } : {}
37237
37316
  }
37238
37317
  }
37239
37318
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neocompose/cli",
3
- "version": "0.66.0",
3
+ "version": "0.67.0",
4
4
  "description": "Neo Compose native project-source CLI with bidirectional sync.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -9,7 +9,7 @@ description: >-
9
9
  `@neocompose/cli` or `node cli/bin/neo.mjs` in the neo-compose repository.
10
10
  ---
11
11
 
12
- <!-- reviewed-through-cli: 0.66.0 -->
12
+ <!-- reviewed-through-cli: 0.67.0 -->
13
13
 
14
14
  # Neo Compose CLI
15
15
 
@@ -110,7 +110,7 @@ wrappers.
110
110
  The marker near the top of `SKILL.md` must exactly match the package version:
111
111
 
112
112
  ```html
113
- <!-- reviewed-through-cli: 0.66.0 -->
113
+ <!-- reviewed-through-cli: 0.67.0 -->
114
114
  ```
115
115
 
116
116
  The quoted version above is checked too, so this instruction cannot go stale
@@ -202,6 +202,14 @@ Save, Session, Setter, or otherwise writable. A constructor writes only its
202
202
  `this` instance. A writable structured-leaf field still inherits the receiver's
203
203
  effective storage restrictions.
204
204
 
205
+ ## Numeric types
206
+
207
+ `int` is a 32-bit integer and `float` is a 32-bit float, matching C#. Float
208
+ literals, stored values, and every arithmetic step round to 32 bits, so
209
+ `0.1f + 0.2f` behaves like Unity. `int` division truncates toward zero.
210
+ `decimal` is exact base-10 and is the only type to use for currency or other
211
+ values that must not drift.
212
+
205
213
  ## Numeric builtins
206
214
 
207
215
  Use the `Math` namespace for numeric utilities with C# names and C# semantics: