@neocompose/cli 0.31.15 → 0.32.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,35 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.32.0] - 2026-08-18
4
+
5
+ ### Added
6
+
7
+ - P69 Math builtins: call `Math.Min`, `Max`, `Clamp`, `Round`, `Floor`,
8
+ `Ceiling`, `Truncate`, `Abs`, `Sign`, and `Sqrt`, and read the `Math.PI` and
9
+ `Math.E` constants, from any NeoScript body. Results are inferred from the
10
+ argument types under the arithmetic join rule, `Round`/`Floor`/`Ceiling`/
11
+ `Truncate` return `int` on `int` and `float` input, and `Math` is now a
12
+ reserved name.
13
+
14
+ ## [0.31.17] - 2026-08-18
15
+
16
+ ### Fixed
17
+
18
+ - Accept runtime interface patterns on open Class hierarchies while retaining
19
+ impossible-pattern diagnostics for sealed Classes.
20
+ - Recognize typed NeoScript setters as writable from initializers and function
21
+ bodies, including compound assignments, and compile their setter bodies.
22
+ - Preserve `NeoVariant<T>` member types in the local manifest used by
23
+ `neo script check --all`, matching `neo test` candidate compilation.
24
+
25
+ ## [0.31.16] - 2026-08-18
26
+
27
+ ### Fixed
28
+
29
+ - Resolve unordered List membership from entry `containerId` rows during
30
+ NeoScript evaluation, so root-backed indexes and collection operations see
31
+ stored entries in CLI tests, web previews, and world construction.
32
+
3
33
  ## [0.31.15] - 2026-08-18
4
34
 
5
35
  ### Fixed
package/dist/neo.mjs CHANGED
@@ -1308,6 +1308,20 @@ function structuredLeafFieldProperties(owner) {
1308
1308
  (field) => property(owner, field.name, field.type, field.documentation, true)
1309
1309
  );
1310
1310
  }
1311
+ function mathFunction(name, parameterNames, documentation, returnType = FLOAT_TYPE) {
1312
+ return {
1313
+ id: `${NEOSCRIPT_MATH_TYPE_ID}:function:${name}`,
1314
+ name,
1315
+ kind: "function",
1316
+ type: returnType,
1317
+ returnType,
1318
+ parameters: parameterNames.map(
1319
+ (parameterName) => parameter(parameterName, FLOAT_TYPE)
1320
+ ),
1321
+ documentation,
1322
+ static: true
1323
+ };
1324
+ }
1311
1325
  function vectorTypes() {
1312
1326
  const names = [
1313
1327
  "Vector2",
@@ -1322,7 +1336,7 @@ function vectorTypes() {
1322
1336
  members: structuredLeafFieldProperties(name)
1323
1337
  }));
1324
1338
  }
1325
- var NEO_PROJECT_IGNORED_DIRECTORY_NAMES, NEOSCRIPT_KEYWORDS, NEOSCRIPT_INFERRED_LOCAL_KEYWORD, NEOSCRIPT_CONSTRUCTOR_KEYWORD, NEOSCRIPT_PRIMITIVE_TYPES, NEOSCRIPT_BUILTIN_TYPES, NEO_VARIANT_TYPE_NAME, NEO_VARIANT_FOLDER_TYPE_NAME, NEO_LOOKUP_VARIANT_TYPE_NAME, NEO_LOOKUP_VARIANT_FOLDER_TYPE_NAME, NEO_VARIANT_SCOPE_NAME, NEO_VARIANT_RESERVED_NAME, NEO_VARIANT_FOLDER_PATH_SEPARATOR, NEOSCRIPT_OPERATORS, NEOSCRIPT_STATEMENT_SNIPPETS, STRING_TYPE, INT_TYPE, FLOAT_TYPE, BOOL_TYPE, IMAGE_REF_TYPE, SPRITE_INFO_TYPE, FLOAT_OPTIONAL_TYPE, NEOSCRIPT_STRUCTURED_LEAF_FIELDS, NEOSCRIPT_IMAGE_REGISTRY_TYPE, NEOSCRIPT_AUDIO_CLIP_REGISTRY_TYPE, NEOSCRIPT_PENDING_SYMBOL_ID_PREFIX, REGISTRY_ENTRY_TYPES, NEOSCRIPT_GLOBALS, NEOSCRIPT_SPRITE_INFO_EMPTY_MEMBER_ID, NEOSCRIPT_SPRITE_INFO_EMPTY_VALUE, NEOSCRIPT_BUILTIN_TYPE_SYMBOLS;
1339
+ var NEO_PROJECT_IGNORED_DIRECTORY_NAMES, NEOSCRIPT_KEYWORDS, NEOSCRIPT_INFERRED_LOCAL_KEYWORD, NEOSCRIPT_CONSTRUCTOR_KEYWORD, NEOSCRIPT_PRIMITIVE_TYPES, NEOSCRIPT_BUILTIN_ANNOTATION_TYPES, NEOSCRIPT_BUILTIN_NAMESPACES, NEOSCRIPT_BUILTIN_TYPES, NEO_VARIANT_TYPE_NAME, NEO_VARIANT_FOLDER_TYPE_NAME, NEO_LOOKUP_VARIANT_TYPE_NAME, NEO_LOOKUP_VARIANT_FOLDER_TYPE_NAME, NEO_VARIANT_SCOPE_NAME, NEO_VARIANT_RESERVED_NAME, NEO_VARIANT_FOLDER_PATH_SEPARATOR, NEOSCRIPT_OPERATORS, NEOSCRIPT_STATEMENT_SNIPPETS, STRING_TYPE, INT_TYPE, FLOAT_TYPE, BOOL_TYPE, IMAGE_REF_TYPE, SPRITE_INFO_TYPE, FLOAT_OPTIONAL_TYPE, NEOSCRIPT_STRUCTURED_LEAF_FIELDS, NEOSCRIPT_IMAGE_REGISTRY_TYPE, NEOSCRIPT_AUDIO_CLIP_REGISTRY_TYPE, NEOSCRIPT_PENDING_SYMBOL_ID_PREFIX, REGISTRY_ENTRY_TYPES, NEOSCRIPT_GLOBALS, NEOSCRIPT_SPRITE_INFO_EMPTY_MEMBER_ID, NEOSCRIPT_SPRITE_INFO_EMPTY_VALUE, NEOSCRIPT_MATH_TYPE_ID, MATH_INFERENCE_RULE, MATH_INTEGRAL_RULE, NEOSCRIPT_BUILTIN_TYPE_SYMBOLS;
1326
1340
  var init_language_spec = __esm({
1327
1341
  "../packages/neoscript-language/src/language-spec.ts"() {
1328
1342
  "use strict";
@@ -1384,7 +1398,7 @@ var init_language_spec = __esm({
1384
1398
  "bool",
1385
1399
  "string"
1386
1400
  ];
1387
- NEOSCRIPT_BUILTIN_TYPES = [
1401
+ NEOSCRIPT_BUILTIN_ANNOTATION_TYPES = [
1388
1402
  "Dictionary",
1389
1403
  "List",
1390
1404
  "Set",
@@ -1410,6 +1424,11 @@ var init_language_spec = __esm({
1410
1424
  "Vector3Int",
1411
1425
  "Color"
1412
1426
  ];
1427
+ NEOSCRIPT_BUILTIN_NAMESPACES = ["Math"];
1428
+ NEOSCRIPT_BUILTIN_TYPES = [
1429
+ ...NEOSCRIPT_BUILTIN_ANNOTATION_TYPES,
1430
+ ...NEOSCRIPT_BUILTIN_NAMESPACES
1431
+ ];
1413
1432
  NEO_VARIANT_TYPE_NAME = "NeoVariant";
1414
1433
  NEO_VARIANT_FOLDER_TYPE_NAME = "NeoVariantFolder";
1415
1434
  NEO_LOOKUP_VARIANT_TYPE_NAME = "NeoLookupVariant";
@@ -1658,6 +1677,9 @@ var init_language_spec = __esm({
1658
1677
  fileId: "",
1659
1678
  sliceIndex: 0
1660
1679
  });
1680
+ NEOSCRIPT_MATH_TYPE_ID = "builtin:Math";
1681
+ MATH_INFERENCE_RULE = "Accepts int, float, or decimal; the result mirrors the widest argument type.";
1682
+ MATH_INTEGRAL_RULE = "Accepts int, float, or decimal; int and float return int, decimal returns decimal.";
1661
1683
  NEOSCRIPT_BUILTIN_TYPE_SYMBOLS = [
1662
1684
  {
1663
1685
  id: "builtin:SpriteInfo",
@@ -1730,6 +1752,87 @@ var init_language_spec = __esm({
1730
1752
  name: "Color",
1731
1753
  kind: "builtin",
1732
1754
  members: structuredLeafFieldProperties("Color")
1755
+ },
1756
+ {
1757
+ id: NEOSCRIPT_MATH_TYPE_ID,
1758
+ name: "Math",
1759
+ kind: "builtin",
1760
+ documentation: "Numeric builtins with C# names and C# semantics. Math is a qualifier only \u2014 it names no type and holds no value, and every member is a pure function or constant.",
1761
+ members: [
1762
+ mathFunction(
1763
+ "Min",
1764
+ ["a", "b"],
1765
+ `Returns the smaller of two values. ${MATH_INFERENCE_RULE} Ties return the first argument.`
1766
+ ),
1767
+ mathFunction(
1768
+ "Max",
1769
+ ["a", "b"],
1770
+ `Returns the larger of two values. ${MATH_INFERENCE_RULE} Ties return the first argument.`
1771
+ ),
1772
+ mathFunction(
1773
+ "Clamp",
1774
+ ["value", "min", "max"],
1775
+ `Returns value pinned to the inclusive range min..max. ${MATH_INFERENCE_RULE} Requires min <= max.`
1776
+ ),
1777
+ mathFunction(
1778
+ "Round",
1779
+ ["x"],
1780
+ `Rounds to the nearest integer; midpoints round to even (banker's rounding). ${MATH_INTEGRAL_RULE}`,
1781
+ INT_TYPE
1782
+ ),
1783
+ mathFunction(
1784
+ "Floor",
1785
+ ["x"],
1786
+ `Rounds toward negative infinity. ${MATH_INTEGRAL_RULE}`,
1787
+ INT_TYPE
1788
+ ),
1789
+ mathFunction(
1790
+ "Ceiling",
1791
+ ["x"],
1792
+ `Rounds toward positive infinity. ${MATH_INTEGRAL_RULE}`,
1793
+ INT_TYPE
1794
+ ),
1795
+ mathFunction(
1796
+ "Truncate",
1797
+ ["x"],
1798
+ `Rounds toward zero. ${MATH_INTEGRAL_RULE}`,
1799
+ INT_TYPE
1800
+ ),
1801
+ mathFunction(
1802
+ "Abs",
1803
+ ["x"],
1804
+ `Returns the absolute value. ${MATH_INFERENCE_RULE}`
1805
+ ),
1806
+ mathFunction(
1807
+ "Sign",
1808
+ ["x"],
1809
+ "Returns -1, 0, or 1 for a negative, zero, or positive value. Accepts int, float, or decimal and always returns int.",
1810
+ INT_TYPE
1811
+ ),
1812
+ mathFunction(
1813
+ "Sqrt",
1814
+ ["x"],
1815
+ "Returns the square root. Accepts int or float and returns float; does not accept decimal."
1816
+ ),
1817
+ {
1818
+ ...property(
1819
+ "Math",
1820
+ "PI",
1821
+ FLOAT_TYPE,
1822
+ "The ratio of a circle's circumference to its diameter (3.141592653589793)."
1823
+ ),
1824
+ static: true
1825
+ },
1826
+ {
1827
+ ...property(
1828
+ "Math",
1829
+ "E",
1830
+ FLOAT_TYPE,
1831
+ "The base of the natural logarithm (2.718281828459045)."
1832
+ ),
1833
+ static: true
1834
+ }
1835
+ ]
1733
1836
  }
1734
1837
  ];
1735
1838
  }
@@ -2320,12 +2423,11 @@ function complete(snapshot, position) {
2320
2423
  word
2321
2424
  );
2322
2425
  } else {
2323
- const directStaticType = receiverTokens.length === 1 && receiverTokens[0]?.kind === "type" ? snapshot.project.typeByName.get(receiverTokens[0].text) : void 0;
2324
- const resolved = directStaticType ? {
2325
- type: { kind: "named", typeId: directStaticType.id },
2326
- staticType: directStaticType
2327
- } : resolveChain(snapshot, receiverTokens, offset);
2328
- candidates = completionItemsForResolution(snapshot, resolved, word);
2426
+ candidates = completionItemsForResolution(
2427
+ snapshot,
2428
+ resolveChain(snapshot, receiverTokens, offset),
2429
+ word
2430
+ );
2329
2431
  }
2330
2432
  } else if (tail?.kind === "identifier" && tail.text === NEOSCRIPT_CONSTRUCTOR_KEYWORD) {
2331
2433
  candidates = constructorCompletionItems(
@@ -3205,6 +3307,7 @@ function typeCompletionItems(snapshot, word) {
3205
3307
  )
3206
3308
  );
3207
3309
  for (const type of snapshot.project.typeByName.values()) {
3310
+ if (type.id === NEOSCRIPT_MATH_TYPE_ID) continue;
3208
3311
  items.push(typeCompletion(type, word, snapshot));
3209
3312
  }
3210
3313
  return items;
@@ -3409,9 +3512,16 @@ function switchCaseEnumContext(snapshot, offset) {
3409
3512
  function resolveChain(snapshot, tokens, offset) {
3410
3513
  if (tokens.length === 0) return null;
3411
3514
  const constructorType = tokens[0]?.kind === "identifier" && tokens[0].text === NEOSCRIPT_CONSTRUCTOR_KEYWORD && tokens[1]?.kind === "identifier" ? snapshot.project.typeByName.get(tokens[1].text) : void 0;
3515
+ const staticRoot = tokens[0]?.kind === "type" ? snapshot.project.typeByName.get(tokens[0].text) : void 0;
3412
3516
  let current;
3413
3517
  let cursor;
3414
- if (constructorType?.constructorSignature) {
3518
+ if (staticRoot) {
3519
+ current = {
3520
+ type: { kind: "named", typeId: staticRoot.id },
3521
+ staticType: staticRoot
3522
+ };
3523
+ cursor = 1;
3524
+ } else if (constructorType?.constructorSignature) {
3415
3525
  current = { type: constructorType.constructorSignature.returnType };
3416
3526
  const open = tokens.findIndex(
3417
3527
  (token, index) => index >= 2 && token.kind === "punctuation" && token.text === "("
@@ -4509,6 +4619,7 @@ var init_analyzer = __esm({
4509
4619
  RESERVED_NAMES = /* @__PURE__ */ new Set([
4510
4620
  ...NEOSCRIPT_KEYWORDS,
4511
4621
  ...NEOSCRIPT_PRIMITIVE_TYPES,
4622
+ ...NEOSCRIPT_BUILTIN_NAMESPACES,
4512
4623
  "Dictionary",
4513
4624
  "Set"
4514
4625
  ]);
@@ -8817,6 +8928,18 @@ function typesOverlap(left, right, project) {
8817
8928
  if (isNull(right)) return isNullable(left);
8818
8929
  if (isNeoScriptTypeAssignable(left, right, project) || isNeoScriptTypeAssignable(right, left, project))
8819
8930
  return true;
8931
+ if (left.kind === "named" && right.kind === "named") {
8932
+ const leftDeclaration = project.typeById.get(left.typeId);
8933
+ const rightDeclaration = project.typeById.get(right.typeId);
8934
+ if (leftDeclaration?.kind === "interface" && rightDeclaration?.kind === "interface") {
8935
+ return true;
8936
+ }
8937
+ const classDeclaration = leftDeclaration?.kind === "class" ? leftDeclaration : rightDeclaration?.kind === "class" ? rightDeclaration : null;
8938
+ const interfaceDeclaration = leftDeclaration?.kind === "interface" ? leftDeclaration : rightDeclaration?.kind === "interface" ? rightDeclaration : null;
8939
+ if (classDeclaration !== null && interfaceDeclaration !== null) {
8940
+ return classDeclaration.sealed !== true;
8941
+ }
8942
+ }
8820
8943
  return isNumeric(left) && isNumeric(right);
8821
8944
  }
8822
8945
  function isUnknown(type) {
@@ -8855,6 +8978,30 @@ function numericResult(left, right) {
8855
8978
  }
8856
8979
  return { kind: "primitive", name: "int" };
8857
8980
  }
8981
+ function isContextualNumericLiteral(expression) {
8982
+ if (expression.kind === "litInt" || expression.kind === "litFloat") {
8983
+ return true;
8984
+ }
8985
+ return expression.kind === "unary" && expression.op === "-" && (expression.operand.kind === "litInt" || expression.operand.kind === "litFloat");
8986
+ }
8987
+ function mathResultType(rule, joined, pos) {
8988
+ switch (rule) {
8989
+ case "join":
8990
+ return joined;
8991
+ case "integral":
8992
+ return isPrimitive(joined, "decimal") ? joined : { kind: "primitive", name: "int" };
8993
+ case "int":
8994
+ return { kind: "primitive", name: "int" };
8995
+ case "float":
8996
+ if (isPrimitive(joined, "decimal")) {
8997
+ throw new CompileError(
8998
+ "Math.Sqrt does not accept decimal; convert explicitly with value.ToFloat().",
8999
+ pos
9000
+ );
9001
+ }
9002
+ return { kind: "primitive", name: "float" };
9003
+ }
9004
+ }
8858
9005
  function normalizedReturnType(type, kind) {
8859
9006
  if (kind === "action" || kind === "setter" || kind === "constructor") {
8860
9007
  return { kind: "primitive", name: "null" };
@@ -9197,7 +9344,7 @@ function switchValueKey(value) {
9197
9344
  const enumId = value.typeInfo.type === 8 /* Enum */ ? value.typeInfo.enumId : null;
9198
9345
  return JSON.stringify([value.typeInfo.type, enumId, value.value]);
9199
9346
  }
9200
- var CONSTRUCTING_FUNCTION_KINDS, VARIANT_INITIALIZE_NAME, VARIANT_APPLY_NAME, WRITE_TARGET_DESCRIPTIONS, FALLTHROUGH_LABELS, Scope, StrictNeoScriptResolver, SPRITE_DERIVED_MEMBERS;
9347
+ var CONSTRUCTING_FUNCTION_KINDS, VARIANT_INITIALIZE_NAME, VARIANT_APPLY_NAME, MATH_NAMESPACE_NAME, MATH_FUNCTIONS, MATH_CONSTANTS, WRITE_TARGET_DESCRIPTIONS, FALLTHROUGH_LABELS, Scope, StrictNeoScriptResolver, SPRITE_DERIVED_MEMBERS;
9201
9348
  var init_strict_resolver = __esm({
9202
9349
  "../packages/neoscript-language/src/strict-resolver.ts"() {
9203
9350
  "use strict";
@@ -9221,6 +9368,23 @@ var init_strict_resolver = __esm({
9221
9368
  );
9222
9369
  VARIANT_INITIALIZE_NAME = "Initialize";
9223
9370
  VARIANT_APPLY_NAME = "ToVariant";
9371
+ MATH_NAMESPACE_NAME = "Math";
9372
+ MATH_FUNCTIONS = /* @__PURE__ */ new Map([
9373
+ ["Min", { op: "min", arity: 2, result: "join" }],
9374
+ ["Max", { op: "max", arity: 2, result: "join" }],
9375
+ ["Clamp", { op: "clamp", arity: 3, result: "join" }],
9376
+ ["Round", { op: "round", arity: 1, result: "integral" }],
9377
+ ["Floor", { op: "floor", arity: 1, result: "integral" }],
9378
+ ["Ceiling", { op: "ceiling", arity: 1, result: "integral" }],
9379
+ ["Truncate", { op: "truncate", arity: 1, result: "integral" }],
9380
+ ["Abs", { op: "abs", arity: 1, result: "join" }],
9381
+ ["Sign", { op: "sign", arity: 1, result: "int" }],
9382
+ ["Sqrt", { op: "sqrt", arity: 1, result: "float" }]
9383
+ ]);
9384
+ MATH_CONSTANTS = /* @__PURE__ */ new Map([
9385
+ ["PI", 3.141592653589793],
9386
+ ["E", 2.718281828459045]
9387
+ ]);
9224
9388
  WRITE_TARGET_DESCRIPTIONS = {
9225
9389
  this: "`this`",
9226
9390
  local: "a local",
@@ -10218,6 +10382,12 @@ var init_strict_resolver = __esm({
10218
10382
  };
10219
10383
  }
10220
10384
  assertLocalNameAvailable(name, scope, pos) {
10385
+ if (name === MATH_NAMESPACE_NAME) {
10386
+ throw new CompileError(
10387
+ `Cannot declare a local named '${MATH_NAMESPACE_NAME}' because it is a builtin namespace.`,
10388
+ pos
10389
+ );
10390
+ }
10221
10391
  if (!scope.lookup(name)) return;
10222
10392
  throw new CompileError(
10223
10393
  `Cannot declare local '${name}' because that name is already used in an enclosing or current scope.`,
@@ -11435,6 +11605,12 @@ var init_strict_resolver = __esm({
11435
11605
  false
11436
11606
  );
11437
11607
  }
11608
+ if (name === MATH_NAMESPACE_NAME) {
11609
+ throw new CompileError(
11610
+ `'${MATH_NAMESPACE_NAME}' is a builtin namespace, not a value.`,
11611
+ pos
11612
+ );
11613
+ }
11438
11614
  const type = this.project.typeByName.get(name);
11439
11615
  if (type?.kind === "enum") {
11440
11616
  return {
@@ -11832,6 +12008,15 @@ var init_strict_resolver = __esm({
11832
12008
  { ...NEOSCRIPT_SPRITE_INFO_EMPTY_VALUE }
11833
12009
  );
11834
12010
  }
12011
+ if (receiverAst.name === MATH_NAMESPACE_NAME) {
12012
+ if (optional) {
12013
+ throw new CompileError(
12014
+ `Optional chaining is not valid on builtin namespace '${MATH_NAMESPACE_NAME}'.`,
12015
+ pos
12016
+ );
12017
+ }
12018
+ return this.resolveMathMemberRead(name, pos);
12019
+ }
11835
12020
  const staticType = this.project.typeByName.get(receiverAst.name);
11836
12021
  if (staticType && staticType.kind !== "enum") {
11837
12022
  if (optional) {
@@ -12353,6 +12538,114 @@ var init_strict_resolver = __esm({
12353
12538
  );
12354
12539
  }
12355
12540
  }
12541
+ /**
12542
+ * P69 §2.7. A member read on the `Math` namespace: the two constants lower
12543
+ * to `float` literals, and nothing else on `Math` is readable as a value.
12544
+ */
12545
+ resolveMathMemberRead(name, pos) {
12546
+ const constant = MATH_CONSTANTS.get(name);
12547
+ if (constant !== void 0) {
12548
+ return literal({ kind: "primitive", name: "float" }, constant);
12549
+ }
12550
+ if (MATH_FUNCTIONS.has(name)) {
12551
+ throw new CompileError(
12552
+ `Math function '${MATH_NAMESPACE_NAME}.${name}' must be called with \`()\`; it cannot be read as a value.`,
12553
+ pos
12554
+ );
12555
+ }
12556
+ throw new CompileError(
12557
+ `${MATH_NAMESPACE_NAME} has no function '${name}'.`,
12558
+ pos
12559
+ );
12560
+ }
12561
+ /**
12562
+ * P69 §2. A call on the `Math` namespace. Every function is one symbol
12563
+ * whose result type is computed here from the resolved argument types —
12564
+ * the arithmetic operator join rule, verbatim — and lowers to a `mathOp`
12565
+ * intrinsic carrying the operands in declaration order.
12566
+ */
12567
+ resolveMathCall(name, args, scope, pos) {
12568
+ const fn = MATH_FUNCTIONS.get(name);
12569
+ if (!fn) {
12570
+ if (MATH_CONSTANTS.has(name)) {
12571
+ throw new CompileError(
12572
+ `${MATH_NAMESPACE_NAME}.${name} is a constant, not a function; read it without \`()\`.`,
12573
+ pos
12574
+ );
12575
+ }
12576
+ throw new CompileError(
12577
+ `${MATH_NAMESPACE_NAME} has no function '${name}'.`,
12578
+ pos
12579
+ );
12580
+ }
12581
+ const label = `${MATH_NAMESPACE_NAME}.${name}`;
12582
+ requireArgCount(label, args, fn.arity, pos);
12583
+ const resolved = args.map((argument2, index) => {
12584
+ const value = this.resolveExpression(argument2, scope);
12585
+ if (isNullable(value.type)) {
12586
+ throw new CompileError(
12587
+ `${label} requires non-optional arguments; got ${this.describe(value.type)} for argument ${index + 1}.`,
12588
+ argument2.pos
12589
+ );
12590
+ }
12591
+ if (!isNumeric(value.type)) {
12592
+ throw new CompileError(
12593
+ `${label} requires numeric arguments; got ${this.describe(value.type)} for argument ${index + 1}.`,
12594
+ argument2.pos
12595
+ );
12596
+ }
12597
+ return value;
12598
+ });
12599
+ const joined = this.joinMathArguments(args, resolved, scope, pos);
12600
+ return intrinsic(
12601
+ "mathOp" /* MathOp */,
12602
+ {
12603
+ op: fn.op,
12604
+ argPointers: joined.arguments.map((argument2) => argument2.pointer),
12605
+ ...isPrimitive(joined.type, "decimal") ? { decimal: true } : {}
12606
+ },
12607
+ mathResultType(fn.result, joined.type, pos)
12608
+ );
12609
+ }
12610
+ /**
12611
+ * P69 §2.1. The join of a `Math` call's argument types, after the second
12612
+ * resolution pass that gives numeric literals beside a `decimal` argument
12613
+ * their decimal reading.
12614
+ *
12615
+ * A call's argument list carries no declared expected type once typing is
12616
+ * inferred, so `Math.Max(price, 0.99)` would otherwise type `0.99` as
12617
+ * `float` and die on the mix rule with no decimal literal suffix to escape
12618
+ * with. Re-resolving from the preserved raw lexeme is the assignment-context
12619
+ * rule applied across the argument list, not a new coercion: a non-literal
12620
+ * `float` expression still refuses to mix.
12621
+ */
12622
+ joinMathArguments(args, resolved, scope, pos) {
12623
+ const decimalType = {
12624
+ kind: "primitive",
12625
+ name: "decimal"
12626
+ };
12627
+ const anyDecimal = resolved.some(
12628
+ (argument2) => isPrimitive(argument2.type, "decimal")
12629
+ );
12630
+ const settled = !anyDecimal ? resolved : resolved.map((argument2, index) => {
12631
+ const ast = requiredAt(args, index);
12632
+ if (!isContextualNumericLiteral(ast)) return argument2;
12633
+ return this.resolveExpression(ast, scope, decimalType);
12634
+ });
12635
+ if (anyDecimal && settled.some((argument2) => isPrimitive(argument2.type, "float"))) {
12636
+ throw new CompileError(
12637
+ "Decimal and Float cannot be mixed implicitly; convert explicitly with value.ToDecimal(digits) or value.ToFloat().",
12638
+ pos
12639
+ );
12640
+ }
12641
+ return {
12642
+ arguments: settled,
12643
+ type: settled.reduce(
12644
+ (left, right) => numericResult(left, right.type),
12645
+ { kind: "primitive", name: "int" }
12646
+ )
12647
+ };
12648
+ }
12356
12649
  resolveCall(callee, argumentsList2, scope, pos) {
12357
12650
  const variantCall = this.resolveVariantCall(
12358
12651
  callee,
@@ -12446,6 +12739,15 @@ var init_strict_resolver = __esm({
12446
12739
  );
12447
12740
  }
12448
12741
  if (callee.receiver.kind === "ident" && !scope.lookup(callee.receiver.name)) {
12742
+ if (callee.receiver.name === MATH_NAMESPACE_NAME) {
12743
+ if (callee.optional) {
12744
+ throw new CompileError(
12745
+ `Optional chaining is not valid on builtin namespace '${MATH_NAMESPACE_NAME}'.`,
12746
+ pos
12747
+ );
12748
+ }
12749
+ return this.resolveMathCall(callee.name, argumentsList2, scope, pos);
12750
+ }
12449
12751
  const staticType = this.project.typeByName.get(callee.receiver.name);
12450
12752
  if (staticType && staticType.kind !== "enum") {
12451
12753
  if (callee.optional) {
@@ -14306,6 +14608,12 @@ var init_strict_resolver = __esm({
14306
14608
  resolved = { kind: "primitive", name: type.name };
14307
14609
  break;
14308
14610
  case "named": {
14611
+ if (type.name === MATH_NAMESPACE_NAME) {
14612
+ throw new CompileError(
14613
+ `'${MATH_NAMESPACE_NAME}' is a builtin namespace, not a type.`,
14614
+ type.pos
14615
+ );
14616
+ }
14309
14617
  const builtin = namedPrimitive(type.name);
14310
14618
  if (builtin) {
14311
14619
  resolved = { kind: "primitive", name: builtin };
@@ -24110,6 +24418,7 @@ function buildProjectGraph(documents) {
24110
24418
  ...interfaceTypeIds.length ? { interfaceTypeIds } : {},
24111
24419
  ...typeParameters.length ? { typeParameters } : {},
24112
24420
  ...classDeclaration?.modifiers.includes("abstract") ? { abstract: true } : {},
24421
+ ...classDeclaration?.modifiers.includes("sealed") ? { sealed: true } : {},
24113
24422
  location: location(info.uri, info.declaration.range),
24114
24423
  selectionLocation: location(info.uri, info.declaration.nameRange)
24115
24424
  };
@@ -24612,10 +24921,23 @@ function accessorBodyRange(source, bodyRange, keyword) {
24612
24921
  continue;
24613
24922
  }
24614
24923
  if (depth !== 1 || token.text !== keyword) continue;
24615
- const open = tokens[index + 1];
24924
+ let openIndex = index + 1;
24925
+ if (tokens[openIndex]?.text === "(") {
24926
+ let parameterDepth = 1;
24927
+ for (let cursor = openIndex + 1; cursor < tokens.length; cursor++) {
24928
+ const candidate = tokens[cursor];
24929
+ if (candidate.text === "(") parameterDepth++;
24930
+ if (candidate.text === ")") parameterDepth--;
24931
+ if (parameterDepth !== 0) continue;
24932
+ openIndex = cursor + 1;
24933
+ break;
24934
+ }
24935
+ if (parameterDepth !== 0) return null;
24936
+ }
24937
+ const open = tokens[openIndex];
24616
24938
  if (open?.text !== "{") return null;
24617
24939
  let accessorDepth = 1;
24618
- for (let cursor = index + 2; cursor < tokens.length; cursor++) {
24940
+ for (let cursor = openIndex + 1; cursor < tokens.length; cursor++) {
24619
24941
  const candidate = tokens[cursor];
24620
24942
  if (candidate.text === "{") accessorDepth++;
24621
24943
  if (candidate.text === "}") accessorDepth--;
@@ -26169,7 +26491,7 @@ var init_project_source_type_checker = __esm({
26169
26491
  ]);
26170
26492
  BUILTIN_TYPES = /* @__PURE__ */ new Set([
26171
26493
  ...NEOSCRIPT_PRIMITIVE_TYPES,
26172
- ...NEOSCRIPT_BUILTIN_TYPES,
26494
+ ...NEOSCRIPT_BUILTIN_ANNOTATION_TYPES,
26173
26495
  "void",
26174
26496
  "null",
26175
26497
  "Project",
@@ -26187,7 +26509,10 @@ var init_project_source_type_checker = __esm({
26187
26509
  "NeoAudioClip",
26188
26510
  "ProjectRelation"
26189
26511
  ]);
26190
- RESERVED_NAMES2 = new Set(NEOSCRIPT_KEYWORDS);
26512
+ RESERVED_NAMES2 = /* @__PURE__ */ new Set([
26513
+ ...NEOSCRIPT_KEYWORDS,
26514
+ ...NEOSCRIPT_BUILTIN_NAMESPACES
26515
+ ]);
26191
26516
  }
26192
26517
  });
26193
26518
 
@@ -27090,7 +27415,7 @@ var init_project_source_analysis = __esm({
27090
27415
  init_project_source_parameter_defaults();
27091
27416
  BUILTIN_TYPE_NAMES = /* @__PURE__ */ new Set([
27092
27417
  ...NEOSCRIPT_PRIMITIVE_TYPES,
27093
- ...NEOSCRIPT_BUILTIN_TYPES,
27418
+ ...NEOSCRIPT_BUILTIN_ANNOTATION_TYPES,
27094
27419
  "void",
27095
27420
  "null",
27096
27421
  "Project",
@@ -27108,7 +27433,10 @@ var init_project_source_analysis = __esm({
27108
27433
  "NeoAudioClip",
27109
27434
  "ProjectRelation"
27110
27435
  ]);
27111
- RESERVED_NAMES3 = new Set(NEOSCRIPT_KEYWORDS);
27436
+ RESERVED_NAMES3 = /* @__PURE__ */ new Set([
27437
+ ...NEOSCRIPT_KEYWORDS,
27438
+ ...NEOSCRIPT_BUILTIN_NAMESPACES
27439
+ ]);
27112
27440
  SYSTEM_RECORD_ID_PREFIX = "system_";
27113
27441
  RFC_4122_UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u;
27114
27442
  QUARANTINED_LEGACY_SYSTEM_ID_PREFIXES = [
@@ -27608,7 +27936,7 @@ function projectFallbackCompletions(analysis, document, position) {
27608
27936
  }
27609
27937
  for (const typeName of [
27610
27938
  ...NEOSCRIPT_PRIMITIVE_TYPES,
27611
- ...NEOSCRIPT_BUILTIN_TYPES
27939
+ ...NEOSCRIPT_BUILTIN_ANNOTATION_TYPES
27612
27940
  ]) {
27613
27941
  if (expression) continue;
27614
27942
  items.set(typeName, {
@@ -31325,6 +31653,7 @@ function schemaClass(value, environment, field) {
31325
31653
  `${field}.implementsInterfaceIds`
31326
31654
  ),
31327
31655
  ...value.declarationModifier === "abstract" ? { abstract: true } : {},
31656
+ ...value.isSealed === true ? { sealed: true } : {},
31328
31657
  ...sourceLocation(value.source, environment, `${field}.source`),
31329
31658
  typeParameters: records(
31330
31659
  value.genericParameters ?? [],
@@ -31945,6 +32274,20 @@ function memberType(member, environment, visiting, field) {
31945
32274
  nullable: false
31946
32275
  };
31947
32276
  }
32277
+ if (kind === "variant") {
32278
+ const variantClass = [...environment.classes.values()].find(
32279
+ (candidate) => candidate.name === NEO_VARIANT_TYPE_NAME
32280
+ );
32281
+ if (variantClass === void 0) return { ...unknownType(), nullable };
32282
+ return {
32283
+ kind: "named",
32284
+ typeId: string2(variantClass.id, `${field}.variantClass.id`),
32285
+ typeArguments: [
32286
+ manifestType2(member.targetType, environment, `${field}.targetType`)
32287
+ ],
32288
+ nullable
32289
+ };
32290
+ }
31948
32291
  if (kind === "generic") {
31949
32292
  return typeParameter(
31950
32293
  string2(member.genericParamId, `${field}.genericParamId`),
@@ -32487,6 +32830,7 @@ var init_project_schema_manifest = __esm({
32487
32830
  "use strict";
32488
32831
  init_project();
32489
32832
  init_project_schema_contract_generated();
32833
+ init_language_spec();
32490
32834
  }
32491
32835
  });
32492
32836
 
@@ -41523,6 +41867,18 @@ function isNSFunctionDecimalOp(value) {
41523
41867
  const digits = info.digitsPointer;
41524
41868
  return digits === void 0 || isNSPointer(digits);
41525
41869
  }
41870
+ function isNSFunctionMathOp(value) {
41871
+ const v = value;
41872
+ if (v?.type !== "mathOp" /* mathOp */) return false;
41873
+ const info = v.info;
41874
+ if (typeof info !== "object" || info === null) return false;
41875
+ if (!NS_MATH_OPS.includes(info.op)) return false;
41876
+ const args = info.argPointers;
41877
+ if (!Array.isArray(args)) return false;
41878
+ if (!args.every(isNSPointer)) return false;
41879
+ const decimal2 = info.decimal;
41880
+ return decimal2 === void 0 || decimal2 === true;
41881
+ }
41526
41882
  function isNSFunctionCount(value) {
41527
41883
  const v = value;
41528
41884
  if (v?.type !== "count" /* count */) return false;
@@ -41570,7 +41926,7 @@ function isNSFunction(value) {
41570
41926
  return isNSFunctionClassConstructor(value) || isNSFunctionClassClone(value) || isNSFunctionListIndex(value) || isNSFunctionSelect(value) || isNSFunctionFirst(value) || isNSFunctionFirstOrDefault(value) || isNSFunctionWhere(value) || isNSFunctionContains(value) || // stringOp was missing from this chain since its introduction — a
41571
41927
  // compiled getter using ToLower()/StartsWith() would fail pointer
41572
41928
  // validation anywhere isNSFunction gates. Fixed alongside decimalOp.
41573
- isNSFunctionStringOp(value) || isNSFunctionDecimalOp(value) || isNSFunctionCount(value) || isNSFunctionVisitCount(value) || isNSFunctionHasVisited(value) || isNSFunctionVectorConstructor(value) || isNSFunctionImageSlice(value) || isNSFunctionVariantInitialize(value) || isNSFunctionVariantApply(value) || isNSFunctionDeclaredConstructor(value);
41929
+ isNSFunctionStringOp(value) || isNSFunctionDecimalOp(value) || isNSFunctionMathOp(value) || isNSFunctionCount(value) || isNSFunctionVisitCount(value) || isNSFunctionHasVisited(value) || isNSFunctionVectorConstructor(value) || isNSFunctionImageSlice(value) || isNSFunctionVariantInitialize(value) || isNSFunctionVariantApply(value) || isNSFunctionDeclaredConstructor(value);
41574
41930
  }
41575
41931
  function isNSInstructionVariable(value) {
41576
41932
  const v = value;
@@ -41861,7 +42217,7 @@ function isNSVoidBody(value) {
41861
42217
  if (value.typeInfo.type !== 0 /* Null */) return false;
41862
42218
  return value.typeInfo.required === true;
41863
42219
  }
41864
- var NS_STRING_OPS, NS_DECIMAL_OPS;
42220
+ var NS_STRING_OPS, NS_DECIMAL_OPS, NS_MATH_OPS;
41865
42221
  var init_neoscript_guards = __esm({
41866
42222
  "../src/models/neoscript/neoscript-guards.ts"() {
41867
42223
  "use strict";
@@ -41882,6 +42238,18 @@ var init_neoscript_guards = __esm({
41882
42238
  "toFloat",
41883
42239
  "toDecimal"
41884
42240
  ];
42241
+ NS_MATH_OPS = [
42242
+ "min",
42243
+ "max",
42244
+ "clamp",
42245
+ "round",
42246
+ "floor",
42247
+ "ceiling",
42248
+ "truncate",
42249
+ "abs",
42250
+ "sign",
42251
+ "sqrt"
42252
+ ];
41885
42253
  }
41886
42254
  });
41887
42255
 
@@ -46337,6 +46705,23 @@ var init_member_kinds_db_response = __esm({
46337
46705
  });
46338
46706
 
46339
46707
  // ../src/models/members/unordered-list-membership.ts
46708
+ function buildUnorderedListMembershipIndex(values) {
46709
+ const membersByContainerId = /* @__PURE__ */ new Map();
46710
+ for (const candidate of values) {
46711
+ const containerId = candidate.containerId;
46712
+ if (typeof containerId !== "string") continue;
46713
+ const bucket = membersByContainerId.get(containerId);
46714
+ if (bucket === void 0) {
46715
+ membersByContainerId.set(containerId, [candidate.id]);
46716
+ } else {
46717
+ bucket.push(candidate.id);
46718
+ }
46719
+ }
46720
+ for (const bucket of membersByContainerId.values()) {
46721
+ bucket.sort();
46722
+ }
46723
+ return membersByContainerId;
46724
+ }
46340
46725
  function unorderedListEntryIds(values, listValue2, membershipIndex) {
46341
46726
  if (!Array.isArray(listValue2.value)) return [];
46342
46727
  if (membershipIndex !== void 0) {
@@ -63028,6 +63413,25 @@ function compareDecimalStrings(a, b) {
63028
63413
  if (aligned.a > aligned.b) return 1;
63029
63414
  return 0;
63030
63415
  }
63416
+ function minDecimalStrings(a, b) {
63417
+ return compareDecimalStrings(a, b) <= 0 ? a : b;
63418
+ }
63419
+ function maxDecimalStrings(a, b) {
63420
+ return compareDecimalStrings(a, b) >= 0 ? a : b;
63421
+ }
63422
+ function clampDecimalStrings(value, min, max) {
63423
+ if (compareDecimalStrings(min, max) > 0) {
63424
+ throw new DecimalClampRangeError(MATH_CLAMP_RANGE_MESSAGE);
63425
+ }
63426
+ if (compareDecimalStrings(value, min) < 0) return min;
63427
+ if (compareDecimalStrings(value, max) > 0) return max;
63428
+ return value;
63429
+ }
63430
+ function absDecimalString(value) {
63431
+ const parts = parseDecimalArg(value, "value");
63432
+ const magnitude = parts.coefficient < 0n ? -parts.coefficient : parts.coefficient;
63433
+ return formatDecimalParts({ coefficient: magnitude, scale: parts.scale });
63434
+ }
63031
63435
  function addDecimalStrings(a, b) {
63032
63436
  const aligned = alignScales(parseDecimalArg(a, "a"), parseDecimalArg(b, "b"));
63033
63437
  return formatDecimalParts(
@@ -63090,6 +63494,32 @@ function roundDecimalString(value, digits) {
63090
63494
  const rounded = roundParts(parseDecimalArg(value, "value"), digits);
63091
63495
  return formatDecimalParts(assertWithinEnvelope(rounded, "round"));
63092
63496
  }
63497
+ function toIntegralParts(parts, direction) {
63498
+ if (parts.scale === 0) return parts;
63499
+ const divisor = pow10(parts.scale);
63500
+ const quotient = parts.coefficient / divisor;
63501
+ const remainder = parts.coefficient % divisor;
63502
+ if (remainder === 0n) return { coefficient: quotient, scale: 0 };
63503
+ if (direction === "floor" && remainder < 0n) {
63504
+ return { coefficient: quotient - 1n, scale: 0 };
63505
+ }
63506
+ if (direction === "ceiling" && remainder > 0n) {
63507
+ return { coefficient: quotient + 1n, scale: 0 };
63508
+ }
63509
+ return { coefficient: quotient, scale: 0 };
63510
+ }
63511
+ function floorDecimalString(value) {
63512
+ const parts = toIntegralParts(parseDecimalArg(value, "value"), "floor");
63513
+ return formatDecimalParts(assertWithinEnvelope(parts, "floor"));
63514
+ }
63515
+ function ceilingDecimalString(value) {
63516
+ const parts = toIntegralParts(parseDecimalArg(value, "value"), "ceiling");
63517
+ return formatDecimalParts(assertWithinEnvelope(parts, "ceiling"));
63518
+ }
63519
+ function truncateDecimalString(value) {
63520
+ const parts = toIntegralParts(parseDecimalArg(value, "value"), "truncate");
63521
+ return formatDecimalParts(assertWithinEnvelope(parts, "truncate"));
63522
+ }
63093
63523
  function divideDecimalStrings(a, b, digits) {
63094
63524
  assertDigitsInRange(digits, "divide");
63095
63525
  const dividend = parseDecimalArg(a, "a");
@@ -63170,7 +63600,7 @@ function floatFromDecimalString(value) {
63170
63600
  assertCanonicalInput(value, "value");
63171
63601
  return Number(value);
63172
63602
  }
63173
- var DecimalOverflowError, DecimalDivisionByZeroError, DecimalDigitsRangeError, DecimalNonFiniteError, TEN;
63603
+ var DecimalOverflowError, DecimalDivisionByZeroError, DecimalDigitsRangeError, DecimalNonFiniteError, DecimalClampRangeError, MATH_CLAMP_RANGE_MESSAGE, TEN;
63174
63604
  var init_decimal_math = __esm({
63175
63605
  "../src/models/decimal/decimal-math.ts"() {
63176
63606
  "use strict";
@@ -63183,6 +63613,9 @@ var init_decimal_math = __esm({
63183
63613
  };
63184
63614
  DecimalNonFiniteError = class extends Error {
63185
63615
  };
63616
+ DecimalClampRangeError = class extends Error {
63617
+ };
63618
+ MATH_CLAMP_RANGE_MESSAGE = "Math.Clamp requires min <= max.";
63186
63619
  TEN = 10n;
63187
63620
  }
63188
63621
  });
@@ -63887,9 +64320,11 @@ function makeEvaluatorLookups(members, values, options = {}) {
63887
64320
  for (const a of members) attrMap.set(a.id, a);
63888
64321
  const valMap = /* @__PURE__ */ new Map();
63889
64322
  for (const v of values) valMap.set(v.id, v);
64323
+ const unorderedListMembership = buildUnorderedListMembershipIndex(values);
63890
64324
  const lookups = {
63891
64325
  memberById: (id2) => attrMap.get(id2) ?? null,
63892
- valueById: (id2) => valMap.get(id2) ?? null
64326
+ valueById: (id2) => valMap.get(id2) ?? null,
64327
+ unorderedListEntryIds: (containerValueId) => unorderedListMembership.get(containerValueId) ?? []
63893
64328
  };
63894
64329
  if (options.includeValueGraphIndexes === true) {
63895
64330
  return {
@@ -67836,9 +68271,24 @@ function resolveValueIfIdForMember(at, member, ctx) {
67836
68271
  );
67837
68272
  if (!row) return at;
67838
68273
  ctx.valueDependencies?.add(row.id);
68274
+ if (isMemberListBase(member) && member.listKind === "unordered" && Array.isArray(row.value)) {
68275
+ return evaluatorUnorderedListEntryIds(row.id, ctx);
68276
+ }
67839
68277
  const value = resolveLocalizedRowValueForMember(row, member, ctx);
67840
68278
  return shouldUnwrapSingleLookupValue(member) ? unwrapSingleLookupValue(value, ctx) : value;
67841
68279
  }
68280
+ function evaluatorUnorderedListEntryIds(containerValueId, ctx) {
68281
+ const runtimeRows = [...ctx.__runtimeSessionValues?.values() ?? []];
68282
+ const overlayRows = [...ctx.__valueOverlay?.values() ?? []];
68283
+ const localRows = [...runtimeRows, ...overlayRows];
68284
+ const shadowedIds = new Set(localRows.map((row) => row.id));
68285
+ const baseIds = ctx.vm.databaseVM?.unorderedListEntryIds?.(containerValueId) ?? ctx.vm.values.filter((row) => row.containerId === containerValueId).map((row) => row.id);
68286
+ const ids = new Set(baseIds.filter((id2) => !shadowedIds.has(id2)));
68287
+ for (const row of localRows) {
68288
+ if (row.containerId === containerValueId) ids.add(row.id);
68289
+ }
68290
+ return [...ids].sort();
68291
+ }
67842
68292
  function shouldUnwrapSingleLookupValue(member) {
67843
68293
  if (member === null) return false;
67844
68294
  if (!isMemberLookup(member)) return false;
@@ -68054,7 +68504,7 @@ function coerceDecimalOperand(value, context) {
68054
68504
  );
68055
68505
  }
68056
68506
  function rethrowDecimalError(error) {
68057
- if (error instanceof DecimalOverflowError || error instanceof DecimalDivisionByZeroError || error instanceof DecimalDigitsRangeError || error instanceof DecimalNonFiniteError) {
68507
+ if (error instanceof DecimalOverflowError || error instanceof DecimalDivisionByZeroError || error instanceof DecimalDigitsRangeError || error instanceof DecimalNonFiniteError || error instanceof DecimalClampRangeError) {
68058
68508
  throw new NSGetterRuntimeError(error.message);
68059
68509
  }
68060
68510
  throw error;
@@ -68081,6 +68531,163 @@ function applyDecimalArithmetic(op, operands) {
68081
68531
  rethrowDecimalError(error);
68082
68532
  }
68083
68533
  }
68534
+ function mathArgPointer(info, index, fn) {
68535
+ const pointer = info.argPointers[index];
68536
+ if (pointer === void 0) {
68537
+ throw new NSGetterRuntimeError(
68538
+ `Math.${fn} is missing argument ${index + 1}.`
68539
+ );
68540
+ }
68541
+ return pointer;
68542
+ }
68543
+ function mathNumberArg(value, fn) {
68544
+ if (value === null || value === void 0) {
68545
+ throw new NSGetterRuntimeError(`Math.${fn} argument is null.`);
68546
+ }
68547
+ if (typeof value !== "number") {
68548
+ throw new NSGetterRuntimeError(
68549
+ `Math.${fn} argument is not numeric: ${typeof value}.`
68550
+ );
68551
+ }
68552
+ return value;
68553
+ }
68554
+ function mathDecimalArg(value, fn) {
68555
+ if (value === null || value === void 0) {
68556
+ throw new NSGetterRuntimeError(`Math.${fn} argument is null.`);
68557
+ }
68558
+ if (typeof value !== "string" && typeof value !== "number") {
68559
+ throw new NSGetterRuntimeError(
68560
+ `Math.${fn} argument is not numeric: ${typeof value}.`
68561
+ );
68562
+ }
68563
+ return coerceDecimalOperand(value, `Math.${fn}`);
68564
+ }
68565
+ function requireFiniteMathArg(value, fn) {
68566
+ if (!Number.isFinite(value)) {
68567
+ throw new NSGetterRuntimeError(`Math.${fn} requires a finite argument.`);
68568
+ }
68569
+ return value;
68570
+ }
68571
+ function roundHalfEven(value) {
68572
+ if (Math.abs(value) >= DOUBLE_INTEGRAL_MAGNITUDE) return value;
68573
+ const integral = Math.floor(value);
68574
+ const fraction = value - integral;
68575
+ if (fraction > 0.5) return integral + 1;
68576
+ if (fraction < 0.5) return integral;
68577
+ if (integral % 2 === 0) return integral;
68578
+ return integral + 1;
68579
+ }
68580
+ function evalMathOp(info, scope, ctx) {
68581
+ const fn = NS_MATH_OP_NAMES[info.op];
68582
+ if (info.decimal === true) return evalDecimalMathOp(info, fn, scope, ctx);
68583
+ const value = mathNumberArg(
68584
+ evalPointer(mathArgPointer(info, 0, fn), scope, ctx),
68585
+ fn
68586
+ );
68587
+ switch (info.op) {
68588
+ case "min":
68589
+ return Math.min(
68590
+ value,
68591
+ mathNumberArg(evalPointer(mathArgPointer(info, 1, fn), scope, ctx), fn)
68592
+ );
68593
+ case "max":
68594
+ return Math.max(
68595
+ value,
68596
+ mathNumberArg(evalPointer(mathArgPointer(info, 1, fn), scope, ctx), fn)
68597
+ );
68598
+ case "clamp": {
68599
+ const min = mathNumberArg(
68600
+ evalPointer(mathArgPointer(info, 1, fn), scope, ctx),
68601
+ fn
68602
+ );
68603
+ const max = mathNumberArg(
68604
+ evalPointer(mathArgPointer(info, 2, fn), scope, ctx),
68605
+ fn
68606
+ );
68607
+ if (min > max) {
68608
+ throw new NSGetterRuntimeError(MATH_CLAMP_RANGE_MESSAGE);
68609
+ }
68610
+ if (value < min) return min;
68611
+ if (value > max) return max;
68612
+ return value;
68613
+ }
68614
+ case "round":
68615
+ return roundHalfEven(requireFiniteMathArg(value, fn));
68616
+ case "floor":
68617
+ return Math.floor(requireFiniteMathArg(value, fn));
68618
+ case "ceiling":
68619
+ return Math.ceil(requireFiniteMathArg(value, fn));
68620
+ case "truncate":
68621
+ return Math.trunc(requireFiniteMathArg(value, fn));
68622
+ case "abs":
68623
+ return Math.abs(value);
68624
+ case "sign":
68625
+ if (Number.isNaN(value)) {
68626
+ throw new NSGetterRuntimeError("Math.Sign is undefined for NaN.");
68627
+ }
68628
+ if (value > 0) return 1;
68629
+ if (value < 0) return -1;
68630
+ return 0;
68631
+ case "sqrt":
68632
+ return Math.sqrt(value);
68633
+ }
68634
+ }
68635
+ function evalDecimalMathOp(info, fn, scope, ctx) {
68636
+ const value = mathDecimalArg(
68637
+ evalPointer(mathArgPointer(info, 0, fn), scope, ctx),
68638
+ fn
68639
+ );
68640
+ try {
68641
+ switch (info.op) {
68642
+ case "min":
68643
+ return minDecimalStrings(
68644
+ value,
68645
+ mathDecimalArg(
68646
+ evalPointer(mathArgPointer(info, 1, fn), scope, ctx),
68647
+ fn
68648
+ )
68649
+ );
68650
+ case "max":
68651
+ return maxDecimalStrings(
68652
+ value,
68653
+ mathDecimalArg(
68654
+ evalPointer(mathArgPointer(info, 1, fn), scope, ctx),
68655
+ fn
68656
+ )
68657
+ );
68658
+ case "clamp":
68659
+ return clampDecimalStrings(
68660
+ value,
68661
+ mathDecimalArg(
68662
+ evalPointer(mathArgPointer(info, 1, fn), scope, ctx),
68663
+ fn
68664
+ ),
68665
+ mathDecimalArg(
68666
+ evalPointer(mathArgPointer(info, 2, fn), scope, ctx),
68667
+ fn
68668
+ )
68669
+ );
68670
+ case "round":
68671
+ return roundDecimalString(value, 0);
68672
+ case "floor":
68673
+ return floorDecimalString(value);
68674
+ case "ceiling":
68675
+ return ceilingDecimalString(value);
68676
+ case "truncate":
68677
+ return truncateDecimalString(value);
68678
+ case "abs":
68679
+ return absDecimalString(value);
68680
+ case "sign":
68681
+ return compareDecimalStrings(value, "0");
68682
+ case "sqrt":
68683
+ throw new NSGetterRuntimeError(
68684
+ "Math.Sqrt does not accept decimal arguments."
68685
+ );
68686
+ }
68687
+ } catch (error) {
68688
+ rethrowDecimalError(error);
68689
+ }
68690
+ }
68084
68691
  function stringifyForInterp(v) {
68085
68692
  if (v === null || v === void 0) return "";
68086
68693
  if (typeof v === "string") return v;
@@ -68600,6 +69207,8 @@ function evalFunction(fn, scope, ctx) {
68600
69207
  }
68601
69208
  break;
68602
69209
  }
69210
+ case "mathOp" /* mathOp */:
69211
+ return evalMathOp(fn.info, scope, ctx);
68603
69212
  case "stringOp" /* stringOp */: {
68604
69213
  const receiver = evalPointer(fn.info.receiverPointer, scope, ctx);
68605
69214
  if (typeof receiver !== "string") {
@@ -71812,7 +72421,7 @@ function iterateCollection(c, ctx, callback) {
71812
72421
  return callback(entry, key, valueId);
71813
72422
  });
71814
72423
  }
71815
- var DELEGATE_LEXICAL_THIS, DELEGATE_LEXICAL_ROOT, NonCatchableNSGetterRuntimeError, NativeFunctionDelegateUnavailableError, CorruptNeoScriptIRError, NeoScriptResourceLimitError, NeoScriptWallClockTimeoutError, DEFAULT_NEO_SCRIPT_EXECUTION_BUDGET_LIMITS, liveListIndexesByProject, evaluatorOwnershipCachesByBase, MAX_CONSTRUCTION_DEPTH, MAX_LOOP_ITERATIONS, resolutionCacheByMembers, NO_SCHEMA_REVISION, LazyValueOverlay, READONLY_FOREACH_BINDING_ERROR, READONLY_CATCH_BINDING_ERROR, EMPTY_SUPPLIED_CONSTRUCTOR_FIELDS;
72424
+ var DELEGATE_LEXICAL_THIS, DELEGATE_LEXICAL_ROOT, NonCatchableNSGetterRuntimeError, NativeFunctionDelegateUnavailableError, CorruptNeoScriptIRError, NeoScriptResourceLimitError, NeoScriptWallClockTimeoutError, DEFAULT_NEO_SCRIPT_EXECUTION_BUDGET_LIMITS, liveListIndexesByProject, evaluatorOwnershipCachesByBase, MAX_CONSTRUCTION_DEPTH, MAX_LOOP_ITERATIONS, resolutionCacheByMembers, NO_SCHEMA_REVISION, LazyValueOverlay, READONLY_FOREACH_BINDING_ERROR, READONLY_CATCH_BINDING_ERROR, NS_MATH_OP_NAMES, DOUBLE_INTEGRAL_MAGNITUDE, EMPTY_SUPPLIED_CONSTRUCTOR_FIELDS;
71816
72425
  var init_evaluateNSGetter = __esm({
71817
72426
  "../src/view-models/neoscript-evaluator/evaluateNSGetter.ts"() {
71818
72427
  "use strict";
@@ -71956,6 +72565,19 @@ var init_evaluateNSGetter = __esm({
71956
72565
  };
71957
72566
  READONLY_FOREACH_BINDING_ERROR = "Cannot assign to a read-only foreach iterator binding.";
71958
72567
  READONLY_CATCH_BINDING_ERROR = "Cannot assign to a read-only catch message binding.";
72568
+ NS_MATH_OP_NAMES = {
72569
+ min: "Min",
72570
+ max: "Max",
72571
+ clamp: "Clamp",
72572
+ round: "Round",
72573
+ floor: "Floor",
72574
+ ceiling: "Ceiling",
72575
+ truncate: "Truncate",
72576
+ abs: "Abs",
72577
+ sign: "Sign",
72578
+ sqrt: "Sqrt"
72579
+ };
72580
+ DOUBLE_INTEGRAL_MAGNITUDE = 2 ** 53;
71959
72581
  EMPTY_SUPPLIED_CONSTRUCTOR_FIELDS = [];
71960
72582
  }
71961
72583
  });
@@ -71980,7 +72602,7 @@ function initializerEvaluatorLookups(document) {
71980
72602
  });
71981
72603
  return lookups;
71982
72604
  }
71983
- function buildInitializerRootValue(document, lookups) {
72605
+ function buildInitializerRootValueWithLookups(document, lookups) {
71984
72606
  const rootValue = {};
71985
72607
  for (const { root, memberId } of projectRootMembersInDisplayOrder(
71986
72608
  document.project
@@ -72020,7 +72642,7 @@ function evaluateMemberInitializer(args) {
72020
72642
  databaseVM
72021
72643
  },
72022
72644
  thisValue: null,
72023
- rootValue: buildInitializerRootValue(args.document, databaseVM),
72645
+ rootValue: buildInitializerRootValueWithLookups(args.document, databaseVM),
72024
72646
  saveStaticBindings: args.saveStaticBindings,
72025
72647
  sessionStaticBindings: args.sessionStaticBindings,
72026
72648
  ...args.storedConstructionReplay === true ? {
@@ -111630,7 +112252,7 @@ var init_registry2 = __esm({
111630
112252
  PROJECT_SCHEMA_CONTRACT = Object.freeze({
111631
112253
  formatVersion: 3,
111632
112254
  contractVersion: "3.13",
111633
- cliVersion: "0.31.15",
112255
+ cliVersion: "0.32.0",
111634
112256
  projectFileUploadBatchSize: 32,
111635
112257
  documentRecords: {
111636
112258
  member: {
@@ -118225,7 +118847,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
118225
118847
  async function main() {
118226
118848
  const args = parseArgs(process.argv.slice(2));
118227
118849
  if (args.command === "--version") {
118228
- console.log("0.31.15");
118850
+ console.log("0.32.0");
118229
118851
  return;
118230
118852
  }
118231
118853
  if (args.command === null || args.command === "help" || args.command === "--help" || args.command === "-h") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neocompose/cli",
3
- "version": "0.31.15",
3
+ "version": "0.32.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.31.15 -->
12
+ <!-- reviewed-through-cli: 0.32.0 -->
13
13
 
14
14
  # Neo Compose CLI
15
15
 
@@ -138,7 +138,8 @@ completely before editing:
138
138
  variant folders, partial animation overrides, segments, tracks, and NeoFlow
139
139
  dialogues.
140
140
  - [NeoScript](references/neoscript.md): inline bodies, snippets, nullability,
141
- loops, switch, try/catch, ownership, evaluation, and migrations.
141
+ numeric builtins, loops, switch, try/catch, ownership, evaluation, and
142
+ migrations.
142
143
  - [Commands and synchronization](references/commands-and-sync.md): pull/push,
143
144
  conflicts, low-level repair, branches, releases, and history.
144
145
  - [CLI development](references/cli-development.md): implementation source of
@@ -83,7 +83,7 @@ wrappers.
83
83
  The marker near the top of `SKILL.md` must exactly match the package version:
84
84
 
85
85
  ```html
86
- <!-- reviewed-through-cli: 0.31.15 -->
86
+ <!-- reviewed-through-cli: 0.32.0 -->
87
87
  ```
88
88
 
89
89
  The quoted version above is checked too, so this instruction cannot go stale
@@ -7,6 +7,7 @@ runtime ownership, evaluation, and migrations.
7
7
 
8
8
  - Inline bodies and snippets
9
9
  - Nullability and ownership
10
+ - Numeric builtins
10
11
  - Logical operators
11
12
  - Loops and transfer
12
13
  - Switch
@@ -56,6 +57,11 @@ or narrowing guard before the next access.
56
57
 
57
58
  Treat lookup-return values with the same nullable narrowing rules.
58
59
 
60
+ Runtime declaration patterns follow the possible runtime type, not only the
61
+ static declaration. An open Class value may match an otherwise unrelated
62
+ interface through a derived Class that implements it; a sealed Class with no
63
+ such relationship remains a compile-time impossible pattern.
64
+
59
65
  A declaration pattern introduced by `is` stays available after a guard when
60
66
  every path that falls through matched the pattern, including when the guard's
61
67
  other paths return from a nested block:
@@ -76,6 +82,27 @@ Save, Session, Setter, or otherwise writable. A constructor writes only its
76
82
  `this` instance. A writable structured-leaf field still inherits the receiver's
77
83
  effective storage restrictions.
78
84
 
85
+ ## Numeric builtins
86
+
87
+ Use the `Math` namespace for numeric utilities with C# names and C# semantics:
88
+ `Min`, `Max`, `Clamp`, `Round`, `Floor`, `Ceiling`, `Truncate`, `Abs`, `Sign`,
89
+ `Sqrt`, and the `PI` and `E` constants.
90
+
91
+ ```neo
92
+ return Math.Clamp(this.Quality + bonus, 0, 10);
93
+ ```
94
+
95
+ Each result follows the arithmetic join of its argument types: all-`int` gives
96
+ `int`, any `float` gives `float`, any `decimal` gives `decimal`, and `decimal`
97
+ never mixes with `float`. `Round`, `Floor`, `Ceiling`, and `Truncate` are the
98
+ language's `float`-to-`int` conversions: they return `int` for `int` and
99
+ `float` input, keep a `decimal` a `decimal`, and reject a non-finite argument
100
+ at runtime. `Round` rounds midpoints to even, matching `decimal.Round(0)`.
101
+ `Sign` always returns `int`; `Sqrt` takes `int` or `float` only.
102
+
103
+ `Math` is a qualifier, never a type or a value, and the name is reserved: no
104
+ Class, enum, member, parameter, or local may be named `Math`.
105
+
79
106
  ## Logical operators
80
107
 
81
108
  Use `&&` and `||` with C# precedence: `&&` binds tighter than `||`, and