@hypersoniclabs/helix-cli 0.1.27-helix3.550 → 0.1.27-helix3.552

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.
Files changed (75) hide show
  1. package/dist/city/assemble/oceanFile.d.ts +33 -0
  2. package/dist/city/assemble/oceanFile.js +96 -0
  3. package/dist/city/assemble/oceanFile.js.map +1 -0
  4. package/dist/city/assemble/sceneVisuals.d.ts +49 -0
  5. package/dist/city/assemble/sceneVisuals.js +66 -0
  6. package/dist/city/assemble/sceneVisuals.js.map +1 -0
  7. package/dist/city/convert/command.d.ts +2 -0
  8. package/dist/city/convert/command.js +98 -0
  9. package/dist/city/convert/command.js.map +1 -0
  10. package/dist/city/convert/config.d.ts +56 -0
  11. package/dist/city/convert/config.js +78 -0
  12. package/dist/city/convert/config.js.map +1 -0
  13. package/dist/city/convert/exec.d.ts +5 -0
  14. package/dist/city/convert/exec.js +36 -0
  15. package/dist/city/convert/exec.js.map +1 -0
  16. package/dist/city/convert/landscapeSource.d.ts +6 -0
  17. package/dist/city/convert/landscapeSource.js +19 -0
  18. package/dist/city/convert/landscapeSource.js.map +1 -0
  19. package/dist/city/convert/optimizations.d.ts +8 -0
  20. package/dist/city/convert/optimizations.js +41 -0
  21. package/dist/city/convert/optimizations.js.map +1 -0
  22. package/dist/city/convert/receipt.d.ts +44 -0
  23. package/dist/city/convert/receipt.js +60 -0
  24. package/dist/city/convert/receipt.js.map +1 -0
  25. package/dist/city/convert/run.d.ts +20 -0
  26. package/dist/city/convert/run.js +82 -0
  27. package/dist/city/convert/run.js.map +1 -0
  28. package/dist/city/convert/spawnPick.d.ts +25 -0
  29. package/dist/city/convert/spawnPick.js +193 -0
  30. package/dist/city/convert/spawnPick.js.map +1 -0
  31. package/dist/city/convert/stages.d.ts +94 -0
  32. package/dist/city/convert/stages.js +335 -0
  33. package/dist/city/convert/stages.js.map +1 -0
  34. package/dist/city/convert/tierArgs.d.ts +10 -0
  35. package/dist/city/convert/tierArgs.js +21 -0
  36. package/dist/city/convert/tierArgs.js.map +1 -0
  37. package/dist/cs2/convert/buildingCompose.d.ts +64 -9
  38. package/dist/cs2/convert/buildingCompose.js +232 -13
  39. package/dist/cs2/convert/buildingCompose.js.map +1 -1
  40. package/dist/cs2/convert/buildingMeshCommand.js +63 -18
  41. package/dist/cs2/convert/buildingMeshCommand.js.map +1 -1
  42. package/dist/cs2/convert/buildingParts.d.ts +6 -0
  43. package/dist/cs2/convert/buildingParts.js +1 -1
  44. package/dist/cs2/convert/buildingParts.js.map +1 -1
  45. package/dist/cs2/convert/hlodAtlas.d.ts +60 -0
  46. package/dist/cs2/convert/hlodAtlas.js +246 -0
  47. package/dist/cs2/convert/hlodAtlas.js.map +1 -0
  48. package/dist/cs2/convert/tierProfiles.d.ts +83 -0
  49. package/dist/cs2/convert/tierProfiles.js +64 -0
  50. package/dist/cs2/convert/tierProfiles.js.map +1 -0
  51. package/dist/index.js +12 -10
  52. package/dist/index.js.map +1 -1
  53. package/dist/sceneCreate/commands.d.ts +2 -0
  54. package/dist/sceneCreate/commands.js +5 -2
  55. package/dist/sceneCreate/commands.js.map +1 -1
  56. package/dist/sceneHlod/commands.js +13 -0
  57. package/dist/sceneHlod/commands.js.map +1 -1
  58. package/dist/tsconfig.build.tsbuildinfo +1 -1
  59. package/docs/scene-create.md +10 -1
  60. package/package.json +1 -1
  61. package/plugins/beamng-vehicle/bin/__pycache__/beamng_abs.cpython-312.pyc +0 -0
  62. package/plugins/beamng-vehicle/bin/__pycache__/beamng_audio.cpython-312.pyc +0 -0
  63. package/plugins/beamng-vehicle/bin/__pycache__/beamng_engine.cpython-312.pyc +0 -0
  64. package/plugins/beamng-vehicle/bin/__pycache__/beamng_expression.cpython-312.pyc +0 -0
  65. package/plugins/beamng-vehicle/bin/__pycache__/beamng_jbeam.cpython-312.pyc +0 -0
  66. package/plugins/beamng-vehicle/bin/__pycache__/beamng_materials.cpython-312.pyc +0 -0
  67. package/plugins/beamng-vehicle/bin/__pycache__/beamng_stock_events.cpython-312.pyc +0 -0
  68. package/plugins/beamng-vehicle/bin/__pycache__/fmod_studio.cpython-312.pyc +0 -0
  69. package/plugins/beamng-vehicle/bin/__pycache__/fsb5.cpython-312.pyc +0 -0
  70. package/plugins/beamng-vehicle/bin/__pycache__/sim_audio.cpython-312.pyc +0 -0
  71. package/plugins/beamng-vehicle/bin/__pycache__/sim_audio_ac.cpython-312.pyc +0 -0
  72. package/plugins/beamng-vehicle/bin/__pycache__/sim_audio_beamng.cpython-312.pyc +0 -0
  73. package/plugins/beamng-vehicle/bin/__pycache__/sim_audio_fixtures.cpython-312.pyc +0 -0
  74. package/plugins/beamng-vehicle/bin/__pycache__/test_beamng_engine.cpython-312.pyc +0 -0
  75. package/plugins/beamng-vehicle/bin/__pycache__/test_sim_audio.cpython-312.pyc +0 -0
@@ -0,0 +1,33 @@
1
+ import type { CitySource } from '../source';
2
+ import { type HeightGrid } from '../../terrain/grid';
3
+ import { type OceanBathymetry } from '../../terrain/island';
4
+ export interface OceanFileOptions {
5
+ /** The height grid the ground height is read from; omitted, the source's DEM resampled at `groundMetresPerSample`. */
6
+ grid?: HeightGrid;
7
+ /** Ground sampling when no grid is given (default 8: the ocean file never needs finer). */
8
+ groundMetresPerSample?: number;
9
+ /** Metres between ocean samples, 1..8. Default: the smallest that keeps the file within the format's 2049 samples a side (at least 2). */
10
+ spacingM?: number;
11
+ /** A sea cell this far above the surface is dry (the polygon edge and a beach overlap by a few decimetres). Default 2 m, the baker's own flood-fill threshold. */
12
+ dryAboveM?: number;
13
+ log?: (line: string) => void;
14
+ }
15
+ export interface OceanFileResult {
16
+ written: boolean;
17
+ /** Why nothing was written. */
18
+ reason?: string;
19
+ /** Surface height the file is pinned to; the visuals' `ocean.level` must equal it (the shell default is 0). */
20
+ level?: number;
21
+ info?: OceanBathymetry;
22
+ files?: string[];
23
+ /** Share of the ocean samples that are open sea. */
24
+ wetFraction?: number;
25
+ warnings: string[];
26
+ }
27
+ /**
28
+ * Writes `ocean.json` and `ocean.rgba` into `packageDir` (the Scene package folder, beside `scene.helix-scene.json`) from a city
29
+ * source's open sea: the `sea`/`ocean` water bodies (the same bodies `citySeaFromWater` turns into the `water.sea` volume) as the
30
+ * wet area, the DEM as the ground. A source without an open sea writes nothing. The Scene document does not reference the pair, so
31
+ * it can be called after `createCityScene` or `composeWorld` and neither document's integrity moves.
32
+ */
33
+ export declare function writeCityOceanFile(source: CitySource, packageDir: string, options?: OceanFileOptions): OceanFileResult;
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ // The ocean bathymetry of a city scene: `ocean.json` + `ocean.rgba` beside the Scene document, so the shell's visual ocean
3
+ // draws the open sea and `visuals.ocean.level` exists (fly landing refuses to set down on water, a swimmer resets to dry ground, the
4
+ // camera floor stops at the sea). `terrain island` writes the same pair for a converted CS2 landscape; a city-source scene had
5
+ // the sea as a `water.sea` volume only (src/city/assemble/sea.ts), so the pair was missing and every World built from a city source
6
+ // shipped without a dry-ground guard.
7
+ //
8
+ // Same file format as `terrain island` (src/terrain/island.ts `bakeOceanBathymetry`), which the engine loads with
9
+ // `loadBathymetry` (systems/visual/src/ocean/bathymetry.ts): version 1, at most 2049 samples a side, R/G height bytes, B wet, A
10
+ // distance to dry ground, SHA-256 of the bytes, `level` equal to the visuals' ocean level (0 unless the shell sets another).
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.writeCityOceanFile = writeCityOceanFile;
13
+ const node_fs_1 = require("node:fs");
14
+ const node_path_1 = require("node:path");
15
+ const sea_1 = require("./sea");
16
+ const terrain_1 = require("./terrain");
17
+ const grid_1 = require("../../terrain/grid");
18
+ const island_1 = require("../../terrain/island");
19
+ /** Even-odd scanline fill of rings (outer ring and holes together) into `mask` at the sample positions the baker uses. */
20
+ function fillRings(mask, rings, origin, step, width, height) {
21
+ const crossings = [];
22
+ for (let j = 0; j < height; j += 1) {
23
+ const z = origin[1] + j * step[1];
24
+ crossings.length = 0;
25
+ for (const ring of rings) {
26
+ for (let k = 0; k < ring.length; k += 1) {
27
+ const [x0, z0] = ring[k], [x1, z1] = ring[(k + 1) % ring.length];
28
+ if ((z0 <= z) === (z1 <= z))
29
+ continue;
30
+ crossings.push(x0 + ((z - z0) / (z1 - z0)) * (x1 - x0));
31
+ }
32
+ }
33
+ if (crossings.length < 2)
34
+ continue;
35
+ crossings.sort((a, b) => a - b);
36
+ for (let c = 0; c + 1 < crossings.length; c += 2) {
37
+ const from = Math.max(0, Math.ceil((crossings[c] - origin[0]) / step[0]));
38
+ const to = Math.min(width, Math.ceil((crossings[c + 1] - origin[0]) / step[0]));
39
+ for (let i = from; i < to; i += 1)
40
+ mask[j * width + i] = 1;
41
+ }
42
+ }
43
+ }
44
+ /**
45
+ * Writes `ocean.json` and `ocean.rgba` into `packageDir` (the Scene package folder, beside `scene.helix-scene.json`) from a city
46
+ * source's open sea: the `sea`/`ocean` water bodies (the same bodies `citySeaFromWater` turns into the `water.sea` volume) as the
47
+ * wet area, the DEM as the ground. A source without an open sea writes nothing. The Scene document does not reference the pair, so
48
+ * it can be called after `createCityScene` or `composeWorld` and neither document's integrity moves.
49
+ */
50
+ function writeCityOceanFile(source, packageDir, options = {}) {
51
+ const warnings = [];
52
+ const water = source.areas?.water ?? [];
53
+ const sea = (0, sea_1.citySeaFromWater)(water);
54
+ if (!sea)
55
+ return { written: false, reason: 'the source holds no open sea (no `sea` body over 20 km2 and no `ocean`)', warnings };
56
+ const level = sea.surfaceHeightM;
57
+ if (level !== 0)
58
+ warnings.push(`sea surface is ${level} m; the shell's visuals must set ocean.level to ${level} or the bathymetry is refused`);
59
+ const grid = options.grid ?? (0, terrain_1.terrainGridFor)(source, options.groundMetresPerSample ?? 8);
60
+ const spanX = (grid.samplesX - 1) * grid.metresPerSample, spanZ = (grid.samplesZ - 1) * grid.metresPerSample;
61
+ const spacing = options.spacingM ?? Math.max(2, Math.ceil(Math.max(spanX, spanZ) / 2048));
62
+ if (!Number.isInteger(spacing) || spacing < 1 || spacing > 8) {
63
+ return { written: false, reason: `a ${Math.round(Math.max(spanX, spanZ))} m map needs ${spacing} m ocean samples; the format allows 1 to 8 m and 2049 samples a side`, warnings };
64
+ }
65
+ const width = Math.ceil(spanX / spacing) + 1, height = Math.ceil(spanZ / spacing) + 1;
66
+ const step = [spanX / (width - 1), spanZ / (height - 1)];
67
+ const origin = [grid.originX, grid.originZ];
68
+ const bodies = water.filter((body) => (body.kind === 'sea' || body.kind === 'ocean') && body.ring);
69
+ const mask = new Uint8Array(width * height);
70
+ for (const body of bodies) {
71
+ const one = new Uint8Array(width * height);
72
+ fillRings(one, [body.ring, ...(body.holes ?? [])], origin, step, width, height);
73
+ for (let i = 0; i < one.length; i += 1)
74
+ if (one[i])
75
+ mask[i] = 1;
76
+ }
77
+ const dryAbove = options.dryAboveM ?? 2;
78
+ const wetAt = (x, z) => {
79
+ const i = Math.round((x - origin[0]) / step[0]), j = Math.round((z - origin[1]) / step[1]);
80
+ return mask[j * width + i] === 1 && (0, grid_1.sampleFieldHeight)(grid, x, z) <= level + dryAbove;
81
+ };
82
+ const ocean = (0, island_1.bakeOceanBathymetry)(grid, level, spacing, { wet: wetAt });
83
+ let wet = 0;
84
+ for (let i = 2; i < ocean.bytes.length; i += 4)
85
+ if (ocean.bytes[i] > 127)
86
+ wet += 1;
87
+ const wetFraction = wet / (ocean.info.width * ocean.info.height);
88
+ if (wet === 0)
89
+ return { written: false, reason: 'no ocean sample is open sea (the sea body does not overlap the terrain, or the terrain there is above the surface)', level, warnings };
90
+ (0, node_fs_1.mkdirSync)(packageDir, { recursive: true });
91
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(packageDir, 'ocean.rgba'), ocean.bytes);
92
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(packageDir, 'ocean.json'), `${JSON.stringify({ ...ocean.info, data: 'ocean.rgba' }, null, 2)}\n`);
93
+ options.log?.(`ocean: ${ocean.info.width} x ${ocean.info.height} at ${spacing} m, ${(wetFraction * 100).toFixed(1)}% open sea, level ${level}`);
94
+ return { written: true, level, info: ocean.info, files: ['ocean.json', 'ocean.rgba'], wetFraction, warnings };
95
+ }
96
+ //# sourceMappingURL=oceanFile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oceanFile.js","sourceRoot":"","sources":["../../../src/city/assemble/oceanFile.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ import type { CitySourceManifest } from '../source';
2
+ export interface SceneVisualsConfig {
3
+ version: 1;
4
+ mode: 'dynamic';
5
+ weather: {
6
+ enabled: boolean;
7
+ preset: string;
8
+ };
9
+ timeOfDay: {
10
+ hours: number;
11
+ cycleSeconds: number;
12
+ latitudeDeg: number;
13
+ declinationDeg: number;
14
+ noonAzimuthDeg: number;
15
+ };
16
+ probes: {
17
+ skyFallback: boolean;
18
+ };
19
+ shadows: {
20
+ extent: number;
21
+ };
22
+ fog: {
23
+ density: number;
24
+ heightDensity: number;
25
+ };
26
+ sky: {
27
+ model: string;
28
+ mieScale: number;
29
+ groundBounceAlbedo: number;
30
+ };
31
+ look: {
32
+ preset: string;
33
+ };
34
+ exposure?: {
35
+ compensation: number;
36
+ };
37
+ }
38
+ /** What the far edge of the map keeps of its contrast through the fog. */
39
+ export declare const FOG_FAR_EDGE_TRANSMITTANCE = 0.2;
40
+ /** Ground fog is this much denser than the air above it. */
41
+ export declare const GROUND_FOG_RATIO = 1.8;
42
+ /** The latitude and hour of a map that has no place on Earth. */
43
+ export declare const FICTIONAL_LATITUDE_DEG = 37.8;
44
+ export declare const DEFAULT_HOURS = 14;
45
+ export declare const CS2_EXPOSURE_COMPENSATION = 1.5;
46
+ /** The scene visuals a city source gets. `scale` multiplies the fog densities (default 1). Pure: same manifest, same JSON. */
47
+ export declare function deriveSceneVisuals(manifest: Pick<CitySourceManifest, 'frame'>, scale?: number): SceneVisualsConfig;
48
+ /** Writes `<dir>/scene-visuals/<sceneName>.json` (the path the shell fetches under its data prefix) and returns it. */
49
+ export declare function writeSceneVisualsFile(dir: string, sceneName: string, visuals: SceneVisualsConfig): string;
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ // The look of a city scene: the per-map `scene-visuals/<scene>.json` the shell loads for a World whose sky and fog are not the
3
+ // default World's. A Leonida World's file was hand-copied from Granit's `helix.visuals.json` and edited (fog, hours, latitude); the
4
+ // shell also had to be told the scene name. This derives the same JSON from the city source's manifest, so a new map gets its
5
+ // own file from the pipeline: `writeSceneVisualsFile` after create/compose, and the shell reads `scene-visuals/<name>.json`.
6
+ //
7
+ // The shape is the visual runtime's contract (`helix.visuals.json`, audited by `helix validate`). Constants that are the same for
8
+ // every map (sky model, probes, shadow extent, weather, the `day` look) come from Granit's file; what depends on the place is derived:
9
+ //
10
+ // latitudeDeg a geographic source (`local-tm`) takes its projection origin's latitude. A fictional map (CS2, synthetic) has
11
+ // none: it gets a mid-latitude default (37.8) and an early-afternoon sun (14 h), a long warm light that
12
+ // still reads a skyline's faces, not a noon top-down one.
13
+ // noonAzimuthDeg 180 (sun due south at noon) in the northern hemisphere, 0 in the southern one.
14
+ // fog exp(-density x extent) = 0.2: the far edge of the map keeps a fifth of its contrast, so a skyline reads from
15
+ // the air at any map size (Leonida, 14,336 m: 0.00011; a 6.4 km map comes out at 0.00025, the value Granit's file has).
16
+ // Ground fog (`heightDensity`) is 1.8 x that. `scale` multiplies both (an art knob; 1 keeps the rule).
17
+ // exposure CS2 buildings come from a game that exposes its HDR; +1.5 EV keeps the converted facades from reading dim.
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.CS2_EXPOSURE_COMPENSATION = exports.DEFAULT_HOURS = exports.FICTIONAL_LATITUDE_DEG = exports.GROUND_FOG_RATIO = exports.FOG_FAR_EDGE_TRANSMITTANCE = void 0;
20
+ exports.deriveSceneVisuals = deriveSceneVisuals;
21
+ exports.writeSceneVisualsFile = writeSceneVisualsFile;
22
+ const node_fs_1 = require("node:fs");
23
+ const node_path_1 = require("node:path");
24
+ /** What the far edge of the map keeps of its contrast through the fog. */
25
+ exports.FOG_FAR_EDGE_TRANSMITTANCE = 0.2;
26
+ /** Ground fog is this much denser than the air above it. */
27
+ exports.GROUND_FOG_RATIO = 1.8;
28
+ /** The latitude and hour of a map that has no place on Earth. */
29
+ exports.FICTIONAL_LATITUDE_DEG = 37.8;
30
+ exports.DEFAULT_HOURS = 14;
31
+ exports.CS2_EXPOSURE_COMPENSATION = 1.5;
32
+ const round5 = (value) => Math.round(value * 1e5) / 1e5;
33
+ /** The scene visuals a city source gets. `scale` multiplies the fog densities (default 1). Pure: same manifest, same JSON. */
34
+ function deriveSceneVisuals(manifest, scale = 1) {
35
+ if (!(scale > 0) || !Number.isFinite(scale))
36
+ throw new Error(`sceneVisuals: scale must be a positive number, got ${scale}`);
37
+ const { projection, boundsXZ } = manifest.frame;
38
+ const extentM = Math.max(boundsXZ[1][0] - boundsXZ[0][0], boundsXZ[1][1] - boundsXZ[0][1]);
39
+ if (!(extentM > 0))
40
+ throw new Error('sceneVisuals: the source frame has no extent');
41
+ const latitudeDeg = projection.kind === 'local-tm' ? projection.originLonLat[1] : exports.FICTIONAL_LATITUDE_DEG;
42
+ const density = Math.min(0.001, Math.max(0.00002, round5((-Math.log(exports.FOG_FAR_EDGE_TRANSMITTANCE) / extentM) * scale)));
43
+ return {
44
+ version: 1,
45
+ mode: 'dynamic',
46
+ weather: { enabled: true, preset: 'fair' },
47
+ timeOfDay: { hours: exports.DEFAULT_HOURS, cycleSeconds: 0, latitudeDeg, declinationDeg: 10, noonAzimuthDeg: latitudeDeg < 0 ? 0 : 180 },
48
+ probes: { skyFallback: true },
49
+ shadows: { extent: 200 },
50
+ fog: { density, heightDensity: round5(density * exports.GROUND_FOG_RATIO) },
51
+ sky: { model: 'hillaire', mieScale: 1.0, groundBounceAlbedo: 0.25 },
52
+ look: { preset: 'day' },
53
+ ...(projection.kind === 'cs2' ? { exposure: { compensation: exports.CS2_EXPOSURE_COMPENSATION } } : {}),
54
+ };
55
+ }
56
+ /** Writes `<dir>/scene-visuals/<sceneName>.json` (the path the shell fetches under its data prefix) and returns it. */
57
+ function writeSceneVisualsFile(dir, sceneName, visuals) {
58
+ if (!/^[a-z0-9][a-z0-9._-]*$/i.test(sceneName))
59
+ throw new Error(`sceneVisuals: '${sceneName}' is not a scene folder name`);
60
+ const folder = (0, node_path_1.join)(dir, 'scene-visuals');
61
+ (0, node_fs_1.mkdirSync)(folder, { recursive: true });
62
+ const file = (0, node_path_1.join)(folder, `${sceneName}.json`);
63
+ (0, node_fs_1.writeFileSync)(file, JSON.stringify(visuals, null, 2));
64
+ return file;
65
+ }
66
+ //# sourceMappingURL=sceneVisuals.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sceneVisuals.js","sourceRoot":"","sources":["../../../src/city/assemble/sceneVisuals.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { Command } from 'commander';
2
+ export declare function registerCityConvert(city: Command, fail: (message: string) => never): void;
@@ -0,0 +1,98 @@
1
+ "use strict";
2
+ // `helix city convert cs2`: the one way to turn a Cities: Skylines II capture into a publish-ready World (docs/city-convert-cs2.md).
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.registerCityConvert = registerCityConvert;
5
+ const node_path_1 = require("node:path");
6
+ const lib_1 = require("../../lib");
7
+ const config_1 = require("./config");
8
+ const exec_1 = require("./exec");
9
+ const optimizations_1 = require("./optimizations");
10
+ const run_1 = require("./run");
11
+ function parseTiers(value) {
12
+ const tiers = [...new Set(value.split(',').map((t) => t.trim()).filter(Boolean))];
13
+ if (!tiers.length || tiers.some((t) => t !== 'desktop' && t !== 'mobile'))
14
+ throw new Error('--tiers must be desktop, mobile or desktop,mobile');
15
+ return tiers;
16
+ }
17
+ function parsePair(value, flag) {
18
+ const parts = value.split(',').map(Number);
19
+ if (parts.length !== 2 || parts.some((n) => !Number.isFinite(n)))
20
+ throw new Error(`${flag} must be "x,z" in metres`);
21
+ return { x: parts[0], z: parts[1] };
22
+ }
23
+ function registerCityConvert(city, fail) {
24
+ const convert = city.command('convert').description('One-shot conversion of a captured city into a publish-ready World (docs/city-convert-cs2.md)');
25
+ convert
26
+ .command('cs2')
27
+ .description('A Cities: Skylines II source-v2 bundle to desktop and phone World roots with every shared optimization applied, collision completed, both tiers perf-gated, and a receipt that proves which ran')
28
+ .argument('[bundle]', 'the CS2 source-v2 bundle folder (terrain/, roads-v3/, placements/, meshes-v2/, areas/, water/, legacy/city/)')
29
+ .requiredOption('--name <slug>', 'World slug and scene root name (lowercase letters, digits, dashes)')
30
+ .option('--out <dir>', 'folder to write everything into (default ./convert-<name>)')
31
+ .option('--title <title>', 'the World title (default: the name, capitalised)')
32
+ .option('--description <text>', 'the World description')
33
+ .option('--config <file>', 'a helix.city-convert/1 file: spawn and the few knobs a map may need; every field has a default')
34
+ .option('--spawn <x,z>', 'open the World here (metres); default: a clear street-level point beside the densest downtown')
35
+ .option('--yaw <deg>', 'spawn heading in degrees (with --spawn); default along the street')
36
+ .option('--tiers <list>', 'desktop, mobile or desktop,mobile (default both)')
37
+ .option('--bounds <minX,minZ,maxX,maxZ>', 'convert only this window of the CS2 world, in scene metres')
38
+ .option('--masses', 'draw buildings as procedural masses even when the bundle has prefab meshes')
39
+ .option('--jobs <n>', 'parallel prototype shards (default 4)')
40
+ .option('--from <stage>', 'rerun this stage and everything after it')
41
+ .option('--stop-after <stage>', 'stop after this stage')
42
+ .option('--force', 'ignore the receipt and run every stage')
43
+ .option('--unlisted', 'the publish plan says unlisted (the default)')
44
+ .option('--public', 'the publish plan says public')
45
+ .option('--private', 'the publish plan says private')
46
+ .option('--list-stages', 'print the stages and the optimizations each applies, then exit')
47
+ .option('--json', 'print the receipt as JSON')
48
+ .action(async (bundle, opts) => {
49
+ try {
50
+ const overrides = {};
51
+ if (opts.title)
52
+ overrides.title = opts.title;
53
+ if (opts.description)
54
+ overrides.description = opts.description;
55
+ if (opts.tiers)
56
+ overrides.tiers = parseTiers(opts.tiers);
57
+ if (opts.spawn)
58
+ overrides.spawn = { ...parsePair(opts.spawn, '--spawn'), ...(opts.yaw !== undefined ? { yawDeg: Number(opts.yaw) } : {}) };
59
+ if (opts.bounds) {
60
+ const b = opts.bounds.split(',').map(Number);
61
+ if (b.length !== 4 || b.some((n) => !Number.isFinite(n)))
62
+ throw new Error('--bounds must be "minX,minZ,maxX,maxZ"');
63
+ overrides.bounds = [b[0], b[1], b[2], b[3]];
64
+ }
65
+ const config = (0, config_1.resolveConvertConfig)(opts.name, opts.config ? (0, node_path_1.resolve)(opts.config) : undefined, overrides);
66
+ if (opts.masses)
67
+ config.buildings.meshes = 'masses';
68
+ const visibility = opts.public ? 'public' : opts.private ? 'private' : opts.unlisted ? 'unlisted' : undefined;
69
+ if (visibility)
70
+ config.publish.visibility = visibility;
71
+ if (opts.listStages) {
72
+ for (const stage of (0, run_1.listStages)(config))
73
+ console.log(`${stage.id.padEnd(22)} ${stage.title}${stage.optimizations.length ? `\n${' '.repeat(23)}applies: ${stage.optimizations.join(', ')}` : ''}`);
74
+ console.log(`\n${optimizations_1.OPTIMIZATIONS.length} named optimizations; runtime (engine) fixes reach every World from the runtime range.`);
75
+ return;
76
+ }
77
+ if (!bundle)
78
+ throw new Error('give the bundle folder: helix city convert cs2 <bundle> --name <slug> --out <dir>');
79
+ const out = (0, node_path_1.resolve)(opts.out ?? `convert-${opts.name}`);
80
+ const receipt = await (0, run_1.runConvert)({
81
+ bundle: (0, node_path_1.resolve)(bundle), out, config, exec: (0, exec_1.childExec)({ out }), cliVersion: (0, lib_1.cliVersion)(), log: (line) => process.stderr.write(`${line}\n`),
82
+ ...(opts.jobs ? { jobs: Math.max(1, Number(opts.jobs)) } : {}), ...(opts.from ? { from: opts.from } : {}), ...(opts.stopAfter ? { stopAfter: opts.stopAfter } : {}), ...(opts.force ? { force: true } : {}),
83
+ });
84
+ if (opts.json) {
85
+ console.log(JSON.stringify(receipt, null, 2));
86
+ return;
87
+ }
88
+ console.log(`✔ ${config.title}: ${receipt.stages.length} stages, ${receipt.applied.length} optimizations applied -> ${out}`);
89
+ for (const stage of receipt.stages)
90
+ console.log(` ${stage.id.padEnd(22)} ${(stage.ms / 1000).toFixed(1)} s`);
91
+ console.log(` receipt: ${(0, node_path_1.resolve)(out, 'convert-receipt.json')}\n publish plan: ${(0, node_path_1.resolve)(out, 'publish', 'publish.json')}`);
92
+ }
93
+ catch (error) {
94
+ fail(`✖ ${error instanceof Error ? error.message : String(error)}`);
95
+ }
96
+ });
97
+ }
98
+ //# sourceMappingURL=command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command.js","sourceRoot":"","sources":["../../../src/city/convert/command.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ export declare const CONVERT_CONFIG_SCHEMA: "helix.city-convert/1";
2
+ export type ConvertTier = 'desktop' | 'mobile';
3
+ export interface ConvertSpawn {
4
+ x: number;
5
+ z: number;
6
+ /** Rotation about Y in degrees (0 faces -z). Omitted: along the street. */
7
+ yawDeg?: number;
8
+ /** Metres around the spawn kept free of trees. */
9
+ clearM?: number;
10
+ }
11
+ export interface ConvertConfig {
12
+ schema: typeof CONVERT_CONFIG_SCHEMA;
13
+ /** Scene root name and World slug base (`leonida` gives `leonida-c1` and `leonida-mobile-c1`). */
14
+ name: string;
15
+ title: string;
16
+ description?: string;
17
+ /** City id written into the city source (default `city.cs2-<name>`). */
18
+ cityId?: string;
19
+ /** Import only this window of the CS2 world, in scene metres. */
20
+ bounds?: [number, number, number, number];
21
+ /** Omitted: the stage `spawn` picks a clear street-level point near the densest downtown. */
22
+ spawn?: ConvertSpawn;
23
+ tiers: ConvertTier[];
24
+ terrain: {
25
+ /** 4 m is what the engine's stride ladder (4/8/16/32 m) is cut from. */
26
+ metresPerSample: 1 | 2 | 4;
27
+ /** Copernicus margin ring: real-world terrain, wrong for a fictional CS2 map, so off. */
28
+ marginM: number;
29
+ /** `natural`: a CS2 map only maps its lots, so unmapped land is grass and shore sand, not pavement. */
30
+ ground: 'natural' | 'urban';
31
+ /** CS2 height maps carry bogus land along the border that the coarse layer extrudes as a hard rectangle. */
32
+ demEdgeRepair: number;
33
+ };
34
+ vegetation: {
35
+ /** `source`: every tree the map has (the default 16,000 cap read as bare on a 292,028-tree map). */
36
+ maxTrees: 'source' | number;
37
+ };
38
+ buildings: {
39
+ /** `auto`: real prefab meshes when the bundle has them, else procedural masses. */
40
+ meshes: 'auto' | 'real' | 'masses';
41
+ /** Prototype builds run as this many parallel processes per tier. */
42
+ shards: number;
43
+ };
44
+ colliders: {
45
+ /** Building boxes inline in the root document (a document near its memory cap cannot afford the mount). */
46
+ inline: boolean;
47
+ };
48
+ publish: {
49
+ visibility: 'private' | 'unlisted' | 'public';
50
+ world: string | null;
51
+ };
52
+ }
53
+ export declare function defaultConvertConfig(name: string): ConvertConfig;
54
+ export declare function validateConvertConfig(config: ConvertConfig, where?: string): ConvertConfig;
55
+ /** Defaults, then the file on top (one level of merge per group), then validation. */
56
+ export declare function resolveConvertConfig(name: string, file?: string, overrides?: Partial<Pick<ConvertConfig, 'title' | 'description' | 'cityId' | 'bounds' | 'spawn' | 'tiers'>>): ConvertConfig;
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ // `helix.city-convert/1`: the one small file a CS2 conversion reads for what only a person knows (a name, optionally a spawn).
3
+ // Everything else has a default that every converted map gets, so two maps converted with the same CLI differ only in what the
4
+ // map itself says. A field here is never a place to hand-apply an optimisation: those are stages (`stages.ts`).
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.CONVERT_CONFIG_SCHEMA = void 0;
7
+ exports.defaultConvertConfig = defaultConvertConfig;
8
+ exports.validateConvertConfig = validateConvertConfig;
9
+ exports.resolveConvertConfig = resolveConvertConfig;
10
+ const node_fs_1 = require("node:fs");
11
+ exports.CONVERT_CONFIG_SCHEMA = 'helix.city-convert/1';
12
+ function defaultConvertConfig(name) {
13
+ const title = name.replace(/[-_]+/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
14
+ return {
15
+ schema: exports.CONVERT_CONFIG_SCHEMA,
16
+ name,
17
+ title,
18
+ tiers: ['desktop', 'mobile'],
19
+ terrain: { metresPerSample: 4, marginM: 0, ground: 'natural', demEdgeRepair: 5 },
20
+ vegetation: { maxTrees: 'source' },
21
+ buildings: { meshes: 'auto', shards: 8 },
22
+ colliders: { inline: true },
23
+ publish: { visibility: 'unlisted', world: null },
24
+ };
25
+ }
26
+ const SLUG = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
27
+ function validateConvertConfig(config, where = 'convert config') {
28
+ if (config.schema !== exports.CONVERT_CONFIG_SCHEMA)
29
+ throw new Error(`${where}: schema must be ${exports.CONVERT_CONFIG_SCHEMA}`);
30
+ if (!SLUG.test(config.name))
31
+ throw new Error(`${where}: name must be a lowercase slug (letters, digits, dashes), got '${config.name}'`);
32
+ if (!config.tiers.length || config.tiers.some((t) => t !== 'desktop' && t !== 'mobile'))
33
+ throw new Error(`${where}: tiers must list desktop and/or mobile`);
34
+ if (config.bounds && (config.bounds.length !== 4 || config.bounds.some((n) => !Number.isFinite(n))))
35
+ throw new Error(`${where}: bounds must be [minX, minZ, maxX, maxZ] in metres`);
36
+ if (config.spawn && !(Number.isFinite(config.spawn.x) && Number.isFinite(config.spawn.z)))
37
+ throw new Error(`${where}: spawn needs numeric x and z`);
38
+ if (![1, 2, 4].includes(config.terrain.metresPerSample))
39
+ throw new Error(`${where}: terrain.metresPerSample must be 1, 2 or 4`);
40
+ if (!Number.isInteger(config.terrain.demEdgeRepair) || config.terrain.demEdgeRepair < 0 || config.terrain.demEdgeRepair > 16)
41
+ throw new Error(`${where}: terrain.demEdgeRepair must be an integer from 0 to 16`);
42
+ if (config.terrain.ground !== 'natural' && config.terrain.ground !== 'urban')
43
+ throw new Error(`${where}: terrain.ground must be natural or urban`);
44
+ if (config.vegetation.maxTrees !== 'source' && !(Number.isInteger(config.vegetation.maxTrees) && config.vegetation.maxTrees >= 0))
45
+ throw new Error(`${where}: vegetation.maxTrees must be 'source' or a count`);
46
+ if (!['auto', 'real', 'masses'].includes(config.buildings.meshes))
47
+ throw new Error(`${where}: buildings.meshes must be auto, real or masses`);
48
+ if (!Number.isInteger(config.buildings.shards) || config.buildings.shards < 1 || config.buildings.shards > 32)
49
+ throw new Error(`${where}: buildings.shards must be 1 to 32`);
50
+ if (!['private', 'unlisted', 'public'].includes(config.publish.visibility))
51
+ throw new Error(`${where}: publish.visibility must be private, unlisted or public`);
52
+ return config;
53
+ }
54
+ /** Defaults, then the file on top (one level of merge per group), then validation. */
55
+ function resolveConvertConfig(name, file, overrides = {}) {
56
+ const base = defaultConvertConfig(name);
57
+ let given = {};
58
+ if (file) {
59
+ if (!(0, node_fs_1.existsSync)(file))
60
+ throw new Error(`convert config ${file} does not exist`);
61
+ given = JSON.parse((0, node_fs_1.readFileSync)(file, 'utf8'));
62
+ }
63
+ const merged = {
64
+ ...base, ...given,
65
+ terrain: { ...base.terrain, ...given.terrain },
66
+ vegetation: { ...base.vegetation, ...given.vegetation },
67
+ buildings: { ...base.buildings, ...given.buildings },
68
+ colliders: { ...base.colliders, ...given.colliders },
69
+ publish: { ...base.publish, ...given.publish },
70
+ };
71
+ if (given.name === undefined)
72
+ merged.name = name;
73
+ for (const [key, value] of Object.entries(overrides))
74
+ if (value !== undefined)
75
+ merged[key] = value;
76
+ return validateConvertConfig(merged, file ?? 'convert config');
77
+ }
78
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/city/convert/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { Exec } from './stages';
2
+ export declare function childExec(options: {
3
+ out: string;
4
+ entry?: string;
5
+ }): Exec;
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ // Runs `helix <args>` as a child process of this very CLI (same node, same entry), with the output in a log file per step. A child
3
+ // per heavy step keeps each step's memory separate (the landscape of a 14 km map wants a 14 GiB heap, a validate does not).
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.childExec = childExec;
6
+ const node_child_process_1 = require("node:child_process");
7
+ const node_fs_1 = require("node:fs");
8
+ const node_path_1 = require("node:path");
9
+ const TAIL_BYTES = 6000;
10
+ function tailOf(file) {
11
+ try {
12
+ const size = (0, node_fs_1.statSync)(file).size;
13
+ const text = (0, node_fs_1.readFileSync)(file, 'utf8');
14
+ return size > TAIL_BYTES ? text.slice(-TAIL_BYTES) : text;
15
+ }
16
+ catch {
17
+ return '';
18
+ }
19
+ }
20
+ function childExec(options) {
21
+ const entry = options.entry ?? process.argv[1];
22
+ const logs = (0, node_path_1.join)(options.out, 'logs');
23
+ (0, node_fs_1.mkdirSync)(logs, { recursive: true });
24
+ return (args, { id, memMiB }) => new Promise((resolve) => {
25
+ const log = (0, node_path_1.join)(logs, `${id}.log`);
26
+ const fd = (0, node_fs_1.openSync)(log, 'w');
27
+ const child = (0, node_child_process_1.spawn)(process.execPath, [...(memMiB ? [`--max-old-space-size=${memMiB}`] : []), entry, ...args], { stdio: ['ignore', fd, fd], env: process.env });
28
+ const done = (code) => { try {
29
+ (0, node_fs_1.closeSync)(fd);
30
+ }
31
+ catch { /* already closed */ } resolve({ code, tail: tailOf(log) }); };
32
+ child.on('error', () => done(127));
33
+ child.on('close', (code) => done(code ?? 1));
34
+ });
35
+ }
36
+ //# sourceMappingURL=exec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exec.js","sourceRoot":"","sources":["../../../src/city/convert/exec.ts"],"names":[],"mappings":";AAAA,mIAAmI;AACnI,4HAA4H;;AAiB5H,8BAYC;AA3BD,2DAA2C;AAC3C,qCAAiF;AACjF,yCAAiC;AAGjC,MAAM,UAAU,GAAG,IAAI,CAAC;AAExB,SAAS,MAAM,CAAC,IAAY;IAC1B,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,IAAA,kBAAQ,EAAC,IAAI,CAAC,CAAC,IAAI,CAAC;QACjC,MAAM,IAAI,GAAG,IAAA,sBAAY,EAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACxC,OAAO,IAAI,GAAG,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC5D,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,EAAE,CAAC;IAAC,CAAC;AACxB,CAAC;AAED,SAAgB,SAAS,CAAC,OAAwC;IAChE,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC/C,MAAM,IAAI,GAAG,IAAA,gBAAI,EAAC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IACvC,IAAA,mBAAS,EAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACrC,OAAO,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,IAAI,OAAO,CAAa,CAAC,OAAO,EAAE,EAAE;QACnE,MAAM,GAAG,GAAG,IAAA,gBAAI,EAAC,IAAI,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;QACpC,MAAM,EAAE,GAAG,IAAA,kBAAQ,EAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QAC9B,MAAM,KAAK,GAAG,IAAA,0BAAK,EAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,wBAAwB,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,QAAQ,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;QAChK,MAAM,IAAI,GAAG,CAAC,IAAY,EAAQ,EAAE,GAAG,IAAI,CAAC;YAAC,IAAA,mBAAS,EAAC,EAAE,CAAC,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QACrI,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QACnC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,6 @@
1
+ export interface LandscapeSourceResult {
2
+ dir: string;
3
+ buildingsRemoved: number;
4
+ manifestSha256: string;
5
+ }
6
+ export declare function deriveLandscapeSource(sourceDir: string, outDir: string): Promise<LandscapeSourceResult>;
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ // A landscape-only copy of a city source: same frame, roads, areas, trees and terrain, no buildings. `scene create city` builds the
3
+ // landscape root from it and the real-mesh building parts are mounted on top (docs/cs2-mesh-buildings.md). Until now this was a
4
+ // hand script per map (Leonida's `masses.py`); it is a stage so every CS2 map gets the same landscape.
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.deriveLandscapeSource = deriveLandscapeSource;
7
+ const source_1 = require("../source");
8
+ async function deriveLandscapeSource(sourceDir, outDir) {
9
+ const source = await (0, source_1.readCitySource)(sourceDir);
10
+ const { manifest, ...layers } = source;
11
+ const draft = {
12
+ ...layers,
13
+ manifest: { ...manifest, provenance: [...manifest.provenance, { source: 'helix-city-convert', note: 'landscape-only: buildings removed, real-mesh parts are mounted on this root' }] },
14
+ buildings: [],
15
+ };
16
+ const written = await (0, source_1.writeCitySource)(outDir, draft);
17
+ return { dir: written.dir, buildingsRemoved: source.buildings?.length ?? 0, manifestSha256: written.manifestSha256 };
18
+ }
19
+ //# sourceMappingURL=landscapeSource.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"landscapeSource.js","sourceRoot":"","sources":["../../../src/city/convert/landscapeSource.ts"],"names":[],"mappings":";AAAA,oIAAoI;AACpI,gIAAgI;AAChI,uGAAuG;;AAMvG,sDAUC;AAdD,sCAAkF;AAI3E,KAAK,UAAU,qBAAqB,CAAC,SAAiB,EAAE,MAAc;IAC3E,MAAM,MAAM,GAAG,MAAM,IAAA,uBAAc,EAAC,SAAS,CAAC,CAAC;IAC/C,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,EAAE,GAAG,MAAM,CAAC;IACvC,MAAM,KAAK,GAAoB;QAC7B,GAAG,MAAM;QACT,QAAQ,EAAE,EAAE,GAAG,QAAQ,EAAE,UAAU,EAAE,CAAC,GAAG,QAAQ,CAAC,UAAU,EAAE,EAAE,MAAM,EAAE,oBAAoB,EAAE,IAAI,EAAE,6EAA6E,EAAE,CAAC,EAAE;QACtL,SAAS,EAAE,EAAE;KACd,CAAC;IACF,MAAM,OAAO,GAAG,MAAM,IAAA,wBAAe,EAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACrD,OAAO,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,gBAAgB,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,IAAI,CAAC,EAAE,cAAc,EAAE,OAAO,CAAC,cAAc,EAAE,CAAC;AACvH,CAAC"}
@@ -0,0 +1,8 @@
1
+ export interface Optimization {
2
+ id: string;
3
+ title: string;
4
+ ref: string;
5
+ stage: string;
6
+ }
7
+ export declare const OPTIMIZATIONS: readonly Optimization[];
8
+ export declare const optimizationById: (id: string) => Optimization;