@neocompose/cli 0.31.17 → 0.32.1

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,24 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.32.1] - 2026-08-18
4
+
5
+ ### Fixed
6
+
7
+ - Lower typed NeoScript setters through the same shared accessor parser used
8
+ by source-body compilation, so `neo test`, animation validation, and
9
+ initializer replay retain property writability in prospective candidates.
10
+
11
+ ## [0.32.0] - 2026-08-18
12
+
13
+ ### Added
14
+
15
+ - P69 Math builtins: call `Math.Min`, `Max`, `Clamp`, `Round`, `Floor`,
16
+ `Ceiling`, `Truncate`, `Abs`, `Sign`, and `Sqrt`, and read the `Math.PI` and
17
+ `Math.E` constants, from any NeoScript body. Results are inferred from the
18
+ argument types under the arithmetic join rule, `Round`/`Floor`/`Ceiling`/
19
+ `Truncate` return `int` on `int` and `float` input, and `Math` is now a
20
+ reserved name.
21
+
3
22
  ## [0.31.17] - 2026-08-18
4
23
 
5
24
  ### 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
  ]);
@@ -8867,6 +8978,30 @@ function numericResult(left, right) {
8867
8978
  }
8868
8979
  return { kind: "primitive", name: "int" };
8869
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
+ }
8870
9005
  function normalizedReturnType(type, kind) {
8871
9006
  if (kind === "action" || kind === "setter" || kind === "constructor") {
8872
9007
  return { kind: "primitive", name: "null" };
@@ -9209,7 +9344,7 @@ function switchValueKey(value) {
9209
9344
  const enumId = value.typeInfo.type === 8 /* Enum */ ? value.typeInfo.enumId : null;
9210
9345
  return JSON.stringify([value.typeInfo.type, enumId, value.value]);
9211
9346
  }
9212
- 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;
9213
9348
  var init_strict_resolver = __esm({
9214
9349
  "../packages/neoscript-language/src/strict-resolver.ts"() {
9215
9350
  "use strict";
@@ -9233,6 +9368,23 @@ var init_strict_resolver = __esm({
9233
9368
  );
9234
9369
  VARIANT_INITIALIZE_NAME = "Initialize";
9235
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
+ ]);
9236
9388
  WRITE_TARGET_DESCRIPTIONS = {
9237
9389
  this: "`this`",
9238
9390
  local: "a local",
@@ -10230,6 +10382,12 @@ var init_strict_resolver = __esm({
10230
10382
  };
10231
10383
  }
10232
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
+ }
10233
10391
  if (!scope.lookup(name)) return;
10234
10392
  throw new CompileError(
10235
10393
  `Cannot declare local '${name}' because that name is already used in an enclosing or current scope.`,
@@ -11447,6 +11605,12 @@ var init_strict_resolver = __esm({
11447
11605
  false
11448
11606
  );
11449
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
+ }
11450
11614
  const type = this.project.typeByName.get(name);
11451
11615
  if (type?.kind === "enum") {
11452
11616
  return {
@@ -11844,6 +12008,15 @@ var init_strict_resolver = __esm({
11844
12008
  { ...NEOSCRIPT_SPRITE_INFO_EMPTY_VALUE }
11845
12009
  );
11846
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
+ }
11847
12020
  const staticType = this.project.typeByName.get(receiverAst.name);
11848
12021
  if (staticType && staticType.kind !== "enum") {
11849
12022
  if (optional) {
@@ -12365,6 +12538,114 @@ var init_strict_resolver = __esm({
12365
12538
  );
12366
12539
  }
12367
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
+ }
12368
12649
  resolveCall(callee, argumentsList2, scope, pos) {
12369
12650
  const variantCall = this.resolveVariantCall(
12370
12651
  callee,
@@ -12458,6 +12739,15 @@ var init_strict_resolver = __esm({
12458
12739
  );
12459
12740
  }
12460
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
+ }
12461
12751
  const staticType = this.project.typeByName.get(callee.receiver.name);
12462
12752
  if (staticType && staticType.kind !== "enum") {
12463
12753
  if (callee.optional) {
@@ -14318,6 +14608,12 @@ var init_strict_resolver = __esm({
14318
14608
  resolved = { kind: "primitive", name: type.name };
14319
14609
  break;
14320
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
+ }
14321
14617
  const builtin = namedPrimitive(type.name);
14322
14618
  if (builtin) {
14323
14619
  resolved = { kind: "primitive", name: builtin };
@@ -23761,6 +24057,74 @@ var init_project_source_semantics = __esm({
23761
24057
  }
23762
24058
  });
23763
24059
 
24060
+ // ../packages/neoscript-language/src/project-source-accessors.ts
24061
+ function findProjectSourceAccessorBody(source, keyword, containingRange) {
24062
+ if (source.length === 0) return null;
24063
+ const full = new SourceText(source);
24064
+ const range2 = containingRange ?? full.fullRange();
24065
+ const bodyStart = full.offsetAt(range2.start);
24066
+ const bodyEnd = full.offsetAt(range2.end);
24067
+ const body = source.slice(bodyStart, bodyEnd);
24068
+ const local = new SourceText(body);
24069
+ const tokens = lex2(body);
24070
+ let depth = 0;
24071
+ for (let index = 0; index < tokens.length; index++) {
24072
+ const token = tokens[index];
24073
+ if (token.text === "{") {
24074
+ depth++;
24075
+ continue;
24076
+ }
24077
+ if (token.text === "}") {
24078
+ depth--;
24079
+ continue;
24080
+ }
24081
+ if (depth !== 1 || token.text !== keyword) continue;
24082
+ let openIndex = index + 1;
24083
+ if (tokens[openIndex]?.text === "(") {
24084
+ let parameterDepth = 1;
24085
+ for (let cursor = openIndex + 1; cursor < tokens.length; cursor++) {
24086
+ const candidate = tokens[cursor];
24087
+ if (candidate.text === "(") parameterDepth++;
24088
+ if (candidate.text === ")") parameterDepth--;
24089
+ if (parameterDepth !== 0) continue;
24090
+ openIndex = cursor + 1;
24091
+ break;
24092
+ }
24093
+ if (parameterDepth !== 0) return null;
24094
+ }
24095
+ const open = tokens[openIndex];
24096
+ if (open?.text !== "{") return null;
24097
+ let accessorDepth = 1;
24098
+ for (let cursor = openIndex + 1; cursor < tokens.length; cursor++) {
24099
+ const candidate = tokens[cursor];
24100
+ if (candidate.text === "{") accessorDepth++;
24101
+ if (candidate.text === "}") accessorDepth--;
24102
+ if (accessorDepth !== 0) continue;
24103
+ const start = bodyStart + sourceOffset2(local, open.pos) + 1;
24104
+ const end = bodyStart + sourceOffset2(local, candidate.pos);
24105
+ return {
24106
+ range: full.range(start, end),
24107
+ source: source.slice(start, end)
24108
+ };
24109
+ }
24110
+ return null;
24111
+ }
24112
+ return null;
24113
+ }
24114
+ function sourceOffset2(source, position) {
24115
+ return source.offsetAt({
24116
+ line: Math.max(0, position.line - 1),
24117
+ character: Math.max(0, position.column - 1)
24118
+ });
24119
+ }
24120
+ var init_project_source_accessors = __esm({
24121
+ "../packages/neoscript-language/src/project-source-accessors.ts"() {
24122
+ "use strict";
24123
+ init_source_text();
24124
+ init_strict_lexer();
24125
+ }
24126
+ });
24127
+
23764
24128
  // ../packages/neoscript-language/src/project-source-script-compiler.ts
23765
24129
  function isPrimitiveName(name) {
23766
24130
  return PRIMITIVE_NAMES.has(name);
@@ -23902,11 +24266,11 @@ function compileProjectSourceBodies(documents) {
23902
24266
  }
23903
24267
  if (member.kind !== "property") continue;
23904
24268
  for (const unit of ["getter", "setter"]) {
23905
- const range2 = accessorBodyRange(
24269
+ const range2 = findProjectSourceAccessorBody(
23906
24270
  document.sourceText,
23907
- member.body.range,
23908
- unit === "getter" ? "get" : "set"
23909
- );
24271
+ unit === "getter" ? "get" : "set",
24272
+ member.body.range
24273
+ )?.range;
23910
24274
  if (!range2) continue;
23911
24275
  compileBody({
23912
24276
  uri,
@@ -24264,7 +24628,7 @@ function sourceMemberSymbol(member, owner, typeIds, source) {
24264
24628
  };
24265
24629
  }
24266
24630
  if (member.kind === "property") {
24267
- const hasSetter = accessorBodyRange(source, member.body.range, "set") !== null || /\bset\s*;/.test(member.body.text);
24631
+ const hasSetter = findProjectSourceAccessorBody(source, "set", member.body.range) !== null || /\bset\s*;/.test(member.body.text);
24268
24632
  return {
24269
24633
  ...common,
24270
24634
  kind: "property",
@@ -24605,61 +24969,6 @@ function memberWritability(annotations) {
24605
24969
  if (/\.Session\b/.test(allowed ?? "")) return "session";
24606
24970
  return "runtime";
24607
24971
  }
24608
- function accessorBodyRange(source, bodyRange, keyword) {
24609
- if (source.length === 0) return null;
24610
- const full = new SourceText(source);
24611
- const bodyStart = full.offsetAt(bodyRange.start);
24612
- const bodyEnd = full.offsetAt(bodyRange.end);
24613
- const body = source.slice(bodyStart, bodyEnd);
24614
- const local = new SourceText(body);
24615
- const tokens = lex2(body);
24616
- let depth = 0;
24617
- for (let index = 0; index < tokens.length; index++) {
24618
- const token = tokens[index];
24619
- if (token.text === "{") {
24620
- depth++;
24621
- continue;
24622
- }
24623
- if (token.text === "}") {
24624
- depth--;
24625
- continue;
24626
- }
24627
- if (depth !== 1 || token.text !== keyword) continue;
24628
- let openIndex = index + 1;
24629
- if (tokens[openIndex]?.text === "(") {
24630
- let parameterDepth = 1;
24631
- for (let cursor = openIndex + 1; cursor < tokens.length; cursor++) {
24632
- const candidate = tokens[cursor];
24633
- if (candidate.text === "(") parameterDepth++;
24634
- if (candidate.text === ")") parameterDepth--;
24635
- if (parameterDepth !== 0) continue;
24636
- openIndex = cursor + 1;
24637
- break;
24638
- }
24639
- if (parameterDepth !== 0) return null;
24640
- }
24641
- const open = tokens[openIndex];
24642
- if (open?.text !== "{") return null;
24643
- let accessorDepth = 1;
24644
- for (let cursor = openIndex + 1; cursor < tokens.length; cursor++) {
24645
- const candidate = tokens[cursor];
24646
- if (candidate.text === "{") accessorDepth++;
24647
- if (candidate.text === "}") accessorDepth--;
24648
- if (accessorDepth !== 0) continue;
24649
- const start = bodyStart + sourceOffset2(local, open.pos) + 1;
24650
- const end = bodyStart + sourceOffset2(local, candidate.pos);
24651
- return full.range(start, end);
24652
- }
24653
- return null;
24654
- }
24655
- return null;
24656
- }
24657
- function sourceOffset2(source, position) {
24658
- return source.offsetAt({
24659
- line: Math.max(0, position.line - 1),
24660
- character: Math.max(0, position.column - 1)
24661
- });
24662
- }
24663
24972
  function innerBodyRange(source, range2) {
24664
24973
  const text = new SourceText(source);
24665
24974
  const start = text.offsetAt(range2.start);
@@ -24728,11 +25037,11 @@ var init_project_source_script_compiler = __esm({
24728
25037
  init_declared_constructors();
24729
25038
  init_language_spec();
24730
25039
  init_project_source_ast();
25040
+ init_project_source_accessors();
24731
25041
  init_project();
24732
25042
  init_project_root();
24733
25043
  init_project_source_registry();
24734
25044
  init_source_text();
24735
- init_strict_lexer();
24736
25045
  PRIMITIVE_NAME_LIST = [
24737
25046
  ...NEOSCRIPT_PRIMITIVE_TYPES,
24738
25047
  "void",
@@ -26195,7 +26504,7 @@ var init_project_source_type_checker = __esm({
26195
26504
  ]);
26196
26505
  BUILTIN_TYPES = /* @__PURE__ */ new Set([
26197
26506
  ...NEOSCRIPT_PRIMITIVE_TYPES,
26198
- ...NEOSCRIPT_BUILTIN_TYPES,
26507
+ ...NEOSCRIPT_BUILTIN_ANNOTATION_TYPES,
26199
26508
  "void",
26200
26509
  "null",
26201
26510
  "Project",
@@ -26213,7 +26522,10 @@ var init_project_source_type_checker = __esm({
26213
26522
  "NeoAudioClip",
26214
26523
  "ProjectRelation"
26215
26524
  ]);
26216
- RESERVED_NAMES2 = new Set(NEOSCRIPT_KEYWORDS);
26525
+ RESERVED_NAMES2 = /* @__PURE__ */ new Set([
26526
+ ...NEOSCRIPT_KEYWORDS,
26527
+ ...NEOSCRIPT_BUILTIN_NAMESPACES
26528
+ ]);
26217
26529
  }
26218
26530
  });
26219
26531
 
@@ -27116,7 +27428,7 @@ var init_project_source_analysis = __esm({
27116
27428
  init_project_source_parameter_defaults();
27117
27429
  BUILTIN_TYPE_NAMES = /* @__PURE__ */ new Set([
27118
27430
  ...NEOSCRIPT_PRIMITIVE_TYPES,
27119
- ...NEOSCRIPT_BUILTIN_TYPES,
27431
+ ...NEOSCRIPT_BUILTIN_ANNOTATION_TYPES,
27120
27432
  "void",
27121
27433
  "null",
27122
27434
  "Project",
@@ -27134,7 +27446,10 @@ var init_project_source_analysis = __esm({
27134
27446
  "NeoAudioClip",
27135
27447
  "ProjectRelation"
27136
27448
  ]);
27137
- RESERVED_NAMES3 = new Set(NEOSCRIPT_KEYWORDS);
27449
+ RESERVED_NAMES3 = /* @__PURE__ */ new Set([
27450
+ ...NEOSCRIPT_KEYWORDS,
27451
+ ...NEOSCRIPT_BUILTIN_NAMESPACES
27452
+ ]);
27138
27453
  SYSTEM_RECORD_ID_PREFIX = "system_";
27139
27454
  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;
27140
27455
  QUARANTINED_LEGACY_SYSTEM_ID_PREFIXES = [
@@ -27634,7 +27949,7 @@ function projectFallbackCompletions(analysis, document, position) {
27634
27949
  }
27635
27950
  for (const typeName of [
27636
27951
  ...NEOSCRIPT_PRIMITIVE_TYPES,
27637
- ...NEOSCRIPT_BUILTIN_TYPES
27952
+ ...NEOSCRIPT_BUILTIN_ANNOTATION_TYPES
27638
27953
  ]) {
27639
27954
  if (expression) continue;
27640
27955
  items.set(typeName, {
@@ -33405,6 +33720,7 @@ var init_src = __esm({
33405
33720
  init_project_source_ast();
33406
33721
  init_project_source_ignore();
33407
33722
  init_project_source_analysis();
33723
+ init_project_source_accessors();
33408
33724
  init_project_source_construction_diagnostics();
33409
33725
  init_project_source_construction_quick_fixes();
33410
33726
  init_project_source_manifest();
@@ -41565,6 +41881,18 @@ function isNSFunctionDecimalOp(value) {
41565
41881
  const digits = info.digitsPointer;
41566
41882
  return digits === void 0 || isNSPointer(digits);
41567
41883
  }
41884
+ function isNSFunctionMathOp(value) {
41885
+ const v = value;
41886
+ if (v?.type !== "mathOp" /* mathOp */) return false;
41887
+ const info = v.info;
41888
+ if (typeof info !== "object" || info === null) return false;
41889
+ if (!NS_MATH_OPS.includes(info.op)) return false;
41890
+ const args = info.argPointers;
41891
+ if (!Array.isArray(args)) return false;
41892
+ if (!args.every(isNSPointer)) return false;
41893
+ const decimal2 = info.decimal;
41894
+ return decimal2 === void 0 || decimal2 === true;
41895
+ }
41568
41896
  function isNSFunctionCount(value) {
41569
41897
  const v = value;
41570
41898
  if (v?.type !== "count" /* count */) return false;
@@ -41612,7 +41940,7 @@ function isNSFunction(value) {
41612
41940
  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
41613
41941
  // compiled getter using ToLower()/StartsWith() would fail pointer
41614
41942
  // validation anywhere isNSFunction gates. Fixed alongside decimalOp.
41615
- isNSFunctionStringOp(value) || isNSFunctionDecimalOp(value) || isNSFunctionCount(value) || isNSFunctionVisitCount(value) || isNSFunctionHasVisited(value) || isNSFunctionVectorConstructor(value) || isNSFunctionImageSlice(value) || isNSFunctionVariantInitialize(value) || isNSFunctionVariantApply(value) || isNSFunctionDeclaredConstructor(value);
41943
+ isNSFunctionStringOp(value) || isNSFunctionDecimalOp(value) || isNSFunctionMathOp(value) || isNSFunctionCount(value) || isNSFunctionVisitCount(value) || isNSFunctionHasVisited(value) || isNSFunctionVectorConstructor(value) || isNSFunctionImageSlice(value) || isNSFunctionVariantInitialize(value) || isNSFunctionVariantApply(value) || isNSFunctionDeclaredConstructor(value);
41616
41944
  }
41617
41945
  function isNSInstructionVariable(value) {
41618
41946
  const v = value;
@@ -41903,7 +42231,7 @@ function isNSVoidBody(value) {
41903
42231
  if (value.typeInfo.type !== 0 /* Null */) return false;
41904
42232
  return value.typeInfo.required === true;
41905
42233
  }
41906
- var NS_STRING_OPS, NS_DECIMAL_OPS;
42234
+ var NS_STRING_OPS, NS_DECIMAL_OPS, NS_MATH_OPS;
41907
42235
  var init_neoscript_guards = __esm({
41908
42236
  "../src/models/neoscript/neoscript-guards.ts"() {
41909
42237
  "use strict";
@@ -41924,6 +42252,18 @@ var init_neoscript_guards = __esm({
41924
42252
  "toFloat",
41925
42253
  "toDecimal"
41926
42254
  ];
42255
+ NS_MATH_OPS = [
42256
+ "min",
42257
+ "max",
42258
+ "clamp",
42259
+ "round",
42260
+ "floor",
42261
+ "ceiling",
42262
+ "truncate",
42263
+ "abs",
42264
+ "sign",
42265
+ "sqrt"
42266
+ ];
41927
42267
  }
41928
42268
  });
41929
42269
 
@@ -58667,40 +59007,15 @@ function memberModifier3(modifiers, baseModifier) {
58667
59007
  return "plain";
58668
59008
  }
58669
59009
  function propertyAccessors(body) {
59010
+ const accessorBody = (keyword) => {
59011
+ const accessor = findProjectSourceAccessorBody(body, keyword);
59012
+ return accessor === null ? null : normalizeBodySource(accessor.source);
59013
+ };
58670
59014
  return {
58671
- get: accessorBody(body, "get"),
58672
- set: accessorBody(body, "set")
59015
+ get: accessorBody("get"),
59016
+ set: accessorBody("set")
58673
59017
  };
58674
59018
  }
58675
- function accessorBody(source, keyword) {
58676
- const match = new RegExp(`\\b${keyword}\\b\\s*\\{`).exec(source);
58677
- if (!match) return null;
58678
- const open = source.indexOf("{", match.index);
58679
- let depth = 0;
58680
- let quote6 = null;
58681
- let escaped = false;
58682
- for (let index = open; index < source.length; index += 1) {
58683
- const character = source[index];
58684
- if (quote6) {
58685
- if (escaped) escaped = false;
58686
- else if (character === "\\") escaped = true;
58687
- else if (character === quote6) quote6 = null;
58688
- continue;
58689
- }
58690
- if (character === '"' || character === "'") {
58691
- quote6 = character;
58692
- continue;
58693
- }
58694
- if (character === "{") depth += 1;
58695
- else if (character === "}") {
58696
- depth -= 1;
58697
- if (depth === 0) {
58698
- return normalizeBodySource(source.slice(open + 1, index));
58699
- }
58700
- }
58701
- }
58702
- throw new Error(`Unclosed ${keyword} accessor.`);
58703
- }
58704
59019
  function innerBody2(body) {
58705
59020
  const start = body.indexOf("{");
58706
59021
  const end = body.lastIndexOf("}");
@@ -63087,6 +63402,25 @@ function compareDecimalStrings(a, b) {
63087
63402
  if (aligned.a > aligned.b) return 1;
63088
63403
  return 0;
63089
63404
  }
63405
+ function minDecimalStrings(a, b) {
63406
+ return compareDecimalStrings(a, b) <= 0 ? a : b;
63407
+ }
63408
+ function maxDecimalStrings(a, b) {
63409
+ return compareDecimalStrings(a, b) >= 0 ? a : b;
63410
+ }
63411
+ function clampDecimalStrings(value, min, max) {
63412
+ if (compareDecimalStrings(min, max) > 0) {
63413
+ throw new DecimalClampRangeError(MATH_CLAMP_RANGE_MESSAGE);
63414
+ }
63415
+ if (compareDecimalStrings(value, min) < 0) return min;
63416
+ if (compareDecimalStrings(value, max) > 0) return max;
63417
+ return value;
63418
+ }
63419
+ function absDecimalString(value) {
63420
+ const parts = parseDecimalArg(value, "value");
63421
+ const magnitude = parts.coefficient < 0n ? -parts.coefficient : parts.coefficient;
63422
+ return formatDecimalParts({ coefficient: magnitude, scale: parts.scale });
63423
+ }
63090
63424
  function addDecimalStrings(a, b) {
63091
63425
  const aligned = alignScales(parseDecimalArg(a, "a"), parseDecimalArg(b, "b"));
63092
63426
  return formatDecimalParts(
@@ -63149,6 +63483,32 @@ function roundDecimalString(value, digits) {
63149
63483
  const rounded = roundParts(parseDecimalArg(value, "value"), digits);
63150
63484
  return formatDecimalParts(assertWithinEnvelope(rounded, "round"));
63151
63485
  }
63486
+ function toIntegralParts(parts, direction) {
63487
+ if (parts.scale === 0) return parts;
63488
+ const divisor = pow10(parts.scale);
63489
+ const quotient = parts.coefficient / divisor;
63490
+ const remainder = parts.coefficient % divisor;
63491
+ if (remainder === 0n) return { coefficient: quotient, scale: 0 };
63492
+ if (direction === "floor" && remainder < 0n) {
63493
+ return { coefficient: quotient - 1n, scale: 0 };
63494
+ }
63495
+ if (direction === "ceiling" && remainder > 0n) {
63496
+ return { coefficient: quotient + 1n, scale: 0 };
63497
+ }
63498
+ return { coefficient: quotient, scale: 0 };
63499
+ }
63500
+ function floorDecimalString(value) {
63501
+ const parts = toIntegralParts(parseDecimalArg(value, "value"), "floor");
63502
+ return formatDecimalParts(assertWithinEnvelope(parts, "floor"));
63503
+ }
63504
+ function ceilingDecimalString(value) {
63505
+ const parts = toIntegralParts(parseDecimalArg(value, "value"), "ceiling");
63506
+ return formatDecimalParts(assertWithinEnvelope(parts, "ceiling"));
63507
+ }
63508
+ function truncateDecimalString(value) {
63509
+ const parts = toIntegralParts(parseDecimalArg(value, "value"), "truncate");
63510
+ return formatDecimalParts(assertWithinEnvelope(parts, "truncate"));
63511
+ }
63152
63512
  function divideDecimalStrings(a, b, digits) {
63153
63513
  assertDigitsInRange(digits, "divide");
63154
63514
  const dividend = parseDecimalArg(a, "a");
@@ -63229,7 +63589,7 @@ function floatFromDecimalString(value) {
63229
63589
  assertCanonicalInput(value, "value");
63230
63590
  return Number(value);
63231
63591
  }
63232
- var DecimalOverflowError, DecimalDivisionByZeroError, DecimalDigitsRangeError, DecimalNonFiniteError, TEN;
63592
+ var DecimalOverflowError, DecimalDivisionByZeroError, DecimalDigitsRangeError, DecimalNonFiniteError, DecimalClampRangeError, MATH_CLAMP_RANGE_MESSAGE, TEN;
63233
63593
  var init_decimal_math = __esm({
63234
63594
  "../src/models/decimal/decimal-math.ts"() {
63235
63595
  "use strict";
@@ -63242,6 +63602,9 @@ var init_decimal_math = __esm({
63242
63602
  };
63243
63603
  DecimalNonFiniteError = class extends Error {
63244
63604
  };
63605
+ DecimalClampRangeError = class extends Error {
63606
+ };
63607
+ MATH_CLAMP_RANGE_MESSAGE = "Math.Clamp requires min <= max.";
63245
63608
  TEN = 10n;
63246
63609
  }
63247
63610
  });
@@ -68130,7 +68493,7 @@ function coerceDecimalOperand(value, context) {
68130
68493
  );
68131
68494
  }
68132
68495
  function rethrowDecimalError(error) {
68133
- if (error instanceof DecimalOverflowError || error instanceof DecimalDivisionByZeroError || error instanceof DecimalDigitsRangeError || error instanceof DecimalNonFiniteError) {
68496
+ if (error instanceof DecimalOverflowError || error instanceof DecimalDivisionByZeroError || error instanceof DecimalDigitsRangeError || error instanceof DecimalNonFiniteError || error instanceof DecimalClampRangeError) {
68134
68497
  throw new NSGetterRuntimeError(error.message);
68135
68498
  }
68136
68499
  throw error;
@@ -68157,6 +68520,163 @@ function applyDecimalArithmetic(op, operands) {
68157
68520
  rethrowDecimalError(error);
68158
68521
  }
68159
68522
  }
68523
+ function mathArgPointer(info, index, fn) {
68524
+ const pointer = info.argPointers[index];
68525
+ if (pointer === void 0) {
68526
+ throw new NSGetterRuntimeError(
68527
+ `Math.${fn} is missing argument ${index + 1}.`
68528
+ );
68529
+ }
68530
+ return pointer;
68531
+ }
68532
+ function mathNumberArg(value, fn) {
68533
+ if (value === null || value === void 0) {
68534
+ throw new NSGetterRuntimeError(`Math.${fn} argument is null.`);
68535
+ }
68536
+ if (typeof value !== "number") {
68537
+ throw new NSGetterRuntimeError(
68538
+ `Math.${fn} argument is not numeric: ${typeof value}.`
68539
+ );
68540
+ }
68541
+ return value;
68542
+ }
68543
+ function mathDecimalArg(value, fn) {
68544
+ if (value === null || value === void 0) {
68545
+ throw new NSGetterRuntimeError(`Math.${fn} argument is null.`);
68546
+ }
68547
+ if (typeof value !== "string" && typeof value !== "number") {
68548
+ throw new NSGetterRuntimeError(
68549
+ `Math.${fn} argument is not numeric: ${typeof value}.`
68550
+ );
68551
+ }
68552
+ return coerceDecimalOperand(value, `Math.${fn}`);
68553
+ }
68554
+ function requireFiniteMathArg(value, fn) {
68555
+ if (!Number.isFinite(value)) {
68556
+ throw new NSGetterRuntimeError(`Math.${fn} requires a finite argument.`);
68557
+ }
68558
+ return value;
68559
+ }
68560
+ function roundHalfEven(value) {
68561
+ if (Math.abs(value) >= DOUBLE_INTEGRAL_MAGNITUDE) return value;
68562
+ const integral = Math.floor(value);
68563
+ const fraction = value - integral;
68564
+ if (fraction > 0.5) return integral + 1;
68565
+ if (fraction < 0.5) return integral;
68566
+ if (integral % 2 === 0) return integral;
68567
+ return integral + 1;
68568
+ }
68569
+ function evalMathOp(info, scope, ctx) {
68570
+ const fn = NS_MATH_OP_NAMES[info.op];
68571
+ if (info.decimal === true) return evalDecimalMathOp(info, fn, scope, ctx);
68572
+ const value = mathNumberArg(
68573
+ evalPointer(mathArgPointer(info, 0, fn), scope, ctx),
68574
+ fn
68575
+ );
68576
+ switch (info.op) {
68577
+ case "min":
68578
+ return Math.min(
68579
+ value,
68580
+ mathNumberArg(evalPointer(mathArgPointer(info, 1, fn), scope, ctx), fn)
68581
+ );
68582
+ case "max":
68583
+ return Math.max(
68584
+ value,
68585
+ mathNumberArg(evalPointer(mathArgPointer(info, 1, fn), scope, ctx), fn)
68586
+ );
68587
+ case "clamp": {
68588
+ const min = mathNumberArg(
68589
+ evalPointer(mathArgPointer(info, 1, fn), scope, ctx),
68590
+ fn
68591
+ );
68592
+ const max = mathNumberArg(
68593
+ evalPointer(mathArgPointer(info, 2, fn), scope, ctx),
68594
+ fn
68595
+ );
68596
+ if (min > max) {
68597
+ throw new NSGetterRuntimeError(MATH_CLAMP_RANGE_MESSAGE);
68598
+ }
68599
+ if (value < min) return min;
68600
+ if (value > max) return max;
68601
+ return value;
68602
+ }
68603
+ case "round":
68604
+ return roundHalfEven(requireFiniteMathArg(value, fn));
68605
+ case "floor":
68606
+ return Math.floor(requireFiniteMathArg(value, fn));
68607
+ case "ceiling":
68608
+ return Math.ceil(requireFiniteMathArg(value, fn));
68609
+ case "truncate":
68610
+ return Math.trunc(requireFiniteMathArg(value, fn));
68611
+ case "abs":
68612
+ return Math.abs(value);
68613
+ case "sign":
68614
+ if (Number.isNaN(value)) {
68615
+ throw new NSGetterRuntimeError("Math.Sign is undefined for NaN.");
68616
+ }
68617
+ if (value > 0) return 1;
68618
+ if (value < 0) return -1;
68619
+ return 0;
68620
+ case "sqrt":
68621
+ return Math.sqrt(value);
68622
+ }
68623
+ }
68624
+ function evalDecimalMathOp(info, fn, scope, ctx) {
68625
+ const value = mathDecimalArg(
68626
+ evalPointer(mathArgPointer(info, 0, fn), scope, ctx),
68627
+ fn
68628
+ );
68629
+ try {
68630
+ switch (info.op) {
68631
+ case "min":
68632
+ return minDecimalStrings(
68633
+ value,
68634
+ mathDecimalArg(
68635
+ evalPointer(mathArgPointer(info, 1, fn), scope, ctx),
68636
+ fn
68637
+ )
68638
+ );
68639
+ case "max":
68640
+ return maxDecimalStrings(
68641
+ value,
68642
+ mathDecimalArg(
68643
+ evalPointer(mathArgPointer(info, 1, fn), scope, ctx),
68644
+ fn
68645
+ )
68646
+ );
68647
+ case "clamp":
68648
+ return clampDecimalStrings(
68649
+ value,
68650
+ mathDecimalArg(
68651
+ evalPointer(mathArgPointer(info, 1, fn), scope, ctx),
68652
+ fn
68653
+ ),
68654
+ mathDecimalArg(
68655
+ evalPointer(mathArgPointer(info, 2, fn), scope, ctx),
68656
+ fn
68657
+ )
68658
+ );
68659
+ case "round":
68660
+ return roundDecimalString(value, 0);
68661
+ case "floor":
68662
+ return floorDecimalString(value);
68663
+ case "ceiling":
68664
+ return ceilingDecimalString(value);
68665
+ case "truncate":
68666
+ return truncateDecimalString(value);
68667
+ case "abs":
68668
+ return absDecimalString(value);
68669
+ case "sign":
68670
+ return compareDecimalStrings(value, "0");
68671
+ case "sqrt":
68672
+ throw new NSGetterRuntimeError(
68673
+ "Math.Sqrt does not accept decimal arguments."
68674
+ );
68675
+ }
68676
+ } catch (error) {
68677
+ rethrowDecimalError(error);
68678
+ }
68679
+ }
68160
68680
  function stringifyForInterp(v) {
68161
68681
  if (v === null || v === void 0) return "";
68162
68682
  if (typeof v === "string") return v;
@@ -68676,6 +69196,8 @@ function evalFunction(fn, scope, ctx) {
68676
69196
  }
68677
69197
  break;
68678
69198
  }
69199
+ case "mathOp" /* mathOp */:
69200
+ return evalMathOp(fn.info, scope, ctx);
68679
69201
  case "stringOp" /* stringOp */: {
68680
69202
  const receiver = evalPointer(fn.info.receiverPointer, scope, ctx);
68681
69203
  if (typeof receiver !== "string") {
@@ -71888,7 +72410,7 @@ function iterateCollection(c, ctx, callback) {
71888
72410
  return callback(entry, key, valueId);
71889
72411
  });
71890
72412
  }
71891
- 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;
72413
+ 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;
71892
72414
  var init_evaluateNSGetter = __esm({
71893
72415
  "../src/view-models/neoscript-evaluator/evaluateNSGetter.ts"() {
71894
72416
  "use strict";
@@ -72032,6 +72554,19 @@ var init_evaluateNSGetter = __esm({
72032
72554
  };
72033
72555
  READONLY_FOREACH_BINDING_ERROR = "Cannot assign to a read-only foreach iterator binding.";
72034
72556
  READONLY_CATCH_BINDING_ERROR = "Cannot assign to a read-only catch message binding.";
72557
+ NS_MATH_OP_NAMES = {
72558
+ min: "Min",
72559
+ max: "Max",
72560
+ clamp: "Clamp",
72561
+ round: "Round",
72562
+ floor: "Floor",
72563
+ ceiling: "Ceiling",
72564
+ truncate: "Truncate",
72565
+ abs: "Abs",
72566
+ sign: "Sign",
72567
+ sqrt: "Sqrt"
72568
+ };
72569
+ DOUBLE_INTEGRAL_MAGNITUDE = 2 ** 53;
72035
72570
  EMPTY_SUPPLIED_CONSTRUCTOR_FIELDS = [];
72036
72571
  }
72037
72572
  });
@@ -111706,7 +112241,7 @@ var init_registry2 = __esm({
111706
112241
  PROJECT_SCHEMA_CONTRACT = Object.freeze({
111707
112242
  formatVersion: 3,
111708
112243
  contractVersion: "3.13",
111709
- cliVersion: "0.31.17",
112244
+ cliVersion: "0.32.1",
111710
112245
  projectFileUploadBatchSize: 32,
111711
112246
  documentRecords: {
111712
112247
  member: {
@@ -118301,7 +118836,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
118301
118836
  async function main() {
118302
118837
  const args = parseArgs(process.argv.slice(2));
118303
118838
  if (args.command === "--version") {
118304
- console.log("0.31.17");
118839
+ console.log("0.32.1");
118305
118840
  return;
118306
118841
  }
118307
118842
  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.17",
3
+ "version": "0.32.1",
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.17 -->
12
+ <!-- reviewed-through-cli: 0.32.1 -->
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.17 -->
86
+ <!-- reviewed-through-cli: 0.32.1 -->
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
@@ -19,6 +20,10 @@ Keep computed getters, setters, functions, dialogue logic, group logic, and
19
20
  constructors inline in their owning `.neo` or `.neoflow` declaration. Do not
20
21
  create `Scripts/<Class>/<Member>.neo` sidecars.
21
22
 
23
+ Declare a setter's value type explicitly with the canonical form
24
+ `set(int value) { ... }`. Project analysis, candidate materialization, and
25
+ focused script commands all use the same accessor extraction.
26
+
22
27
  Use the shared language service through focused commands:
23
28
 
24
29
  ```sh
@@ -81,6 +86,27 @@ Save, Session, Setter, or otherwise writable. A constructor writes only its
81
86
  `this` instance. A writable structured-leaf field still inherits the receiver's
82
87
  effective storage restrictions.
83
88
 
89
+ ## Numeric builtins
90
+
91
+ Use the `Math` namespace for numeric utilities with C# names and C# semantics:
92
+ `Min`, `Max`, `Clamp`, `Round`, `Floor`, `Ceiling`, `Truncate`, `Abs`, `Sign`,
93
+ `Sqrt`, and the `PI` and `E` constants.
94
+
95
+ ```neo
96
+ return Math.Clamp(this.Quality + bonus, 0, 10);
97
+ ```
98
+
99
+ Each result follows the arithmetic join of its argument types: all-`int` gives
100
+ `int`, any `float` gives `float`, any `decimal` gives `decimal`, and `decimal`
101
+ never mixes with `float`. `Round`, `Floor`, `Ceiling`, and `Truncate` are the
102
+ language's `float`-to-`int` conversions: they return `int` for `int` and
103
+ `float` input, keep a `decimal` a `decimal`, and reject a non-finite argument
104
+ at runtime. `Round` rounds midpoints to even, matching `decimal.Round(0)`.
105
+ `Sign` always returns `int`; `Sqrt` takes `int` or `float` only.
106
+
107
+ `Math` is a qualifier, never a type or a value, and the name is reserved: no
108
+ Class, enum, member, parameter, or local may be named `Math`.
109
+
84
110
  ## Logical operators
85
111
 
86
112
  Use `&&` and `||` with C# precedence: `&&` binds tighter than `||`, and