@neocompose/cli 0.64.0 → 0.65.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,16 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.65.0] - 2026-10-03
4
+
5
+ - Move `Collider` from `NeoObject` to `NeoObjectBase`, so sprite objects and layer groups can carry a box collider too. Unity codegen implements `INeoColliderSource` on `NeoObjectBase`.
6
+ - Segment tracks may target a Class member, such as `NeoObjectBase.Collider`. Each frame plays a whole value or `null`. A segment whose class does not fit the target member reports which class it plays. A segment of nullable values cannot target a required member.
7
+ - Constructor arguments and object initializers typed by a class's generic parameter take the site's type argument. `new NeoAnimationSegmentFrame<NeoCollider?>(value: null)` and `{ Value = null }` now compile.
8
+ - Authoring `null` over a member that held a class instance drops the instance's class and constructor metadata. Previously the row kept them, and push rejected it as an invalid value record.
9
+
10
+ ## [0.64.1] - 2026-10-02
11
+
12
+ - Reserve `OnChanged` as a member name. Every generated Unity class declares the SDK's `OnChanged` change subscription, so a member by that name now reports `reserved-project-name` instead of pushing the SDK method aside as `OnChanged_2`.
13
+
3
14
  ## [0.64.0] - 2026-10-02
4
15
 
5
16
  - Compare Class, interface, List, Set, and Dictionary values by identity in `==`, `!=`, `Contains`, `IndexOf`, and `Remove`. Two values with identical fields are no longer equal; two reads of one stored value still are. Primitives, enums, vectors, and media references still compare by value. Compiler revision 16 recompiles every stored body, so existing scripts switch too. A CLI upgrade now invalidates the local build cache, which previously kept serving IR from the older compiler for unchanged source.
package/dist/neo.mjs CHANGED
@@ -822,7 +822,11 @@ var init_project_source_identifiers = __esm({
822
822
  ...NEOSCRIPT_KEYWORDS,
823
823
  ...NEOSCRIPT_BUILTIN_NAMESPACES
824
824
  ]);
825
- PROJECT_MEMBER_RESERVED_NAMES = new Set(NEOSCRIPT_KEYWORDS);
825
+ PROJECT_MEMBER_RESERVED_NAMES = /* @__PURE__ */ new Set([
826
+ ...NEOSCRIPT_KEYWORDS,
827
+ // Every generated C# class declares the SDK's change subscription.
828
+ "OnChanged"
829
+ ]);
826
830
  PROJECT_BINDING_RESERVED_NAMES = /* @__PURE__ */ new Set([
827
831
  ...PROJECT_RESERVED_NAMES,
828
832
  "this",
@@ -14188,11 +14192,18 @@ function validateExpression(expression, expected, scope, environment, uri, range
14188
14192
  siteArgumentNames,
14189
14193
  siteInitializerNames
14190
14194
  );
14195
+ const siteTypeArguments = expression.typeArguments?.map(semanticAstType) ?? (expression.className === null ? expected?.arguments : void 0) ?? [];
14196
+ const siteBindings = new Map(
14197
+ declaration.genericParameters.flatMap((parameter4, index) => {
14198
+ const argument2 = siteTypeArguments[index];
14199
+ return argument2 ? [[parameter4.name, argument2]] : [];
14200
+ })
14201
+ );
14191
14202
  for (const diagnostic of validateNeoConstructionSite(
14192
14203
  environment.construction,
14193
14204
  {
14194
14205
  className: declaration.name,
14195
- typeArguments: expression.typeArguments?.map(semanticAstType) ?? (expression.className === null ? expected?.arguments : void 0) ?? [],
14206
+ typeArguments: siteTypeArguments,
14196
14207
  argumentNames: variantSite?.argumentNames ?? siteArgumentNames,
14197
14208
  initializerNames: variantSite?.initializerNames ?? siteInitializerNames
14198
14209
  }
@@ -14228,7 +14239,7 @@ function validateExpression(expression, expected, scope, environment, uri, range
14228
14239
  (candidate) => candidate.name === (variantMember ?? named)
14229
14240
  ) : fields[index];
14230
14241
  const expectedType = parameter4?.type ?? field?.type;
14231
- const expectedSemanticType = declaration.name === NEO_LOOKUP_VARIANT_FOLDER_TYPE_NAME && named === "collectionValue" ? { name: "string" } : expectedType ? semanticType(expectedType) : void 0;
14242
+ const expectedSemanticType = declaration.name === NEO_LOOKUP_VARIANT_FOLDER_TYPE_NAME && named === "collectionValue" ? { name: "string" } : expectedType ? substituteSemanticType(semanticType(expectedType), siteBindings) : void 0;
14232
14243
  validateExpression(
14233
14244
  argument2,
14234
14245
  expectedSemanticType,
@@ -14249,8 +14260,11 @@ function validateExpression(expression, expected, scope, environment, uri, range
14249
14260
  validateExpression(
14250
14261
  entry.value,
14251
14262
  field ? substituteSemanticType(
14252
- semanticType(field.member.type),
14253
- field.bindings
14263
+ substituteSemanticType(
14264
+ semanticType(field.member.type),
14265
+ field.bindings
14266
+ ),
14267
+ siteBindings
14254
14268
  ) : void 0,
14255
14269
  scope,
14256
14270
  environment,
@@ -14465,7 +14479,7 @@ function sourceClassMemberByName(declaration, name, environment, initialBindings
14465
14479
  function substituteSemanticType(type, bindings) {
14466
14480
  const bound = bindings.get(type.name);
14467
14481
  if (bound !== void 0 && (type.arguments ?? []).length === 0) {
14468
- return type.nullable === void 0 ? bound : { ...bound, nullable: type.nullable };
14482
+ return type.nullable === true ? { ...bound, nullable: true } : bound;
14469
14483
  }
14470
14484
  const argumentsList2 = (type.arguments ?? []).map(
14471
14485
  (argument2) => substituteSemanticType(argument2, bindings)
@@ -22774,7 +22788,12 @@ var init_strict_resolver = __esm({
22774
22788
  };
22775
22789
  });
22776
22790
  fields.push(
22777
- ...this.resolveCallSiteInitializerFields(schemaClass2, expression, scope)
22791
+ ...this.resolveCallSiteInitializerFields(
22792
+ schemaClass2,
22793
+ targetType,
22794
+ expression,
22795
+ scope
22796
+ )
22778
22797
  );
22779
22798
  const schemaClassInfo = toWireType(targetType, this.project);
22780
22799
  if (schemaClassInfo.type !== 7 /* Class */) {
@@ -22900,6 +22919,7 @@ var init_strict_resolver = __esm({
22900
22919
  ) : [];
22901
22920
  const fields = this.resolveCallSiteInitializerFields(
22902
22921
  schemaClass2,
22922
+ targetType,
22903
22923
  expression,
22904
22924
  scope
22905
22925
  );
@@ -23014,7 +23034,7 @@ var init_strict_resolver = __esm({
23014
23034
  * the class's own schema exactly as a `classConstructor` field is, and
23015
23035
  * applied after the body so an explicit assignment always wins.
23016
23036
  */
23017
- resolveCallSiteInitializerFields(schemaClass2, expression, scope) {
23037
+ resolveCallSiteInitializerFields(schemaClass2, targetType, expression, scope) {
23018
23038
  const entries = expression.initializer ?? [];
23019
23039
  const seen = /* @__PURE__ */ new Set();
23020
23040
  return entries.map((entry) => {
@@ -23059,10 +23079,15 @@ var init_strict_resolver = __esm({
23059
23079
  entry.pos
23060
23080
  );
23061
23081
  }
23062
- const value = this.resolveExpression(entry.value, scope, member.type);
23063
- if (!isNeoScriptTypeAssignable(value.type, member.type, this.project)) {
23082
+ const memberType2 = substituteTypeParameters(
23083
+ member.type,
23084
+ targetType,
23085
+ schemaClass2
23086
+ );
23087
+ const value = this.resolveExpression(entry.value, scope, memberType2);
23088
+ if (!isNeoScriptTypeAssignable(value.type, memberType2, this.project)) {
23064
23089
  throw new CompileError(
23065
- `Object initializer '${member.name}' expected ${this.describe(member.type)} but got ${this.describe(value.type)}.`,
23090
+ `Object initializer '${member.name}' expected ${this.describe(memberType2)} but got ${this.describe(value.type)}.`,
23066
23091
  entry.pos
23067
23092
  );
23068
23093
  }
@@ -29930,7 +29955,7 @@ var init_registry = __esm({
29930
29955
  "../packages/manifest/schema-contract/registry.mjs"() {
29931
29956
  "use strict";
29932
29957
  PROJECT_SCHEMA_CONTRACT = Object.freeze({
29933
- cliVersion: "0.64.0",
29958
+ cliVersion: "0.65.0",
29934
29959
  projectFileUploadBatchSize: 32,
29935
29960
  documentRecords: {
29936
29961
  member: {
@@ -40272,7 +40297,7 @@ var init_neoscript_compiler_revision = __esm({
40272
40297
  "use strict";
40273
40298
  init_strict_ir();
40274
40299
  ACTIVE_NEOSCRIPT_COMPILER_REVISION_ROLLOUT = // <neoscript-compiler-revision-rollout>
40275
- { migrationId: "neoscript-compiler-revision-16", revision: 16 };
40300
+ null;
40276
40301
  }
40277
40302
  });
40278
40303
 
@@ -43298,6 +43323,9 @@ function createMergeMemo() {
43298
43323
  function resolveInheritanceChain(classId, classes, draft) {
43299
43324
  return [...indexClasses(classes).inheritanceChain(classId, draft)];
43300
43325
  }
43326
+ function resolveInheritanceChainFromIndex(classId, classIndex, draft) {
43327
+ return resolveChainFromIndex(classId, classIndex, draft);
43328
+ }
43301
43329
  function resolveChainFromIndex(classId, index, draft) {
43302
43330
  const chain = [];
43303
43331
  const visited = /* @__PURE__ */ new Set();
@@ -44370,6 +44398,14 @@ var init_world_system_classes_generated = __esm({
44370
44398
  docsText: "When false, this object and its children are neither rendered nor collided with. The value stays live: member writes still apply and a running animation clip keeps playing. Disabling an object hides its whole subtree regardless of each child's own value, and re-enabling it restores exactly what was there.",
44371
44399
  requirement: 1 /* Required */,
44372
44400
  schemaKey: "Enabled"
44401
+ },
44402
+ {
44403
+ memberId: "system_a0083c92-72f7-405f-8863-ff86f995d36d",
44404
+ memberKind: "class",
44405
+ defaultValue: null,
44406
+ docsText: "The object's box collider, or null for none. Any world object can carry one: placed objects, nested children, and sprites alike.",
44407
+ schemaClassWorldKind: "objectCollider",
44408
+ schemaKey: "Collider"
44373
44409
  }
44374
44410
  ],
44375
44411
  worldKind: "objectBase"
@@ -44579,13 +44615,6 @@ var init_world_system_classes_generated = __esm({
44579
44615
  requirement: 1 /* Required */,
44580
44616
  schemaKey: "Children"
44581
44617
  },
44582
- {
44583
- memberId: "system_a0083c92-72f7-405f-8863-ff86f995d36d",
44584
- memberKind: "class",
44585
- defaultValue: null,
44586
- schemaClassWorldKind: "objectCollider",
44587
- schemaKey: "Collider"
44588
- },
44589
44618
  {
44590
44619
  memberId: "system_c609fed3-53e4-5147-b493-e4aa23031d79",
44591
44620
  memberKind: "computed",
@@ -61096,7 +61125,7 @@ function resolveMemberDeclaredTypeInfoInternal(member, members, seen) {
61096
61125
  members,
61097
61126
  nextSeen
61098
61127
  );
61099
- return argumentType === null ? [] : [[genericParamId, { ...argumentType, required: true }]];
61128
+ return argumentType === null ? [] : [[genericParamId, argumentType]];
61100
61129
  }
61101
61130
  )
61102
61131
  );
@@ -69918,6 +69947,7 @@ function evaluateNSGetterWithEffects(getter, ctx, argumentValues = [], options =
69918
69947
  getter.typeInfo
69919
69948
  );
69920
69949
  result.value = preserveReadOnlyReturnIdentity(result.value, runtimeCtx);
69950
+ options.inspectResult?.(result.value, runtimeCtx);
69921
69951
  const createdSessionValues = exportedSessionValuesSince(
69922
69952
  runtimeCtx,
69923
69953
  creationCheckpoint,
@@ -73502,7 +73532,7 @@ function runtimeTypeCheck(value, checkType, ctx, seenCollections = /* @__PURE__
73502
73532
  function runtimeValueMatchesType(value, typeInfo, ctx, seenCollections = /* @__PURE__ */ new Set()) {
73503
73533
  if (typeInfo.type === NS_TYPE_UNKNOWN) return true;
73504
73534
  if (value === null || value === void 0) {
73505
- return typeInfo.type === 0 /* Null */ || !typeInfo.required;
73535
+ return typeInfo.type === 0 /* Null */ || typeInfo.type === 21 /* Generic */ || !typeInfo.required;
73506
73536
  }
73507
73537
  return runtimeTypeCheck(value, typeInfo, ctx, seenCollections);
73508
73538
  }
@@ -73866,7 +73896,8 @@ function createUnorderedListProjectionCache() {
73866
73896
  byContainerId: /* @__PURE__ */ new Map(),
73867
73897
  containerIdByArray: /* @__PURE__ */ new WeakMap(),
73868
73898
  scalarEntryIds: /* @__PURE__ */ new Set(),
73869
- referenceEntryIndexesByArray: /* @__PURE__ */ new WeakMap()
73899
+ referenceEntryIndexesByArray: /* @__PURE__ */ new WeakMap(),
73900
+ referenceEntryKeysByRecord: /* @__PURE__ */ new WeakMap()
73870
73901
  };
73871
73902
  }
73872
73903
  function createUnorderedScalarProjectionEntry(containerValueId, value, ctx, consumeEntryBudget = true) {
@@ -75399,6 +75430,7 @@ function evalFunction(fn, scope, ctx) {
75399
75430
  const isList = isListCollection(c);
75400
75431
  const out = isList ? [] : {};
75401
75432
  const referenceEntryIndexes = /* @__PURE__ */ new Set();
75433
+ const referenceEntryKeys = /* @__PURE__ */ new Set();
75402
75434
  const callback = prepareCollectionCallback(
75403
75435
  innerFn,
75404
75436
  scope,
@@ -75418,6 +75450,7 @@ function evalFunction(fn, scope, ctx) {
75418
75450
  }
75419
75451
  out.push(valueId ?? entry);
75420
75452
  } else {
75453
+ if (valueId !== null) referenceEntryKeys.add(String(key));
75421
75454
  out[String(key)] = valueId ?? entry;
75422
75455
  }
75423
75456
  return 0 /* Continue */;
@@ -75430,6 +75463,11 @@ function evalFunction(fn, scope, ctx) {
75430
75463
  referenceEntryIndexes
75431
75464
  );
75432
75465
  }
75466
+ if (!isList)
75467
+ unorderedListProjectionCache(ctx).referenceEntryKeysByRecord.set(
75468
+ out,
75469
+ referenceEntryKeys
75470
+ );
75433
75471
  return out;
75434
75472
  }
75435
75473
  case "first" /* first */:
@@ -103103,11 +103141,26 @@ var init_animation_clips = __esm({
103103
103141
  `${args.label} targets "${boundChildName}.${targetEntry.schemaKey}" of kind ${MemberKind[targetMember.kind]}, but its segment plays ${MemberKind[valueBindingMember.kind]} values.`
103104
103142
  );
103105
103143
  }
103106
- if (!this.overrideLeafEligible(
103107
- targetMember,
103144
+ if (isMemberClass(targetMember) && isMemberClass(valueBindingMember) && !resolveInheritanceChainFromIndex(
103145
+ valueBindingMember.classId,
103146
+ this.classById
103147
+ ).some((current) => current.id === targetMember.classId)) {
103148
+ const targetClassName = this.classById.get(targetMember.classId)?.name ?? targetMember.classId;
103149
+ const valueClassName2 = this.classById.get(valueBindingMember.classId)?.name ?? valueBindingMember.classId;
103150
+ throw new Error(
103151
+ `${args.label} targets "${boundChildName}.${targetEntry.schemaKey}" of class ${targetClassName}, but its segment plays ${valueClassName2} values.`
103152
+ );
103153
+ }
103154
+ if (isRequiredMember2(targetMember) && !isRequiredMember2(valueBindingMember)) {
103155
+ throw new Error(
103156
+ `${args.label} targets "${boundChildName}.${targetEntry.schemaKey}", which is required, but its segment plays nullable values.`
103157
+ );
103158
+ }
103159
+ if (animationTrackStorage(
103108
103160
  [targetMember.id],
103161
+ this.memberById,
103109
103162
  args.childStoragePath
103110
- )) {
103163
+ ) === null) {
103111
103164
  throw new Error(
103112
103165
  `${args.label} targets "${boundChildName}.${targetEntry.schemaKey}", which is not a writable Save, Session, or Writable override leaf.`
103113
103166
  );
@@ -124527,7 +124580,7 @@ function lowerValueBody(context, member, expression, base, source, environment,
124527
124580
  if (member.required) {
124528
124581
  throw new Error(`Required member ${member.name} cannot store null.`);
124529
124582
  }
124530
- return { ...next, value: null };
124583
+ return overlayLiteralValueContent(next, { value: null });
124531
124584
  }
124532
124585
  switch (member.kind) {
124533
124586
  case "null":
@@ -156152,7 +156205,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
156152
156205
  async function main() {
156153
156206
  const args = parseArgs(process.argv.slice(2));
156154
156207
  if (args.command === "--version") {
156155
- console.log("0.64.0");
156208
+ console.log("0.65.0");
156156
156209
  return;
156157
156210
  }
156158
156211
  if (args.command === null || args.command === "help" || args.command === "--help" || args.command === "-h") {
@@ -2172,7 +2172,11 @@ var PROJECT_RESERVED_NAMES = /* @__PURE__ */ new Set([
2172
2172
  ...NEOSCRIPT_KEYWORDS,
2173
2173
  ...NEOSCRIPT_BUILTIN_NAMESPACES
2174
2174
  ]);
2175
- var PROJECT_MEMBER_RESERVED_NAMES = new Set(NEOSCRIPT_KEYWORDS);
2175
+ var PROJECT_MEMBER_RESERVED_NAMES = /* @__PURE__ */ new Set([
2176
+ ...NEOSCRIPT_KEYWORDS,
2177
+ // Every generated C# class declares the SDK's change subscription.
2178
+ "OnChanged"
2179
+ ]);
2176
2180
  var PROJECT_BINDING_RESERVED_NAMES = /* @__PURE__ */ new Set([
2177
2181
  ...PROJECT_RESERVED_NAMES,
2178
2182
  "this",
@@ -2224,7 +2228,7 @@ function isValidSchemaNSFunctionBindingIdentifier(value) {
2224
2228
  // ../src/models/neoscript/neoscript-compiler-revision.ts
2225
2229
  var ACTIVE_NEOSCRIPT_COMPILER_REVISION_ROLLOUT = (
2226
2230
  // <neoscript-compiler-revision-rollout>
2227
- { migrationId: "neoscript-compiler-revision-16", revision: 16 }
2231
+ null
2228
2232
  );
2229
2233
  function isAcceptedNeoScriptCompilerRevision(stamp, rollout = ACTIVE_NEOSCRIPT_COMPILER_REVISION_ROLLOUT) {
2230
2234
  if (stamp === NEOSCRIPT_COMPILER_REVISION) return true;
@@ -6054,6 +6058,14 @@ var WORLD_SYSTEM_CLASS_DEFINITIONS = [
6054
6058
  docsText: "When false, this object and its children are neither rendered nor collided with. The value stays live: member writes still apply and a running animation clip keeps playing. Disabling an object hides its whole subtree regardless of each child's own value, and re-enabling it restores exactly what was there.",
6055
6059
  requirement: 1 /* Required */,
6056
6060
  schemaKey: "Enabled"
6061
+ },
6062
+ {
6063
+ memberId: "system_a0083c92-72f7-405f-8863-ff86f995d36d",
6064
+ memberKind: "class",
6065
+ defaultValue: null,
6066
+ docsText: "The object's box collider, or null for none. Any world object can carry one: placed objects, nested children, and sprites alike.",
6067
+ schemaClassWorldKind: "objectCollider",
6068
+ schemaKey: "Collider"
6057
6069
  }
6058
6070
  ],
6059
6071
  worldKind: "objectBase"
@@ -6263,13 +6275,6 @@ var WORLD_SYSTEM_CLASS_DEFINITIONS = [
6263
6275
  requirement: 1 /* Required */,
6264
6276
  schemaKey: "Children"
6265
6277
  },
6266
- {
6267
- memberId: "system_a0083c92-72f7-405f-8863-ff86f995d36d",
6268
- memberKind: "class",
6269
- defaultValue: null,
6270
- schemaClassWorldKind: "objectCollider",
6271
- schemaKey: "Collider"
6272
- },
6273
6278
  {
6274
6279
  memberId: "system_c609fed3-53e4-5147-b493-e4aa23031d79",
6275
6280
  memberKind: "computed",
@@ -15000,6 +15005,7 @@ function evaluateNSGetterWithEffects(getter, ctx, argumentValues = [], options =
15000
15005
  getter.typeInfo
15001
15006
  );
15002
15007
  result.value = preserveReadOnlyReturnIdentity(result.value, runtimeCtx);
15008
+ options.inspectResult?.(result.value, runtimeCtx);
15003
15009
  const createdSessionValues = exportedSessionValuesSince(
15004
15010
  runtimeCtx,
15005
15011
  creationCheckpoint,
@@ -18646,7 +18652,7 @@ function runtimeTypeCheck(value, checkType, ctx, seenCollections = /* @__PURE__
18646
18652
  function runtimeValueMatchesType(value, typeInfo, ctx, seenCollections = /* @__PURE__ */ new Set()) {
18647
18653
  if (typeInfo.type === NS_TYPE_UNKNOWN) return true;
18648
18654
  if (value === null || value === void 0) {
18649
- return typeInfo.type === 0 /* Null */ || !typeInfo.required;
18655
+ return typeInfo.type === 0 /* Null */ || typeInfo.type === 21 /* Generic */ || !typeInfo.required;
18650
18656
  }
18651
18657
  return runtimeTypeCheck(value, typeInfo, ctx, seenCollections);
18652
18658
  }
@@ -19010,7 +19016,8 @@ function createUnorderedListProjectionCache() {
19010
19016
  byContainerId: /* @__PURE__ */ new Map(),
19011
19017
  containerIdByArray: /* @__PURE__ */ new WeakMap(),
19012
19018
  scalarEntryIds: /* @__PURE__ */ new Set(),
19013
- referenceEntryIndexesByArray: /* @__PURE__ */ new WeakMap()
19019
+ referenceEntryIndexesByArray: /* @__PURE__ */ new WeakMap(),
19020
+ referenceEntryKeysByRecord: /* @__PURE__ */ new WeakMap()
19014
19021
  };
19015
19022
  }
19016
19023
  function createUnorderedScalarProjectionEntry(containerValueId, value, ctx, consumeEntryBudget = true) {
@@ -20556,6 +20563,7 @@ function evalFunction(fn, scope, ctx) {
20556
20563
  const isList = isListCollection(c);
20557
20564
  const out = isList ? [] : {};
20558
20565
  const referenceEntryIndexes = /* @__PURE__ */ new Set();
20566
+ const referenceEntryKeys = /* @__PURE__ */ new Set();
20559
20567
  const callback = prepareCollectionCallback(
20560
20568
  innerFn,
20561
20569
  scope,
@@ -20575,6 +20583,7 @@ function evalFunction(fn, scope, ctx) {
20575
20583
  }
20576
20584
  out.push(valueId ?? entry);
20577
20585
  } else {
20586
+ if (valueId !== null) referenceEntryKeys.add(String(key));
20578
20587
  out[String(key)] = valueId ?? entry;
20579
20588
  }
20580
20589
  return 0 /* Continue */;
@@ -20587,6 +20596,11 @@ function evalFunction(fn, scope, ctx) {
20587
20596
  referenceEntryIndexes
20588
20597
  );
20589
20598
  }
20599
+ if (!isList)
20600
+ unorderedListProjectionCache(ctx).referenceEntryKeysByRecord.set(
20601
+ out,
20602
+ referenceEntryKeys
20603
+ );
20590
20604
  return out;
20591
20605
  }
20592
20606
  case "first" /* first */:
@@ -24766,7 +24780,7 @@ function resolveMemberDeclaredTypeInfoInternal(member, members, seen) {
24766
24780
  members,
24767
24781
  nextSeen
24768
24782
  );
24769
- return argumentType === null ? [] : [[genericParamId, { ...argumentType, required: true }]];
24783
+ return argumentType === null ? [] : [[genericParamId, argumentType]];
24770
24784
  }
24771
24785
  )
24772
24786
  );
@@ -35264,7 +35278,12 @@ var StrictNeoScriptResolver = class {
35264
35278
  };
35265
35279
  });
35266
35280
  fields.push(
35267
- ...this.resolveCallSiteInitializerFields(schemaClass, expression, scope)
35281
+ ...this.resolveCallSiteInitializerFields(
35282
+ schemaClass,
35283
+ targetType,
35284
+ expression,
35285
+ scope
35286
+ )
35268
35287
  );
35269
35288
  const schemaClassInfo = toWireType(targetType, this.project);
35270
35289
  if (schemaClassInfo.type !== 7 /* Class */) {
@@ -35390,6 +35409,7 @@ var StrictNeoScriptResolver = class {
35390
35409
  ) : [];
35391
35410
  const fields = this.resolveCallSiteInitializerFields(
35392
35411
  schemaClass,
35412
+ targetType,
35393
35413
  expression,
35394
35414
  scope
35395
35415
  );
@@ -35504,7 +35524,7 @@ var StrictNeoScriptResolver = class {
35504
35524
  * the class's own schema exactly as a `classConstructor` field is, and
35505
35525
  * applied after the body so an explicit assignment always wins.
35506
35526
  */
35507
- resolveCallSiteInitializerFields(schemaClass, expression, scope) {
35527
+ resolveCallSiteInitializerFields(schemaClass, targetType, expression, scope) {
35508
35528
  const entries = expression.initializer ?? [];
35509
35529
  const seen = /* @__PURE__ */ new Set();
35510
35530
  return entries.map((entry) => {
@@ -35549,10 +35569,15 @@ var StrictNeoScriptResolver = class {
35549
35569
  entry.pos
35550
35570
  );
35551
35571
  }
35552
- const value = this.resolveExpression(entry.value, scope, member.type);
35553
- if (!isNeoScriptTypeAssignable(value.type, member.type, this.project)) {
35572
+ const memberType = substituteTypeParameters(
35573
+ member.type,
35574
+ targetType,
35575
+ schemaClass
35576
+ );
35577
+ const value = this.resolveExpression(entry.value, scope, memberType);
35578
+ if (!isNeoScriptTypeAssignable(value.type, memberType, this.project)) {
35554
35579
  throw new CompileError(
35555
- `Object initializer '${member.name}' expected ${this.describe(member.type)} but got ${this.describe(value.type)}.`,
35580
+ `Object initializer '${member.name}' expected ${this.describe(memberType)} but got ${this.describe(value.type)}.`,
35556
35581
  entry.pos
35557
35582
  );
35558
35583
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neocompose/cli",
3
- "version": "0.64.0",
3
+ "version": "0.65.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.64.0 -->
12
+ <!-- reviewed-through-cli: 0.65.0 -->
13
13
 
14
14
  # Neo Compose CLI
15
15
 
@@ -388,6 +388,37 @@ frame after a watched dependency changes.
388
388
  When multiple tracks write the same member in one frame, apply list order and
389
389
  let the last write win.
390
390
 
391
+ A track can target a Class member. Each frame writes its whole value, and a
392
+ `null` frame clears the member. Every `NeoObjectBase`, sprites included,
393
+ declares `Collider`, so a per-frame trigger such as an attack hit box is a
394
+ Session `Collider` override on the child plus a track of `NeoCollider?`
395
+ frames:
396
+
397
+ ```neo
398
+ class ColliderAnimationSegment : NeoAnimationSegment<NeoCollider?> {
399
+ }
400
+
401
+ @settings(target: NeoObjectBase.Collider)
402
+ class SwingColliderTrack(NeoDelegate<SwordSprite> selector)
403
+ : NeoAnimationSegmentTrack<SwordSprite, NeoCollider?>(selector: selector) {
404
+ public override ColliderAnimationSegment Segment = new ColliderAnimationSegment() {
405
+ Duration = 3,
406
+ Frames = [
407
+ new NeoAnimationSegmentFrame<NeoCollider?>(value: new NeoCollider() {
408
+ Size = new(1, 1),
409
+ Offset = new(1, 1),
410
+ IsTrigger = true
411
+ }) { Index = 1 },
412
+ new NeoAnimationSegmentFrame<NeoCollider?>(value: null) { Index = 2 }
413
+ ]
414
+ };
415
+ }
416
+ ```
417
+
418
+ The segment's class must fit the target member's class. Like any track target,
419
+ the member must be a writable Save, Session, or Writable leaf on the child, for
420
+ example `@storage(allowed: .Session) public override NeoCollider? Collider;`.
421
+
391
422
  ## NeoFlow dialogues
392
423
 
393
424
  Lookup dialogue groups use the same collection paths as lookup members:
@@ -110,7 +110,7 @@ wrappers.
110
110
  The marker near the top of `SKILL.md` must exactly match the package version:
111
111
 
112
112
  ```html
113
- <!-- reviewed-through-cli: 0.64.0 -->
113
+ <!-- reviewed-through-cli: 0.65.0 -->
114
114
  ```
115
115
 
116
116
  The quoted version above is checked too, so this instruction cannot go stale
@@ -48,7 +48,7 @@ keeps its meaning.
48
48
 
49
49
  In a void context — a `void` function, a setter, or a void delegate — `=> expr`
50
50
  is an expression statement rather than a return, so the expression must do
51
- something: a call such as `void Ping() => this.OnChanged();`, or an assignment
51
+ something: a call such as `void Ping() => this.OnUpdated();`, or an assignment
52
52
  such as `set(int value) => this.Raw = value;`. An expression whose value would
53
53
  simply be discarded is rejected with a diagnostic that names the block body as
54
54
  the fix.
@@ -106,11 +106,11 @@ migration, and text-variable bodies against that same source transaction, so
106
106
  new declarations do not depend on a prior push or a network fetch.
107
107
 
108
108
  Inside an inline Class body, unqualified names resolve against that Class;
109
- invoke delegate and action members directly with `Selector()` or `OnChanged()`.
109
+ invoke delegate and action members directly with `Selector()` or `OnUpdated()`.
110
110
  Spell a zero-argument action as bare `NeoAction`; type arguments name action
111
111
  parameters, so `NeoAction<void>` is invalid rather than a zero-argument alias.
112
112
  Clear every subscribed listener from a stored action with
113
- `this.OnChanged.Clear()`. `Clear()` is a mutating statement and follows the
113
+ `this.OnUpdated.Clear()`. `Clear()` is a mutating statement and follows the
114
114
  action member's effective storage and writability. Only the action's declaring
115
115
  class and derived classes may invoke it or call `Clear()`; public external
116
116
  callers may subscribe and unsubscribe with `+=` and `-=` but cannot raise or