@neocompose/cli 0.49.0 → 0.50.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,10 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.50.0] - 2026-09-11
4
+
5
+ - Expose tile placement assets with `@tile(VoidTile)` or `@tile(asset: VoidTile)`. Pull emits the tile class beside each placement; authored bindings resolve to the existing asset metadata.
6
+ - Add tile asset validation, completion, and class navigation to the shared language service.
7
+
3
8
  ## [0.49.0] - 2026-09-11
4
9
 
5
10
  - Require every constructed object's non-nullable members to be supplied by a member default, constructor, or initializer. Missing values are now compile errors.
package/dist/neo.mjs CHANGED
@@ -10166,6 +10166,14 @@ var init_strict_resolver = __esm({
10166
10166
  );
10167
10167
  }
10168
10168
  case "annotated":
10169
+ if (expression.annotations.some(
10170
+ (annotation2) => annotation2.name === "tile"
10171
+ )) {
10172
+ throw new CompileError(
10173
+ "@tile is only supported on persisted literal placements, not executable NeoScript constructions.",
10174
+ expression.pos
10175
+ );
10176
+ }
10169
10177
  return this.resolveExpression(expression.expression, scope, expected);
10170
10178
  case "binary":
10171
10179
  return this.resolveBinary(expression, scope);
@@ -17880,6 +17888,55 @@ var init_project_source_registry = __esm({
17880
17888
  }
17881
17889
  });
17882
17890
 
17891
+ // ../packages/neoscript-language/src/project-source-tile.ts
17892
+ function tileAssetAnnotation(expression) {
17893
+ if (expression.kind !== "annotated") return null;
17894
+ const annotations = expression.annotations.filter(
17895
+ (entry) => entry.name === "tile"
17896
+ );
17897
+ const annotation2 = annotations[0];
17898
+ if (annotation2 === void 0) return null;
17899
+ if (annotations.length > 1) {
17900
+ throw new CompileError(
17901
+ "A placement can have only one @tile annotation.",
17902
+ annotations[1].pos
17903
+ );
17904
+ }
17905
+ if (expression.expression.kind !== "new") {
17906
+ throw new CompileError(
17907
+ "@tile requires a NeoTileInstance construction.",
17908
+ annotation2.pos
17909
+ );
17910
+ }
17911
+ if (annotation2.args.length !== 1) {
17912
+ throw new CompileError(
17913
+ "@tile requires exactly one tile class: @tile(VoidTile) or @tile(asset: VoidTile).",
17914
+ annotation2.pos
17915
+ );
17916
+ }
17917
+ const name = annotation2.argumentNames?.[0];
17918
+ if (name != null && name !== "asset") {
17919
+ throw new CompileError(
17920
+ `@tile has no '${name}' option. Use 'asset'.`,
17921
+ annotation2.pos
17922
+ );
17923
+ }
17924
+ const asset = annotation2.args[0];
17925
+ if (asset.kind !== "ident") {
17926
+ throw new CompileError(
17927
+ "@tile asset must name a concrete tile class.",
17928
+ asset.pos
17929
+ );
17930
+ }
17931
+ return asset.name;
17932
+ }
17933
+ var init_project_source_tile = __esm({
17934
+ "../packages/neoscript-language/src/project-source-tile.ts"() {
17935
+ "use strict";
17936
+ init_strict_compile_error();
17937
+ }
17938
+ });
17939
+
17883
17940
  // ../packages/neoscript-language/src/project-schema-contract.generated.ts
17884
17941
  var PROJECT_FILE_UPLOAD_BATCH_SIZE, NEO_PROJECT_SOURCE_RECORD_CONTRACT;
17885
17942
  var init_project_schema_contract_generated = __esm({
@@ -22911,7 +22968,48 @@ function validateExpression(expression, expected, scope, environment, uri, range
22911
22968
  return;
22912
22969
  }
22913
22970
  if (expression.kind === "annotated") {
22971
+ const assetName = tileAssetAnnotation(expression);
22972
+ if (assetName !== null) {
22973
+ const constructed = expression.expression;
22974
+ const className = constructed.kind === "new" ? constructed.className ?? expected?.name : void 0;
22975
+ const asset = environment.types.get(assetName);
22976
+ if (!className || !classExtends(className, "NeoTileInstance", environment, /* @__PURE__ */ new Set())) {
22977
+ pushDiagnostic(
22978
+ diagnostics,
22979
+ uri,
22980
+ range2,
22981
+ "invalid-tile-placement",
22982
+ "@tile requires a NeoTileInstance construction."
22983
+ );
22984
+ }
22985
+ if (!asset || !classExtends(assetName, "NeoTile", environment, /* @__PURE__ */ new Set())) {
22986
+ pushDiagnostic(
22987
+ diagnostics,
22988
+ uri,
22989
+ range2,
22990
+ "invalid-tile-asset",
22991
+ `@tile asset '${assetName}' must name a tile class.`
22992
+ );
22993
+ } else if (asset.modifiers.includes("abstract")) {
22994
+ pushDiagnostic(
22995
+ diagnostics,
22996
+ uri,
22997
+ range2,
22998
+ "invalid-tile-asset",
22999
+ `@tile asset '${assetName}' must be a concrete tile class.`
23000
+ );
23001
+ } else if (asset.genericParameters.length > 0) {
23002
+ pushDiagnostic(
23003
+ diagnostics,
23004
+ uri,
23005
+ range2,
23006
+ "invalid-tile-asset",
23007
+ `@tile asset '${assetName}' must be a non-generic tile class.`
23008
+ );
23009
+ }
23010
+ }
22914
23011
  for (const annotation2 of expression.annotations) {
23012
+ if (annotation2.name === "tile") continue;
22915
23013
  validateInlineAnnotation(
22916
23014
  annotation2,
22917
23015
  environment,
@@ -23444,7 +23542,7 @@ function validateInlineAnnotation(annotation2, environment, uri, range2, diagnos
23444
23542
  uri,
23445
23543
  range2,
23446
23544
  "invalid-inline-annotation",
23447
- `Only @id is valid on an inline persisted value; got @${annotation2.name}.`
23545
+ `Only @id and @tile are valid on an inline persisted value; got @${annotation2.name}.`
23448
23546
  );
23449
23547
  return;
23450
23548
  }
@@ -24011,6 +24109,7 @@ var init_project_source_semantics = __esm({
24011
24109
  "../packages/neoscript-language/src/project-source-semantics.ts"() {
24012
24110
  "use strict";
24013
24111
  init_project_local_values();
24112
+ init_project_source_tile();
24014
24113
  init_language_spec();
24015
24114
  init_project_schema_contract_generated();
24016
24115
  init_strict_compile_error();
@@ -27739,6 +27838,28 @@ function projectCompletions(analysis, document, position) {
27739
27838
  items: [...script.items, variantScopeInBody]
27740
27839
  };
27741
27840
  }
27841
+ const tileCall = activeTileAnnotation(document, position);
27842
+ if (tileCall) {
27843
+ const items = analysis.symbols.filter(
27844
+ (symbol) => symbol.kind === "class" && projectTypeNameAssignable(analysis, symbol.name, "NeoTile")
27845
+ ).filter((symbol) => {
27846
+ const declaration = analysis.documents.get(symbol.location.uri)?.declarations.find(
27847
+ (entry) => entry.kind === "class" && entry.name === symbol.name
27848
+ );
27849
+ return declaration?.kind === "class" && !declaration.modifiers.includes("abstract") && declaration.genericParameters.length === 0;
27850
+ }).map((symbol) => ({
27851
+ label: symbol.name,
27852
+ kind: "class",
27853
+ insertText: symbol.name
27854
+ }));
27855
+ if (!tileCall.named)
27856
+ items.unshift({
27857
+ label: "asset",
27858
+ kind: "property",
27859
+ insertText: "asset: "
27860
+ });
27861
+ return { isIncomplete: false, items };
27862
+ }
27742
27863
  const annotations = projectAnnotationCompletions(
27743
27864
  analysis,
27744
27865
  document,
@@ -27864,12 +27985,32 @@ function projectCompletions(analysis, document, position) {
27864
27985
  }
27865
27986
  return projectFallbackCompletions(analysis, document, position);
27866
27987
  }
27988
+ function isTileAssetToken(tokens, index) {
27989
+ const open = tokens[index - 1]?.text === ":" && tokens[index - 2]?.text === "asset" ? index - 3 : index - 1;
27990
+ return tokens[index + 1]?.text !== ":" && tokens[open]?.text === "(" && tokens[open - 1]?.text === "tile" && tokens[open - 2]?.text === "@";
27991
+ }
27992
+ function activeTileAnnotation(document, position) {
27993
+ const tokens = projectTokens(document);
27994
+ const open = activeCallOpenIndex(tokens, position);
27995
+ if (tokens[open - 1]?.text !== "tile" || tokens[open - 2]?.text !== "@")
27996
+ return null;
27997
+ const before = tokens.slice(open + 1).filter((token) => positionCompare2(token.range.start, position) < 0);
27998
+ if (before.some((token) => token.text === ",")) return null;
27999
+ const named = before.some((token) => token.text === ":");
28000
+ return { named };
28001
+ }
27867
28002
  function projectAnnotationCompletions(analysis, document, position) {
27868
28003
  const source = new SourceText(document.text);
27869
28004
  const offset = source.offsetAt(position);
27870
28005
  const word = projectWordRange(document.text, offset);
27871
28006
  if (document.text[word.start - 1] !== "@") return null;
27872
- if (initializerRootAt(analysis, document, position)) return [];
28007
+ if (initializerRootAt(analysis, document, position)) {
28008
+ return ["id", "tile"].map((name) => ({
28009
+ label: `@${name}`,
28010
+ kind: "snippet",
28011
+ insertText: name
28012
+ }));
28013
+ }
27873
28014
  const names = projectAnnotationNamesAt(analysis, document, position);
27874
28015
  return names.map((name) => ({
27875
28016
  label: `@${name}`,
@@ -29307,7 +29448,7 @@ function indexedProjectSymbolAt(analysis, document, position, index) {
29307
29448
  );
29308
29449
  const source = analysis.documents.get(document.uri);
29309
29450
  const typePosition = source && sourceTypeAt(source, token.range.start);
29310
- if (typePosition || sourceTokens[tokenIndex - 1]?.text === "new") {
29451
+ if (typePosition || sourceTokens[tokenIndex - 1]?.text === "new" || isTileAssetToken(sourceTokens, tokenIndex)) {
29311
29452
  const types = candidates.filter(
29312
29453
  (symbol) => symbol.kind === "class" || symbol.kind === "interface" || symbol.kind === "enum" || symbol.kind === "genericParameter"
29313
29454
  );
@@ -29547,6 +29688,8 @@ function annotationSignatureParameters(name) {
29547
29688
  switch (name) {
29548
29689
  case "id":
29549
29690
  return ["string id"];
29691
+ case "tile":
29692
+ return ["NeoTile class asset"];
29550
29693
  case "settings":
29551
29694
  return ["named settings"];
29552
29695
  case "storage":
@@ -34012,6 +34155,7 @@ var init_src = __esm({
34012
34155
  init_project_source_construction_quick_fixes();
34013
34156
  init_project_source_manifest();
34014
34157
  init_project_source_variants();
34158
+ init_project_source_tile();
34015
34159
  init_project_source_parameter_defaults();
34016
34160
  init_project_source_settlement();
34017
34161
  init_project_source_parser();
@@ -52159,6 +52303,18 @@ function constructionMembers(classId, context) {
52159
52303
  }
52160
52304
  return result;
52161
52305
  }
52306
+ function createMemberDefaultConstructionChecker(document) {
52307
+ const context = contextFor(document);
52308
+ return (member, genericEnv = /* @__PURE__ */ new Map()) => {
52309
+ const previousEnv = context.env;
52310
+ context.env = genericEnv;
52311
+ try {
52312
+ return defaultSettlesConstruction(member, context, /* @__PURE__ */ new Set());
52313
+ } finally {
52314
+ context.env = previousEnv;
52315
+ }
52316
+ };
52317
+ }
52162
52318
  function storedValueSettlesConstruction(member, sourceValue, document, genericEnv) {
52163
52319
  return valueSettlesConstruction(
52164
52320
  member,
@@ -53450,7 +53606,8 @@ function createDefaultValueResolutionCache() {
53450
53606
  materializationPlanByClassAndEnvironment: /* @__PURE__ */ new Map(),
53451
53607
  resolvedMemberById: /* @__PURE__ */ new Map(),
53452
53608
  storedSchemaByClassId: /* @__PURE__ */ new Map(),
53453
- validatedInitializerContainers: /* @__PURE__ */ new WeakSet()
53609
+ validatedInitializerContainers: /* @__PURE__ */ new WeakSet(),
53610
+ memberDefaultConstructionChecker: null
53454
53611
  };
53455
53612
  }
53456
53613
  function withIndexedDefaultValueDocument(document, options = {}) {
@@ -53504,6 +53661,13 @@ function withIndexedDefaultValueDocument(document, options = {}) {
53504
53661
  cache.validatedInitializerContainers.add(value);
53505
53662
  return true;
53506
53663
  },
53664
+ memberDefaultSettlesConstruction: (member, environment) => {
53665
+ cache.memberDefaultConstructionChecker ??= createMemberDefaultConstructionChecker({
53666
+ ...document,
53667
+ constructors: document.constructors ?? []
53668
+ });
53669
+ return cache.memberDefaultConstructionChecker(member, environment);
53670
+ },
53507
53671
  createValueRow: options.createValueRow ?? document.createValueRow
53508
53672
  };
53509
53673
  indexed.storedInstanceMaterializationPlan = (classId, environment) => {
@@ -53549,6 +53713,10 @@ function directScalarDefaultBody(document, member) {
53549
53713
  return { value: body.value, classId: body.classId };
53550
53714
  }
53551
53715
  function buildDefaultClassMaterializationPlan(document, classId, instanceEnv) {
53716
+ const settlesConstruction = document.memberDefaultSettlesConstruction ?? createMemberDefaultConstructionChecker({
53717
+ ...document,
53718
+ constructors: document.constructors ?? []
53719
+ });
53552
53720
  const merged = document.storedInstanceSchema?.(classId) ?? mergeStoredInstanceSchema(classId, document.classes, document.members);
53553
53721
  return merged.flatMap((entry) => {
53554
53722
  if (entry.memberId === null) return [];
@@ -53564,6 +53732,7 @@ function buildDefaultClassMaterializationPlan(document, classId, instanceEnv) {
53564
53732
  {
53565
53733
  schemaKey: entry.schemaKey,
53566
53734
  member,
53735
+ defaultSettlesConstruction: settlesConstruction(member, instanceEnv),
53567
53736
  directScalarBody: directScalarDefaultBody(document, member)
53568
53737
  }
53569
53738
  ];
@@ -53772,6 +53941,7 @@ function buildDefaultMemberValue(args) {
53772
53941
  for (const {
53773
53942
  schemaKey,
53774
53943
  member: resolvedChildMember,
53944
+ defaultSettlesConstruction: defaultSettlesConstruction2,
53775
53945
  directScalarBody
53776
53946
  } of member.payload === 1 /* Partial */ ? [] : materializationPlan) {
53777
53947
  if (storageKeyReferencesParentClass(
@@ -53787,6 +53957,7 @@ function buildDefaultMemberValue(args) {
53787
53957
  args.constructorRoot,
53788
53958
  schemaKey,
53789
53959
  resolvedChildMember,
53960
+ defaultSettlesConstruction2,
53790
53961
  documentHasInitValueContent(
53791
53962
  args.document,
53792
53963
  resolvedChildMember.defaultValue
@@ -53794,7 +53965,7 @@ function buildDefaultMemberValue(args) {
53794
53965
  )) {
53795
53966
  continue;
53796
53967
  }
53797
- if (isRequiredMember2(resolvedChildMember) && resolvedChildMember.defaultValue == null && !(isMemberClassBase(resolvedChildMember) && resolvedChildMember.payload === 1 /* Partial */)) {
53968
+ if (isRequiredMember2(resolvedChildMember) && !defaultSettlesConstruction2 && !(isMemberClassBase(resolvedChildMember) && resolvedChildMember.payload === 1 /* Partial */)) {
53798
53969
  if (args.constructorRoot?.runsMemberInitializers === true) continue;
53799
53970
  if (args.constructorRoot?.omitMissingRequired === true) continue;
53800
53971
  throw new Error(
@@ -53804,6 +53975,7 @@ function buildDefaultMemberValue(args) {
53804
53975
  if (!shouldMaterializeChildDefaultForRoot(
53805
53976
  args.constructorRoot,
53806
53977
  resolvedChildMember,
53978
+ defaultSettlesConstruction2,
53807
53979
  documentHasInitValueContent(
53808
53980
  args.document,
53809
53981
  resolvedChildMember.defaultValue
@@ -54309,18 +54481,18 @@ function shouldMaterializeConstructorChildDefault(member, isInitializer = isInit
54309
54481
  if (!isRequiredMember2(member)) return false;
54310
54482
  return shouldMaterializeChildDefault(member);
54311
54483
  }
54312
- function skipProvidedConstructorSchemaKey(constructorRoot, schemaKey, member, isInitializer = isInitValueContent(member.defaultValue)) {
54484
+ function skipProvidedConstructorSchemaKey(constructorRoot, schemaKey, member, defaultSettlesConstruction2, isInitializer = isInitValueContent(member.defaultValue)) {
54313
54485
  if (constructorRoot === void 0) return false;
54314
54486
  if (!constructorRoot.providedSchemaKeys.has(schemaKey)) return false;
54315
54487
  if (constructorRoot.runsMemberInitializers !== true) {
54316
54488
  return !isInitializer;
54317
54489
  }
54318
- return member.defaultValue == null;
54490
+ return !isInitializer && !defaultSettlesConstruction2;
54319
54491
  }
54320
- function shouldMaterializeChildDefaultForRoot(constructorRoot, member, isInitializer = isInitValueContent(member.defaultValue)) {
54492
+ function shouldMaterializeChildDefaultForRoot(constructorRoot, member, defaultSettlesConstruction2, isInitializer = isInitValueContent(member.defaultValue)) {
54321
54493
  if (constructorRoot === void 0)
54322
54494
  return shouldMaterializeChildDefault(member);
54323
- if (constructorRoot.omitMissingRequired === true && isRequiredMember2(member) && member.defaultValue == null) {
54495
+ if (constructorRoot.omitMissingRequired === true && !defaultSettlesConstruction2) {
54324
54496
  return false;
54325
54497
  }
54326
54498
  if (constructorRoot.runsMemberInitializers === true) {
@@ -113956,7 +114128,18 @@ function animationConstructorProjectionTargetValueIds(context, classId, value) {
113956
114128
  }
113957
114129
  return targetIds;
113958
114130
  }
113959
- function rowBackedDefaultBody(member, isPulledValueRow) {
114131
+ function isTileInstanceClass(classId, classes) {
114132
+ const visited = /* @__PURE__ */ new Set();
114133
+ while (typeof classId === "string" && !visited.has(classId)) {
114134
+ visited.add(classId);
114135
+ const schemaClass2 = classes.get(classId);
114136
+ if (isObjectRecord2(schemaClass2?.system) && schemaClass2.system.worldKind === "tileInstance")
114137
+ return true;
114138
+ classId = schemaClass2?.extendsClassId;
114139
+ }
114140
+ return false;
114141
+ }
114142
+ function rowBackedDefaultBody(member, isPulledValueRow, classes) {
113960
114143
  if (member.modifier === 3) return null;
113961
114144
  const defaultValue = member.defaultValue;
113962
114145
  if (!isObjectRecord2(defaultValue)) return null;
@@ -113965,7 +114148,10 @@ function rowBackedDefaultBody(member, isPulledValueRow) {
113965
114148
  (child) => typeof child === "string" && isPulledValueRow(child)
113966
114149
  );
113967
114150
  if (member.kind === MEMBER_KIND_CLASS2 && isObjectRecord2(body)) {
113968
- const children = Object.values(body);
114151
+ const children = isTileInstanceClass(
114152
+ defaultValue.classId ?? member.classId,
114153
+ classes
114154
+ ) ? Object.entries(body).filter(([key]) => key !== "assetClassId" && key !== "variantId").map(([, value]) => value) : Object.values(body);
113969
114155
  return referencesRows(children) ? { kind: "class", body } : null;
113970
114156
  }
113971
114157
  if (member.kind === MEMBER_KIND_LIST2 && member.listKind !== "unordered" && Array.isArray(body)) {
@@ -113981,15 +114167,18 @@ function rowBackedDefaultBody(member, isPulledValueRow) {
113981
114167
  }
113982
114168
  function rowBackedDefaultMemberIdsV4(state) {
113983
114169
  const values = /* @__PURE__ */ new Set();
114170
+ const classes = /* @__PURE__ */ new Map();
113984
114171
  for (const record4 of Object.values(state)) {
113985
114172
  if (record4.recordKind === "value") values.add(record4.recordId);
114173
+ if (record4.recordKind === "class" && isObjectRecord2(record4.data))
114174
+ classes.set(record4.recordId, record4.data);
113986
114175
  }
113987
114176
  const result = /* @__PURE__ */ new Set();
113988
114177
  for (const record4 of Object.values(state)) {
113989
114178
  if (record4.recordKind !== "member" || !isObjectRecord2(record4.data)) {
113990
114179
  continue;
113991
114180
  }
113992
- if (rowBackedDefaultBody(record4.data, (id2) => values.has(id2)) !== null) {
114181
+ if (rowBackedDefaultBody(record4.data, (id2) => values.has(id2), classes) !== null) {
113993
114182
  result.add(record4.recordId);
113994
114183
  }
113995
114184
  }
@@ -114023,7 +114212,11 @@ function emitMemberDefaultSourcesV4(records2, manifest) {
114023
114212
  context.localizedTextIds.clear();
114024
114213
  continue;
114025
114214
  }
114026
- const backed = rowBackedDefaultBody(member, (id2) => context.values.has(id2));
114215
+ const backed = rowBackedDefaultBody(
114216
+ member,
114217
+ (id2) => context.values.has(id2),
114218
+ context.manifestClasses
114219
+ );
114027
114220
  if (backed === null) continue;
114028
114221
  const visited = /* @__PURE__ */ new Set();
114029
114222
  const environment = declaringClassGenericEnvironment(context, member);
@@ -114750,7 +114943,11 @@ function lowerMemberDefaultSourcesV4(state, analysis, manifest, options = {}) {
114750
114943
  });
114751
114944
  continue;
114752
114945
  }
114753
- const backed = baseData3 === null || requiresEvaluation ? null : rowBackedDefaultBody(baseData3, (id2) => pulledValueIds.has(id2));
114946
+ const backed = baseData3 === null || requiresEvaluation ? null : rowBackedDefaultBody(
114947
+ baseData3,
114948
+ (id2) => pulledValueIds.has(id2),
114949
+ context.classes
114950
+ );
114754
114951
  if (backed !== null && baseData3 !== null) {
114755
114952
  const existingReconstructedKeys = new Set(context.reconstructed.keys());
114756
114953
  const existingPendingValueIds = new Set(context.pendingValues.keys());
@@ -115349,9 +115546,11 @@ function classGenericEnvironment(context, classId) {
115349
115546
  function lowerRowBackedDefault(context, member, backed, baseData3, binding) {
115350
115547
  const baseDefault = isObjectRecord2(baseData3.defaultValue) ? baseData3.defaultValue : {};
115351
115548
  const storedClassId = stringOrNull2(baseDefault.classId);
115352
- const expression = annotatedValue(
115353
- parseCachedInitializer(context.parsedInitializers, binding.initializer)
115354
- ).expression;
115549
+ const sourceExpression = parseCachedInitializer(
115550
+ context.parsedInitializers,
115551
+ binding.initializer
115552
+ );
115553
+ const expression = annotatedValue(sourceExpression).expression;
115355
115554
  const bindingEnvironment = bindingGenericEnvironment(context, binding);
115356
115555
  if (member.kind === "class" && backed.kind === "class") {
115357
115556
  if (expression.kind === "litNull") {
@@ -115390,6 +115589,21 @@ function lowerRowBackedDefault(context, member, backed, baseData3, binding) {
115390
115589
  );
115391
115590
  const baseBody = backed.body;
115392
115591
  const body = {};
115592
+ if (isTileInstanceClass(classId, context.classes) && isObjectRecord2(baseBody)) {
115593
+ for (const key of ["assetClassId", "assetValueId", "variantId"]) {
115594
+ if (baseBody[key] !== void 0) body[key] = baseBody[key];
115595
+ }
115596
+ if (typeof body.assetValueId === "string") {
115597
+ retainStoredValueSubgraph(
115598
+ context,
115599
+ body.assetValueId,
115600
+ binding.source,
115601
+ /* @__PURE__ */ new Set(),
115602
+ null,
115603
+ environment
115604
+ );
115605
+ }
115606
+ }
115393
115607
  lowerConstructorProjections(
115394
115608
  context,
115395
115609
  schemaClass2,
@@ -115449,7 +115663,10 @@ function lowerRowBackedDefault(context, member, backed, baseData3, binding) {
115449
115663
  );
115450
115664
  }
115451
115665
  return {
115452
- value: body,
115666
+ value: applyTileAnnotation(context, sourceExpression, {
115667
+ classId,
115668
+ value: body
115669
+ }).value,
115453
115670
  classId: classId === currentClassId ? storedClassId : classId
115454
115671
  };
115455
115672
  }
@@ -115815,7 +116032,6 @@ function preserveReboundValue(context, currentValueId, nextValueId, binding) {
115815
116032
  );
115816
116033
  }
115817
116034
  function lowerStaticSeed(context, member, sourceExpression, source, valueId) {
115818
- const expression = annotatedValue(sourceExpression).expression;
115819
116035
  const rows = /* @__PURE__ */ new Map();
115820
116036
  const localizedTexts = /* @__PURE__ */ new Map();
115821
116037
  const existingBindingMemberIds = new Set(
@@ -115826,7 +116042,7 @@ function lowerStaticSeed(context, member, sourceExpression, source, valueId) {
115826
116042
  const root = lowerSeedRow(
115827
116043
  context,
115828
116044
  member,
115829
- expression,
116045
+ sourceExpression,
115830
116046
  source,
115831
116047
  valueId,
115832
116048
  source.label,
@@ -115880,7 +116096,8 @@ function lowerSeedRow(context, member, sourceExpression, source, valueId, path,
115880
116096
  localizedTexts,
115881
116097
  containerId,
115882
116098
  inheritedEnvironment,
115883
- authoredSlice
116099
+ authoredSlice,
116100
+ sourceExpression
115884
116101
  );
115885
116102
  }
115886
116103
  if (!isStoredCallableLiteral(resolvedMember.kind, expression) && memberInitializerRequiresEvaluation(
@@ -115889,6 +116106,11 @@ function lowerSeedRow(context, member, sourceExpression, source, valueId, path,
115889
116106
  expression,
115890
116107
  source
115891
116108
  )) {
116109
+ if (tileAssetAnnotation(sourceExpression) !== null) {
116110
+ throw new Error(
116111
+ "@tile requires a literal placement construction. Construct NeoTileInstance directly and assign its members in the initializer."
116112
+ );
116113
+ }
115892
116114
  const unattachedId = annotated.id === null ? unattachedAuthoredRowId(authoredSlice) : null;
115893
116115
  if (unattachedId !== null) {
115894
116116
  throw new Error(
@@ -116204,6 +116426,10 @@ function lowerSeedRow(context, member, sourceExpression, source, valueId, path,
116204
116426
  value = lowered.value;
116205
116427
  classId = typeof lowered.classId === "string" ? lowered.classId : null;
116206
116428
  }
116429
+ value = applyTileAnnotation(context, sourceExpression, {
116430
+ classId,
116431
+ value
116432
+ }).value;
116207
116433
  const row = {
116208
116434
  nodeType: "literal",
116209
116435
  id: valueId,
@@ -116288,7 +116514,7 @@ function structurallyConstructedClassId(context, member, expression) {
116288
116514
  if (expression.args.length === 0) return null;
116289
116515
  return effective.id;
116290
116516
  }
116291
- function lowerStructuralConstructionRow(context, member, expression, source, valueId, path, rows, localizedTexts, containerId, inheritedEnvironment, authoredSlice) {
116517
+ function lowerStructuralConstructionRow(context, member, expression, source, valueId, path, rows, localizedTexts, containerId, inheritedEnvironment, authoredSlice, annotatedExpression = expression) {
116292
116518
  const effectiveClass = constructedClass(
116293
116519
  context,
116294
116520
  member,
@@ -116416,7 +116642,10 @@ function lowerStructuralConstructionRow(context, member, expression, source, val
116416
116642
  nodeType: "literal",
116417
116643
  id: valueId,
116418
116644
  memberId: member.id,
116419
- value,
116645
+ value: applyTileAnnotation(context, annotatedExpression, {
116646
+ classId: effectiveClass.id,
116647
+ value
116648
+ }).value,
116420
116649
  classId: effectiveClass.id,
116421
116650
  constructorArgs,
116422
116651
  // Provenance is stamped explicitly on every construction this lowerer
@@ -116617,7 +116846,13 @@ function lowerValueRow(context, member, sourceExpression, expectedValueId, sourc
116617
116846
  authoredSlice,
116618
116847
  projectedConstructionEditable ? "reproject" : "preserve"
116619
116848
  );
116620
- addReconstructed(context, "value", expectedValueId, value2, source.source);
116849
+ addReconstructed(
116850
+ context,
116851
+ "value",
116852
+ expectedValueId,
116853
+ applyTileAnnotation(context, sourceExpression, value2),
116854
+ source.source
116855
+ );
116621
116856
  return expectedValueId;
116622
116857
  }
116623
116858
  const literalFields = valueFileFields(base);
@@ -116641,7 +116876,13 @@ function lowerValueRow(context, member, sourceExpression, expectedValueId, sourc
116641
116876
  environment,
116642
116877
  authoredSlice
116643
116878
  );
116644
- addReconstructed(context, "value", expectedValueId, value, source.source);
116879
+ addReconstructed(
116880
+ context,
116881
+ "value",
116882
+ expectedValueId,
116883
+ applyTileAnnotation(context, sourceExpression, value),
116884
+ source.source
116885
+ );
116645
116886
  return expectedValueId;
116646
116887
  }
116647
116888
  function lowerValueBody(context, member, expression, base, source, environment, authoredSlice) {
@@ -118894,6 +119135,38 @@ function memberPath(expression) {
118894
119135
  const receiver = memberPath(expression.receiver);
118895
119136
  return receiver === null ? null : `${receiver}.${expression.name}`;
118896
119137
  }
119138
+ function applyTileAnnotation(context, expression, value) {
119139
+ const assetName = tileAssetAnnotation(expression);
119140
+ if (assetName === null) return value;
119141
+ const instanceBase = requiredClassByName(context, "NeoTileInstance");
119142
+ if (typeof value.classId !== "string" || !classAssignableToClass2(context, value.classId, instanceBase.id)) {
119143
+ throw new Error("@tile requires a NeoTileInstance construction.");
119144
+ }
119145
+ const asset = requiredClassByName(context, assetName);
119146
+ const tileBase = requiredClassByName(context, "NeoTile");
119147
+ if (!classAssignableToClass2(context, asset.id, tileBase.id)) {
119148
+ throw new Error(`@tile asset '${assetName}' must name a tile class.`);
119149
+ }
119150
+ if (asset.declarationModifier === "abstract") {
119151
+ throw new Error(
119152
+ `@tile asset '${assetName}' must be a concrete tile class.`
119153
+ );
119154
+ }
119155
+ if (asset.genericParameters.length > 0) {
119156
+ throw new Error(
119157
+ `@tile asset '${assetName}' must be a non-generic tile class.`
119158
+ );
119159
+ }
119160
+ if (!isObjectRecord2(value.value))
119161
+ throw new Error("@tile requires a stored placement body.");
119162
+ const body = value.value;
119163
+ if (body.assetClassId !== asset.id && (body.assetValueId !== void 0 || body.variantId !== void 0)) {
119164
+ throw new Error(
119165
+ "Cannot change a tile asset while the placement has overrides or a variant. Recreate the placement to select a different tile."
119166
+ );
119167
+ }
119168
+ return { ...value, value: { ...body, assetClassId: asset.id } };
119169
+ }
118897
119170
  function annotatedValue(expression) {
118898
119171
  if (expression.kind !== "annotated") return { expression, id: null };
118899
119172
  const id2 = annotationId2(expression.annotations);
@@ -119135,6 +119408,27 @@ function emitValueBody(context, member, value, visited, targetTyped, environment
119135
119408
  );
119136
119409
  }
119137
119410
  function classValue(context, member, value, visited, targetTyped, outerEnvironment) {
119411
+ const source = classValueBody(
119412
+ context,
119413
+ member,
119414
+ value,
119415
+ visited,
119416
+ targetTyped,
119417
+ outerEnvironment
119418
+ );
119419
+ const assetId = isObjectRecord2(value.value) ? value.value.assetClassId : void 0;
119420
+ if (typeof assetId !== "string") return source;
119421
+ const classId = stringOrNull2(value.classId) ?? stringField3(member, "classId");
119422
+ if (!isTileInstanceClass(classId, context.manifestClasses)) return source;
119423
+ const asset = context.manifestClasses.get(assetId);
119424
+ if (asset === void 0)
119425
+ throw new Error(
119426
+ `Tile placement ${String(value.id)} references unknown asset class ${assetId}.`
119427
+ );
119428
+ return `@tile(${asset.name})
119429
+ ${source}`;
119430
+ }
119431
+ function classValueBody(context, member, value, visited, targetTyped, outerEnvironment) {
119138
119432
  if (value.value === null) return "null";
119139
119433
  const partial = member.payload === 1 /* Partial */;
119140
119434
  const classId = stringOrNull2(value.classId) ?? stringField3(member, "classId");
@@ -131846,7 +132140,7 @@ var init_registry2 = __esm({
131846
132140
  "schema-contract/registry.mjs"() {
131847
132141
  "use strict";
131848
132142
  PROJECT_SCHEMA_CONTRACT = Object.freeze({
131849
- cliVersion: "0.49.0",
132143
+ cliVersion: "0.50.0",
131850
132144
  projectFileUploadBatchSize: 32,
131851
132145
  documentRecords: {
131852
132146
  member: {
@@ -138896,7 +139190,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
138896
139190
  async function main() {
138897
139191
  const args = parseArgs(process.argv.slice(2));
138898
139192
  if (args.command === "--version") {
138899
- console.log("0.49.0");
139193
+ console.log("0.50.0");
138900
139194
  return;
138901
139195
  }
138902
139196
  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.49.0",
3
+ "version": "0.50.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.49.0 -->
12
+ <!-- reviewed-through-cli: 0.50.0 -->
13
13
 
14
14
  # Neo Compose CLI
15
15
 
@@ -24,6 +24,29 @@ For world layer links, derive a concrete project class from
24
24
  `targetLayer` relation. Do not add value-level target metadata or ask painting
25
25
  operations to create/repair relation targets.
26
26
 
27
+ ## Tile placements
28
+
29
+ Use `@tile(VoidTile)` on a `NeoTileInstance` construction to select its tile
30
+ class. The named spelling `@tile(asset: VoidTile)` is equivalent; the asset is
31
+ required and only the argument label is optional.
32
+
33
+ ```neo
34
+ @tile(VoidTile)
35
+ new NeoTileInstance() {
36
+ Cell = new(9, -4)
37
+ }
38
+ ```
39
+
40
+ Pull emits the positional spelling. The annotation writes the placement's
41
+ `assetClassId` metadata and does not construct a tile value or add a generic
42
+ argument. The asset must be a concrete, non-generic descendant of `NeoTile`.
43
+ Use the annotation on persisted literal constructions; executable NeoScript
44
+ and evaluated placement constructors reject it.
45
+ The grid's imports and the tile's compatible-layer relations still constrain
46
+ where the placement can be used. Existing placement overrides remain attached
47
+ when the selected asset is unchanged; recreate a placement to change its asset
48
+ when it has overrides or a variant.
49
+
27
50
  ## Object variants
28
51
 
29
52
  A variant is a named configuration of one `NeoObject`-derived class: how to
@@ -88,7 +88,7 @@ wrappers.
88
88
  The marker near the top of `SKILL.md` must exactly match the package version:
89
89
 
90
90
  ```html
91
- <!-- reviewed-through-cli: 0.49.0 -->
91
+ <!-- reviewed-through-cli: 0.50.0 -->
92
92
  ```
93
93
 
94
94
  The quoted version above is checked too, so this instruction cannot go stale