@neocompose/cli 0.31.2 → 0.31.4

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/dist/neo.mjs CHANGED
@@ -1253,7 +1253,15 @@ function neoProjectPathHasIgnoredDirectory(path) {
1253
1253
  }
1254
1254
  function isNeoVariantFamilyTypeName(name) {
1255
1255
  if (name === NEO_VARIANT_TYPE_NAME) return true;
1256
- return name === NEO_VARIANT_FOLDER_TYPE_NAME;
1256
+ if (name === NEO_VARIANT_FOLDER_TYPE_NAME) return true;
1257
+ if (name === NEO_LOOKUP_VARIANT_TYPE_NAME) return true;
1258
+ return name === NEO_LOOKUP_VARIANT_FOLDER_TYPE_NAME;
1259
+ }
1260
+ function isNeoVariantTypeName(name) {
1261
+ return name === NEO_VARIANT_TYPE_NAME || name === NEO_LOOKUP_VARIANT_TYPE_NAME;
1262
+ }
1263
+ function isNeoVariantFolderTypeName(name) {
1264
+ return name === NEO_VARIANT_FOLDER_TYPE_NAME || name === NEO_LOOKUP_VARIANT_FOLDER_TYPE_NAME;
1257
1265
  }
1258
1266
  function isNeoScriptStructuredLeafTypeName(name) {
1259
1267
  return Object.prototype.hasOwnProperty.call(
@@ -1314,7 +1322,7 @@ function vectorTypes() {
1314
1322
  members: structuredLeafFieldProperties(name)
1315
1323
  }));
1316
1324
  }
1317
- 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_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;
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;
1318
1326
  var init_language_spec = __esm({
1319
1327
  "../packages/neoscript-language/src/language-spec.ts"() {
1320
1328
  "use strict";
@@ -1404,6 +1412,8 @@ var init_language_spec = __esm({
1404
1412
  ];
1405
1413
  NEO_VARIANT_TYPE_NAME = "NeoVariant";
1406
1414
  NEO_VARIANT_FOLDER_TYPE_NAME = "NeoVariantFolder";
1415
+ NEO_LOOKUP_VARIANT_TYPE_NAME = "NeoLookupVariant";
1416
+ NEO_LOOKUP_VARIANT_FOLDER_TYPE_NAME = "NeoLookupVariantFolder";
1407
1417
  NEO_VARIANT_SCOPE_NAME = "Variants";
1408
1418
  NEO_VARIANT_RESERVED_NAME = "Base";
1409
1419
  NEO_VARIANT_FOLDER_PATH_SEPARATOR = "/";
@@ -7928,7 +7938,7 @@ var NEOSCRIPT_COMPILER_REVISION;
7928
7938
  var init_strict_ir = __esm({
7929
7939
  "../packages/neoscript-language/src/strict-ir.ts"() {
7930
7940
  "use strict";
7931
- NEOSCRIPT_COMPILER_REVISION = 10;
7941
+ NEOSCRIPT_COMPILER_REVISION = 11;
7932
7942
  }
7933
7943
  });
7934
7944
 
@@ -11464,10 +11474,11 @@ var init_strict_resolver = __esm({
11464
11474
  * over {@link NeoScriptTypeRef} already handles a `named` type.
11465
11475
  */
11466
11476
  variantExpression(classType, variant, pos) {
11467
- const variantType = this.project.typeByName.get(NEO_VARIANT_TYPE_NAME);
11477
+ const typeName = variant?.valueTypeId == null ? NEO_VARIANT_TYPE_NAME : NEO_LOOKUP_VARIANT_TYPE_NAME;
11478
+ const variantType = this.project.typeByName.get(typeName);
11468
11479
  if (variantType === void 0) {
11469
11480
  throw new CompileError(
11470
- `The seeded '${NEO_VARIANT_TYPE_NAME}' class is not in this project's type table, so '${classType.name}.${NEO_VARIANT_SCOPE_NAME}' cannot be typed.`,
11481
+ `The seeded '${typeName}' class is not in this project's type table, so '${classType.name}.${NEO_VARIANT_SCOPE_NAME}' cannot be typed.`,
11471
11482
  pos
11472
11483
  );
11473
11484
  }
@@ -11475,24 +11486,31 @@ var init_strict_resolver = __esm({
11475
11486
  pointer: {
11476
11487
  type: "variant" /* Variant */,
11477
11488
  classId: classType.id,
11478
- variantId: variant === null ? null : variant.id
11489
+ variantId: variant === null ? null : variant.id,
11490
+ rowValueId: null
11479
11491
  },
11480
11492
  type: {
11481
11493
  kind: "named",
11482
11494
  typeId: variantType.id,
11483
- typeArguments: [{ kind: "named", typeId: classType.id }]
11495
+ typeArguments: [
11496
+ { kind: "named", typeId: classType.id },
11497
+ ...variant?.valueTypeId == null ? [] : [{ kind: "named", typeId: variant.valueTypeId }]
11498
+ ]
11484
11499
  }
11485
11500
  };
11486
11501
  }
11487
- /** The `TObject` of a `NeoVariant<TObject>` type, or null for anything else. */
11488
- variantTargetClassId(type) {
11502
+ variantTypeInfo(type) {
11489
11503
  if (type.kind !== "named") return null;
11490
11504
  const declared = this.project.typeById.get(type.typeId);
11491
11505
  if (declared === void 0) return null;
11492
- if (declared.name !== NEO_VARIANT_TYPE_NAME) return null;
11506
+ if (declared.name !== NEO_VARIANT_TYPE_NAME && declared.name !== NEO_LOOKUP_VARIANT_TYPE_NAME)
11507
+ return null;
11493
11508
  const argument2 = type.typeArguments?.[0];
11494
11509
  if (argument2 === void 0 || argument2.kind !== "named") return null;
11495
- return argument2.typeId;
11510
+ return {
11511
+ targetClassId: argument2.typeId,
11512
+ valueType: declared.name === NEO_LOOKUP_VARIANT_TYPE_NAME ? type.typeArguments?.[1] ?? null : null
11513
+ };
11496
11514
  }
11497
11515
  /**
11498
11516
  * P67 §4.1/§4.2 — `<variant>.Initialize()` and `<source>.ToVariant(<variant>)`.
@@ -11512,23 +11530,35 @@ var init_strict_resolver = __esm({
11512
11530
  }
11513
11531
  const receiver = this.resolveExpression(callee.receiver, scope);
11514
11532
  if (isInitialize) {
11515
- const targetClassId2 = this.variantTargetClassId(receiver.type);
11516
- if (targetClassId2 === null) return null;
11533
+ const variantInfo2 = this.variantTypeInfo(receiver.type);
11534
+ if (variantInfo2 === null) return null;
11517
11535
  if (callee.optional === true) {
11518
11536
  throw new CompileError(
11519
11537
  `\`?.\` is not valid on ${NEO_VARIANT_TYPE_NAME}.${VARIANT_INITIALIZE_NAME}; narrow the variant with a null check or force-unwrap it first.`,
11520
11538
  pos
11521
11539
  );
11522
11540
  }
11541
+ const expectedArgCount = variantInfo2.valueType === null ? 0 : 1;
11523
11542
  requireArgCount(
11524
- `${NEO_VARIANT_TYPE_NAME}.${VARIANT_INITIALIZE_NAME}`,
11543
+ `${variantInfo2.valueType === null ? NEO_VARIANT_TYPE_NAME : NEO_LOOKUP_VARIANT_TYPE_NAME}.${VARIANT_INITIALIZE_NAME}`,
11525
11544
  argumentsList2,
11526
- 0,
11545
+ expectedArgCount,
11527
11546
  pos
11528
11547
  );
11548
+ const row2 = variantInfo2.valueType === null ? null : this.resolveExpression(
11549
+ argumentsList2[0],
11550
+ scope,
11551
+ variantInfo2.valueType
11552
+ );
11553
+ if (row2 !== null && !isNeoScriptTypeAssignable(row2.type, variantInfo2.valueType, this.project)) {
11554
+ throw new CompileError(
11555
+ `Lookup variant row expected ${this.describe(variantInfo2.valueType)} but got ${this.describe(row2.type)}.`,
11556
+ pos
11557
+ );
11558
+ }
11529
11559
  const targetType2 = {
11530
11560
  kind: "named",
11531
- typeId: targetClassId2
11561
+ typeId: variantInfo2.targetClassId
11532
11562
  };
11533
11563
  return {
11534
11564
  pointer: {
@@ -11537,6 +11567,7 @@ var init_strict_resolver = __esm({
11537
11567
  type: "variantInitialize" /* VariantInitialize */,
11538
11568
  info: {
11539
11569
  variantPointer: receiver.pointer,
11570
+ ...row2 === null ? {} : { rowPointer: row2.pointer },
11540
11571
  schemaClassInfo: this.requireClassWireType(targetType2, pos)
11541
11572
  }
11542
11573
  }
@@ -11558,25 +11589,36 @@ var init_strict_resolver = __esm({
11558
11589
  pos
11559
11590
  );
11560
11591
  }
11592
+ const argumentAst = argumentsList2[0];
11593
+ if (argumentAst === void 0) return null;
11594
+ const argument2 = this.resolveExpression(argumentAst, scope);
11595
+ const variantInfo = this.variantTypeInfo(argument2.type);
11596
+ if (variantInfo === null) {
11597
+ throw new CompileError(
11598
+ `${receiverType.name}.${VARIANT_APPLY_NAME} takes a ${NEO_VARIANT_TYPE_NAME}<${receiverType.name}>, got ${this.describe(argument2.type)}.`,
11599
+ pos
11600
+ );
11601
+ }
11561
11602
  requireArgCount(
11562
11603
  `${receiverType.name}.${VARIANT_APPLY_NAME}`,
11563
11604
  argumentsList2,
11564
- 1,
11605
+ variantInfo.valueType === null ? 1 : 2,
11565
11606
  pos
11566
11607
  );
11567
- const argumentAst = argumentsList2[0];
11568
- if (argumentAst === void 0) return null;
11569
- const argument2 = this.resolveExpression(argumentAst, scope);
11570
- const targetClassId = this.variantTargetClassId(argument2.type);
11571
- if (targetClassId === null) {
11608
+ const row = variantInfo.valueType === null ? null : this.resolveExpression(
11609
+ argumentsList2[1],
11610
+ scope,
11611
+ variantInfo.valueType
11612
+ );
11613
+ if (row !== null && !isNeoScriptTypeAssignable(row.type, variantInfo.valueType, this.project)) {
11572
11614
  throw new CompileError(
11573
- `${receiverType.name}.${VARIANT_APPLY_NAME} takes a ${NEO_VARIANT_TYPE_NAME}<${receiverType.name}>, got ${this.describe(argument2.type)}.`,
11615
+ `Lookup variant row expected ${this.describe(variantInfo.valueType)} but got ${this.describe(row.type)}.`,
11574
11616
  pos
11575
11617
  );
11576
11618
  }
11577
11619
  const targetType = {
11578
11620
  kind: "named",
11579
- typeId: targetClassId
11621
+ typeId: variantInfo.targetClassId
11580
11622
  };
11581
11623
  if (!isNeoScriptTypeAssignable(receiver.type, targetType, this.project)) {
11582
11624
  throw new CompileError(
@@ -11592,6 +11634,7 @@ var init_strict_resolver = __esm({
11592
11634
  info: {
11593
11635
  receiverPointer: receiver.pointer,
11594
11636
  variantPointer: argument2.pointer,
11637
+ ...row === null ? {} : { rowPointer: row.pointer },
11595
11638
  schemaClassInfo: this.requireClassWireType(receiver.type, pos)
11596
11639
  }
11597
11640
  }
@@ -14772,7 +14815,7 @@ function applyDelimiterToken(stack, token, lambdaBraceOffsets) {
14772
14815
  if (token.text === "{" && lambdaBraceOffsets.has(token.start)) {
14773
14816
  for (let index = stack.length - 1; index >= 0; index--) {
14774
14817
  const candidate = stack[index];
14775
- if (candidate?.text === "(" && candidate.contributes) {
14818
+ if (candidate?.text === "(" && candidate.contributes && candidate.openingLine === token.range.start.line) {
14776
14819
  restores = candidate;
14777
14820
  break;
14778
14821
  }
@@ -14781,6 +14824,7 @@ function applyDelimiterToken(stack, token, lambdaBraceOffsets) {
14781
14824
  }
14782
14825
  stack.push({
14783
14826
  text: token.text,
14827
+ openingLine: token.range.start.line,
14784
14828
  contributes: true,
14785
14829
  ...restores ? { restores } : {}
14786
14830
  });
@@ -14804,6 +14848,7 @@ function delimiterDepth(stack) {
14804
14848
  function cloneDelimiterStack(stack) {
14805
14849
  const clones = stack.map((frame) => ({
14806
14850
  text: frame.text,
14851
+ openingLine: frame.openingLine,
14807
14852
  contributes: frame.contributes
14808
14853
  }));
14809
14854
  for (let index = 0; index < stack.length; index++) {
@@ -17705,6 +17750,7 @@ var init_project_schema_contract_generated = __esm({
17705
17750
  "collectionValueId",
17706
17751
  "declaredTypeInfo",
17707
17752
  "targetTypeInfo",
17753
+ "variantValueTypeInfo",
17708
17754
  "dialogueGroupId",
17709
17755
  "code",
17710
17756
  "setterCode",
@@ -17756,7 +17802,8 @@ var init_project_schema_contract_generated = __esm({
17756
17802
  "variant-folder": [
17757
17803
  "id",
17758
17804
  "classId",
17759
- "path"
17805
+ "path",
17806
+ "binding"
17760
17807
  ],
17761
17808
  "interface": [
17762
17809
  "id",
@@ -17967,7 +18014,9 @@ var init_project_schema_contract_generated = __esm({
17967
18014
  "AnimationSegmentTrack",
17968
18015
  "SpriteAnimationSegmentTrack",
17969
18016
  "Variant",
17970
- "VariantFolder"
18017
+ "VariantFolder",
18018
+ "LookupVariant",
18019
+ "LookupVariantFolder"
17971
18020
  ],
17972
18021
  "NeoTextureType": [
17973
18022
  "Default",
@@ -19832,7 +19881,15 @@ var init_project_source_construction_diagnostics = __esm({
19832
19881
 
19833
19882
  // ../packages/neoscript-language/src/project-source-variants.ts
19834
19883
  function neoVariantConstructionSiteCall(className, argumentNames, initializerNames) {
19835
- if (className !== NEO_VARIANT_TYPE_NAME) return null;
19884
+ if (className === NEO_LOOKUP_VARIANT_FOLDER_TYPE_NAME) {
19885
+ return {
19886
+ argumentNames: argumentNames.filter(
19887
+ (name) => name !== "collection" && name !== "collectionValue"
19888
+ ),
19889
+ initializerNames
19890
+ };
19891
+ }
19892
+ if (!isNeoVariantTypeName(className)) return null;
19836
19893
  const settled = [...initializerNames];
19837
19894
  const remaining = [];
19838
19895
  for (const name of argumentNames) {
@@ -19851,12 +19908,15 @@ function collectNeoVariantDeclarations(documents) {
19851
19908
  for (const declaration of document.declarations) {
19852
19909
  if (declaration.kind !== "global") continue;
19853
19910
  if (!isNeoVariantFamilyTypeName(declaration.type.name)) continue;
19854
- const family = declaration.type.name === NEO_VARIANT_TYPE_NAME ? "variant" : "folder";
19911
+ const family = isNeoVariantTypeName(declaration.type.name) ? "variant" : "folder";
19855
19912
  declarations.push({
19856
19913
  uri,
19857
19914
  declaration,
19858
19915
  family,
19916
+ lookup: declaration.type.name === NEO_LOOKUP_VARIANT_TYPE_NAME || declaration.type.name === NEO_LOOKUP_VARIANT_FOLDER_TYPE_NAME,
19859
19917
  owningClassName: declaration.type.typeArguments[0]?.name ?? null,
19918
+ valueClassName: declaration.type.typeArguments[1]?.name ?? null,
19919
+ collectionPath: family === "folder" && declaration.type.name === NEO_LOOKUP_VARIANT_FOLDER_TYPE_NAME ? folderCollectionPath(declaration) : null,
19860
19920
  folderSymbol: neoVariantFolderAnnotationSymbol(declaration.annotations),
19861
19921
  path: family === "folder" ? folderPath(declaration) : null
19862
19922
  });
@@ -19887,10 +19947,32 @@ function neoVariantFolderAnnotationSymbol(annotations) {
19887
19947
  function folderPath(declaration) {
19888
19948
  const expression = parseInitializer(declaration);
19889
19949
  if (expression === null || expression.kind !== "new") return null;
19890
- const argument2 = expression.args[0];
19950
+ const pathIndex = expression.argumentNames?.findIndex(
19951
+ (name) => name === "path"
19952
+ );
19953
+ const argument2 = expression.args[pathIndex !== void 0 && pathIndex >= 0 ? pathIndex : 0];
19891
19954
  if (argument2 === void 0 || argument2.kind !== "litString") return null;
19892
19955
  return argument2.value;
19893
19956
  }
19957
+ function folderCollectionPath(declaration) {
19958
+ const expression = parseInitializer(declaration);
19959
+ if (expression === null || expression.kind !== "new") return null;
19960
+ const index = expression.argumentNames?.findIndex(
19961
+ (name) => name === "collection"
19962
+ );
19963
+ if (index === void 0 || index < 0) return null;
19964
+ const argument2 = expression.args[index];
19965
+ if (argument2 === void 0) return null;
19966
+ const parts = [];
19967
+ let cursor = argument2;
19968
+ while (cursor.kind === "member") {
19969
+ parts.unshift(cursor.name);
19970
+ cursor = cursor.receiver;
19971
+ }
19972
+ if (cursor.kind !== "ident") return null;
19973
+ parts.unshift(cursor.name);
19974
+ return parts.join(".");
19975
+ }
19894
19976
  function parseInitializer(declaration) {
19895
19977
  try {
19896
19978
  return unwrapAnnotated(parseExpression(declaration.initializer.text));
@@ -19932,7 +20014,7 @@ function buildNeoVariantScopeIndex(documents) {
19932
20014
  const className = folder.owningClassName;
19933
20015
  if (folder.family !== "folder" || className === null) continue;
19934
20016
  if (folder.path === null) continue;
19935
- const segments = folder.path.split(NEO_VARIANT_FOLDER_PATH_SEPARATOR);
20017
+ const segments = folder.path.length === 0 ? [] : folder.path.split(NEO_VARIANT_FOLDER_PATH_SEPARATOR);
19936
20018
  segments.forEach((segment, index) => {
19937
20019
  const parent = dottedScope(
19938
20020
  className,
@@ -20077,8 +20159,29 @@ function validateVariantDeclarations(documents) {
20077
20159
  validateScopeName(entry, entry.declaration.name, diagnostics);
20078
20160
  }
20079
20161
  const folderPathsByClass = /* @__PURE__ */ new Map();
20162
+ const rootFolderByClass = /* @__PURE__ */ new Map();
20080
20163
  for (const folder of folders) {
20081
- validateFolderDeclaration(folder, folderPathsByClass, diagnostics);
20164
+ validateFolderDeclaration(
20165
+ folder,
20166
+ folderPathsByClass,
20167
+ classesByName,
20168
+ diagnostics
20169
+ );
20170
+ if (folder.path === "" && folder.owningClassName !== null) {
20171
+ const prior = rootFolderByClass.get(folder.owningClassName);
20172
+ if (prior !== void 0) {
20173
+ diagnostics.push(
20174
+ variantDiagnostic(
20175
+ folder,
20176
+ folder.declaration.initializer.range,
20177
+ "duplicate-root-lookup-variant-folder",
20178
+ `Class '${folder.owningClassName}' already has root lookup folder '${prior.declaration.name}'; at most one bound folder may enumerate the class root.`
20179
+ )
20180
+ );
20181
+ } else {
20182
+ rootFolderByClass.set(folder.owningClassName, folder);
20183
+ }
20184
+ }
20082
20185
  }
20083
20186
  const foldersBySymbol = /* @__PURE__ */ new Map();
20084
20187
  for (const folder of folders) {
@@ -20125,7 +20228,7 @@ function validateVariantDeclarations(documents) {
20125
20228
  for (const folder of folders) {
20126
20229
  const owningClassName2 = folder.owningClassName;
20127
20230
  if (owningClassName2 === null || folder.path === null) continue;
20128
- const segments = folder.path.split(NEO_VARIANT_FOLDER_PATH_SEPARATOR);
20231
+ const segments = folder.path.length === 0 ? [] : folder.path.split(NEO_VARIANT_FOLDER_PATH_SEPARATOR);
20129
20232
  segments.forEach((segment, index) => {
20130
20233
  claimFolderSegment(
20131
20234
  owningClassName2,
@@ -20146,12 +20249,49 @@ function validateVariantDeclarations(documents) {
20146
20249
  for (const variant of variants) {
20147
20250
  validateVariantInitializer(variant, diagnostics);
20148
20251
  if (variant.owningClassName === null) continue;
20252
+ if (variant.folderSymbol === null && rootFolderByClass.has(variant.owningClassName)) {
20253
+ diagnostics.push(
20254
+ variantDiagnostic(
20255
+ variant,
20256
+ variant.declaration.nameRange,
20257
+ "root-lookup-variant-folder-conflict",
20258
+ `Class '${variant.owningClassName}' has a root lookup folder, so variants at the class root must belong to that bound folder rather than coexist as plain root variants.`
20259
+ )
20260
+ );
20261
+ continue;
20262
+ }
20149
20263
  const scopePath = resolveVariantScopePath(
20150
20264
  variant,
20151
20265
  foldersBySymbol,
20152
20266
  diagnostics
20153
20267
  );
20154
20268
  if (scopePath === null) continue;
20269
+ const folder = variant.folderSymbol === null ? null : foldersBySymbol.get(
20270
+ `${variant.owningClassName} ${variant.folderSymbol}`
20271
+ ) ?? null;
20272
+ if ((folder?.lookup ?? false) !== variant.lookup) {
20273
+ const argument2 = variantFolderAnnotationArgument(variant);
20274
+ diagnostics.push(
20275
+ variantDiagnostic(
20276
+ variant,
20277
+ argument2?.range ?? variant.declaration.type.range,
20278
+ "variant-folder-family-mismatch",
20279
+ variant.lookup ? `NeoLookupVariant requires a NeoLookupVariantFolder with a collection binding.` : `NeoVariant cannot be placed in a NeoLookupVariantFolder; lookup variants receive the bound row parameter.`
20280
+ )
20281
+ );
20282
+ continue;
20283
+ }
20284
+ if (variant.lookup && folder !== null && variant.valueClassName !== folder.valueClassName) {
20285
+ diagnostics.push(
20286
+ variantDiagnostic(
20287
+ variant,
20288
+ variant.declaration.type.range,
20289
+ "lookup-variant-value-type-mismatch",
20290
+ `Lookup variant '${variant.declaration.name}' uses '${variant.valueClassName ?? "<missing>"}', but folder '${folder.declaration.name}' expects '${folder.valueClassName ?? "<missing>"}'.`
20291
+ )
20292
+ );
20293
+ continue;
20294
+ }
20155
20295
  claim(
20156
20296
  variant.owningClassName,
20157
20297
  scopePath,
@@ -20273,13 +20413,14 @@ function validateVariantInitializer(variant, diagnostics) {
20273
20413
  function validateInitialize(variant, argument2, diagnostics) {
20274
20414
  const lambda = unwrapAnnotated(argument2);
20275
20415
  if (lambda.kind !== "lambda") return;
20276
- if (lambda.params.length > 0) {
20416
+ const expected = variant.lookup ? 1 : 0;
20417
+ if (lambda.params.length !== expected) {
20277
20418
  diagnostics.push(
20278
20419
  variantDiagnostic(
20279
20420
  variant,
20280
20421
  variant.declaration.initializer.range,
20281
20422
  "variant-initialize-arity",
20282
- `A variant's initialize delegate takes no parameters and returns ${variant.owningClassName ?? "the target class"}; it was declared with ${lambda.params.length}.`
20423
+ variant.lookup ? `A lookup variant's initialize delegate takes exactly one ${variant.valueClassName ?? "row"} parameter and returns ${variant.owningClassName ?? "the target class"}; it was declared with ${lambda.params.length}.` : `A variant's initialize delegate takes no parameters and returns ${variant.owningClassName ?? "the target class"}; it was declared with ${lambda.params.length}.`
20283
20424
  )
20284
20425
  );
20285
20426
  }
@@ -20299,13 +20440,14 @@ function validateInitialize(variant, argument2, diagnostics) {
20299
20440
  function validateApply(variant, argument2, diagnostics) {
20300
20441
  const lambda = unwrapAnnotated(argument2);
20301
20442
  if (lambda.kind !== "lambda") return;
20302
- if (lambda.params.length !== 1) {
20443
+ const expected = variant.lookup ? 2 : 1;
20444
+ if (lambda.params.length !== expected) {
20303
20445
  diagnostics.push(
20304
20446
  variantDiagnostic(
20305
20447
  variant,
20306
20448
  variant.declaration.initializer.range,
20307
20449
  "variant-apply-arity",
20308
- `A variant's apply delegate takes exactly one ${variant.owningClassName ?? "target class"} parameter; it was declared with ${lambda.params.length}.`
20450
+ variant.lookup ? `A lookup variant's apply delegate takes exactly (${variant.owningClassName ?? "source"}, ${variant.valueClassName ?? "row"}); it was declared with ${lambda.params.length} parameters.` : `A variant's apply delegate takes exactly one ${variant.owningClassName ?? "target class"} parameter; it was declared with ${lambda.params.length}.`
20309
20451
  )
20310
20452
  );
20311
20453
  }
@@ -20322,7 +20464,7 @@ function validateApply(variant, argument2, diagnostics) {
20322
20464
  )
20323
20465
  );
20324
20466
  }
20325
- function validateFolderDeclaration(folder, folderPathsByClass, diagnostics) {
20467
+ function validateFolderDeclaration(folder, folderPathsByClass, classesByName, diagnostics) {
20326
20468
  if (folder.path === null) {
20327
20469
  diagnostics.push(
20328
20470
  variantDiagnostic(
@@ -20334,7 +20476,43 @@ function validateFolderDeclaration(folder, folderPathsByClass, diagnostics) {
20334
20476
  );
20335
20477
  return;
20336
20478
  }
20479
+ if (folder.path.length === 0 && !folder.lookup) {
20480
+ diagnostics.push(
20481
+ variantDiagnostic(
20482
+ folder,
20483
+ folder.declaration.initializer.range,
20484
+ "invalid-variant-folder-path",
20485
+ "Only a bound NeoLookupVariantFolder may use the class-root path ''."
20486
+ )
20487
+ );
20488
+ return;
20489
+ }
20490
+ if (folder.lookup) {
20491
+ const [className, memberName, ...rest] = folder.collectionPath?.split(".") ?? [];
20492
+ const collectionMember = className !== void 0 && memberName !== void 0 && rest.length === 0 ? classesByName.get(className)?.members.find((member) => member.name === memberName) : void 0;
20493
+ const entryType = collectionMember?.type.name === "List" ? collectionMember.type.typeArguments[0]?.name : void 0;
20494
+ if (entryType === void 0) {
20495
+ diagnostics.push(
20496
+ variantDiagnostic(
20497
+ folder,
20498
+ folder.declaration.initializer.range,
20499
+ "invalid-lookup-variant-folder-collection",
20500
+ `Lookup variant folder '${folder.declaration.name}' requires collection: Class.ListMember whose entries are Class values.`
20501
+ )
20502
+ );
20503
+ } else if (entryType !== folder.valueClassName) {
20504
+ diagnostics.push(
20505
+ variantDiagnostic(
20506
+ folder,
20507
+ folder.declaration.type.range,
20508
+ "lookup-folder-value-type-mismatch",
20509
+ `Lookup variant folder '${folder.declaration.name}' declares TValue '${folder.valueClassName ?? "<missing>"}', but collection '${folder.collectionPath}' contains '${entryType}'.`
20510
+ )
20511
+ );
20512
+ }
20513
+ }
20337
20514
  for (const segment of folder.path.split(NEO_VARIANT_FOLDER_PATH_SEPARATOR)) {
20515
+ if (segment.length === 0) continue;
20338
20516
  validateScopeName(folder, segment, diagnostics);
20339
20517
  }
20340
20518
  if (folder.owningClassName === null) return;
@@ -20386,7 +20564,7 @@ function validateOwningClass(entry, classesByName, diagnostics) {
20386
20564
  entry,
20387
20565
  entry.declaration.type.range,
20388
20566
  "variant-target-missing",
20389
- `${entry.declaration.type.name} names the class it belongs to as its single type argument.`
20567
+ `${entry.declaration.type.name} names the class it belongs to as its first type argument.`
20390
20568
  )
20391
20569
  );
20392
20570
  return;
@@ -20459,7 +20637,7 @@ function validateVariantTypePositions(documents, diagnostics) {
20459
20637
  }
20460
20638
  }
20461
20639
  function reportVariantTypePositions(uri, type, diagnostics, root = "nested") {
20462
- if (type.name === NEO_VARIANT_TYPE_NAME && root === "reference") {
20640
+ if (isNeoVariantTypeName(type.name) && root === "reference") {
20463
20641
  for (const argument2 of type.typeArguments) {
20464
20642
  reportVariantTypePositions(uri, argument2, diagnostics);
20465
20643
  }
@@ -20481,7 +20659,7 @@ function reportVariantTypePositions(uri, type, diagnostics, root = "nested") {
20481
20659
  }
20482
20660
  }
20483
20661
  function variantTypePositionMessage(typeName, root) {
20484
- if (typeName === NEO_VARIANT_FOLDER_TYPE_NAME) {
20662
+ if (isNeoVariantFolderTypeName(typeName)) {
20485
20663
  return `'${typeName}' is reference-only: it declares variant folders as top-level globals and is never a member, entry, parameter, or generic binding type.`;
20486
20664
  }
20487
20665
  return `'${typeName}' cannot be nested inside another type. A variant reference is a member, parameter, or return type in its own right (P67 \xA76); wrapping it in a collection, lookup, or 'Reference' asks for an instance of a class that has none.`;
@@ -21178,7 +21356,7 @@ function validateProjectSourceSemantics(documents) {
21178
21356
  }
21179
21357
  function annotationContextForDeclaration(declaration) {
21180
21358
  if (declaration.kind === "class") return { kind: "class", declaration };
21181
- if (declaration.kind === "global" && declaration.type.name === NEO_VARIANT_TYPE_NAME) {
21359
+ if (declaration.kind === "global" && isNeoVariantTypeName(declaration.type.name)) {
21182
21360
  return { kind: "variantGlobal", declaration };
21183
21361
  }
21184
21362
  return { kind: "schema" };
@@ -21843,10 +22021,11 @@ function fieldsForAnnotation(name, context, environment) {
21843
22021
  }
21844
22022
  function settingsFields(context) {
21845
22023
  if (context.kind === "variantGlobal") {
22024
+ const lookup = context.declaration.type.name !== NEO_VARIANT_TYPE_NAME;
21846
22025
  return [
21847
22026
  {
21848
22027
  name: NEO_VARIANT_FOLDER_SETTING,
21849
- type: `${NEO_VARIANT_FOLDER_TYPE_NAME}<${context.declaration.type.typeArguments[0]?.name ?? "TObject"}>`
22028
+ type: lookup ? `${NEO_LOOKUP_VARIANT_FOLDER_TYPE_NAME}<${context.declaration.type.typeArguments[0]?.name ?? "TObject"}, ${context.declaration.type.typeArguments[1]?.name ?? "TValue"}>` : `${NEO_VARIANT_FOLDER_TYPE_NAME}<${context.declaration.type.typeArguments[0]?.name ?? "TObject"}>`
21850
22029
  }
21851
22030
  ];
21852
22031
  }
@@ -22347,7 +22526,7 @@ function validateStatements(statements, returnType, scope, environment, uri, ran
22347
22526
  }
22348
22527
  }
22349
22528
  function validateExpression(expression, expected, scope, environment, uri, range2, diagnostics, anchor) {
22350
- if (expected?.name === NEO_VARIANT_TYPE_NAME) {
22529
+ if (expected !== void 0 && isNeoVariantTypeName(expected.name)) {
22351
22530
  validateVariantValuedExpression(
22352
22531
  expression,
22353
22532
  expected,
@@ -22565,6 +22744,9 @@ function validateExpression(expression, expected, scope, environment, uri, range
22565
22744
  const overload = declaration && declaration.constructors.length > 0 ? matchingDeclaredConstructor(declaration, expression.argumentNames) : null;
22566
22745
  expression.args.forEach((argument2, index) => {
22567
22746
  const named = expression.argumentNames?.[index];
22747
+ if (declaration.name === NEO_LOOKUP_VARIANT_FOLDER_TYPE_NAME && named === "collection") {
22748
+ return;
22749
+ }
22568
22750
  const parameter4 = named ? (declaration.headerParameters ?? []).find(
22569
22751
  (candidate) => candidate.name === named
22570
22752
  ) ?? overload?.parameters.find((candidate) => candidate.name === named) : void 0;
@@ -22573,9 +22755,10 @@ function validateExpression(expression, expected, scope, environment, uri, range
22573
22755
  (candidate) => candidate.name === (variantMember ?? named)
22574
22756
  ) : fields[index];
22575
22757
  const expectedType = parameter4?.type ?? field?.type;
22758
+ const expectedSemanticType = declaration.name === NEO_LOOKUP_VARIANT_FOLDER_TYPE_NAME && named === "collectionValue" ? { name: "string" } : expectedType ? semanticType(expectedType) : void 0;
22576
22759
  validateExpression(
22577
22760
  argument2,
22578
- expectedType ? semanticType(expectedType) : void 0,
22761
+ expectedSemanticType,
22579
22762
  scope,
22580
22763
  environment,
22581
22764
  uri,
@@ -23424,7 +23607,11 @@ function compileProjectSourceBodies(documents) {
23424
23607
  if (declaration.kind !== "class") continue;
23425
23608
  const owner = graph.declarationsByName.get(declaration.name);
23426
23609
  if (!owner) continue;
23427
- const declaringType = declarationTypeRef(owner, declaration);
23610
+ const declaringType = declarationTypeRef(
23611
+ owner,
23612
+ declaration,
23613
+ graph.typeIdsByName
23614
+ );
23428
23615
  diagnostics.push(
23429
23616
  ...declaredConstructorOverloadDiagnostics(
23430
23617
  uri,
@@ -23740,16 +23927,22 @@ function buildProjectGraph(documents) {
23740
23927
  const target = declarationsByName.get(base.name);
23741
23928
  return target?.declaration.kind === "interface" ? [target.id] : [];
23742
23929
  }) : [];
23743
- const typeParameters = classDeclaration ? classDeclaration.genericParameters.map((parameter4) => ({
23744
- id: sourceIdentity(
23745
- parameter4.annotations,
23746
- `generic:${info.id}:${parameter4.name}`
23747
- ),
23748
- name: parameter4.name,
23749
- type: { kind: "primitive", name: "unknown" },
23750
- location: location(info.uri, parameter4.range),
23751
- selectionLocation: location(info.uri, parameter4.nameRange)
23752
- })) : [];
23930
+ const typeParameters = classDeclaration ? classDeclaration.genericParameters.map((parameter4) => {
23931
+ const type = sourceTypeParameterRef(
23932
+ parameter4,
23933
+ info,
23934
+ typeIds,
23935
+ /* @__PURE__ */ new Set(),
23936
+ false
23937
+ );
23938
+ return {
23939
+ id: type.id,
23940
+ name: parameter4.name,
23941
+ type,
23942
+ location: location(info.uri, parameter4.range),
23943
+ selectionLocation: location(info.uri, parameter4.nameRange)
23944
+ };
23945
+ }) : [];
23753
23946
  return {
23754
23947
  id: info.id,
23755
23948
  name: info.declaration.name,
@@ -24087,50 +24280,49 @@ function projectRootType(documents, declarations) {
24087
24280
  }
24088
24281
  return { id: "builtin:Root", name: "Root", kind: "synthetic", members };
24089
24282
  }
24090
- function sourceTypeRef(type, owner, typeIds) {
24283
+ function sourceTypeRef(type, owner, typeIds, resolvingTypeParameters = /* @__PURE__ */ new Set()) {
24091
24284
  const nullable = type.nullable || void 0;
24285
+ const resolveNested = (nested) => sourceTypeRef(nested, owner, typeIds, resolvingTypeParameters);
24092
24286
  if (type.name === "List") {
24093
24287
  return {
24094
24288
  kind: "list",
24095
- elementType: type.typeArguments[0] ? sourceTypeRef(type.typeArguments[0], owner, typeIds) : { kind: "primitive", name: "unknown" },
24289
+ elementType: type.typeArguments[0] ? resolveNested(type.typeArguments[0]) : { kind: "primitive", name: "unknown" },
24096
24290
  ...nullable ? { nullable } : {}
24097
24291
  };
24098
24292
  }
24099
24293
  if (type.name === "Set") {
24100
24294
  return {
24101
24295
  kind: "set",
24102
- elementType: type.typeArguments[0] ? sourceTypeRef(type.typeArguments[0], owner, typeIds) : { kind: "primitive", name: "unknown" },
24296
+ elementType: type.typeArguments[0] ? resolveNested(type.typeArguments[0]) : { kind: "primitive", name: "unknown" },
24103
24297
  ...nullable ? { nullable } : {}
24104
24298
  };
24105
24299
  }
24106
24300
  if (type.name === "Dictionary") {
24107
24301
  return {
24108
24302
  kind: "dictionary",
24109
- keyType: type.typeArguments[0] ? sourceTypeRef(type.typeArguments[0], owner, typeIds) : { kind: "primitive", name: "unknown" },
24110
- valueType: type.typeArguments[1] ? sourceTypeRef(type.typeArguments[1], owner, typeIds) : { kind: "primitive", name: "unknown" },
24303
+ keyType: type.typeArguments[0] ? resolveNested(type.typeArguments[0]) : { kind: "primitive", name: "unknown" },
24304
+ valueType: type.typeArguments[1] ? resolveNested(type.typeArguments[1]) : { kind: "primitive", name: "unknown" },
24111
24305
  ...nullable ? { nullable } : {}
24112
24306
  };
24113
24307
  }
24114
24308
  if (type.name === "NeoDelegate") {
24115
- const returnType = type.typeArguments[0] ? sourceTypeRef(type.typeArguments[0], owner, typeIds) : { kind: "primitive", name: "unknown" };
24309
+ const returnType = type.typeArguments[0] ? resolveNested(type.typeArguments[0]) : { kind: "primitive", name: "unknown" };
24116
24310
  return {
24117
24311
  kind: "delegate",
24118
24312
  returnType,
24119
- parameterTypes: type.typeArguments.slice(1).map((argument2) => sourceTypeRef(argument2, owner, typeIds)),
24313
+ parameterTypes: type.typeArguments.slice(1).map(resolveNested),
24120
24314
  ...nullable ? { nullable } : {}
24121
24315
  };
24122
24316
  }
24123
24317
  if (type.name === "NeoAction") {
24124
24318
  return {
24125
24319
  kind: "action",
24126
- parameterTypes: type.typeArguments.map(
24127
- (argument2) => sourceTypeRef(argument2, owner, typeIds)
24128
- )
24320
+ parameterTypes: type.typeArguments.map(resolveNested)
24129
24321
  };
24130
24322
  }
24131
24323
  if (type.name === "Reference" && type.typeArguments[0]) {
24132
24324
  return {
24133
- ...sourceTypeRef(type.typeArguments[0], owner, typeIds),
24325
+ ...resolveNested(type.typeArguments[0]),
24134
24326
  ...nullable ? { nullable } : {}
24135
24327
  };
24136
24328
  }
@@ -24139,16 +24331,13 @@ function sourceTypeRef(type, owner, typeIds) {
24139
24331
  (candidate) => candidate.name === type.name
24140
24332
  );
24141
24333
  if (parameter4) {
24142
- return {
24143
- kind: "typeParameter",
24144
- id: sourceIdentity(
24145
- parameter4.annotations,
24146
- `generic:${owner.id}:${parameter4.name}`
24147
- ),
24148
- name: parameter4.name,
24149
- ownerClassId: owner.id,
24150
- ...nullable ? { nullable } : {}
24151
- };
24334
+ return sourceTypeParameterRef(
24335
+ parameter4,
24336
+ owner,
24337
+ typeIds,
24338
+ resolvingTypeParameters,
24339
+ nullable === true
24340
+ );
24152
24341
  }
24153
24342
  }
24154
24343
  const primitive3 = primitiveTypeName(type.name);
@@ -24164,27 +24353,37 @@ function sourceTypeRef(type, owner, typeIds) {
24164
24353
  kind: "named",
24165
24354
  typeId: targetId ?? `builtin:${type.name}`,
24166
24355
  ...type.typeArguments.length ? {
24167
- typeArguments: type.typeArguments.map(
24168
- (argument2) => sourceTypeRef(argument2, owner, typeIds)
24169
- )
24356
+ typeArguments: type.typeArguments.map(resolveNested)
24170
24357
  } : {},
24171
24358
  ...nullable ? { nullable } : {}
24172
24359
  };
24173
24360
  }
24174
- function declarationTypeRef(info, declaration) {
24361
+ function sourceTypeParameterRef(parameter4, owner, typeIds, resolvingTypeParameters, nullable) {
24362
+ const id2 = sourceIdentity(
24363
+ parameter4.annotations,
24364
+ `generic:${owner.id}:${parameter4.name}`
24365
+ );
24366
+ const next = new Set(resolvingTypeParameters);
24367
+ next.add(id2);
24368
+ const constraintSource = parameter4.constraint;
24369
+ const constraint = constraintSource !== void 0 && !resolvingTypeParameters.has(id2) ? sourceTypeRef(constraintSource, owner, typeIds, next) : void 0;
24370
+ return {
24371
+ kind: "typeParameter",
24372
+ id: id2,
24373
+ name: parameter4.name,
24374
+ ownerClassId: owner.id,
24375
+ ...constraint === void 0 ? {} : { constraint },
24376
+ ...nullable ? { nullable: true } : {}
24377
+ };
24378
+ }
24379
+ function declarationTypeRef(info, declaration, typeIds) {
24175
24380
  return {
24176
24381
  kind: "named",
24177
24382
  typeId: info.id,
24178
24383
  ...declaration.genericParameters.length ? {
24179
- typeArguments: declaration.genericParameters.map((parameter4) => ({
24180
- kind: "typeParameter",
24181
- id: sourceIdentity(
24182
- parameter4.annotations,
24183
- `generic:${info.id}:${parameter4.name}`
24184
- ),
24185
- name: parameter4.name,
24186
- ownerClassId: info.id
24187
- }))
24384
+ typeArguments: declaration.genericParameters.map(
24385
+ (parameter4) => sourceTypeParameterRef(parameter4, info, typeIds, /* @__PURE__ */ new Set(), false)
24386
+ )
24188
24387
  } : {}
24189
24388
  };
24190
24389
  }
@@ -27537,10 +27736,18 @@ function projectConstructorArgumentCompletions(analysis, document, position) {
27537
27736
  used.add(token.text);
27538
27737
  }
27539
27738
  }
27540
- const fields = graphConstructorNamedArguments(typeName) ?? declaredConstructorNamedArguments(analysis, typeName, used);
27739
+ const fields = lookupVariantFolderArgumentFields(typeName) ?? graphConstructorNamedArguments(typeName) ?? declaredConstructorNamedArguments(analysis, typeName, used);
27541
27740
  if (!fields) return null;
27542
27741
  return fields.filter((field) => !used.has(field.name));
27543
27742
  }
27743
+ function lookupVariantFolderArgumentFields(typeName) {
27744
+ if (typeName !== NEO_LOOKUP_VARIANT_FOLDER_TYPE_NAME) return null;
27745
+ return [
27746
+ { name: "path", type: "string" },
27747
+ { name: "collection", type: "List<TValue>" },
27748
+ { name: "collectionValue", type: "Reference<List<TValue>>" }
27749
+ ];
27750
+ }
27544
27751
  function declaredConstructorNamedArguments(analysis, typeName, used) {
27545
27752
  const contract = classConstructionContract(
27546
27753
  projectConstructionIndex(analysis),
@@ -27673,7 +27880,7 @@ function projectDottedPathBefore(document, position) {
27673
27880
  }
27674
27881
  function projectVariantFolderCompletions(analysis, source, position) {
27675
27882
  const declaration = source.declarations.find(
27676
- (candidate) => candidate.kind === "global" && candidate.type.name === NEO_VARIANT_TYPE_NAME && candidate.annotations.some(
27883
+ (candidate) => candidate.kind === "global" && isNeoVariantTypeName(candidate.type.name) && candidate.annotations.some(
27677
27884
  (annotation2) => rangeContains(annotation2.range, position)
27678
27885
  )
27679
27886
  );
@@ -28836,6 +29043,8 @@ function projectConstructorTypeName(analysis, document, callee, beforeCallee) {
28836
29043
  return contextualConstructorType(analysis, document, callee.range.start);
28837
29044
  }
28838
29045
  function projectConstructorParameters(analysis, typeName) {
29046
+ const lookup = lookupVariantFolderArgumentFields(typeName);
29047
+ if (lookup !== null) return lookup;
28839
29048
  const declaration = findTypeDeclaration(analysis, typeName);
28840
29049
  return declaration ? declarationConstructorParameters(declaration) : graphConstructorParameters(typeName);
28841
29050
  }
@@ -32970,6 +33179,7 @@ var init_document_contracts = __esm({
32970
33179
  "collectionValueId",
32971
33180
  "declaredTypeInfo",
32972
33181
  "targetTypeInfo",
33182
+ "variantValueTypeInfo",
32973
33183
  "dialogueGroupId",
32974
33184
  "code",
32975
33185
  "setterCode",
@@ -33085,10 +33295,12 @@ var init_document_contracts = __esm({
33085
33295
  }
33086
33296
  },
33087
33297
  "variant-folder": {
33088
- authored: ["id", "classId", "path"],
33298
+ authored: ["id", "classId", "path", "binding"],
33089
33299
  derived: [],
33090
33300
  volatile: ["projectId", "createdAt", "updatedAt"],
33091
- normalization: {}
33301
+ normalization: {
33302
+ binding: "missingIsNull"
33303
+ }
33092
33304
  },
33093
33305
  interface: {
33094
33306
  authored: [
@@ -34425,6 +34637,11 @@ function memberFromDocument(record3, members, owners, classNames, context) {
34425
34637
  field("targetTypeInfo", void 0),
34426
34638
  record3,
34427
34639
  "targetTypeInfo"
34640
+ ),
34641
+ valueType: field("variantValueTypeInfo", null) === null ? null : documentTypeToManifest(
34642
+ field("variantValueTypeInfo", void 0),
34643
+ record3,
34644
+ "variantValueTypeInfo"
34428
34645
  )
34429
34646
  };
34430
34647
  }
@@ -34572,6 +34789,7 @@ function memberToDocumentFields(member, baseData3) {
34572
34789
  }
34573
34790
  if (member.kind === "variant") {
34574
34791
  fields.targetTypeInfo = manifestTypeToDocument(member.targetType);
34792
+ fields.variantValueTypeInfo = member.valueType === null ? null : manifestTypeToDocument(member.valueType);
34575
34793
  }
34576
34794
  if (member.kind === "generic") {
34577
34795
  fields.genericParamId = member.genericParamId;
@@ -35109,7 +35327,9 @@ function isSchemaSystemMetadata(value) {
35109
35327
  "animationSegmentTrack",
35110
35328
  "spriteAnimationSegmentTrack",
35111
35329
  "variant",
35112
- "variantFolder"
35330
+ "variantFolder",
35331
+ "lookupVariant",
35332
+ "lookupVariantFolder"
35113
35333
  ]);
35114
35334
  return typeof value.kind === "string" && kinds.has(value.kind) && (value.worldKind === null || typeof value.worldKind === "string" && worlds.has(value.worldKind));
35115
35335
  }
@@ -36382,6 +36602,7 @@ function variantFromDocument(record3, context) {
36382
36602
  }
36383
36603
  function variantFolderFromDocument(record3, context) {
36384
36604
  const data = normalizeDocumentFields("variant-folder", record3.data);
36605
+ const binding = variantFolderBinding(data.binding, record3);
36385
36606
  return {
36386
36607
  id: record3.recordId,
36387
36608
  source: context.sourceForRecord(
@@ -36390,9 +36611,21 @@ function variantFolderFromDocument(record3, context) {
36390
36611
  "variantFolder"
36391
36612
  ),
36392
36613
  classId: requiredString(data, "classId", record3),
36393
- path: requiredString(data, "path", record3)
36614
+ path: variantFolderPath(data.path, binding, record3),
36615
+ binding
36394
36616
  };
36395
36617
  }
36618
+ function variantFolderPath(value, binding, record3) {
36619
+ if (typeof value !== "string") {
36620
+ throw new Error(`Variant folder ${record3.recordId} has a non-string path.`);
36621
+ }
36622
+ if (value.length === 0 && binding === null) {
36623
+ throw new Error(
36624
+ `Variant folder ${record3.recordId} may use the class-root path only with a collection binding.`
36625
+ );
36626
+ }
36627
+ return value;
36628
+ }
36396
36629
  function variantFolderId(value, record3) {
36397
36630
  if (value === null) return null;
36398
36631
  if (typeof value !== "string") {
@@ -36420,9 +36653,27 @@ function variantFolderToDocument(folder) {
36420
36653
  return omitUndefined({
36421
36654
  id: folder.id,
36422
36655
  classId: folder.classId,
36423
- path: folder.path
36656
+ path: folder.path,
36657
+ binding: folder.binding
36424
36658
  });
36425
36659
  }
36660
+ function variantFolderBinding(value, record3) {
36661
+ if (value === null || value === void 0) return null;
36662
+ if (typeof value !== "object" || Array.isArray(value)) {
36663
+ throw new Error(
36664
+ `Variant folder ${record3.recordId} has an invalid binding.`
36665
+ );
36666
+ }
36667
+ const binding = value;
36668
+ const collectionMemberId = binding.collectionMemberId;
36669
+ const collectionValueId = binding.collectionValueId;
36670
+ if (typeof collectionMemberId !== "string" || collectionMemberId.length === 0 || typeof collectionValueId !== "string" || collectionValueId.length === 0) {
36671
+ throw new Error(
36672
+ `Variant folder ${record3.recordId} binding requires collectionMemberId and collectionValueId.`
36673
+ );
36674
+ }
36675
+ return { collectionMemberId, collectionValueId };
36676
+ }
36426
36677
  var VARIANT_SCHEMA_RECORD_ADAPTER, VARIANT_FOLDER_SCHEMA_RECORD_ADAPTER;
36427
36678
  var init_variant = __esm({
36428
36679
  "src/project-manifest/record-adapters/variant.ts"() {
@@ -36761,10 +37012,33 @@ function assertVariant(value, path) {
36761
37012
  nonEmptyString(variant.folderId, `${path}.folderId`);
36762
37013
  }
36763
37014
  function assertVariantFolder(value, path) {
36764
- const folder = objectAt(value, path, ["id", "source", "classId", "path"]);
37015
+ const folder = objectAt(value, path, [
37016
+ "id",
37017
+ "source",
37018
+ "classId",
37019
+ "path",
37020
+ "binding"
37021
+ ]);
36765
37022
  assertIdentity2(folder, path, "variantFolder");
36766
37023
  nonEmptyString(folder.classId, `${path}.classId`);
36767
- nonEmptyString(folder.path, `${path}.path`);
37024
+ if (typeof folder.path !== "string")
37025
+ invalid(`${path}.path`, "must be a string.");
37026
+ if (folder.binding === null) {
37027
+ nonEmptyString(folder.path, `${path}.path`);
37028
+ return;
37029
+ }
37030
+ const binding = objectAt(folder.binding, `${path}.binding`, [
37031
+ "collectionMemberId",
37032
+ "collectionValueId"
37033
+ ]);
37034
+ nonEmptyString(
37035
+ binding.collectionMemberId,
37036
+ `${path}.binding.collectionMemberId`
37037
+ );
37038
+ nonEmptyString(
37039
+ binding.collectionValueId,
37040
+ `${path}.binding.collectionValueId`
37041
+ );
36768
37042
  }
36769
37043
  function assertConstructorBaseArgument(value, path) {
36770
37044
  const argument2 = objectAt(value, path, ["name", "code"]);
@@ -37702,6 +37976,19 @@ function assertMember2(value, path) {
37702
37976
  `a variant member targets a class or a generic parameter, not ${JSON.stringify(targetKind)}.`
37703
37977
  );
37704
37978
  }
37979
+ if (member.valueType !== null) {
37980
+ assertType2(member.valueType, `${path}.valueType`);
37981
+ const valueKind = requireRecord(
37982
+ member.valueType,
37983
+ `${path}.valueType`
37984
+ ).kind;
37985
+ if (valueKind !== "class") {
37986
+ invalid(
37987
+ `${path}.valueType.kind`,
37988
+ "a lookup variant member's TValue must be a collection entry class."
37989
+ );
37990
+ }
37991
+ }
37705
37992
  return;
37706
37993
  }
37707
37994
  if (kind === "generic") {
@@ -38903,7 +39190,9 @@ var init_validate = __esm({
38903
39190
  "animationSegmentTrack",
38904
39191
  "spriteAnimationSegmentTrack",
38905
39192
  "variant",
38906
- "variantFolder"
39193
+ "variantFolder",
39194
+ "lookupVariant",
39195
+ "lookupVariantFolder"
38907
39196
  ]);
38908
39197
  MEMBER_COMMON_KEYS = [
38909
39198
  "id",
@@ -38959,7 +39248,7 @@ var init_validate = __esm({
38959
39248
  action: ["arguments"],
38960
39249
  // P67 §6: a variant member carries only its declared target; the selection
38961
39250
  // itself is an ordinary `defaultValue`.
38962
- variant: ["targetType"]
39251
+ variant: ["targetType", "valueType"]
38963
39252
  };
38964
39253
  PRIMITIVE_TYPE_KINDS = /* @__PURE__ */ new Set([
38965
39254
  "null",
@@ -39705,7 +39994,9 @@ var init_world_system_kinds_generated = __esm({
39705
39994
  AnimationSegmentTrack: "animationSegmentTrack",
39706
39995
  SpriteAnimationSegmentTrack: "spriteAnimationSegmentTrack",
39707
39996
  Variant: "variant",
39708
- VariantFolder: "variantFolder"
39997
+ VariantFolder: "variantFolder",
39998
+ LookupVariant: "lookupVariant",
39999
+ LookupVariantFolder: "lookupVariantFolder"
39709
40000
  };
39710
40001
  NEO_WORLD_SYSTEM_CLASS_KIND_VALUES = new Set(
39711
40002
  Object.values(NeoWorldSystemClassKind)
@@ -40462,6 +40753,8 @@ function isNSFunctionVariantInitialize(value) {
40462
40753
  const info = v.info;
40463
40754
  if (typeof info !== "object" || info === null) return false;
40464
40755
  if (!isNSPointer(info.variantPointer)) return false;
40756
+ if (info.rowPointer !== void 0 && !isNSPointer(info.rowPointer))
40757
+ return false;
40465
40758
  return isNSTypeInfoClass(info.schemaClassInfo) && info.schemaClassInfo.required;
40466
40759
  }
40467
40760
  function isNSFunctionVariantApply(value) {
@@ -40471,6 +40764,8 @@ function isNSFunctionVariantApply(value) {
40471
40764
  if (typeof info !== "object" || info === null) return false;
40472
40765
  if (!isNSPointer(info.receiverPointer)) return false;
40473
40766
  if (!isNSPointer(info.variantPointer)) return false;
40767
+ if (info.rowPointer !== void 0 && !isNSPointer(info.rowPointer))
40768
+ return false;
40474
40769
  return isNSTypeInfoClass(info.schemaClassInfo) && info.schemaClassInfo.required;
40475
40770
  }
40476
40771
  function isNSFunctionClassConstructor(value) {
@@ -41619,6 +41914,9 @@ function isMemberListBase(value) {
41619
41914
  if (v.listKind !== void 0 && v.listKind !== null && !isListKind(v.listKind)) {
41620
41915
  return false;
41621
41916
  }
41917
+ if (v.valueTypeInfo !== void 0 && v.valueTypeInfo !== null && (!isNSTypeInfo(v.valueTypeInfo) || v.valueTypeInfo.type !== 7 /* Class */)) {
41918
+ return false;
41919
+ }
41622
41920
  if (v.indexes !== void 0 && v.indexes !== null) {
41623
41921
  if (!Array.isArray(v.indexes)) return false;
41624
41922
  const seenIndexKeys = /* @__PURE__ */ new Set();
@@ -42018,10 +42316,14 @@ function isMemberFunctionRefBase(value) {
42018
42316
  function isVariantRefValue(value) {
42019
42317
  if (!value || typeof value !== "object" || Array.isArray(value)) return false;
42020
42318
  const record3 = value;
42021
- if (!hasExactKeys(record3, ["classId", "variantId"])) return false;
42319
+ const keys = Object.hasOwn(record3, "rowValueId") ? ["classId", "variantId", "rowValueId"] : ["classId", "variantId"];
42320
+ if (!hasExactKeys(record3, keys)) return false;
42022
42321
  if (!isString(record3.classId)) return false;
42023
42322
  if (record3.classId.length === 0) return false;
42024
- if (record3.variantId === null) return true;
42323
+ if (record3.rowValueId !== void 0 && record3.rowValueId !== null && (!isString(record3.rowValueId) || record3.rowValueId.length === 0)) {
42324
+ return false;
42325
+ }
42326
+ if (record3.variantId === null) return record3.rowValueId == null;
42025
42327
  if (!isString(record3.variantId)) return false;
42026
42328
  return record3.variantId.length > 0;
42027
42329
  }
@@ -44031,6 +44333,222 @@ var init_world_system_classes_generated = __esm({
44031
44333
  }
44032
44334
  ],
44033
44335
  worldKind: "variantFolder"
44336
+ },
44337
+ {
44338
+ classId: "system_38ddb113-5df8-4385-9324-1cecf7f3bd99",
44339
+ name: "NeoLookupVariant",
44340
+ docsText: "A NeoObject configuration parameterized by a row from a bound collection.",
44341
+ isAbstract: false,
44342
+ isSealed: true,
44343
+ hiddenInMemberSelector: true,
44344
+ allowedStorage: "immutable" /* Immutable */,
44345
+ requiredConstructorId: "system_d29a7faa-7196-52ab-ac8f-0f4d6230cc0e",
44346
+ constructors: [
44347
+ {
44348
+ constructorId: "system_d29a7faa-7196-52ab-ac8f-0f4d6230cc0e",
44349
+ argumentTypes: [
44350
+ {
44351
+ name: "initialize",
44352
+ type: 25,
44353
+ required: true,
44354
+ returnTypeInfo: {
44355
+ type: 21,
44356
+ required: true,
44357
+ ownerClassId: "system_38ddb113-5df8-4385-9324-1cecf7f3bd99",
44358
+ genericParamId: "system_1d416b31-fa1a-4bbe-a716-249eb855712d"
44359
+ },
44360
+ argumentTypes: [
44361
+ {
44362
+ type: 21,
44363
+ required: true,
44364
+ ownerClassId: "system_38ddb113-5df8-4385-9324-1cecf7f3bd99",
44365
+ genericParamId: "system_4031d9da-38a7-4332-892e-b3fd8282378a"
44366
+ }
44367
+ ]
44368
+ },
44369
+ {
44370
+ name: "apply",
44371
+ type: 25,
44372
+ required: false,
44373
+ returnTypeInfo: { type: "Void", required: true },
44374
+ argumentTypes: [
44375
+ {
44376
+ type: 21,
44377
+ required: true,
44378
+ ownerClassId: "system_38ddb113-5df8-4385-9324-1cecf7f3bd99",
44379
+ genericParamId: "system_1d416b31-fa1a-4bbe-a716-249eb855712d"
44380
+ },
44381
+ {
44382
+ type: 21,
44383
+ required: true,
44384
+ ownerClassId: "system_38ddb113-5df8-4385-9324-1cecf7f3bd99",
44385
+ genericParamId: "system_4031d9da-38a7-4332-892e-b3fd8282378a"
44386
+ }
44387
+ ],
44388
+ defaultValue: { value: null }
44389
+ },
44390
+ {
44391
+ name: "overrides",
44392
+ type: 21,
44393
+ required: false,
44394
+ ownerClassId: "system_38ddb113-5df8-4385-9324-1cecf7f3bd99",
44395
+ genericParamId: "system_1d416b31-fa1a-4bbe-a716-249eb855712d",
44396
+ defaultValue: { value: null }
44397
+ },
44398
+ {
44399
+ name: "childOverrides",
44400
+ type: 6,
44401
+ required: false,
44402
+ entryTypeInfo: {
44403
+ type: 7,
44404
+ required: true,
44405
+ classId: "system_e2e88eba-5335-4a16-9dcf-2c0e1951e8bd",
44406
+ typeArguments: {
44407
+ "system_de23448a-45be-4e3f-8965-072a545e08f0": {
44408
+ type: 7,
44409
+ required: true,
44410
+ classId: "system_61b30a92-90dc-4bf8-8503-ee4f6414effc"
44411
+ }
44412
+ }
44413
+ },
44414
+ defaultValue: { value: null }
44415
+ }
44416
+ ],
44417
+ code: null
44418
+ }
44419
+ ],
44420
+ genericParams: [
44421
+ {
44422
+ id: "system_1d416b31-fa1a-4bbe-a716-249eb855712d",
44423
+ name: "TObject",
44424
+ constraintClassWorldKind: "object"
44425
+ },
44426
+ { id: "system_4031d9da-38a7-4332-892e-b3fd8282378a", name: "TValue" }
44427
+ ],
44428
+ schemaFields: [
44429
+ {
44430
+ memberId: "system_ddff93cb-2e4e-43ed-ae1a-b523eb53e0e9",
44431
+ memberKind: "delegate",
44432
+ defaultInitializerCode: "initialize",
44433
+ returnTypeInfo: {
44434
+ type: 21,
44435
+ required: true,
44436
+ ownerClassId: "system_38ddb113-5df8-4385-9324-1cecf7f3bd99",
44437
+ genericParamId: "system_1d416b31-fa1a-4bbe-a716-249eb855712d"
44438
+ },
44439
+ argumentTypes: [
44440
+ {
44441
+ type: 21,
44442
+ required: true,
44443
+ ownerClassId: "system_38ddb113-5df8-4385-9324-1cecf7f3bd99",
44444
+ genericParamId: "system_4031d9da-38a7-4332-892e-b3fd8282378a",
44445
+ name: "p1"
44446
+ }
44447
+ ],
44448
+ required: true,
44449
+ schemaKey: "Initialize"
44450
+ },
44451
+ {
44452
+ memberId: "system_42da97f6-c714-47b6-b750-232232ec4f83",
44453
+ memberKind: "delegate",
44454
+ defaultInitializerCode: "apply",
44455
+ returnTypeInfo: { type: "Void", required: true },
44456
+ argumentTypes: [
44457
+ {
44458
+ type: 21,
44459
+ required: true,
44460
+ ownerClassId: "system_38ddb113-5df8-4385-9324-1cecf7f3bd99",
44461
+ genericParamId: "system_1d416b31-fa1a-4bbe-a716-249eb855712d",
44462
+ name: "p1"
44463
+ },
44464
+ {
44465
+ type: 21,
44466
+ required: true,
44467
+ ownerClassId: "system_38ddb113-5df8-4385-9324-1cecf7f3bd99",
44468
+ genericParamId: "system_4031d9da-38a7-4332-892e-b3fd8282378a",
44469
+ name: "p2"
44470
+ }
44471
+ ],
44472
+ required: false,
44473
+ schemaKey: "Apply"
44474
+ },
44475
+ {
44476
+ memberId: "system_070ae22a-7603-48cf-94e2-528d88b72825",
44477
+ memberKind: "generic",
44478
+ defaultInitializerCode: "overrides",
44479
+ genericParamId: "system_1d416b31-fa1a-4bbe-a716-249eb855712d",
44480
+ partial: true,
44481
+ required: false,
44482
+ schemaKey: "Overrides"
44483
+ },
44484
+ {
44485
+ memberId: "system_ae23d675-2531-44a2-b792-8a121d99aec2",
44486
+ memberKind: "list",
44487
+ defaultInitializerCode: "childOverrides",
44488
+ entryMemberId: "system_d922e73b-d2d8-5b2e-b333-75b80ca9c3ab",
44489
+ required: false,
44490
+ schemaKey: "ChildOverrides"
44491
+ }
44492
+ ],
44493
+ supportingFields: [
44494
+ {
44495
+ memberId: "system_d922e73b-d2d8-5b2e-b333-75b80ca9c3ab",
44496
+ memberKind: "class",
44497
+ schemaClassWorldKind: "animationChildOverride",
44498
+ classArguments: {
44499
+ "system_de23448a-45be-4e3f-8965-072a545e08f0": {
44500
+ kind: "member",
44501
+ memberId: "system_788215c7-3dcf-5df0-8810-79f08da02c66"
44502
+ }
44503
+ },
44504
+ required: true,
44505
+ schemaKey: "__ChildOverrideEntry"
44506
+ },
44507
+ {
44508
+ memberId: "system_788215c7-3dcf-5df0-8810-79f08da02c66",
44509
+ memberKind: "class",
44510
+ schemaClassWorldKind: "objectBase",
44511
+ required: true,
44512
+ schemaKey: "__NeoObjectBaseTypeArgument"
44513
+ }
44514
+ ],
44515
+ worldKind: "lookupVariant"
44516
+ },
44517
+ {
44518
+ classId: "system_b3bd4d91-6e03-4034-b201-05afd78f7c72",
44519
+ name: "NeoLookupVariantFolder",
44520
+ docsText: "A variant folder whose variants receive a row from a bound collection.",
44521
+ isAbstract: false,
44522
+ isSealed: true,
44523
+ hiddenInMemberSelector: true,
44524
+ allowedStorage: "immutable" /* Immutable */,
44525
+ requiredConstructorId: "system_b0239c15-91f3-5ddb-bd4e-43fd86e42e50",
44526
+ constructors: [
44527
+ {
44528
+ constructorId: "system_b0239c15-91f3-5ddb-bd4e-43fd86e42e50",
44529
+ argumentTypes: [{ name: "path", type: 3, required: true }],
44530
+ code: null
44531
+ }
44532
+ ],
44533
+ genericParams: [
44534
+ {
44535
+ id: "system_a5488f2b-966e-4643-93df-71995c9c24ad",
44536
+ name: "TObject",
44537
+ constraintClassWorldKind: "object"
44538
+ },
44539
+ { id: "system_39544f5d-e527-44fb-96b7-f584b7cd1a25", name: "TValue" }
44540
+ ],
44541
+ schemaFields: [
44542
+ {
44543
+ memberId: "system_d36ebea5-151f-4d9d-95f2-28d8cfb05870",
44544
+ memberKind: "string",
44545
+ defaultInitializerCode: "path",
44546
+ localizable: false,
44547
+ required: true,
44548
+ schemaKey: "Path"
44549
+ }
44550
+ ],
44551
+ worldKind: "lookupVariantFolder"
44034
44552
  }
44035
44553
  ];
44036
44554
  }
@@ -45161,6 +45679,28 @@ var init_member_kinds_db_response = __esm({
45161
45679
  });
45162
45680
 
45163
45681
  // ../src/models/members/unordered-list-membership.ts
45682
+ function unorderedListEntryIds(values, listValue2, membershipIndex) {
45683
+ if (!Array.isArray(listValue2.value)) return [];
45684
+ if (membershipIndex !== void 0) {
45685
+ return [...membershipIndex.get(listValue2.id) ?? []];
45686
+ }
45687
+ const ids = [];
45688
+ for (const candidate of values) {
45689
+ if (candidate.containerId === listValue2.id) ids.push(candidate.id);
45690
+ }
45691
+ ids.sort();
45692
+ return ids;
45693
+ }
45694
+ function listEntryIdsForValue(member, listValue2, values) {
45695
+ if (listValue2.value === null) return [];
45696
+ if (listKindOf(member) === "unordered") {
45697
+ return unorderedListEntryIds(values, listValue2);
45698
+ }
45699
+ if (!Array.isArray(listValue2.value)) return [];
45700
+ return listValue2.value.filter(
45701
+ (entry) => typeof entry === "string"
45702
+ );
45703
+ }
45164
45704
  var init_unordered_list_membership = __esm({
45165
45705
  "../src/models/members/unordered-list-membership.ts"() {
45166
45706
  "use strict";
@@ -47267,6 +47807,11 @@ function primitiveFallbackValue(document, member) {
47267
47807
  return { value: { listeners: [] } };
47268
47808
  }
47269
47809
  if (isMemberVariantBase(member)) {
47810
+ if (member.valueTypeInfo != null) {
47811
+ throw new Error(
47812
+ `Cannot create default value: lookup variant member "${member.name}" requires an authored lookup-variant selection.`
47813
+ );
47814
+ }
47270
47815
  if (!member.required) return { value: null };
47271
47816
  if (member.targetTypeInfo.type !== 7 /* Class */) {
47272
47817
  throw new Error(
@@ -47274,7 +47819,11 @@ function primitiveFallbackValue(document, member) {
47274
47819
  );
47275
47820
  }
47276
47821
  return {
47277
- value: { classId: member.targetTypeInfo.classId, variantId: null }
47822
+ value: {
47823
+ classId: member.targetTypeInfo.classId,
47824
+ variantId: null,
47825
+ rowValueId: null
47826
+ }
47278
47827
  };
47279
47828
  }
47280
47829
  if (member.kind === 21 /* Generic */) {
@@ -49209,7 +49758,7 @@ function renderMemberType(context, member, enclosingClassId) {
49209
49758
  );
49210
49759
  break;
49211
49760
  case "variant":
49212
- value = `NeoVariant<${renderType(context, member.targetType)}>`;
49761
+ value = member.valueType === null ? `NeoVariant<${renderType(context, member.targetType)}>` : `${NEO_LOOKUP_VARIANT_TYPE_NAME}<${renderType(context, member.targetType)}, ${renderType(context, member.valueType)}>`;
49213
49762
  break;
49214
49763
  }
49215
49764
  if ((member.kind === "class" || member.kind === "generic") && member.partial === true) {
@@ -49842,7 +50391,7 @@ function emitVariantFiles(context, variantInitializers) {
49842
50391
  const foldersById = new Map(
49843
50392
  manifest.variantFolders.map((folder) => [folder.id, folder])
49844
50393
  );
49845
- const pathFor = (className, folderPath3) => folderPath3 === null ? `Variants/${className}.neo` : `Variants/${className}/${folderPath3}.neo`;
50394
+ const pathFor = (className, folderPath3) => folderPath3 === null || folderPath3.length === 0 ? `Variants/${className}.neo` : `Variants/${className}/${folderPath3}.neo`;
49846
50395
  const fileFor = (className, folder) => {
49847
50396
  const path = pathFor(className, folder?.path ?? null);
49848
50397
  const existing = files.get(path);
@@ -49873,7 +50422,7 @@ function emitVariantFiles(context, variantInitializers) {
49873
50422
  const declarations = [];
49874
50423
  const recordKeys = [];
49875
50424
  if (file.folder !== null) {
49876
- declarations.push(emitVariantFolder(className, file.folder));
50425
+ declarations.push(emitVariantFolder(context, className, file.folder));
49877
50426
  recordKeys.push(`variant-folder:${file.folder.id}`);
49878
50427
  }
49879
50428
  const ordered = [...file.variants].sort(
@@ -49895,11 +50444,27 @@ function emitVariantFiles(context, variantInitializers) {
49895
50444
  `, recordKeys };
49896
50445
  });
49897
50446
  }
49898
- function emitVariantFolder(className, folder) {
50447
+ function emitVariantFolder(context, className, folder) {
49899
50448
  for (const segment of folder.path.split("/")) {
50449
+ if (segment.length === 0) continue;
49900
50450
  assertIdentifier(segment, "variant folder path segment");
49901
50451
  }
49902
- return `${id(folder.id)}NeoVariantFolder<${className}> ${variantFolderSymbol(folder)} = new(${quote(folder.path)});`;
50452
+ if (folder.binding === null) {
50453
+ return `${id(folder.id)}NeoVariantFolder<${className}> ${variantFolderSymbol(folder)} = new(${quote(folder.path)});`;
50454
+ }
50455
+ const valueType = renderLookupEntryType(
50456
+ context,
50457
+ folder.binding.collectionMemberId
50458
+ );
50459
+ const rootPath = context.collectionValuePaths.get(
50460
+ folder.binding.collectionValueId
50461
+ );
50462
+ const collectionValue = rootPath === void 0 ? `Reference(id: ${quote(folder.binding.collectionValueId)})` : `Reference(${rootPath})`;
50463
+ return `${id(folder.id)}${NEO_LOOKUP_VARIANT_FOLDER_TYPE_NAME}<${className}, ${valueType}> ${variantFolderSymbol(folder)} = new(
50464
+ path: ${quote(folder.path)},
50465
+ collection: ${qualifiedMember(context, folder.binding.collectionMemberId)},
50466
+ collectionValue: ${collectionValue},
50467
+ );`;
49903
50468
  }
49904
50469
  function emitVariant(context, className, variant, folder, variantInitializers) {
49905
50470
  assertIdentifier(variant.name, "variant");
@@ -49913,11 +50478,13 @@ function emitVariant(context, className, variant, folder, variantInitializers) {
49913
50478
  id(variant.id).trimEnd(),
49914
50479
  ...folder === null ? [] : [`@settings(folder: ${variantFolderSymbol(folder)})`]
49915
50480
  ];
50481
+ const type = folder?.binding == null ? `NeoVariant<${className}>` : `${NEO_LOOKUP_VARIANT_TYPE_NAME}<${className}, ${renderLookupEntryType(context, folder.binding.collectionMemberId)}>`;
49916
50482
  return `${annotations.join("\n")}
49917
- NeoVariant<${className}> ${variant.name} =
50483
+ ${type} ${variant.name} =
49918
50484
  ${indentNeoSourceNonEmptyLines(initializer, 2)};`;
49919
50485
  }
49920
50486
  function variantFolderSymbol(folder) {
50487
+ if (folder.path.length === 0) return "Root";
49921
50488
  return neoVariantFolderSymbol(folder.path);
49922
50489
  }
49923
50490
  function assertUniqueVariantFolderSymbols(folders) {
@@ -50031,7 +50598,16 @@ function renderVariantSelection(context, member, value) {
50031
50598
  `Variant member ${member.name} selects variant ${JSON.stringify(variantId)}, which this project does not declare.`
50032
50599
  );
50033
50600
  }
50034
- return path;
50601
+ const rowValueId = value.rowValueId;
50602
+ if (rowValueId === null || rowValueId === void 0) return path;
50603
+ if (typeof rowValueId !== "string") {
50604
+ throw new Error(
50605
+ `Variant member ${member.name} stores a malformed rowValueId.`
50606
+ );
50607
+ }
50608
+ const rootPath = context.collectionValuePaths.get(rowValueId);
50609
+ const reference2 = rootPath === void 0 ? `Reference(id: ${quote(rowValueId)})` : `Reference(${rootPath})`;
50610
+ return `${path}.Bind(${reference2})`;
50035
50611
  }
50036
50612
  var SPECIALIZED_RELATION_PROPERTIES;
50037
50613
  var init_emitter = __esm({
@@ -55540,7 +56116,7 @@ function lowerFieldMember(context, ownerClass, declaration, id2, commonInput, ba
55540
56116
  }))
55541
56117
  };
55542
56118
  }
55543
- if (name === NEO_VARIANT_TYPE_NAME) {
56119
+ if (name === NEO_VARIANT_TYPE_NAME || name === NEO_LOOKUP_VARIANT_TYPE_NAME) {
55544
56120
  if (partial) {
55545
56121
  throw new Error("Partial<T> requires a Class or Class generic target.");
55546
56122
  }
@@ -55551,7 +56127,8 @@ function lowerFieldMember(context, ownerClass, declaration, id2, commonInput, ba
55551
56127
  context,
55552
56128
  requiredTypeArgument(fieldType, 0),
55553
56129
  ownerClass
55554
- )
56130
+ ),
56131
+ valueType: name === NEO_LOOKUP_VARIANT_TYPE_NAME ? lowerType(context, requiredTypeArgument(fieldType, 1), ownerClass) : null
55555
56132
  };
55556
56133
  }
55557
56134
  if (name === "NeoAction") {
@@ -56429,7 +57006,7 @@ function lowerExpressionValue(context, expression, declaredExpected, ownerClass,
56429
57006
  if (expected.name === "NeoAction") {
56430
57007
  return lowerActionDefault(context, expression, ownerClass, path);
56431
57008
  }
56432
- if (expected.name === NEO_VARIANT_TYPE_NAME) {
57009
+ if (expected.name === NEO_VARIANT_TYPE_NAME || expected.name === NEO_LOOKUP_VARIANT_TYPE_NAME) {
56433
57010
  return lowerVariantDefault(context, expression, expected, path);
56434
57011
  }
56435
57012
  switch (expression.kind) {
@@ -57420,7 +57997,22 @@ function lowerVariantDefault(context, expression, expected, path) {
57420
57997
  }
57421
57998
  return null;
57422
57999
  }
57423
- const segments = variantPathSegments2(unwrapped);
58000
+ let candidate = unwrapped;
58001
+ let rowValueId = null;
58002
+ if (candidate.kind === "call" && candidate.callee.kind === "member" && candidate.callee.name === "Bind") {
58003
+ if (expected.name !== NEO_VARIANT_TYPE_NAME) {
58004
+ throw new Error(
58005
+ `${path} cannot Bind a row to NeoLookupVariant; keep the lookup handle unbound.`
58006
+ );
58007
+ }
58008
+ const row = candidate.args[0];
58009
+ if (row === void 0) {
58010
+ throw new Error(`${path}.Bind requires one Reference row.`);
58011
+ }
58012
+ rowValueId = lowerVariantBindingReference(context, row, path);
58013
+ candidate = candidate.callee.receiver;
58014
+ }
58015
+ const segments = variantPathSegments2(candidate);
57424
58016
  if (segments === null) {
57425
58017
  throw new Error(
57426
58018
  `${path} must be a variant path such as Tree.${NEO_VARIANT_SCOPE_NAME}.ChoppedTree (or Tree.${NEO_VARIANT_SCOPE_NAME}.${NEO_VARIANT_RESERVED_NAME} for the class itself). A variant is a record, so Reference(...) cannot name one.`
@@ -57433,7 +58025,59 @@ function lowerVariantDefault(context, expression, expected, path) {
57433
58025
  `${path} names variant path ${JSON.stringify(spelled)}, which this project does not declare.`
57434
58026
  );
57435
58027
  }
57436
- return { classId: target.classId, variantId: target.variantId };
58028
+ if (target.valueClassId !== null && expected.name === NEO_VARIANT_TYPE_NAME) {
58029
+ if (rowValueId === null) {
58030
+ throw new Error(
58031
+ `${path} selects a lookup variant and must bind a collection row with .Bind(Reference(...)).`
58032
+ );
58033
+ }
58034
+ } else if (rowValueId !== null) {
58035
+ throw new Error(`${path} can bind a row only to a lookup variant.`);
58036
+ }
58037
+ if (expected.name === NEO_LOOKUP_VARIANT_TYPE_NAME && target.valueClassId === null) {
58038
+ throw new Error(
58039
+ `${path} expects NeoLookupVariant but selects a plain NeoVariant.`
58040
+ );
58041
+ }
58042
+ return {
58043
+ classId: target.classId,
58044
+ variantId: target.variantId,
58045
+ rowValueId
58046
+ };
58047
+ }
58048
+ function lowerVariantBindingReference(context, expression, path) {
58049
+ const id2 = referenceId(expressionSource(expression));
58050
+ if (id2 === null) {
58051
+ throw new Error(
58052
+ `${path}.Bind requires Reference(id: ...) or Reference(root....).`
58053
+ );
58054
+ }
58055
+ const prefix = `${PENDING_ID_PREFIX}root-path:`;
58056
+ if (!id2.startsWith(prefix)) return id2;
58057
+ const rootPath = id2.slice(prefix.length);
58058
+ const target = context.rootValueTargetsByPath.get(rootPath);
58059
+ if (target === void 0) {
58060
+ throw new Error(`${path}.Bind references unknown row path ${rootPath}.`);
58061
+ }
58062
+ return target.valueId;
58063
+ }
58064
+ function expressionSource(expression) {
58065
+ if (expression.kind === "annotated") {
58066
+ return expressionSource(expression.expression);
58067
+ }
58068
+ if (expression.kind === "litString") return JSON.stringify(expression.value);
58069
+ if (expression.kind === "ident") return expression.name;
58070
+ if (expression.kind === "member") {
58071
+ return `${expressionSource(expression.receiver)}.${expression.name}`;
58072
+ }
58073
+ if (expression.kind === "call") {
58074
+ const args = expression.args.map((argument2, index) => {
58075
+ const name = expression.argumentNames?.[index];
58076
+ return `${name == null ? "" : `${name}: `}${expressionSource(argument2)}`;
58077
+ });
58078
+ return `${expressionSource(expression.callee)}(${args.join(", ")})`;
58079
+ }
58080
+ throw new Error("Unsupported Reference expression in variant binding.");
57437
58081
  }
57438
58082
  function variantPathSegments2(expression) {
57439
58083
  const segments = [];
@@ -57617,7 +58261,12 @@ function isNeoClassVariantFolder(value) {
57617
58261
  const v = value;
57618
58262
  if (!isNonEmptyString2(v.id)) return false;
57619
58263
  if (!isNonEmptyString2(v.classId)) return false;
57620
- if (!isNonEmptyString2(v.path)) return false;
58264
+ if (typeof v.path !== "string") return false;
58265
+ if (v.binding === void 0 || v.binding === null) return v.path.length > 0;
58266
+ if (typeof v.binding !== "object" || Array.isArray(v.binding)) return false;
58267
+ const binding = v.binding;
58268
+ if (!isNonEmptyString2(binding.collectionMemberId)) return false;
58269
+ if (!isNonEmptyString2(binding.collectionValueId)) return false;
57621
58270
  return true;
57622
58271
  }
57623
58272
  var init_variants = __esm({
@@ -57627,14 +58276,19 @@ var init_variants = __esm({
57627
58276
  });
57628
58277
 
57629
58278
  // ../src/models/variants/variant-member.ts
57630
- function neoVariantSystemClass(classes) {
58279
+ function neoVariantSystemClass(classes, lookup = false) {
57631
58280
  const declared = classes.find(
57632
- (item) => item.system?.worldKind === NeoWorldSystemClassKind.Variant
58281
+ (item) => item.system?.worldKind === (lookup ? NeoWorldSystemClassKind.LookupVariant : NeoWorldSystemClassKind.Variant)
57633
58282
  );
57634
58283
  if (declared === void 0) return null;
57635
58284
  const targetParamId = declared.genericParams?.[0]?.id;
57636
58285
  if (targetParamId === void 0) return null;
57637
- return { classId: declared.id, targetParamId };
58286
+ const valueParamId = declared.genericParams?.[1]?.id;
58287
+ return {
58288
+ classId: declared.id,
58289
+ targetParamId,
58290
+ ...valueParamId === void 0 ? {} : { valueParamId }
58291
+ };
57638
58292
  }
57639
58293
  var init_variant_member = __esm({
57640
58294
  "../src/models/variants/variant-member.ts"() {
@@ -57876,11 +58530,25 @@ function variantProjection(context) {
57876
58530
  (folder) => [folder.id, folder.path]
57877
58531
  )
57878
58532
  );
58533
+ const foldersById = new Map(
58534
+ (context.variantFolders ?? []).map(
58535
+ (folder) => [folder.id, folder]
58536
+ )
58537
+ );
58538
+ const membersById = new Map(
58539
+ context.vm.members.map((member) => [member.id, member])
58540
+ );
57879
58541
  return variants.map((variant) => ({
57880
58542
  id: variant.id,
57881
58543
  classId: variant.classId,
57882
58544
  name: variant.name,
57883
- folder: variant.folderId === null ? null : pathByFolderId.get(variant.folderId) ?? null
58545
+ folder: variant.folderId === null ? null : pathByFolderId.get(variant.folderId) ?? null,
58546
+ ...(() => {
58547
+ const folder = variant.folderId === null ? void 0 : foldersById.get(variant.folderId);
58548
+ const collection = folder?.binding == null ? void 0 : membersById.get(folder.binding.collectionMemberId);
58549
+ const entry = isMemberListBase(collection) ? membersById.get(collection.entryMemberId) : void 0;
58550
+ return isMemberClassBase(entry) ? { valueTypeId: entry.classId } : {};
58551
+ })()
57884
58552
  }));
57885
58553
  }
57886
58554
  function virtualCSharpIdentifier(value) {
@@ -58658,7 +59326,10 @@ function memberRuntimeType(record3, context, seen = /* @__PURE__ */ new Set(), g
58658
59326
  }
58659
59327
  case 27 /* Variant */: {
58660
59328
  if (!isMemberVariantBase(member)) return UNKNOWN_TYPE2;
58661
- const systemClass = neoVariantSystemClass(context.vm.classes);
59329
+ const systemClass = neoVariantSystemClass(
59330
+ context.vm.classes,
59331
+ member.valueTypeInfo != null
59332
+ );
58662
59333
  if (systemClass === null) return UNKNOWN_TYPE2;
58663
59334
  const target = toLanguageType(
58664
59335
  member.targetTypeInfo,
@@ -58669,7 +59340,16 @@ function memberRuntimeType(record3, context, seen = /* @__PURE__ */ new Set(), g
58669
59340
  kind: "named",
58670
59341
  typeId: systemClass.classId,
58671
59342
  nullable: !required2,
58672
- typeArguments: [target]
59343
+ typeArguments: [
59344
+ target,
59345
+ ...member.valueTypeInfo == null ? [] : [
59346
+ toLanguageType(
59347
+ member.valueTypeInfo,
59348
+ context,
59349
+ genericEnvironment
59350
+ )
59351
+ ]
59352
+ ]
58673
59353
  };
58674
59354
  }
58675
59355
  case 22 /* Interface */:
@@ -64699,7 +65379,11 @@ function evalPointer(pointer, scope, ctx) {
64699
65379
  return row.value;
64700
65380
  }
64701
65381
  case "variant" /* variant */:
64702
- return { classId: pointer.classId, variantId: pointer.variantId };
65382
+ return {
65383
+ classId: pointer.classId,
65384
+ variantId: pointer.variantId,
65385
+ rowValueId: pointer.rowValueId ?? null
65386
+ };
64703
65387
  case "staticMember" /* staticMember */:
64704
65388
  return evalStaticMember(pointer.memberId, ctx);
64705
65389
  case "keyOf" /* keyOf */:
@@ -68508,13 +69192,22 @@ function readVariantSelection(value, label) {
68508
69192
  throw new NSGetterRuntimeError(`${label} carries no class id.`);
68509
69193
  }
68510
69194
  const variantId = record3.variantId;
69195
+ const rowValueId = record3.rowValueId;
69196
+ if (rowValueId !== void 0 && rowValueId !== null && (typeof rowValueId !== "string" || rowValueId.length === 0)) {
69197
+ throw new NSGetterRuntimeError(
69198
+ `${label} carries a malformed lookup row id.`
69199
+ );
69200
+ }
68511
69201
  if (variantId === null || variantId === void 0) {
68512
- return { classId, variantId: null };
69202
+ if (rowValueId != null) {
69203
+ throw new NSGetterRuntimeError(`${label} cannot bind a row to Base.`);
69204
+ }
69205
+ return { classId, variantId: null, rowValueId: null };
68513
69206
  }
68514
69207
  if (typeof variantId !== "string" || variantId.length === 0) {
68515
69208
  throw new NSGetterRuntimeError(`${label} carries a malformed variant id.`);
68516
69209
  }
68517
- return { classId, variantId };
69210
+ return { classId, variantId, rowValueId: rowValueId ?? null };
68518
69211
  }
68519
69212
  function resolveVariantGraph(selection2, ctx) {
68520
69213
  const variantId = selection2.variantId;
@@ -68557,9 +69250,67 @@ function resolveVariantGraph(selection2, ctx) {
68557
69250
  variantId,
68558
69251
  name: record3.name,
68559
69252
  targetClassId: record3.classId,
68560
- root
69253
+ root,
69254
+ binding: record3.folderId === null ? null : (ctx.vm.variantFolders ?? []).find(
69255
+ (folder) => folder.id === record3.folderId
69256
+ )?.binding ?? null
68561
69257
  };
68562
69258
  }
69259
+ function resolveVariantRowParameter(args) {
69260
+ const { graph, selection: selection2, explicitPointer, scope, ctx } = args;
69261
+ const binding = graph.binding;
69262
+ if (binding === null) {
69263
+ if (explicitPointer !== void 0 || selection2.rowValueId !== null) {
69264
+ throw new NSGetterRuntimeError(
69265
+ `Variant '${graph.name}' is not collection-bound and cannot receive a row.`
69266
+ );
69267
+ }
69268
+ return [];
69269
+ }
69270
+ let rowId = selection2.rowValueId;
69271
+ let rowValue;
69272
+ if (explicitPointer !== void 0) {
69273
+ rowValue = evalPointer(explicitPointer, scope, ctx);
69274
+ if (typeof rowValue === "object" && rowValue !== null) {
69275
+ rowId = trackedRowForValueReference(rowValue, ctx)?.id ?? null;
69276
+ }
69277
+ }
69278
+ if (rowId === null) {
69279
+ throw new NSGetterRuntimeError(
69280
+ `Lookup variant '${graph.name}' requires a row from its bound collection.`
69281
+ );
69282
+ }
69283
+ const row = evalValueById(
69284
+ ctx,
69285
+ rowId,
69286
+ ctx.__runtimeSessionValues,
69287
+ ctx.__valueOverlay
69288
+ );
69289
+ if (row === null) {
69290
+ throw new NSGetterRuntimeError(
69291
+ `Lookup variant '${graph.name}' references missing row '${rowId}'.`
69292
+ );
69293
+ }
69294
+ const collection = evalValueById(
69295
+ ctx,
69296
+ binding.collectionValueId,
69297
+ ctx.__runtimeSessionValues,
69298
+ ctx.__valueOverlay
69299
+ );
69300
+ const isOrderedEntry = Array.isArray(collection?.value) ? collection.value.includes(rowId) : false;
69301
+ const isUnorderedEntry = row.containerId === binding.collectionValueId;
69302
+ if (!isOrderedEntry && !isUnorderedEntry) {
69303
+ throw new NSGetterRuntimeError(
69304
+ `Lookup variant '${graph.name}' row '${rowId}' is not an entry of collection '${binding.collectionValueId}'.`
69305
+ );
69306
+ }
69307
+ if (explicitPointer !== void 0 && rowValue !== row.value) {
69308
+ throw new NSGetterRuntimeError(
69309
+ `Lookup variant '${graph.name}' received a value that does not resolve to bound row '${rowId}'.`
69310
+ );
69311
+ }
69312
+ return [row.value];
69313
+ }
68563
69314
  function invokeVariantClosure(args) {
68564
69315
  const { closureValueId, ctx, label, lexicalThis, parameters } = args;
68565
69316
  if (typeof closureValueId !== "string" || closureValueId.length === 0) {
@@ -68743,6 +69494,13 @@ function evalVariantInitialize(info, scope, ctx) {
68743
69494
  );
68744
69495
  }
68745
69496
  const graph = resolveVariantGraph(selection2, ctx);
69497
+ const parameters = resolveVariantRowParameter({
69498
+ graph,
69499
+ selection: selection2,
69500
+ explicitPointer: info.rowPointer,
69501
+ scope,
69502
+ ctx
69503
+ });
68746
69504
  const closeFrame = pushConstructionFrame(ctx, `Variants.${graph.name}`);
68747
69505
  try {
68748
69506
  const instance = invokeVariantClosure({
@@ -68750,7 +69508,7 @@ function evalVariantInitialize(info, scope, ctx) {
68750
69508
  ctx,
68751
69509
  label: `Variant '${graph.name}' Initialize`,
68752
69510
  lexicalThis: null,
68753
- parameters: []
69511
+ parameters
68754
69512
  });
68755
69513
  if (instance === null || instance === void 0) {
68756
69514
  throw new NSGetterRuntimeError(
@@ -68777,6 +69535,13 @@ function evalVariantApply(info, scope, ctx) {
68777
69535
  );
68778
69536
  if (selection2.variantId === null) return receiver;
68779
69537
  const graph = resolveVariantGraph(selection2, ctx);
69538
+ const rowParameters = resolveVariantRowParameter({
69539
+ graph,
69540
+ selection: selection2,
69541
+ explicitPointer: info.rowPointer,
69542
+ scope,
69543
+ ctx
69544
+ });
68780
69545
  const applyValueId = graph.root.Apply;
68781
69546
  if (typeof applyValueId === "string" && applyValueId.length > 0) {
68782
69547
  invokeVariantClosure({
@@ -68784,7 +69549,7 @@ function evalVariantApply(info, scope, ctx) {
68784
69549
  ctx,
68785
69550
  label: `Variant '${graph.name}' Apply`,
68786
69551
  lexicalThis: null,
68787
- parameters: [receiver]
69552
+ parameters: [receiver, ...rowParameters]
68788
69553
  });
68789
69554
  }
68790
69555
  applyVariantDeclarativeHalves(receiver, graph, ctx);
@@ -73573,17 +74338,17 @@ var init_lower_relations = __esm({
73573
74338
  function lowerVariants(context, analysis) {
73574
74339
  const folders = lowerVariantFolders(context, analysis);
73575
74340
  const foldersBySymbol = new Map(
73576
- analysis.variants.globals.filter((global) => global.type.name === NEO_VARIANT_FOLDER_TYPE_NAME).map(
74341
+ analysis.variants.globals.filter((global) => isNeoVariantFolderTypeName(global.type.name)).map(
73577
74342
  (global) => [
73578
74343
  folderSymbolKey(owningClassName(global), global.name),
73579
- variantRecordId(global, NEO_VARIANT_FOLDER_TYPE_NAME)
74344
+ variantRecordId(global, global.type.name)
73580
74345
  ]
73581
74346
  )
73582
74347
  );
73583
74348
  const variants = variantGlobals(analysis).map((global) => {
73584
74349
  const className = owningClassName(global);
73585
74350
  const classId = requiredName2(context.classIdsByName, className, "class");
73586
- const variantId = variantRecordId(global, NEO_VARIANT_TYPE_NAME);
74351
+ const variantId = variantRecordId(global, global.type.name);
73587
74352
  return {
73588
74353
  id: variantId,
73589
74354
  source: sourceIdentity2(global, "variant", global.name),
@@ -73613,6 +74378,14 @@ function variantValueBindingsV4(manifest, analysis) {
73613
74378
  variantId: variant.id,
73614
74379
  targetClassId: variant.classId,
73615
74380
  targetClassName: requiredClassName2(classNames, variant.classId),
74381
+ variantTypeName: global.type.name,
74382
+ valueClassId: global.type.name === NEO_LOOKUP_VARIANT_TYPE_NAME ? requiredName2(
74383
+ new Map(
74384
+ manifest.classes.map((entry) => [entry.name, entry.id])
74385
+ ),
74386
+ valueClassName(global),
74387
+ "class"
74388
+ ) : null,
73616
74389
  valueId: variant.valueId,
73617
74390
  initializer: global.initializer,
73618
74391
  source: global.source,
@@ -73632,17 +74405,18 @@ function requiredClassName2(classNames, classId) {
73632
74405
  }
73633
74406
  function variantGlobals(analysis) {
73634
74407
  return analysis.variants.globals.filter(
73635
- (global) => global.type.name === NEO_VARIANT_TYPE_NAME
74408
+ (global) => isNeoVariantTypeName(global.type.name)
73636
74409
  );
73637
74410
  }
73638
74411
  function lowerVariantFolders(context, analysis) {
73639
- return analysis.variants.globals.filter((global) => global.type.name === NEO_VARIANT_FOLDER_TYPE_NAME).map((global) => {
74412
+ return analysis.variants.globals.filter((global) => isNeoVariantFolderTypeName(global.type.name)).map((global) => {
73640
74413
  const className = owningClassName(global);
73641
74414
  return {
73642
74415
  id: variantRecordId(global, NEO_VARIANT_FOLDER_TYPE_NAME),
73643
74416
  source: sourceIdentity2(global, "variantFolder", global.name),
73644
74417
  classId: requiredName2(context.classIdsByName, className, "class"),
73645
- path: folderPath2(global)
74418
+ path: folderPath2(global),
74419
+ binding: folderBinding(context, global)
73646
74420
  };
73647
74421
  });
73648
74422
  }
@@ -73656,7 +74430,7 @@ function owningClassName(global) {
73656
74430
  return argument2;
73657
74431
  }
73658
74432
  function variantRecordId(global, typeName) {
73659
- const kind = typeName === NEO_VARIANT_TYPE_NAME ? "variant" : "variant-folder";
74433
+ const kind = isNeoVariantTypeName(typeName) ? "variant" : "variant-folder";
73660
74434
  return materializedIdentityId(global, kind, global.name);
73661
74435
  }
73662
74436
  function variantRootValueId(context, variantId, global, className) {
@@ -73702,14 +74476,19 @@ function folderPath2(global) {
73702
74476
  `Variant folder ${global.name} must be declared as new("Path").`
73703
74477
  );
73704
74478
  }
73705
- const argument2 = expression.args[0];
74479
+ const pathIndex = expression.argumentNames?.findIndex(
74480
+ (name) => name === "path"
74481
+ );
74482
+ const argument2 = expression.args[pathIndex !== void 0 && pathIndex >= 0 ? pathIndex : 0];
73706
74483
  if (argument2 === void 0 || argument2.kind !== "litString") {
73707
74484
  throw new Error(
73708
74485
  `Variant folder ${global.name} requires one string-literal path argument.`
73709
74486
  );
73710
74487
  }
73711
- if (argument2.value.length === 0) {
73712
- throw new Error(`Variant folder ${global.name} has an empty path.`);
74488
+ if (argument2.value.length === 0 && global.type.name !== NEO_LOOKUP_VARIANT_FOLDER_TYPE_NAME) {
74489
+ throw new Error(
74490
+ `Only a bound lookup variant folder may have an empty path.`
74491
+ );
73713
74492
  }
73714
74493
  return argument2.value;
73715
74494
  }
@@ -73721,7 +74500,7 @@ function unwrapAnnotated2(expression) {
73721
74500
  function buildVariantPathIndexV4(classIdsByName, analysis) {
73722
74501
  const index = /* @__PURE__ */ new Map();
73723
74502
  const foldersBySymbol = new Map(
73724
- analysis.variants.globals.filter((global) => global.type.name === NEO_VARIANT_FOLDER_TYPE_NAME).map(
74503
+ analysis.variants.globals.filter((global) => isNeoVariantFolderTypeName(global.type.name)).map(
73725
74504
  (global) => [
73726
74505
  folderSymbolKey(owningClassName(global), global.name),
73727
74506
  folderPath2(global)
@@ -73733,7 +74512,8 @@ function buildVariantPathIndexV4(classIdsByName, analysis) {
73733
74512
  `${className}.${NEO_VARIANT_SCOPE_NAME}.${NEO_VARIANT_RESERVED_NAME}`,
73734
74513
  {
73735
74514
  classId,
73736
- variantId: null
74515
+ variantId: null,
74516
+ valueClassId: null
73737
74517
  }
73738
74518
  );
73739
74519
  }
@@ -73748,16 +74528,88 @@ function buildVariantPathIndexV4(classIdsByName, analysis) {
73748
74528
  const scope = path === null ? `${className}.${NEO_VARIANT_SCOPE_NAME}` : `${className}.${NEO_VARIANT_SCOPE_NAME}.${path.split(NEO_VARIANT_FOLDER_PATH_SEPARATOR).join(".")}`;
73749
74529
  index.set(`${scope}.${global.name}`, {
73750
74530
  classId,
73751
- variantId: variantRecordId(global, NEO_VARIANT_TYPE_NAME)
74531
+ variantId: variantRecordId(global, global.type.name),
74532
+ valueClassId: global.type.name === NEO_LOOKUP_VARIANT_TYPE_NAME ? classIdsByName.get(valueClassName(global)) ?? null : null
73752
74533
  });
73753
74534
  }
73754
74535
  return index;
73755
74536
  }
74537
+ function valueClassName(global) {
74538
+ const argument2 = global.type.arguments[1]?.name;
74539
+ if (argument2 === void 0) {
74540
+ throw new Error(
74541
+ `${global.type.name} ${global.name} requires a TValue type argument.`
74542
+ );
74543
+ }
74544
+ return argument2;
74545
+ }
74546
+ function folderBinding(context, global) {
74547
+ if (global.type.name !== NEO_LOOKUP_VARIANT_FOLDER_TYPE_NAME) return null;
74548
+ const expression = unwrapAnnotated2(parseExpression(global.initializer));
74549
+ if (expression.kind !== "new") {
74550
+ throw new Error(
74551
+ `Lookup variant folder ${global.name} must be declared with new(...).`
74552
+ );
74553
+ }
74554
+ const named = /* @__PURE__ */ new Map();
74555
+ expression.args.forEach((argument2, index) => {
74556
+ const name = expression.argumentNames?.[index];
74557
+ if (name !== void 0 && name !== null) named.set(name, argument2);
74558
+ });
74559
+ const collection = named.get("collection");
74560
+ const collectionValue = named.get("collectionValue");
74561
+ const collectionPath = collection === void 0 ? null : dottedPath(collection);
74562
+ if (collectionPath === null) {
74563
+ throw new Error(
74564
+ `Lookup variant folder ${global.name} requires collection: Class.Member.`
74565
+ );
74566
+ }
74567
+ const collectionMemberId = resolveQualifiedMemberId(context, collectionPath);
74568
+ if (collectionMemberId === null) {
74569
+ throw new Error(
74570
+ `Lookup variant folder ${global.name} names unknown collection ${collectionPath}.`
74571
+ );
74572
+ }
74573
+ if (collectionValue === void 0) {
74574
+ throw new Error(
74575
+ `Lookup variant folder ${global.name} requires collectionValue: Reference(...).`
74576
+ );
74577
+ }
74578
+ const collectionValueId = lowerReferenceValue(collectionValue);
74579
+ if (collectionValueId === null) {
74580
+ throw new Error(
74581
+ `Lookup variant folder ${global.name} has an invalid collectionValue reference.`
74582
+ );
74583
+ }
74584
+ return { collectionMemberId, collectionValueId };
74585
+ }
74586
+ function lowerReferenceValue(expression) {
74587
+ if (expression.kind !== "call" || expression.callee.kind !== "ident" || expression.callee.name !== "Reference")
74588
+ return null;
74589
+ const idIndex = expression.argumentNames?.findIndex((name) => name === "id") ?? -1;
74590
+ const target = expression.args[idIndex >= 0 ? idIndex : 0];
74591
+ if (target?.kind === "litString") return target.value;
74592
+ const path = target === void 0 ? null : dottedPath(target);
74593
+ if (path === null || !path.startsWith("root.")) return null;
74594
+ return referenceId(`Reference(${path})`);
74595
+ }
74596
+ function dottedPath(expression) {
74597
+ const parts = [];
74598
+ let cursor = expression;
74599
+ while (cursor.kind === "member") {
74600
+ parts.unshift(cursor.name);
74601
+ cursor = cursor.receiver;
74602
+ }
74603
+ if (cursor.kind !== "ident") return null;
74604
+ parts.unshift(cursor.name);
74605
+ return parts.join(".");
74606
+ }
73756
74607
  var init_lower_variants = __esm({
73757
74608
  "src/project-source/lower-variants.ts"() {
73758
74609
  "use strict";
73759
74610
  init_src();
73760
74611
  init_lower_support();
74612
+ init_lower_members();
73761
74613
  }
73762
74614
  });
73763
74615
 
@@ -76406,6 +77258,30 @@ function resolveRootPathCollectionValuesV4(args) {
76406
77258
  }
76407
77259
  return resolved;
76408
77260
  }
77261
+ function resolveRootPathVariantFolderCollectionValuesV4(args) {
77262
+ const resolved = /* @__PURE__ */ new Map();
77263
+ for (const folder of args.manifest.variantFolders) {
77264
+ const spelled = folder.binding?.collectionValueId;
77265
+ if (typeof spelled !== "string") continue;
77266
+ if (!spelled.startsWith(ROOT_PATH_PENDING_PREFIX)) continue;
77267
+ const path = spelled.slice(ROOT_PATH_PENDING_PREFIX.length);
77268
+ const site = args.siteForFolder(folder.id);
77269
+ const fail = (message) => {
77270
+ args.registry.loweringFailures.push({
77271
+ message,
77272
+ site: {
77273
+ uri: site?.uri ?? "<schema>",
77274
+ declarationStart: site?.start ?? { line: 1, character: 0 },
77275
+ expression: null,
77276
+ label: folder.path
77277
+ }
77278
+ });
77279
+ };
77280
+ const target = walkRootValuePath(path, args.state, args.registry, fail);
77281
+ if (target !== null) resolved.set(folder.id, target);
77282
+ }
77283
+ return resolved;
77284
+ }
76409
77285
  function walkRootValuePath(path, state, registry, fail) {
76410
77286
  const segments = path.split(".");
76411
77287
  const slotName = segments[1];
@@ -77890,18 +78766,31 @@ function syntheticMember(fields) {
77890
78766
  function variantOwnershipRoots(args) {
77891
78767
  const empty = { roots: [], bindingMembers: [] };
77892
78768
  if (args.variants.length === 0) return empty;
77893
- const variantClass = args.classes.find(
78769
+ const plainVariantClass = args.classes.find(
77894
78770
  (schemaClass2) => schemaClass2.system?.worldKind === NeoWorldSystemClassKind.Variant
77895
78771
  );
77896
- if (variantClass === void 0) return empty;
77897
- const parameterId = variantClass.genericParams?.[0]?.id;
77898
- if (parameterId === void 0) return empty;
78772
+ if (plainVariantClass === void 0) return empty;
77899
78773
  const valueById = new Map(args.values.map((value) => [value.id, value]));
78774
+ const folderById = new Map(
78775
+ (args.variantFolders ?? []).map((folder) => [folder.id, folder])
78776
+ );
78777
+ const memberById = new Map(
78778
+ (args.members ?? []).map((member) => [member.id, member])
78779
+ );
77900
78780
  const roots = [];
77901
78781
  const bindingMembers = [];
77902
78782
  for (const variant of args.variants) {
77903
78783
  const root = valueById.get(variant.valueId);
77904
78784
  if (root === void 0) continue;
78785
+ const lookupFolder = variant.folderId == null ? void 0 : folderById.get(variant.folderId);
78786
+ const inferredVariantKind = lookupFolder?.binding == null ? NeoWorldSystemClassKind.Variant : NeoWorldSystemClassKind.LookupVariant;
78787
+ const variantClass = args.classes.find(
78788
+ (schemaClass2) => schemaClass2.id === root.classId && (schemaClass2.system?.worldKind === NeoWorldSystemClassKind.Variant || schemaClass2.system?.worldKind === NeoWorldSystemClassKind.LookupVariant)
78789
+ ) ?? args.classes.find(
78790
+ (schemaClass2) => schemaClass2.system?.worldKind === inferredVariantKind
78791
+ ) ?? plainVariantClass;
78792
+ const parameterId = variantClass.genericParams?.[0]?.id;
78793
+ if (parameterId === void 0) continue;
77905
78794
  const stampedTargetMemberId = readStampedTargetMemberId(root, parameterId);
77906
78795
  const targetClassId = variant.classId;
77907
78796
  let binding = null;
@@ -77919,13 +78808,31 @@ function variantOwnershipRoots(args) {
77919
78808
  );
77920
78809
  binding = { kind: "member", memberId: bindingMemberId };
77921
78810
  }
78811
+ const stampedArguments = Object.fromEntries(
78812
+ (variantClass.genericParams ?? []).flatMap((parameter4) => {
78813
+ const memberId = root.genericBindings?.[parameter4.id];
78814
+ return typeof memberId === "string" ? [[parameter4.id, { kind: "member", memberId }]] : [];
78815
+ })
78816
+ );
78817
+ const lookupParameterId = variantClass.genericParams?.[1]?.id;
78818
+ const lookupCollection = lookupFolder?.binding == null ? void 0 : memberById.get(lookupFolder.binding.collectionMemberId);
78819
+ const lookupBinding = lookupParameterId !== void 0 && isMemberListBase(lookupCollection) ? {
78820
+ [lookupParameterId]: {
78821
+ kind: "member",
78822
+ memberId: lookupCollection.entryMemberId
78823
+ }
78824
+ } : {};
77922
78825
  roots.push({
77923
78826
  valueId: variant.valueId,
77924
78827
  member: syntheticMember({
77925
78828
  id: variantRootMemberId(variant.id),
77926
78829
  name: "Variant",
77927
78830
  classId: variantClass.id,
77928
- classArguments: binding === null ? {} : { [parameterId]: binding }
78831
+ classArguments: {
78832
+ ...stampedArguments,
78833
+ ...binding === null ? {} : { [parameterId]: binding },
78834
+ ...lookupBinding
78835
+ }
77929
78836
  })
77930
78837
  });
77931
78838
  }
@@ -77941,8 +78848,10 @@ function readStampedTargetMemberId(root, parameterId) {
77941
78848
  function withVariantOwnershipRoots(document, variants) {
77942
78849
  const { roots, bindingMembers } = variantOwnershipRoots({
77943
78850
  variants,
78851
+ variantFolders: document.variantFolders,
77944
78852
  values: document.values,
77945
- classes: document.classes
78853
+ classes: document.classes,
78854
+ members: document.members
77946
78855
  });
77947
78856
  if (bindingMembers.length === 0) return { document, roots };
77948
78857
  return {
@@ -79716,6 +80625,28 @@ var init_world_layer_link_target = __esm({
79716
80625
  });
79717
80626
 
79718
80627
  // ../src/models/project/world-content-sidecar-validation.ts
80628
+ function worldPlacementVariantBindings(args) {
80629
+ const foldersById = new Map(
80630
+ args.variantFolders.map((folder) => [folder.id, folder])
80631
+ );
80632
+ const membersById = new Map(
80633
+ args.members.map((member) => [member.id, member])
80634
+ );
80635
+ const valuesById = new Map(args.values.map((value) => [value.id, value]));
80636
+ return args.variants.map((variant) => {
80637
+ const folder = variant.folderId === null ? null : foldersById.get(variant.folderId) ?? null;
80638
+ const binding = folder?.binding ?? null;
80639
+ if (binding === null) return { id: variant.id, classId: variant.classId };
80640
+ const collection = membersById.get(binding.collectionMemberId);
80641
+ const value = valuesById.get(binding.collectionValueId);
80642
+ const rows = isMemberListBase(collection) && value !== void 0 ? listEntryIdsForValue(collection, value, args.values) : [];
80643
+ return {
80644
+ id: variant.id,
80645
+ classId: variant.classId,
80646
+ lookupRowValueIds: rows
80647
+ };
80648
+ });
80649
+ }
79719
80650
  function validateWorldContentSidecars(args) {
79720
80651
  const variants = args.variants ?? [];
79721
80652
  const valuesById = new Map(args.values.map((value) => [value.id, value]));
@@ -79984,11 +80915,19 @@ function validateWorldPlacementSidecars(args) {
79984
80915
  assetClassId,
79985
80916
  placementId: args.placement.id,
79986
80917
  variantId: optionalNonEmptyString(record3.variantId),
80918
+ rowValueId: optionalNonEmptyString(record3.rowValueId),
79987
80919
  variants: args.variants
79988
80920
  });
79989
80921
  }
79990
80922
  function validatePlacementVariantBinding(args) {
79991
- if (args.variantId === null) return;
80923
+ if (args.variantId === null) {
80924
+ if (args.rowValueId !== null) {
80925
+ throw new Error(
80926
+ `Placement value "${args.placementId}" binds row "${args.rowValueId}" without a variant.`
80927
+ );
80928
+ }
80929
+ return;
80930
+ }
79992
80931
  const variant = args.variants.find(
79993
80932
  (candidate) => candidate.id === args.variantId
79994
80933
  );
@@ -80002,6 +80941,20 @@ function validatePlacementVariantBinding(args) {
80002
80941
  `Placement value "${args.placementId}" places class "${args.assetClassId}" but names variant "${args.variantId}" of class "${variant.classId}"; variant lookup is not virtual.`
80003
80942
  );
80004
80943
  }
80944
+ const lookupRows = variant.lookupRowValueIds;
80945
+ if (lookupRows === void 0) {
80946
+ if (args.rowValueId !== null) {
80947
+ throw new Error(
80948
+ `Placement value "${args.placementId}" binds a row to plain variant "${args.variantId}".`
80949
+ );
80950
+ }
80951
+ return;
80952
+ }
80953
+ if (args.rowValueId === null || !lookupRows.includes(args.rowValueId)) {
80954
+ throw new Error(
80955
+ `Placement value "${args.placementId}" must bind a live row from lookup variant "${args.variantId}".`
80956
+ );
80957
+ }
80005
80958
  }
80006
80959
  function validateOwnedSparseValue(args) {
80007
80960
  if (args.referenceId === null) return;
@@ -80136,6 +81089,7 @@ var init_world_content_sidecar_validation = __esm({
80136
81089
  init_classes();
80137
81090
  init_world_layer_link_target();
80138
81091
  init_core();
81092
+ init_members();
80139
81093
  init_internal_record_relations();
80140
81094
  WORLD_PLACEMENT_SIDECAR_KEYS = [
80141
81095
  "assetClassId",
@@ -80199,10 +81153,29 @@ function readVariantFolderRecord(value) {
80199
81153
  if (typeof value.classId !== "string" || value.classId.length === 0) {
80200
81154
  throw new Error(`Variant folder "${value.id}" classId is invalid.`);
80201
81155
  }
80202
- if (typeof value.path !== "string" || value.path.length === 0) {
81156
+ if (typeof value.path !== "string") {
80203
81157
  throw new Error(`Variant folder "${value.id}" path is invalid.`);
80204
81158
  }
80205
- return { id: value.id, classId: value.classId, path: value.path };
81159
+ let binding = null;
81160
+ if (value.binding !== void 0 && value.binding !== null) {
81161
+ if (!isRecordObject(value.binding)) {
81162
+ throw new Error(`Variant folder "${value.id}" binding is invalid.`);
81163
+ }
81164
+ const collectionMemberId = value.binding.collectionMemberId;
81165
+ const collectionValueId = value.binding.collectionValueId;
81166
+ if (typeof collectionMemberId !== "string" || collectionMemberId.length === 0 || typeof collectionValueId !== "string" || collectionValueId.length === 0) {
81167
+ throw new Error(
81168
+ `Variant folder "${value.id}" binding requires collectionMemberId and collectionValueId.`
81169
+ );
81170
+ }
81171
+ binding = { collectionMemberId, collectionValueId };
81172
+ }
81173
+ if (value.path.length === 0 && binding === null) {
81174
+ throw new Error(
81175
+ `Variant folder "${value.id}" may use the class-root path only with a collection binding.`
81176
+ );
81177
+ }
81178
+ return { id: value.id, classId: value.classId, path: value.path, binding };
80206
81179
  }
80207
81180
  function resolveVariantWorldKind(classId, classesById) {
80208
81181
  const visited = /* @__PURE__ */ new Set();
@@ -80220,7 +81193,9 @@ function resolveVariantWorldKind(classId, classesById) {
80220
81193
  function isVariantFamilyClassId(classId, classesById) {
80221
81194
  const worldKind = resolveVariantWorldKind(classId, classesById);
80222
81195
  if (worldKind === VARIANT_WORLD_KIND) return true;
80223
- return worldKind === VARIANT_FOLDER_WORLD_KIND;
81196
+ if (worldKind === VARIANT_FOLDER_WORLD_KIND) return true;
81197
+ if (worldKind === LOOKUP_VARIANT_WORLD_KIND) return true;
81198
+ return worldKind === LOOKUP_VARIANT_FOLDER_WORLD_KIND;
80224
81199
  }
80225
81200
  function variantFamilyLabel(classId, classesById) {
80226
81201
  const schemaClass2 = classesById.get(classId);
@@ -80325,7 +81300,7 @@ function assertNoVariantTypePositions(args) {
80325
81300
  function assertNoAuthoredVariantMembers(args) {
80326
81301
  for (const schemaClass2 of args.classesById.values()) {
80327
81302
  const worldKind = schemaClass2.system?.worldKind;
80328
- if (worldKind !== VARIANT_WORLD_KIND && worldKind !== VARIANT_FOLDER_WORLD_KIND) {
81303
+ if (worldKind !== VARIANT_WORLD_KIND && worldKind !== VARIANT_FOLDER_WORLD_KIND && worldKind !== LOOKUP_VARIANT_WORLD_KIND && worldKind !== LOOKUP_VARIANT_FOLDER_WORLD_KIND) {
80329
81304
  continue;
80330
81305
  }
80331
81306
  for (const [schemaKey, memberId] of Object.entries(schemaClass2.schema)) {
@@ -80354,6 +81329,12 @@ function assertOpaqueVariantsValid(args) {
80354
81329
  args.folders.map((folder) => [folder.id, folder])
80355
81330
  );
80356
81331
  const folderPathsByClass = /* @__PURE__ */ new Map();
81332
+ const rootFolderByClass = /* @__PURE__ */ new Map();
81333
+ const valuesById = new Map(
81334
+ (args.values ?? []).flatMap(
81335
+ (value) => isRecordObject(value) && typeof value.id === "string" ? [[value.id, value]] : []
81336
+ )
81337
+ );
80357
81338
  for (const folder of args.folders) {
80358
81339
  const owner = args.classesById.get(folder.classId);
80359
81340
  if (owner === void 0) {
@@ -80362,7 +81343,42 @@ function assertOpaqueVariantsValid(args) {
80362
81343
  );
80363
81344
  }
80364
81345
  assertVariantTargetClass(owner, args, `Variant folder "${folder.id}"`);
80365
- const segments = folder.path.split(VARIANT_FOLDER_PATH_SEPARATOR);
81346
+ const binding = folder.binding ?? null;
81347
+ if (binding !== null) {
81348
+ const collection = args.membersById?.get(binding.collectionMemberId);
81349
+ if (collection === void 0 || collection.kind !== MEMBER_KIND_LIST) {
81350
+ throw new Error(
81351
+ `Lookup variant folder "${folder.id}" binds missing or non-list collection member "${binding.collectionMemberId}".`
81352
+ );
81353
+ }
81354
+ const entry = typeof collection.entryMemberId === "string" ? args.membersById?.get(collection.entryMemberId) : void 0;
81355
+ if (entry === void 0 || entry.kind !== MEMBER_KIND_CLASS || typeof entry.classId !== "string") {
81356
+ throw new Error(
81357
+ `Lookup variant folder "${folder.id}" collection "${binding.collectionMemberId}" must contain Class entries.`
81358
+ );
81359
+ }
81360
+ const collectionValue = valuesById.get(binding.collectionValueId);
81361
+ if (collectionValue === void 0) {
81362
+ throw new Error(
81363
+ `Lookup variant folder "${folder.id}" binds missing collection value "${binding.collectionValueId}".`
81364
+ );
81365
+ }
81366
+ if (Object.hasOwn(collectionValue, "memberId") && collectionValue.memberId !== null && collectionValue.memberId !== void 0 && collectionValue.memberId !== binding.collectionMemberId) {
81367
+ throw new Error(
81368
+ `Lookup variant folder "${folder.id}" binds collection value "${binding.collectionValueId}" from member "${String(collectionValue.memberId)}" instead of collection "${binding.collectionMemberId}".`
81369
+ );
81370
+ }
81371
+ }
81372
+ if (folder.path.length === 0) {
81373
+ const prior = rootFolderByClass.get(folder.classId);
81374
+ if (prior !== void 0) {
81375
+ throw new Error(
81376
+ `Lookup variant folders "${prior}" and "${folder.id}" both enumerate the class root; at most one is allowed per class.`
81377
+ );
81378
+ }
81379
+ rootFolderByClass.set(folder.classId, folder.id);
81380
+ }
81381
+ const segments = folder.path.length === 0 ? [] : folder.path.split(VARIANT_FOLDER_PATH_SEPARATOR);
80366
81382
  for (const segment of segments) {
80367
81383
  assertVariantIdentifier(
80368
81384
  segment,
@@ -80402,7 +81418,7 @@ function assertOpaqueVariantsValid(args) {
80402
81418
  scopeMembers.set(key, members);
80403
81419
  };
80404
81420
  for (const folder of args.folders) {
80405
- const segments = folder.path.split(VARIANT_FOLDER_PATH_SEPARATOR);
81421
+ const segments = folder.path.length === 0 ? [] : folder.path.split(VARIANT_FOLDER_PATH_SEPARATOR);
80406
81422
  segments.forEach((segment, index) => {
80407
81423
  const parentPath = segments.slice(0, index).join(VARIANT_FOLDER_PATH_SEPARATOR);
80408
81424
  const ownPath = segments.slice(0, index + 1).join(VARIANT_FOLDER_PATH_SEPARATOR);
@@ -80449,6 +81465,19 @@ function assertOpaqueVariantsValid(args) {
80449
81465
  );
80450
81466
  }
80451
81467
  scopePath = folder.path;
81468
+ const root = valuesById.get(variant.valueId);
81469
+ const rootClassId = root?.classId;
81470
+ const rootWorldKind = typeof rootClassId === "string" ? resolveVariantWorldKind(rootClassId, args.classesById) : null;
81471
+ const expectedWorldKind = (folder.binding ?? null) === null ? VARIANT_WORLD_KIND : LOOKUP_VARIANT_WORLD_KIND;
81472
+ if (rootWorldKind !== null && rootWorldKind !== expectedWorldKind) {
81473
+ throw new Error(
81474
+ `Variant "${variant.id}" in folder "${folder.id}" roots world kind "${rootWorldKind}"; this folder requires "${expectedWorldKind}".`
81475
+ );
81476
+ }
81477
+ } else if (rootFolderByClass.has(variant.classId)) {
81478
+ throw new Error(
81479
+ `Variant "${variant.id}" is declared at the class root beside root lookup folder "${rootFolderByClass.get(variant.classId)}"; the root is either authored or collection-bound, never both.`
81480
+ );
80452
81481
  }
80453
81482
  claimScope(
80454
81483
  variant.classId,
@@ -80467,13 +81496,15 @@ function assertVariantTargetClass(owner, args, describe2) {
80467
81496
  );
80468
81497
  }
80469
81498
  }
80470
- var VARIANT_WORLD_KIND, VARIANT_FOLDER_WORLD_KIND, RESERVED_VARIANT_NAME, VARIANT_FOLDER_PATH_SEPARATOR, MEMBER_KIND_DICTIONARY, MEMBER_KIND_LIST, MEMBER_KIND_CLASS, MEMBER_KIND_LOOKUP, MEMBER_KIND_VARIANT, variantScopeKey, variantFolderScopeKey;
81499
+ var VARIANT_WORLD_KIND, VARIANT_FOLDER_WORLD_KIND, LOOKUP_VARIANT_WORLD_KIND, LOOKUP_VARIANT_FOLDER_WORLD_KIND, RESERVED_VARIANT_NAME, VARIANT_FOLDER_PATH_SEPARATOR, MEMBER_KIND_DICTIONARY, MEMBER_KIND_LIST, MEMBER_KIND_CLASS, MEMBER_KIND_LOOKUP, MEMBER_KIND_VARIANT, variantScopeKey, variantFolderScopeKey;
80471
81500
  var init_projectVariantValidation = __esm({
80472
81501
  "../convex/projectVariantValidation.ts"() {
80473
81502
  "use strict";
80474
81503
  init_src();
80475
81504
  VARIANT_WORLD_KIND = "variant";
80476
81505
  VARIANT_FOLDER_WORLD_KIND = "variantFolder";
81506
+ LOOKUP_VARIANT_WORLD_KIND = "lookupVariant";
81507
+ LOOKUP_VARIANT_FOLDER_WORLD_KIND = "lookupVariantFolder";
80477
81508
  RESERVED_VARIANT_NAME = "Base";
80478
81509
  VARIANT_FOLDER_PATH_SEPARATOR = "/";
80479
81510
  MEMBER_KIND_DICTIONARY = 5;
@@ -80873,7 +81904,9 @@ function assertProjectInterfaceDocumentValid(view, options = {}) {
80873
81904
  variants: (view.variants ?? []).map(readVariantRecord),
80874
81905
  folders: (view.variantFolders ?? []).map(readVariantFolderRecord),
80875
81906
  classesById,
80876
- objectWorldKind: NEO_OBJECT_WORLD_KIND
81907
+ objectWorldKind: NEO_OBJECT_WORLD_KIND,
81908
+ membersById,
81909
+ values: view.values
80877
81910
  });
80878
81911
  }
80879
81912
  for (const member of members) {
@@ -90083,7 +91116,13 @@ function assertStagedWorldContentLayerBindingsValid(current, projected, changes)
90083
91116
  validateWorldContentSidecars({
90084
91117
  classes,
90085
91118
  relations,
90086
- values: projected.values
91119
+ values: projected.values,
91120
+ variants: worldPlacementVariantBindings({
91121
+ variants: projected.variants ?? [],
91122
+ variantFolders: projected.variantFolders ?? [],
91123
+ members: projected.members,
91124
+ values: projected.values
91125
+ })
90087
91126
  });
90088
91127
  validateWorldLayerLinkClassTargets({
90089
91128
  classes,
@@ -90235,7 +91274,8 @@ function assertStagedProjectInterfaceValid(projected, changes) {
90235
91274
  constructors: projected.constructors ?? [],
90236
91275
  variants: projected.variants ?? [],
90237
91276
  variantFolders: projected.variantFolders ?? [],
90238
- interfaces: projected.interfaces
91277
+ interfaces: projected.interfaces,
91278
+ values: projected.values
90239
91279
  },
90240
91280
  {
90241
91281
  validateMemberSchemaKeys: relevant.some(
@@ -90555,6 +91595,15 @@ function assertStagedVariantMemberValuesValid(projected, changes) {
90555
91595
  const variantsById = new Map(
90556
91596
  (projected.variants ?? []).map((variant) => [variant.id, variant])
90557
91597
  );
91598
+ const foldersById = new Map(
91599
+ (projected.variantFolders ?? []).map((folder) => [folder.id, folder])
91600
+ );
91601
+ const membersById = new Map(
91602
+ projected.members.map((member) => [member.id, member])
91603
+ );
91604
+ const valuesById = new Map(
91605
+ projected.values.map((value) => [value.id, value])
91606
+ );
90558
91607
  const graded = [];
90559
91608
  for (const member of variantMembers) {
90560
91609
  const declaration = substituteMemberForListenerGrading(member, projected);
@@ -90594,7 +91643,11 @@ function assertStagedVariantMemberValuesValid(projected, changes) {
90594
91643
  declaration: entry.declaration,
90595
91644
  owner: entry.label,
90596
91645
  classesById,
90597
- variantsById
91646
+ variantsById,
91647
+ foldersById,
91648
+ membersById,
91649
+ valuesById,
91650
+ values: projected.values
90598
91651
  });
90599
91652
  }
90600
91653
  }
@@ -90625,7 +91678,16 @@ function assertVariantMemberTargetValid(args) {
90625
91678
  }
90626
91679
  }
90627
91680
  function assertVariantReferenceValid(args) {
90628
- const { value, declaration, owner, classesById, variantsById } = args;
91681
+ const {
91682
+ value,
91683
+ declaration,
91684
+ owner,
91685
+ classesById,
91686
+ variantsById,
91687
+ foldersById,
91688
+ membersById,
91689
+ valuesById
91690
+ } = args;
90629
91691
  const target = variantMemberTargetTypeInfo(declaration);
90630
91692
  if (target === null) return;
90631
91693
  if (value === null) {
@@ -90659,7 +91721,18 @@ function assertVariantReferenceValid(args) {
90659
91721
  );
90660
91722
  }
90661
91723
  }
90662
- if (value.variantId === null) return;
91724
+ const declaredValueType = variantMemberValueTypeInfo(declaration);
91725
+ if (value.variantId === null) {
91726
+ if (declaredValueType !== null) {
91727
+ throw new Error(
91728
+ `${owner} declares NeoLookupVariant and must select a lookup variant rather than the base class.`
91729
+ );
91730
+ }
91731
+ if (value.rowValueId !== null && value.rowValueId !== void 0) {
91732
+ throw new Error(`${owner} cannot bind a row to the base class.`);
91733
+ }
91734
+ return;
91735
+ }
90663
91736
  const variant = variantsById.get(value.variantId);
90664
91737
  if (variant === void 0) {
90665
91738
  throw new Error(
@@ -90672,6 +91745,58 @@ function assertVariantReferenceValid(args) {
90672
91745
  `${owner} selects class "${selectedClass.name}" but names variant "${variant.name}" of "${ownerClassName}". Variant lookup is not virtual, so a variant belongs to exactly one class (P67 \xA74.3).`
90673
91746
  );
90674
91747
  }
91748
+ const folder = variant.folderId === null ? null : foldersById.get(variant.folderId) ?? null;
91749
+ const binding = folder?.binding ?? null;
91750
+ if (declaredValueType !== null) {
91751
+ if (binding === null) {
91752
+ throw new Error(
91753
+ `${owner} declares NeoLookupVariant but selects plain variant "${variant.name}".`
91754
+ );
91755
+ }
91756
+ if (value.rowValueId !== null && value.rowValueId !== void 0) {
91757
+ throw new Error(
91758
+ `${owner} keeps its lookup row unbound, so rowValueId must be null.`
91759
+ );
91760
+ }
91761
+ if (declaredValueType.type === 7 /* Class */ && lookupBindingEntryClassId(binding, membersById) !== declaredValueType.classId) {
91762
+ throw new Error(
91763
+ `${owner} lookup value type does not match variant folder "${folder?.path ?? ""}".`
91764
+ );
91765
+ }
91766
+ return;
91767
+ }
91768
+ if (binding === null) {
91769
+ if (value.rowValueId !== null && value.rowValueId !== void 0) {
91770
+ throw new Error(
91771
+ `${owner} binds a row to plain variant "${variant.name}".`
91772
+ );
91773
+ }
91774
+ return;
91775
+ }
91776
+ if (value.rowValueId === null || value.rowValueId === void 0) {
91777
+ throw new Error(
91778
+ `${owner} selects lookup variant "${variant.name}" and must bind one collection row.`
91779
+ );
91780
+ }
91781
+ const collection = membersById.get(binding.collectionMemberId);
91782
+ const collectionValue = valuesById.get(binding.collectionValueId);
91783
+ if (!isMemberListBase(collection) || collectionValue === void 0 || !valuesById.has(value.rowValueId) || !listEntryIdsForValue(collection, collectionValue, args.values).includes(
91784
+ value.rowValueId
91785
+ )) {
91786
+ throw new Error(
91787
+ `${owner} binds row "${value.rowValueId}", which is not in lookup variant folder "${folder?.path ?? ""}".`
91788
+ );
91789
+ }
91790
+ }
91791
+ function variantMemberValueTypeInfo(declaration) {
91792
+ const valueType = Reflect.get(declaration, "valueTypeInfo");
91793
+ return isNSTypeInfo(valueType) ? valueType : null;
91794
+ }
91795
+ function lookupBindingEntryClassId(binding, membersById) {
91796
+ const collection = membersById.get(binding.collectionMemberId);
91797
+ if (!isMemberListBase(collection)) return null;
91798
+ const entry = membersById.get(collection.entryMemberId);
91799
+ return isMemberClassBase(entry) ? entry.classId : null;
90675
91800
  }
90676
91801
  function variantMemberTargetTypeInfo(declaration) {
90677
91802
  const target = Reflect.get(declaration, "targetTypeInfo");
@@ -91054,6 +92179,43 @@ function computeWorkspaceStatus(workspace, options) {
91054
92179
  }
91055
92180
  }
91056
92181
  }
92182
+ const resolvedVariantFolderPaths = resolveRootPathVariantFolderCollectionValuesV4({
92183
+ manifest,
92184
+ state: rootPathResolutionState,
92185
+ registry: valueLowerRegistry,
92186
+ siteForFolder: (folderId) => {
92187
+ const span2 = sourceByKey.get(
92188
+ recordStateKey("variant-folder", folderId)
92189
+ )?.span;
92190
+ return span2 === void 0 ? null : { uri: span2.path, start: span2.start };
92191
+ }
92192
+ });
92193
+ if (resolvedVariantFolderPaths.size > 0) {
92194
+ manifest = {
92195
+ ...manifest,
92196
+ variantFolders: manifest.variantFolders.map((folder) => {
92197
+ const resolvedId = resolvedVariantFolderPaths.get(folder.id);
92198
+ return resolvedId === void 0 || folder.binding === null ? folder : {
92199
+ ...folder,
92200
+ binding: { ...folder.binding, collectionValueId: resolvedId }
92201
+ };
92202
+ })
92203
+ };
92204
+ for (const [folderId, resolvedId] of resolvedVariantFolderPaths) {
92205
+ const document = documentsByKey.get(
92206
+ recordStateKey("variant-folder", folderId)
92207
+ );
92208
+ if (document !== void 0 && isObjectRecord2(document.data.binding)) {
92209
+ document.data.binding.collectionValueId = resolvedId;
92210
+ }
92211
+ const record3 = records2.find(
92212
+ (candidate) => candidate.recordKind === "variant-folder" && candidate.recordId === folderId
92213
+ );
92214
+ if (record3 !== void 0 && isObjectRecord2(record3.fileFields.binding)) {
92215
+ record3.fileFields.binding.collectionValueId = resolvedId;
92216
+ }
92217
+ }
92218
+ }
91057
92219
  const resolvedDefaultKeys = resolveKeyReferenceMemberDefaultsV4({
91058
92220
  manifest,
91059
92221
  state: rootPathResolutionState,
@@ -93967,8 +95129,14 @@ function lowerVariantRootRow(context, binding, rootMember, source, rows, localiz
93967
95129
  rows,
93968
95130
  localizedTexts,
93969
95131
  void 0,
93970
- /* @__PURE__ */ new Map([
93971
- [parameter4.id, inferredGenericClassBinding(binding.targetClassId)]
95132
+ new Map([
95133
+ [parameter4.id, inferredGenericClassBinding(binding.targetClassId)],
95134
+ ...binding.valueClassId === null || variantClass.genericParameters[1] === void 0 ? [] : [
95135
+ [
95136
+ variantClass.genericParameters[1].id,
95137
+ inferredGenericClassBinding(binding.valueClassId)
95138
+ ]
95139
+ ]
93972
95140
  ]),
93973
95141
  binding.initializer
93974
95142
  );
@@ -94081,10 +95249,10 @@ function variantValueRecord(binding, fileFields, source) {
94081
95249
  };
94082
95250
  }
94083
95251
  function variantRootMember(context, binding) {
94084
- const variantClass = context.classesByName.get(NEO_VARIANT_TYPE_NAME);
95252
+ const variantClass = context.classesByName.get(binding.variantTypeName);
94085
95253
  if (variantClass === void 0) {
94086
95254
  throw new Error(
94087
- `Variant ${binding.label} cannot be lowered: this project has no seeded ${NEO_VARIANT_TYPE_NAME} class. Run \`neo pull\` against a project on the current platform schema.`
95255
+ `Variant ${binding.label} cannot be lowered: this project has no seeded ${binding.variantTypeName} class. Run \`neo pull\` against a project on the current platform schema.`
94088
95256
  );
94089
95257
  }
94090
95258
  const parameter4 = variantClass.genericParameters[0];
@@ -94115,6 +95283,12 @@ function variantRootMember(context, binding) {
94115
95283
  [parameter4.id]: {
94116
95284
  kind: "member",
94117
95285
  memberId: inferredGenericClassBinding(binding.targetClassId)
95286
+ },
95287
+ ...binding.valueClassId === null || variantClass.genericParameters[1] === void 0 ? {} : {
95288
+ [variantClass.genericParameters[1].id]: {
95289
+ kind: "member",
95290
+ memberId: inferredGenericClassBinding(binding.valueClassId)
95291
+ }
94118
95292
  }
94119
95293
  },
94120
95294
  isStatic: false,
@@ -109477,7 +110651,7 @@ var init_registry2 = __esm({
109477
110651
  PROJECT_SCHEMA_CONTRACT = Object.freeze({
109478
110652
  formatVersion: 3,
109479
110653
  contractVersion: "3.12",
109480
- cliVersion: "0.31.2",
110654
+ cliVersion: "0.31.4",
109481
110655
  projectFileUploadBatchSize: 32,
109482
110656
  documentRecords: {
109483
110657
  member: {
@@ -109519,6 +110693,7 @@ var init_registry2 = __esm({
109519
110693
  "collectionValueId",
109520
110694
  "declaredTypeInfo",
109521
110695
  "targetTypeInfo",
110696
+ "variantValueTypeInfo",
109522
110697
  "dialogueGroupId",
109523
110698
  "code",
109524
110699
  "setterCode",
@@ -109650,10 +110825,10 @@ var init_registry2 = __esm({
109650
110825
  "variant-folder": {
109651
110826
  // P67 §3.3. A folder's whole content is its path, which is what lets a
109652
110827
  // rename be a one-record edit that orphans nothing.
109653
- authored: ["id", "classId", "path"],
110828
+ authored: ["id", "classId", "path", "binding"],
109654
110829
  derived: [],
109655
110830
  volatile: ["projectId", "createdAt", "updatedAt"],
109656
- normalization: {}
110831
+ normalization: { binding: "missingIsNull" }
109657
110832
  },
109658
110833
  interface: {
109659
110834
  authored: [
@@ -110169,7 +111344,9 @@ var init_registry2 = __esm({
110169
111344
  "AnimationSegmentTrack",
110170
111345
  "SpriteAnimationSegmentTrack",
110171
111346
  "Variant",
110172
- "VariantFolder"
111347
+ "VariantFolder",
111348
+ "LookupVariant",
111349
+ "LookupVariantFolder"
110173
111350
  ],
110174
111351
  NeoTextureType: [
110175
111352
  "Default",
@@ -116069,7 +117246,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
116069
117246
  async function main() {
116070
117247
  const args = parseArgs(process.argv.slice(2));
116071
117248
  if (args.command === "--version") {
116072
- console.log("0.31.2");
117249
+ console.log("0.31.4");
116073
117250
  return;
116074
117251
  }
116075
117252
  if (args.command === null || args.command === "help" || args.command === "--help" || args.command === "-h") {