@neocompose/cli 0.64.1 → 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,12 @@
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
+
3
10
  ## [0.64.1] - 2026-10-02
4
11
 
5
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`.
package/dist/neo.mjs CHANGED
@@ -14192,11 +14192,18 @@ function validateExpression(expression, expected, scope, environment, uri, range
14192
14192
  siteArgumentNames,
14193
14193
  siteInitializerNames
14194
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
+ );
14195
14202
  for (const diagnostic of validateNeoConstructionSite(
14196
14203
  environment.construction,
14197
14204
  {
14198
14205
  className: declaration.name,
14199
- typeArguments: expression.typeArguments?.map(semanticAstType) ?? (expression.className === null ? expected?.arguments : void 0) ?? [],
14206
+ typeArguments: siteTypeArguments,
14200
14207
  argumentNames: variantSite?.argumentNames ?? siteArgumentNames,
14201
14208
  initializerNames: variantSite?.initializerNames ?? siteInitializerNames
14202
14209
  }
@@ -14232,7 +14239,7 @@ function validateExpression(expression, expected, scope, environment, uri, range
14232
14239
  (candidate) => candidate.name === (variantMember ?? named)
14233
14240
  ) : fields[index];
14234
14241
  const expectedType = parameter4?.type ?? field?.type;
14235
- 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;
14236
14243
  validateExpression(
14237
14244
  argument2,
14238
14245
  expectedSemanticType,
@@ -14253,8 +14260,11 @@ function validateExpression(expression, expected, scope, environment, uri, range
14253
14260
  validateExpression(
14254
14261
  entry.value,
14255
14262
  field ? substituteSemanticType(
14256
- semanticType(field.member.type),
14257
- field.bindings
14263
+ substituteSemanticType(
14264
+ semanticType(field.member.type),
14265
+ field.bindings
14266
+ ),
14267
+ siteBindings
14258
14268
  ) : void 0,
14259
14269
  scope,
14260
14270
  environment,
@@ -14469,7 +14479,7 @@ function sourceClassMemberByName(declaration, name, environment, initialBindings
14469
14479
  function substituteSemanticType(type, bindings) {
14470
14480
  const bound = bindings.get(type.name);
14471
14481
  if (bound !== void 0 && (type.arguments ?? []).length === 0) {
14472
- return type.nullable === void 0 ? bound : { ...bound, nullable: type.nullable };
14482
+ return type.nullable === true ? { ...bound, nullable: true } : bound;
14473
14483
  }
14474
14484
  const argumentsList2 = (type.arguments ?? []).map(
14475
14485
  (argument2) => substituteSemanticType(argument2, bindings)
@@ -22778,7 +22788,12 @@ var init_strict_resolver = __esm({
22778
22788
  };
22779
22789
  });
22780
22790
  fields.push(
22781
- ...this.resolveCallSiteInitializerFields(schemaClass2, expression, scope)
22791
+ ...this.resolveCallSiteInitializerFields(
22792
+ schemaClass2,
22793
+ targetType,
22794
+ expression,
22795
+ scope
22796
+ )
22782
22797
  );
22783
22798
  const schemaClassInfo = toWireType(targetType, this.project);
22784
22799
  if (schemaClassInfo.type !== 7 /* Class */) {
@@ -22904,6 +22919,7 @@ var init_strict_resolver = __esm({
22904
22919
  ) : [];
22905
22920
  const fields = this.resolveCallSiteInitializerFields(
22906
22921
  schemaClass2,
22922
+ targetType,
22907
22923
  expression,
22908
22924
  scope
22909
22925
  );
@@ -23018,7 +23034,7 @@ var init_strict_resolver = __esm({
23018
23034
  * the class's own schema exactly as a `classConstructor` field is, and
23019
23035
  * applied after the body so an explicit assignment always wins.
23020
23036
  */
23021
- resolveCallSiteInitializerFields(schemaClass2, expression, scope) {
23037
+ resolveCallSiteInitializerFields(schemaClass2, targetType, expression, scope) {
23022
23038
  const entries = expression.initializer ?? [];
23023
23039
  const seen = /* @__PURE__ */ new Set();
23024
23040
  return entries.map((entry) => {
@@ -23063,10 +23079,15 @@ var init_strict_resolver = __esm({
23063
23079
  entry.pos
23064
23080
  );
23065
23081
  }
23066
- const value = this.resolveExpression(entry.value, scope, member.type);
23067
- 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)) {
23068
23089
  throw new CompileError(
23069
- `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)}.`,
23070
23091
  entry.pos
23071
23092
  );
23072
23093
  }
@@ -29934,7 +29955,7 @@ var init_registry = __esm({
29934
29955
  "../packages/manifest/schema-contract/registry.mjs"() {
29935
29956
  "use strict";
29936
29957
  PROJECT_SCHEMA_CONTRACT = Object.freeze({
29937
- cliVersion: "0.64.1",
29958
+ cliVersion: "0.65.0",
29938
29959
  projectFileUploadBatchSize: 32,
29939
29960
  documentRecords: {
29940
29961
  member: {
@@ -43302,6 +43323,9 @@ function createMergeMemo() {
43302
43323
  function resolveInheritanceChain(classId, classes, draft) {
43303
43324
  return [...indexClasses(classes).inheritanceChain(classId, draft)];
43304
43325
  }
43326
+ function resolveInheritanceChainFromIndex(classId, classIndex, draft) {
43327
+ return resolveChainFromIndex(classId, classIndex, draft);
43328
+ }
43305
43329
  function resolveChainFromIndex(classId, index, draft) {
43306
43330
  const chain = [];
43307
43331
  const visited = /* @__PURE__ */ new Set();
@@ -44374,6 +44398,14 @@ var init_world_system_classes_generated = __esm({
44374
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.",
44375
44399
  requirement: 1 /* Required */,
44376
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"
44377
44409
  }
44378
44410
  ],
44379
44411
  worldKind: "objectBase"
@@ -44583,13 +44615,6 @@ var init_world_system_classes_generated = __esm({
44583
44615
  requirement: 1 /* Required */,
44584
44616
  schemaKey: "Children"
44585
44617
  },
44586
- {
44587
- memberId: "system_a0083c92-72f7-405f-8863-ff86f995d36d",
44588
- memberKind: "class",
44589
- defaultValue: null,
44590
- schemaClassWorldKind: "objectCollider",
44591
- schemaKey: "Collider"
44592
- },
44593
44618
  {
44594
44619
  memberId: "system_c609fed3-53e4-5147-b493-e4aa23031d79",
44595
44620
  memberKind: "computed",
@@ -61100,7 +61125,7 @@ function resolveMemberDeclaredTypeInfoInternal(member, members, seen) {
61100
61125
  members,
61101
61126
  nextSeen
61102
61127
  );
61103
- return argumentType === null ? [] : [[genericParamId, { ...argumentType, required: true }]];
61128
+ return argumentType === null ? [] : [[genericParamId, argumentType]];
61104
61129
  }
61105
61130
  )
61106
61131
  );
@@ -73507,7 +73532,7 @@ function runtimeTypeCheck(value, checkType, ctx, seenCollections = /* @__PURE__
73507
73532
  function runtimeValueMatchesType(value, typeInfo, ctx, seenCollections = /* @__PURE__ */ new Set()) {
73508
73533
  if (typeInfo.type === NS_TYPE_UNKNOWN) return true;
73509
73534
  if (value === null || value === void 0) {
73510
- return typeInfo.type === 0 /* Null */ || !typeInfo.required;
73535
+ return typeInfo.type === 0 /* Null */ || typeInfo.type === 21 /* Generic */ || !typeInfo.required;
73511
73536
  }
73512
73537
  return runtimeTypeCheck(value, typeInfo, ctx, seenCollections);
73513
73538
  }
@@ -103116,11 +103141,26 @@ var init_animation_clips = __esm({
103116
103141
  `${args.label} targets "${boundChildName}.${targetEntry.schemaKey}" of kind ${MemberKind[targetMember.kind]}, but its segment plays ${MemberKind[valueBindingMember.kind]} values.`
103117
103142
  );
103118
103143
  }
103119
- if (!this.overrideLeafEligible(
103120
- 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(
103121
103160
  [targetMember.id],
103161
+ this.memberById,
103122
103162
  args.childStoragePath
103123
- )) {
103163
+ ) === null) {
103124
103164
  throw new Error(
103125
103165
  `${args.label} targets "${boundChildName}.${targetEntry.schemaKey}", which is not a writable Save, Session, or Writable override leaf.`
103126
103166
  );
@@ -124540,7 +124580,7 @@ function lowerValueBody(context, member, expression, base, source, environment,
124540
124580
  if (member.required) {
124541
124581
  throw new Error(`Required member ${member.name} cannot store null.`);
124542
124582
  }
124543
- return { ...next, value: null };
124583
+ return overlayLiteralValueContent(next, { value: null });
124544
124584
  }
124545
124585
  switch (member.kind) {
124546
124586
  case "null":
@@ -156165,7 +156205,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
156165
156205
  async function main() {
156166
156206
  const args = parseArgs(process.argv.slice(2));
156167
156207
  if (args.command === "--version") {
156168
- console.log("0.64.1");
156208
+ console.log("0.65.0");
156169
156209
  return;
156170
156210
  }
156171
156211
  if (args.command === null || args.command === "help" || args.command === "--help" || args.command === "-h") {
@@ -6058,6 +6058,14 @@ var WORLD_SYSTEM_CLASS_DEFINITIONS = [
6058
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.",
6059
6059
  requirement: 1 /* Required */,
6060
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"
6061
6069
  }
6062
6070
  ],
6063
6071
  worldKind: "objectBase"
@@ -6267,13 +6275,6 @@ var WORLD_SYSTEM_CLASS_DEFINITIONS = [
6267
6275
  requirement: 1 /* Required */,
6268
6276
  schemaKey: "Children"
6269
6277
  },
6270
- {
6271
- memberId: "system_a0083c92-72f7-405f-8863-ff86f995d36d",
6272
- memberKind: "class",
6273
- defaultValue: null,
6274
- schemaClassWorldKind: "objectCollider",
6275
- schemaKey: "Collider"
6276
- },
6277
6278
  {
6278
6279
  memberId: "system_c609fed3-53e4-5147-b493-e4aa23031d79",
6279
6280
  memberKind: "computed",
@@ -18651,7 +18652,7 @@ function runtimeTypeCheck(value, checkType, ctx, seenCollections = /* @__PURE__
18651
18652
  function runtimeValueMatchesType(value, typeInfo, ctx, seenCollections = /* @__PURE__ */ new Set()) {
18652
18653
  if (typeInfo.type === NS_TYPE_UNKNOWN) return true;
18653
18654
  if (value === null || value === void 0) {
18654
- return typeInfo.type === 0 /* Null */ || !typeInfo.required;
18655
+ return typeInfo.type === 0 /* Null */ || typeInfo.type === 21 /* Generic */ || !typeInfo.required;
18655
18656
  }
18656
18657
  return runtimeTypeCheck(value, typeInfo, ctx, seenCollections);
18657
18658
  }
@@ -24779,7 +24780,7 @@ function resolveMemberDeclaredTypeInfoInternal(member, members, seen) {
24779
24780
  members,
24780
24781
  nextSeen
24781
24782
  );
24782
- return argumentType === null ? [] : [[genericParamId, { ...argumentType, required: true }]];
24783
+ return argumentType === null ? [] : [[genericParamId, argumentType]];
24783
24784
  }
24784
24785
  )
24785
24786
  );
@@ -35277,7 +35278,12 @@ var StrictNeoScriptResolver = class {
35277
35278
  };
35278
35279
  });
35279
35280
  fields.push(
35280
- ...this.resolveCallSiteInitializerFields(schemaClass, expression, scope)
35281
+ ...this.resolveCallSiteInitializerFields(
35282
+ schemaClass,
35283
+ targetType,
35284
+ expression,
35285
+ scope
35286
+ )
35281
35287
  );
35282
35288
  const schemaClassInfo = toWireType(targetType, this.project);
35283
35289
  if (schemaClassInfo.type !== 7 /* Class */) {
@@ -35403,6 +35409,7 @@ var StrictNeoScriptResolver = class {
35403
35409
  ) : [];
35404
35410
  const fields = this.resolveCallSiteInitializerFields(
35405
35411
  schemaClass,
35412
+ targetType,
35406
35413
  expression,
35407
35414
  scope
35408
35415
  );
@@ -35517,7 +35524,7 @@ var StrictNeoScriptResolver = class {
35517
35524
  * the class's own schema exactly as a `classConstructor` field is, and
35518
35525
  * applied after the body so an explicit assignment always wins.
35519
35526
  */
35520
- resolveCallSiteInitializerFields(schemaClass, expression, scope) {
35527
+ resolveCallSiteInitializerFields(schemaClass, targetType, expression, scope) {
35521
35528
  const entries = expression.initializer ?? [];
35522
35529
  const seen = /* @__PURE__ */ new Set();
35523
35530
  return entries.map((entry) => {
@@ -35562,10 +35569,15 @@ var StrictNeoScriptResolver = class {
35562
35569
  entry.pos
35563
35570
  );
35564
35571
  }
35565
- const value = this.resolveExpression(entry.value, scope, member.type);
35566
- 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)) {
35567
35579
  throw new CompileError(
35568
- `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)}.`,
35569
35581
  entry.pos
35570
35582
  );
35571
35583
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neocompose/cli",
3
- "version": "0.64.1",
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.1 -->
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.1 -->
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