@onimaxxing/worldgen-node 0.20.1 → 0.21.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,10 +9,13 @@
9
9
  // '@onimaxxing/worldgen';
10
10
  //
11
11
  // await init();
12
+ // // snapshot.files() is keyed by Klei on-disk path (optionally
13
+ // // DLC-qualified, e.g. 'expansion1::worldgen/worlds/...') — NOT by
14
+ // // the short registry keys used inside WorldgenBundle.
12
15
  // const files = worldgen.snapshot.files();
13
- // const world: World = JSON.parse(files['worlds/SandstoneDefault']);
16
+ // const world: World = JSON.parse(files['worldgen/worlds/SandstoneDefault.yaml']);
14
17
  // world.iconScale = 2.0;
15
- // files['worlds/SandstoneDefault'] = JSON.stringify(world);
18
+ // files['worldgen/worlds/SandstoneDefault.yaml'] = JSON.stringify(world);
16
19
  // worldgen.snapshot.load(files);
17
20
  // // Cluster cache invalidated; next worldgen.generate(...) uses mutated settings.
18
21
 
@@ -60,11 +63,12 @@ export interface WorldgenBundle {
60
63
  * (`SNDST-C-42-0-4A-*`) names them.
61
64
  */
62
65
  story_traits: Record<string, WorldTrait>;
63
- /** Per-feature configuration (geyser tables, ruins tables, etc.). */
66
+ /** Per-feature geometry + element choice groups keyed by feature path. */
64
67
  feature_settings: Record<string, FeatureSettings>;
65
68
  /**
66
69
  * DLC-level mixing settings keyed by DLC id (e.g. `"expansion1"`).
67
- * Controls which mixing options are available per cluster.
70
+ * Holds starmap additions (space destinations, space POIs, lore
71
+ * unlocks) appended when that DLC's mixing setting is enabled.
68
72
  */
69
73
  dlc_mixing: Record<string, DlcMixingSettings>;
70
74
  /**
@@ -87,10 +91,41 @@ export interface LookupsBundle {
87
91
  rooms: Record<string, RoomConfig>;
88
92
  /** Named `[minKelvin, maxKelvin]` ranges. Subworlds reference these via `temperatureRange`. */
89
93
  temperatures: Record<string, [number, number]>;
94
+ /**
95
+ * Border-biome element bands keyed by border name (e.g. `"seaBed"`).
96
+ * Subworlds reference these via `borderOverride`.
97
+ */
98
+ borders: Record<string, WeightedSimHash[]>;
90
99
  /** Pre-authored templates (bases, geyser rooms, POIs) keyed by template path. */
91
100
  templates: Record<string, TemplateContainer>;
92
101
  }
93
102
 
103
+ /**
104
+ * Per-entry mass/temperature overrides on a {@link WeightedSimHash}.
105
+ * Used by border entries like `seaBed.Lime { massOverride: 250 }`.
106
+ */
107
+ export interface WeightedSimHashOverrides {
108
+ massOverride?: number | null;
109
+ temperatureOverride?: number | null;
110
+ }
111
+
112
+ /**
113
+ * Weighted element reference (ProcGen.WeightedSimHash). Used in border
114
+ * definitions and biome element bands.
115
+ */
116
+ export interface WeightedSimHash {
117
+ /** Element name (e.g. `"SandStone"`). */
118
+ element: string;
119
+ /** Relative weight within the band. */
120
+ weight: number;
121
+ /** Disease name applied to spawned mass. Null for sterile. */
122
+ disease?: string | null;
123
+ /** Disease germ count. */
124
+ diseaseCount: number;
125
+ /** Optional per-entry mass/temperature overrides. */
126
+ overrides: WeightedSimHashOverrides;
127
+ }
128
+
94
129
  /** Full snapshot: worldgen and lookups in one blob. */
95
130
  export interface FullBundle extends WorldgenBundle, LookupsBundle {}
96
131
 
@@ -125,9 +160,12 @@ export type WorldSize = Vector2I;
125
160
 
126
161
  /**
127
162
  * Overworld-cell layout algorithm.
128
- * - `Default`: standard Voronoi-tree layout (used by every stock world).
129
- * - `PowerTree`: weighted Voronoi via MIConvexHull, used by a handful of
130
- * special worlds.
163
+ * - `PowerTree`: weighted Voronoi via MIConvexHull. This is what nearly
164
+ * every stock world uses (94 of 96 world YAMLs set it explicitly).
165
+ * - `Default`: plain Voronoi-tree layout; only worlds that omit
166
+ * `layoutMethod` entirely (Helicona, TheRock) fall back to it.
167
+ *
168
+ * Input also accepts the legacy alias `"VoronoiTree"` for `Default`.
131
169
  */
132
170
  export type LayoutMethod = 'Default' | 'PowerTree';
133
171
 
@@ -188,10 +226,16 @@ export type FilterCommand =
188
226
  /**
189
227
  * Point-sampling strategy used when placing subworld centroids and
190
228
  * mob/room sample points. `PoissonDisk` (the default) produces
191
- * evenly-spaced points; `UniformSquare` is a dense grid used by a
192
- * few special cases.
229
+ * evenly-spaced points; the `Uniform*` variants are regular lattices.
230
+ * Stock `defaults.yaml` sets `OverworldSampleBehaviour: UniformHex`.
193
231
  */
194
- export type SampleBehaviour = 'PoissonDisk' | 'UniformSquare';
232
+ export type SampleBehaviour =
233
+ | 'PoissonDisk'
234
+ | 'UniformSquare'
235
+ | 'UniformHex'
236
+ | 'UniformScaledHex'
237
+ | 'UniformSpiral'
238
+ | 'UniformCircle';
195
239
 
196
240
  /**
197
241
  * Subworld zone type. Drives the game's zone colour, the
@@ -230,12 +274,11 @@ export type ZoneType =
230
274
  | 'Reef';
231
275
 
232
276
  /**
233
- * Named temperature band that subworlds reference. The actual Kelvin
234
- * range for each name lives in `LookupsBundle.temperatures`. Stock
235
- * ONI defines 13 bands spanning extremely cold → extremely hot; the
236
- * last few aliases are kept for older configs.
277
+ * The temperature band names stock ONI defines: 14 bands spanning
278
+ * extremely cold → extremely hot, plus 5 legacy aliases kept for
279
+ * older configs.
237
280
  */
238
- export type TemperatureRange =
281
+ export type KnownTemperatureRange =
239
282
  | 'ExtremelyCold'
240
283
  | 'VeryVeryCold'
241
284
  | 'VeryCold'
@@ -246,6 +289,7 @@ export type TemperatureRange =
246
289
  | 'Room'
247
290
  | 'HumanWarm'
248
291
  | 'HumanHot'
292
+ | 'SomewhatHot'
249
293
  | 'Hot'
250
294
  | 'VeryHot'
251
295
  | 'ExtremelyHot'
@@ -255,6 +299,15 @@ export type TemperatureRange =
255
299
  | 'Warm'
256
300
  | 'CoolMarginal';
257
301
 
302
+ /**
303
+ * Named temperature band that subworlds reference. The actual Kelvin
304
+ * range for each name lives in `LookupsBundle.temperatures`. In
305
+ * `SnapshotMode: 'Strict'` only {@link KnownTemperatureRange} names
306
+ * are accepted; in `'Extensions'` mode any string is valid provided
307
+ * it resolves to a `tables.temperatures.add` entry.
308
+ */
309
+ export type TemperatureRange = KnownTemperatureRange | (string & {});
310
+
258
311
  /**
259
312
  * A constraint on which overworld cells a subworld/template is
260
313
  * allowed to occupy. Multiple filters chain via `command`; the
@@ -264,7 +317,7 @@ export type TemperatureRange =
264
317
  export interface AllowedCellsFilter {
265
318
  /** How to interpret the `tag` reference (see {@link TagCommand}). */
266
319
  tagcommand?: TagCommand;
267
- /** Layout tag this filter keys off. Omitted when `tagcommand` is `Default`. */
320
+ /** Layout tag this filter keys off. Serialized as `null` (not omitted) when unset. */
268
321
  tag?: string | null;
269
322
  /** Minimum cell distance when `tagcommand` is `DistanceFromTag`. */
270
323
  minDistance?: number;
@@ -582,6 +635,12 @@ export interface Subworld {
582
635
  overrideNoise?: string | null;
583
636
  /** Path into `noise`: optional density modulation. */
584
637
  densityNoise?: string | null;
638
+ /**
639
+ * Backwall noise tree key (e.g. `dlc5::noise/reefNoise`). When set,
640
+ * the subworld's cells get a `<biome>_backwall` element band applied
641
+ * to populate the backwall layer.
642
+ */
643
+ backwallNoise?: string | null;
585
644
  /** Name of the border-biome config applied around this subworld. */
586
645
  borderOverride?: string | null;
587
646
  /** Higher wins when two neighbouring subworlds both claim a border. */
@@ -632,7 +691,11 @@ export interface Subworld {
632
691
  minEnergy?: number;
633
692
  /** Template-spawn rules scoped to this subworld's cells. */
634
693
  subworldTemplateRules?: TemplateSpawnRules[] | null;
635
- /** Catch-all for Subworld fields not enumerated above. */
694
+ /**
695
+ * Index signature for forward compatibility when reading. Note:
696
+ * the Rust struct is closed — keys it doesn't declare are silently
697
+ * dropped on `snapshot.load()`, not round-tripped.
698
+ */
636
699
  [key: string]: unknown;
637
700
  }
638
701
 
@@ -739,7 +802,7 @@ export interface DefaultSettings {
739
802
  // Mob & room configs.
740
803
  // ---------------------------------------------------------------------------
741
804
 
742
- /** Integer count range used by mob/room `density`. */
805
+ /** Float count range used by mob/room `density`. */
743
806
  export interface MinMaxConfig {
744
807
  min: number;
745
808
  max: number;
@@ -766,14 +829,16 @@ export type MobLocation =
766
829
  | 'AnyFloor'
767
830
  | 'LiquidCeiling'
768
831
  | 'Liquid'
769
- | 'EntombedFloorPeek';
832
+ | 'EntombedFloorPeek'
833
+ | 'AnchoredToBackWall'
834
+ | 'LiquidFloorCavityNoRequired';
770
835
 
771
836
  /**
772
837
  * Mob spawn configuration (ProcGen.Mob). Controls how many copies of
773
838
  * a mob prefab get scattered across the grid after layout.
774
839
  */
775
840
  export interface MobConfig {
776
- /** Prefab-selection strategy name (e.g. `"WeightedRandom"`). */
841
+ /** Prefab-selection strategy name: `"RandomPoints"` or `"Centroid"`. */
777
842
  selectMethod?: string;
778
843
  /** Roll range for the count of this mob placed per world. */
779
844
  density?: MinMaxConfig;
@@ -829,8 +894,21 @@ export interface TemplateInfo {
829
894
  area: number;
830
895
  /** Lower-left cell of the bounding box, relative to the template origin. */
831
896
  min: TemplateVec2I;
832
- /** Upper-right cell of the bounding box. */
897
+ /**
898
+ * Upper-right cell of the bounding box. Legacy: the game's
899
+ * `TemplateContainer.Info` has no `max` — it is kept only for
900
+ * back-compat with snapshots this package already emitted, and no
901
+ * game YAML sets it.
902
+ */
833
903
  max: TemplateVec2I;
904
+ /** Game `Info.tags`. Rare; omitted when absent. */
905
+ tags?: string[] | null;
906
+ /**
907
+ * Game `Info.discover_tags` — resource tags revealed to the player
908
+ * when the template is discovered (e.g. ceresBase declares
909
+ * `Pemmican`). Omitted when absent.
910
+ */
911
+ discover_tags?: string[] | null;
834
912
  }
835
913
 
836
914
  /**
@@ -858,6 +936,18 @@ export interface TemplateCell {
858
936
  * templates are normally pre-revealed. Used by puzzle/POI cells.
859
937
  */
860
938
  preventFoWReveal?: boolean | null;
939
+ /**
940
+ * Backwall element name behind this cell (e.g. `"Salt"`). Defaults
941
+ * to Vacuum when absent, and `TemplateLoader` stamps backwall data
942
+ * for EVERY template cell — omitting this field actively CLEARS the
943
+ * backwall behind the template footprint (the vacuum cavity around
944
+ * the printing pod is exactly that).
945
+ */
946
+ backwallElement?: string | null;
947
+ /** Backwall mass in kg. Default 0 (Vacuum backwall). */
948
+ backwallMass?: number;
949
+ /** Backwall temperature in K. Default 0 (Vacuum backwall). */
950
+ backwallTemperature?: number;
861
951
  }
862
952
 
863
953
  /**
@@ -871,12 +961,28 @@ export interface TemplateStorageItem {
871
961
  element?: string | null;
872
962
  /** Quantity (mass for elements, count for creatures/items). */
873
963
  units: number;
964
+ /** True when the stored item is raw ore (C# `StorageItem.isOre`). Omitted when false. */
965
+ isOre?: boolean;
874
966
  /** Temperature in Kelvin. */
875
967
  temperature: number;
876
- /** Free-form rottable state; carried through verbatim. */
968
+ /** Disease on the stored item (e.g. `"SlimeLung"`). Omitted when absent. */
969
+ diseaseName?: string | null;
970
+ /** Disease germ count on the stored item. Omitted when 0. */
971
+ diseaseCount?: number;
972
+ /** Free-form rottable state (`{ rotAmount: number }`); carried through verbatim. */
877
973
  rottable?: unknown;
878
974
  }
879
975
 
976
+ /**
977
+ * One `{ id, value }` override pair applied to a spawned prefab
978
+ * (C# `Prefab.template_amount_value`) — e.g. ocean geyser POIs preset
979
+ * `AirPressure` / `Temperature` this way.
980
+ */
981
+ export interface TemplateAmountValue {
982
+ id?: string | null;
983
+ value: number;
984
+ }
985
+
880
986
  /**
881
987
  * A prefab placed by a template (building, pickupable, ore deposit,
882
988
  * or other entity). Which `TemplateContainer` array it lives in
@@ -885,9 +991,9 @@ export interface TemplateStorageItem {
885
991
  export interface TemplatePrefab {
886
992
  /** Prefab id (e.g. `"Tile"`, `"Headquarters"`, `"Hatch"`). */
887
993
  id?: string | null;
888
- /** X offset from template origin (can be fractional). */
994
+ /** X offset from template origin. Integer in game files (C# `int`). */
889
995
  location_x: number;
890
- /** Y offset from template origin (can be fractional). */
996
+ /** Y offset from template origin. Integer in game files (C# `int`). */
891
997
  location_y: number;
892
998
  /** Element override (for ore-like prefabs). */
893
999
  element?: string | null;
@@ -897,12 +1003,36 @@ export interface TemplatePrefab {
897
1003
  units: number;
898
1004
  /** Prefab type hint (e.g. `"Pickupable"`, `"Ore"`). */
899
1005
  type?: string | null;
1006
+ /** Disease on the spawned prefab (e.g. `"SlimeLung"`). Omitted when absent. */
1007
+ diseaseName?: string | null;
1008
+ /** Disease germ count on the spawned prefab. Omitted when 0. */
1009
+ diseaseCount?: number;
900
1010
  /** Container contents (for storage buildings like RationBox). */
901
1011
  storage?: TemplateStorageItem[];
902
1012
  /** Rotation hint for orientable buildings. */
903
1013
  rotationOrientation?: string | null;
1014
+ /** Building skin/facade id (C# `Prefab.facadeId`). Omitted when absent. */
1015
+ facadeId?: string | null;
904
1016
  /** Connections bitfield for connectable prefabs (pipes, wires). */
905
1017
  connections?: number | null;
1018
+ /** Lore-unlock id granted when this prefab is discovered. Omitted when absent. */
1019
+ loreUnlockId?: string | null;
1020
+ /** Display text for the lore unlock. Omitted when absent. */
1021
+ loreDisplayText?: string | null;
1022
+ /** Next collection id in the lore chain. Omitted when absent. */
1023
+ loreNextCollectionId?: string | null;
1024
+ /**
1025
+ * Rottable state on the prefab itself (`{ rotAmount: number }`);
1026
+ * carried through verbatim. Omitted when absent.
1027
+ */
1028
+ rottable?: unknown;
1029
+ /**
1030
+ * Amount overrides applied at spawn — e.g. geyser `AirPressure` /
1031
+ * `Temperature` presets in ocean POI templates. Omitted when empty.
1032
+ */
1033
+ amounts?: TemplateAmountValue[];
1034
+ /** Non-Amount value overrides (same shape as `amounts`). Omitted when empty. */
1035
+ other_values?: TemplateAmountValue[];
906
1036
  }
907
1037
 
908
1038
  /**
@@ -914,9 +1044,14 @@ export interface TemplatePrefab {
914
1044
  export interface TemplateContainer {
915
1045
  /** Internal template name. Usually matches the config path's last segment. */
916
1046
  name?: string | null;
1047
+ /**
1048
+ * Template stamping priority (C# `TemplateContainer.priority`). No
1049
+ * stock worldgen YAML sets it; carried for round-trip. Omitted when 0.
1050
+ */
1051
+ priority?: number;
917
1052
  /** Bounding-box metadata. */
918
1053
  info: TemplateInfo;
919
- /** Element/mass/temperature per cell. */
1054
+ /** Element/mass/temperature per cell, including backwall data. */
920
1055
  cells: TemplateCell[];
921
1056
  /** Building prefabs (tiles, machines). */
922
1057
  buildings: TemplatePrefab[];
@@ -924,6 +1059,12 @@ export interface TemplateContainer {
924
1059
  pickupables: TemplatePrefab[];
925
1060
  /** Elemental-ore deposits. */
926
1061
  elementalOres: TemplatePrefab[];
1062
+ /**
1063
+ * Backwall entity prefabs. Written when the game saves a template
1064
+ * that captures backwall decor; no stock worldgen YAML uses it yet.
1065
+ * Omitted when empty.
1066
+ */
1067
+ backwallEntities?: TemplatePrefab[];
927
1068
  /** Other entities (lights, furniture, decor). */
928
1069
  otherEntities: TemplatePrefab[];
929
1070
  }
@@ -937,22 +1078,33 @@ export interface TemplateContainer {
937
1078
  export interface ClusterLayout { [key: string]: unknown }
938
1079
 
939
1080
  /**
940
- * Noise-tree definition (base / override / density). Editing primitive
941
- * frequencies and seeds changes every cell of every world that
942
- * references the tree.
1081
+ * Noise-tree definition (base / override / density). Editing filter
1082
+ * frequencies or primitive seeds changes every cell of every world
1083
+ * that references the tree.
943
1084
  */
944
1085
  export interface NoiseTree { [key: string]: unknown }
945
1086
 
946
- /** Element/mass bands keyed on noise-field thresholds. */
1087
+ /**
1088
+ * Biome element bands. Shape: `{ lookup_table: { <variantName>:
1089
+ * band[] } }` keyed by biome variant name (e.g. `"Basic"`, `"Snowy"`);
1090
+ * each band is `{ content, bandSize, overrides? }` where `bandSize`
1091
+ * is a probability weight.
1092
+ */
947
1093
  export interface BiomeSettings { [key: string]: unknown }
948
1094
 
949
1095
  /** Trait definition: names, tags, and modifiers applied when a roll picks it. */
950
1096
  export interface WorldTrait { [key: string]: unknown }
951
1097
 
952
- /** Per-feature configuration (geyser type pool, ruins content tables, etc.). */
1098
+ /**
1099
+ * Feature configuration: shape, blob size, borders, tags, internal
1100
+ * mobs, and `ElementChoiceGroups`.
1101
+ */
953
1102
  export interface FeatureSettings { [key: string]: unknown }
954
1103
 
955
- /** DLC-level mixing settings: which asteroid/subworld substitutions are eligible. */
1104
+ /**
1105
+ * DLC-level mixing settings: starmap space destinations, space POIs,
1106
+ * and lore unlocks appended when the DLC mixing setting is enabled.
1107
+ */
956
1108
  export interface DlcMixingSettings { [key: string]: unknown }
957
1109
 
958
1110
  /** Whole-world mixing option (substitute asteroid type A for B). */
@@ -988,8 +1140,10 @@ export type FilePath = string;
988
1140
  * the `add` maps (last-wins) and unioning `remove`.
989
1141
  */
990
1142
  export interface ComposableDict<V> {
991
- add: Record<string, V>;
992
- remove: string[];
1143
+ /** Optional on input; always present on output. */
1144
+ add?: Record<string, V>;
1145
+ /** Optional on input; always present on output. */
1146
+ remove?: string[];
993
1147
  }
994
1148
 
995
1149
  /** Whole-file-replace categories, keyed by the on-disk Klei path. */
@@ -1014,7 +1168,87 @@ export interface SnapshotTables {
1014
1168
  rivers: ComposableDict<unknown>;
1015
1169
  rooms: ComposableDict<RoomConfig>;
1016
1170
  temperatures: ComposableDict<[number, number]>;
1017
- borders: ComposableDict<Array<unknown>>;
1171
+ borders: ComposableDict<WeightedSimHash[]>;
1172
+ /**
1173
+ * `worldgen/layers.yaml` (ProcGen.LevelLayerSettings). Stored
1174
+ * verbatim for round-trip; not consumed by Rust worldgen.
1175
+ */
1176
+ layers: LevelLayerSettings;
1177
+ /**
1178
+ * `elements/*.yaml` runtime overlay, in load order. First entry
1179
+ * wins for a given elementId (base elements beat DLC duplicates),
1180
+ * matching Klei's `ElementLoader.Load`.
1181
+ */
1182
+ extra_elements: ElementYaml[];
1183
+ }
1184
+
1185
+ /** `worldgen/layers.yaml` — vertical biome band list. */
1186
+ export interface LevelLayerSettings {
1187
+ LevelLayers: LevelLayer[];
1188
+ }
1189
+
1190
+ /** One vertical band: element/feature tokens + height fraction. */
1191
+ export interface LevelLayer {
1192
+ content: string[];
1193
+ bandSize: number;
1194
+ }
1195
+
1196
+ /**
1197
+ * Runtime element definition parsed from `elements/*.yaml`. Field set
1198
+ * mirrors the compile-time element table; keys are Klei's camelCase.
1199
+ */
1200
+ export interface ElementYaml {
1201
+ elementId: string;
1202
+ state: 'Vacuum' | 'Solid' | 'Liquid' | 'Gas';
1203
+ defaultTemperature: number;
1204
+ defaultMass: number;
1205
+ defaultPressure: number;
1206
+ maxMass: number;
1207
+ specificHeatCapacity: number;
1208
+ thermalConductivity: number;
1209
+ highTemp: number;
1210
+ highTempTransitionTarget: string;
1211
+ lowTemp: number;
1212
+ lowTempTransitionTarget: string;
1213
+ lowTempTransitionOreId: string;
1214
+ lowTempTransitionOreMassConversion: number;
1215
+ highTempTransitionOreId: string;
1216
+ highTempTransitionOreMassConversion: number;
1217
+ hardness: number;
1218
+ isDisabled: boolean;
1219
+ strength: number;
1220
+ molarMass: number;
1221
+ lightAbsorptionFactor: number;
1222
+ radiationAbsorptionFactor: number;
1223
+ radiationPer1000Mass: number;
1224
+ solidSurfaceAreaMultiplier: number;
1225
+ liquidSurfaceAreaMultiplier: number;
1226
+ gasSurfaceAreaMultiplier: number;
1227
+ flow: number;
1228
+ toxicity: number;
1229
+ materialCategory: string;
1230
+ dlcId: string;
1231
+ tags: string[];
1232
+ sublimateId: string;
1233
+ sublimateProbability: number;
1234
+ sublimateRate: number;
1235
+ sublimateEfficiency: number;
1236
+ offGasPercentage: number;
1237
+ speed: number;
1238
+ minHorizontalFlow: number;
1239
+ minVerticalFlow: number;
1240
+ }
1241
+
1242
+ /**
1243
+ * One element-field override carried by a mod wrapper that can't be
1244
+ * expressed in YAML (mod DLLs patch `Element` at runtime). Keys are
1245
+ * snake_case on the wire.
1246
+ */
1247
+ export interface ElementOverride {
1248
+ /** Element name as in Klei's element YAML (e.g. `"Radium"`). */
1249
+ element_name: string;
1250
+ /** New `Element.radiationPer1000Mass`; omitted means unchanged. */
1251
+ radiation_per1000_mass?: number;
1018
1252
  }
1019
1253
 
1020
1254
  /** Wholesale-replace singletons: defaults + DLC/world/subworld mixing. */
@@ -1025,10 +1259,16 @@ export interface SnapshotMixing {
1025
1259
  subworld_mixing: Record<string, SubworldMixingSettings>;
1026
1260
  }
1027
1261
 
1028
- /** The mod-ingestion snapshot returned and accepted by the snapshot helpers. */
1262
+ /**
1263
+ * The mod-ingestion snapshot returned and accepted by the snapshot
1264
+ * helpers. Every field (here and in the nested Snapshot* shapes) is
1265
+ * optional on input — missing fields default to empty.
1266
+ */
1029
1267
  export interface WorldgenSnapshot {
1030
1268
  mode: SnapshotMode;
1031
1269
  files: SnapshotFiles;
1032
1270
  tables: SnapshotTables;
1033
1271
  mixing: SnapshotMixing;
1272
+ /** Runtime element patches from mod wrappers. Omitted when empty. */
1273
+ element_overrides?: ElementOverride[];
1034
1274
  }
package/index.cjs CHANGED
@@ -19,6 +19,10 @@ let _checksumDirty = true;
19
19
  // nothing to await.
20
20
  async function init(_opts) {}
21
21
 
22
+ // Decode a v5 `settle_cluster_advance` binary snapshot.
23
+ // v5 extends v4 with the U59 backwall layer (u16 elem + f32 mass +
24
+ // f32 temp = +10 bytes per cell, 25 bytes total). Worlds without
25
+ // `backwallNoise` in their subworld YAML emit Vacuum / 0 / 0.
22
26
  function decodeSnapshot(buf) {
23
27
  if (!buf || buf.length === 0) {
24
28
  throw new Error(
@@ -28,8 +32,8 @@ function decodeSnapshot(buf) {
28
32
  const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
29
33
  let offset = 0;
30
34
  const version = view.getUint32(offset, true); offset += 4;
31
- if (version !== 4) {
32
- throw new Error(`decodeSnapshot: expected version 4, got ${version}`);
35
+ if (version !== 5) {
36
+ throw new Error(`decodeSnapshot: expected version 5, got ${version}`);
33
37
  }
34
38
  const tick = view.getUint32(offset, true); offset += 4;
35
39
  const worldCount = view.getUint32(offset, true); offset += 4;
@@ -45,6 +49,9 @@ function decodeSnapshot(buf) {
45
49
  const temperature = new Float32Array(cells);
46
50
  const disease_idx = new Uint8Array(cells);
47
51
  const disease_count = new Int32Array(cells);
52
+ const backwall_element_idx = new Uint16Array(cells);
53
+ const backwall_mass = new Float32Array(cells);
54
+ const backwall_temperature = new Float32Array(cells);
48
55
 
49
56
  for (let c = 0; c < cells; c++) {
50
57
  element_idx[c] = view.getUint16(offset, true); offset += 2;
@@ -52,12 +59,16 @@ function decodeSnapshot(buf) {
52
59
  temperature[c] = view.getFloat32(offset, true); offset += 4;
53
60
  disease_idx[c] = view.getUint8(offset); offset += 1;
54
61
  disease_count[c] = view.getInt32(offset, true); offset += 4;
62
+ backwall_element_idx[c] = view.getUint16(offset, true); offset += 2;
63
+ backwall_mass[c] = view.getFloat32(offset, true); offset += 4;
64
+ backwall_temperature[c] = view.getFloat32(offset, true); offset += 4;
55
65
  }
56
66
 
57
67
  worlds.push({
58
68
  width, height,
59
69
  element_idx, mass, temperature,
60
70
  disease_idx, disease_count,
71
+ backwall_element_idx, backwall_mass, backwall_temperature,
61
72
  });
62
73
  }
63
74
 
@@ -131,11 +142,38 @@ const worldgen = {
131
142
  _checksumDirty = true;
132
143
  },
133
144
 
145
+ // Noise editor preview. See index.js for the tree-descriptor contract.
146
+ evaluateNoise(tree, width, height, seed) {
147
+ if (!tree || typeof tree !== 'object') {
148
+ throw new TypeError("evaluateNoise: tree must be { kind: 'named'|'inline', ... }");
149
+ }
150
+ const kind = tree.kind;
151
+ let value;
152
+ if (kind === 'named') {
153
+ value = tree.name;
154
+ } else if (kind === 'inline') {
155
+ value = tree.yaml;
156
+ } else {
157
+ throw new TypeError(`evaluateNoise: unknown tree.kind '${kind}'`);
158
+ }
159
+ return _wasm.evaluate_noise(kind, value, width, height, seed);
160
+ },
161
+
162
+ noiseNodeKinds() {
163
+ return JSON.parse(_wasm.noise_node_kinds());
164
+ },
165
+
134
166
  version() {
135
167
  return _wasm.game_version();
136
168
  },
137
169
  };
138
170
 
171
+ // The nodejs build is always serial; mirrors index.js's signature
172
+ // (null before init is impossible here — WASM loads at require time).
173
+ function isParallel() {
174
+ return false;
175
+ }
176
+
139
177
  // Re-export the Klei-faithful YAML parser so consumers have a
140
178
  // one-stop import for the full ingestion pipeline (YAML → JSON →
141
179
  // worldgen.snapshot.merge). Mirrors the ESM variant in `index.js`.
@@ -144,4 +182,5 @@ const { parseKleiYaml } = require('./yaml.cjs');
144
182
  module.exports = init;
145
183
  module.exports.default = init;
146
184
  module.exports.worldgen = worldgen;
185
+ module.exports.isParallel = isParallel;
147
186
  module.exports.parseKleiYaml = parseKleiYaml;
package/index.d.ts CHANGED
@@ -66,6 +66,24 @@ import type {
66
66
  DlcMixingSettings,
67
67
  WorldMixingSettings,
68
68
  SubworldMixingSettings,
69
+ KnownTemperatureRange,
70
+ WeightedSimHash,
71
+ WeightedSimHashOverrides,
72
+ TemplateAmountValue,
73
+ WorldgenBundle,
74
+ LookupsBundle,
75
+ FullBundle,
76
+ SnapshotMode,
77
+ FilePath,
78
+ ComposableDict,
79
+ SnapshotFiles,
80
+ SnapshotTables,
81
+ SnapshotMixing,
82
+ LevelLayerSettings,
83
+ LevelLayer,
84
+ ElementYaml,
85
+ ElementOverride,
86
+ WorldgenSnapshot,
69
87
  } from './editor_settings.js';
70
88
 
71
89
  export type {
@@ -114,6 +132,24 @@ export type {
114
132
  DlcMixingSettings,
115
133
  WorldMixingSettings,
116
134
  SubworldMixingSettings,
135
+ KnownTemperatureRange,
136
+ WeightedSimHash,
137
+ WeightedSimHashOverrides,
138
+ TemplateAmountValue,
139
+ WorldgenBundle,
140
+ LookupsBundle,
141
+ FullBundle,
142
+ SnapshotMode,
143
+ FilePath,
144
+ ComposableDict,
145
+ SnapshotFiles,
146
+ SnapshotTables,
147
+ SnapshotMixing,
148
+ LevelLayerSettings,
149
+ LevelLayer,
150
+ ElementYaml,
151
+ ElementOverride,
152
+ WorldgenSnapshot,
117
153
  };
118
154
 
119
155
  // -----------------------------------------------------------------------
@@ -438,10 +474,11 @@ export interface InitOptions {
438
474
  /**
439
475
  * - `'auto'` (default): pick parallel when `crossOriginIsolated` and
440
476
  * `SharedArrayBuffer` are available, with
441
- * `navigator.hardwareConcurrency` workers. Otherwise serial.
477
+ * `navigator.hardwareConcurrency` workers (4 when unavailable).
478
+ * Otherwise serial.
442
479
  * - `'serial'`: force the serial build.
443
- * - `number`: force parallel with this many workers (falls back to
444
- * serial when parallel isn't available).
480
+ * - `number`: worker count to use *if* feature detection picks
481
+ * parallel. Cannot force parallel on a host that fails detection.
445
482
  */
446
483
  threads?: number | 'auto' | 'serial';
447
484
  }
@@ -451,6 +488,10 @@ export interface InitOptions {
451
488
  * a parallel (rayon + SharedArrayBuffer) build; `init()` feature-detects
452
489
  * the runtime, loads the appropriate one, and on parallel hosts also
453
490
  * spawns the worker pool. Memoized — extra calls short-circuit.
491
+ *
492
+ * On the node package (`@onimaxxing/worldgen-node`) this is a no-op:
493
+ * the WASM loads synchronously at require time and `InitOptions` is
494
+ * ignored (always serial).
454
495
  */
455
496
  export default function init(options?: InitOptions): Promise<void>;
456
497
 
@@ -466,8 +507,12 @@ export const worldgen: {
466
507
  /**
467
508
  * Generate a cluster from a game coordinate. Caches the cluster in
468
509
  * the WASM thread-local slot, replacing any previously cached cluster.
510
+ *
511
+ * Returns `{ error }` (not a throw) when the coordinate fails to
512
+ * parse, the cluster isn't found, or serialization fails — check
513
+ * for the `error` key before reading `MapData` fields.
469
514
  */
470
- generate(coordinate: string): MapData;
515
+ generate(coordinate: string): MapData | { error: string };
471
516
 
472
517
  /**
473
518
  * Advance the cached cluster's settle sim to `targetTick` (in
@@ -485,8 +530,11 @@ export const worldgen: {
485
530
  * cell state and return the refreshed entity lists. Pairs with
486
531
  * `advance()`. liquid/gas settling can invalidate earlier mob
487
532
  * placements. Template-placed entities persist across calls.
533
+ *
534
+ * Returns `{ error }` when no cluster is cached (call `generate`
535
+ * first).
488
536
  */
489
- entities(): EntitySpawners;
537
+ entities(): EntitySpawners | { error: string };
490
538
 
491
539
  /**
492
540
  * Drop the cached generated cluster. Editor settings (if mutated)
@@ -523,9 +571,13 @@ export const worldgen: {
523
571
  * typing below is advisory: it nudges callers toward
524
572
  * defensive-copy hygiene at the API surface even though the
525
573
  * underlying object is yours to mutate.
526
- * - **Path-set preservation.** `load(filesMap)` neither adds nor
527
- * drops paths. The set of keys in the next `files()` exactly
528
- * matches the keys you passed in.
574
+ * - **Path-set near-preservation.** After `load(filesMap)`, the
575
+ * next `files()` returns the paths you passed in, except:
576
+ * `_snapshot/manifest.json`, `worldgen/defaults.yaml`, and
577
+ * per-DLC `worldgen/mixing.yaml` are always emitted (added if
578
+ * you omitted them), while empty table files
579
+ * (`worldgen/borders.yaml`, `worldgen/rivers.yaml`,
580
+ * `_snapshot/element_overrides.json`) are dropped.
529
581
  * - **Merge collision = last-wins.** When `merge(files)` is called
530
582
  * with a path already present in SETTINGS, the new value
531
583
  * replaces the old one. Within a single `merge()` call the same
@@ -590,9 +642,10 @@ export const worldgen: {
590
642
  * settings.
591
643
  *
592
644
  * Cached: the hash is computed once per snapshot version, on the
593
- * first call after any `loadVanilla` / `load` / `merge` / `remove`
594
- * / `reset`. Subsequent calls return the cached string until the
595
- * next mutation. Cold recompute is ≈80 ms on the full vanilla
645
+ * first call after any mutation (`loadVanilla` /
646
+ * `loadVanillaExtensions` / `load` / `loadExtensions` / `merge` /
647
+ * `remove` / `reset`). Subsequent calls return the cached string
648
+ * until the next mutation. Cold recompute is ≈80 ms on the full vanilla
596
649
  * snapshot; steady-state cache hit is a single property read.
597
650
  */
598
651
  checksum(): string;
@@ -646,8 +699,10 @@ export const worldgen: {
646
699
  * back into the active SETTINGS yet.
647
700
  *
648
701
  * Sample coords map `width × height` linearly onto the tree's
649
- * `settings.lowerBound..upperBound` rectangle (cell-centred), so the
650
- * preview honours the editor's `SampleSettings.zoom` / bounds.
702
+ * `settings.lowerBound..upperBound` rectangle (cell-centred). The
703
+ * preview honours the editor's bounds but NOT `SampleSettings.zoom`
704
+ * — real worldgen sampling applies zoom, so previews of zoomed
705
+ * trees differ from engine output.
651
706
  *
652
707
  * Throws if the named tree doesn't exist, the inline YAML fails to
653
708
  * parse, or the tree has no terminator link.
@@ -702,8 +757,9 @@ export interface NoiseNodeKindSpec {
702
757
  export interface NoiseNodeParam {
703
758
  /** YAML field name (e.g. `"frequency"`, `"modifyType"`). */
704
759
  name: string;
705
- /** Scalar type the editor should render. */
706
- type: "f32" | "i32" | "bool" | "enum" | "vec2" | "vec3" | "string";
760
+ /** Scalar type the editor should render. `"array"` is emitted for
761
+ * list-valued params (none in the current node structs). */
762
+ type: "f32" | "i32" | "bool" | "enum" | "vec2" | "vec3" | "string" | "array";
707
763
  /** Default value matching the corresponding struct's `Default` impl. */
708
764
  default: number | string | boolean | { X: number; Y: number } | { X: number; Y: number; Z: number };
709
765
  /** When `type === "enum"`, the allowed string values. */
package/oni_wasm_bg.wasm CHANGED
Binary file
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@onimaxxing/worldgen-node",
3
3
  "description": "WASM entry point for ONI worldgen",
4
- "version": "0.20.1",
4
+ "version": "0.21.1",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
package/yaml.cjs CHANGED
@@ -1,7 +1,7 @@
1
- // CommonJS variant of yaml.js. The published wrapper ships one or
2
- // the other depending on `--target` (web → yaml.js, nodejs →
3
- // yaml.cjs). The logic is identical; only the module syntax differs.
4
- // See yaml.js for the full Klei-compatibility commentary.
1
+ // CommonJS variant of yaml.js — keep the two in lockstep. The
2
+ // published wrapper ships one or the other depending on `--target`
3
+ // (web → yaml.js, nodejs → yaml.cjs). See yaml.js for the full
4
+ // Klei-compatibility commentary.
5
5
 
6
6
  const yaml = require('js-yaml');
7
7
 
@@ -14,9 +14,15 @@ const KleiBool = new yaml.Type('tag:yaml.org,2002:bool', {
14
14
 
15
15
  const KleiSchema = yaml.DEFAULT_SCHEMA.extend({ implicit: [KleiBool] });
16
16
 
17
+ // Leading-dot floats (`.5`, `-.5`, `+.5`) that js-yaml's default
18
+ // resolver leaves as strings. Klei ships them (e.g.
19
+ // `worldgen/traits/MisalignedStart.yaml`: `min: -.2`).
20
+ const LEADING_DOT_FLOAT = /^[-+]?\.[0-9]+(?:[eE][-+]?[0-9]+)?$/;
21
+
17
22
  function parseKleiYaml(text) {
18
23
  const preprocessed = text.replace(/\t/g, ' ');
19
- const raw = yaml.load(preprocessed, { schema: KleiSchema });
24
+ // `json: true` gives YamlDotNet's last-wins on duplicate keys.
25
+ const raw = yaml.load(preprocessed, { schema: KleiSchema, json: true });
20
26
  return stripNulls(raw);
21
27
  }
22
28
 
@@ -38,6 +44,9 @@ function stripNulls(node) {
38
44
  }
39
45
  return out;
40
46
  }
47
+ if (typeof node === 'string' && LEADING_DOT_FLOAT.test(node)) {
48
+ return parseFloat(node);
49
+ }
41
50
  return node;
42
51
  }
43
52
 
package/yaml.d.ts CHANGED
@@ -8,11 +8,16 @@
8
8
  *
9
9
  * 1. Tabs are pre-expanded to 4 spaces (Klei's `readText.Replace`).
10
10
  * 2. YAML 1.1 boolean sentinels (`yes/no/on/off`) on plain scalars
11
- * become booleans; quoted forms stay as strings.
11
+ * become booleans; quoted forms stay as strings. Single-letter
12
+ * `y`/`n` deliberately stay strings (`Y:` is a vector key).
12
13
  * 3. Duplicate mapping keys: last-wins.
13
- * 4. Unknown YAML tags are silently stripped.
14
- * 5. Null mapping values are dropped recursively (Klei's reflection-
15
- * based loader treats `key:` as "field not present").
14
+ * 4. Null values are dropped recursively — mapping values (Klei's
15
+ * reflection-based loader treats `key:` as "field not present")
16
+ * and array elements alike.
17
+ * 5. Leading-dot floats (`.5`, `-.2`) become numbers; js-yaml's
18
+ * default resolver leaves them as strings.
19
+ *
20
+ * Unknown YAML tags (`!Foo`) throw a js-yaml parse error.
16
21
  *
17
22
  * Returns a plain JS object/array/scalar suitable for JSON.stringify.
18
23
  */