@hypersoniclabs/helix-cli 0.1.27-helix3.551 → 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 (43) 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/cs2/convert/buildingCompose.d.ts +64 -9
  8. package/dist/cs2/convert/buildingCompose.js +232 -13
  9. package/dist/cs2/convert/buildingCompose.js.map +1 -1
  10. package/dist/cs2/convert/buildingMeshCommand.js +63 -18
  11. package/dist/cs2/convert/buildingMeshCommand.js.map +1 -1
  12. package/dist/cs2/convert/buildingParts.d.ts +6 -0
  13. package/dist/cs2/convert/buildingParts.js +1 -1
  14. package/dist/cs2/convert/buildingParts.js.map +1 -1
  15. package/dist/cs2/convert/hlodAtlas.d.ts +60 -0
  16. package/dist/cs2/convert/hlodAtlas.js +246 -0
  17. package/dist/cs2/convert/hlodAtlas.js.map +1 -0
  18. package/dist/cs2/convert/tierProfiles.d.ts +83 -0
  19. package/dist/cs2/convert/tierProfiles.js +64 -0
  20. package/dist/cs2/convert/tierProfiles.js.map +1 -0
  21. package/dist/sceneCreate/commands.d.ts +2 -0
  22. package/dist/sceneCreate/commands.js +5 -2
  23. package/dist/sceneCreate/commands.js.map +1 -1
  24. package/dist/sceneHlod/commands.js +13 -0
  25. package/dist/sceneHlod/commands.js.map +1 -1
  26. package/dist/tsconfig.build.tsbuildinfo +1 -1
  27. package/docs/scene-create.md +8 -1
  28. package/package.json +1 -1
  29. package/plugins/beamng-vehicle/bin/__pycache__/beamng_abs.cpython-312.pyc +0 -0
  30. package/plugins/beamng-vehicle/bin/__pycache__/beamng_audio.cpython-312.pyc +0 -0
  31. package/plugins/beamng-vehicle/bin/__pycache__/beamng_engine.cpython-312.pyc +0 -0
  32. package/plugins/beamng-vehicle/bin/__pycache__/beamng_expression.cpython-312.pyc +0 -0
  33. package/plugins/beamng-vehicle/bin/__pycache__/beamng_jbeam.cpython-312.pyc +0 -0
  34. package/plugins/beamng-vehicle/bin/__pycache__/beamng_materials.cpython-312.pyc +0 -0
  35. package/plugins/beamng-vehicle/bin/__pycache__/beamng_stock_events.cpython-312.pyc +0 -0
  36. package/plugins/beamng-vehicle/bin/__pycache__/fmod_studio.cpython-312.pyc +0 -0
  37. package/plugins/beamng-vehicle/bin/__pycache__/fsb5.cpython-312.pyc +0 -0
  38. package/plugins/beamng-vehicle/bin/__pycache__/sim_audio.cpython-312.pyc +0 -0
  39. package/plugins/beamng-vehicle/bin/__pycache__/sim_audio_ac.cpython-312.pyc +0 -0
  40. package/plugins/beamng-vehicle/bin/__pycache__/sim_audio_beamng.cpython-312.pyc +0 -0
  41. package/plugins/beamng-vehicle/bin/__pycache__/sim_audio_fixtures.cpython-312.pyc +0 -0
  42. package/plugins/beamng-vehicle/bin/__pycache__/test_beamng_engine.cpython-312.pyc +0 -0
  43. 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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"}
@@ -1,17 +1,56 @@
1
1
  import { type HelixSceneV2 } from '@hypersoniclabs/helix-manifest';
2
+ import { type ComposeProfile } from './tierProfiles';
2
3
  export declare const MOUNT_CAPABILITY = "helix.scene-mount/1";
3
- export interface ComposeTier {
4
- budgetMiB: number;
5
- nearAllowanceMiB: number;
6
- nearRadiusM: number;
7
- /** Fraction of the bake's uncompressed-RGBA worst case the shared HLOD atlas is declared at (it ships KTX2). */
8
- hlodMaterialScale: number;
9
- streamRadiusM?: number;
10
- }
4
+ /** A tier's compose policy (`TIER_PROFILES[tier].compose`): budgets, radii, how the shared HLOD is declared, group cut, skyline atlas. */
5
+ export type ComposeTier = ComposeProfile;
11
6
  export declare const COMPOSE_TIERS: Record<'desktop' | 'mobile', ComposeTier>;
12
7
  export declare function reseal(scene: HelixSceneV2): HelixSceneV2;
8
+ /** How `retargetPart` reads the HLOD files it declares (the engine's own accounting of what they hold once decoded). */
9
+ export interface HlodDeclarations {
10
+ /** Decoded bytes of a distant-mass cluster resource; `undefined`: keep the bake's figure. */
11
+ geometryBytes?: (resource: HelixSceneV2['resources'][number] | undefined) => number | undefined;
12
+ /** GPU bytes of a shared-appearance resource (KTX2 headers, transcoded 8 bpp, mips, twice); `undefined`: scale the bake's RGBA worst case. */
13
+ atlasBytes?: (resource: HelixSceneV2['resources'][number] | undefined) => number | undefined;
14
+ }
13
15
  /** The part with its own allowance and near radius, sealed again. */
14
- export declare function retargetPart(part: HelixSceneV2, tier: ComposeTier): HelixSceneV2;
16
+ export declare function retargetPart(part: HelixSceneV2, tier: ComposeTier, declarations?: HlodDeclarations): HelixSceneV2;
17
+ type Box = {
18
+ min: [number, number, number];
19
+ max: [number, number, number];
20
+ };
21
+ /** The world box of a local box under a Scene v2 node transform (position, quaternion rotation, scale). */
22
+ export declare function transformedBounds(local: Box, transform: {
23
+ position: number[];
24
+ rotation: number[];
25
+ scale: number[];
26
+ }): Box;
27
+ /** What a near model costs resident and the box it fills, by Scene resource id (`res.proto.<sha256[:24]>`), from the prototype index. */
28
+ export interface NearResidency {
29
+ resident: Map<string, number>;
30
+ bounds: Map<string, Box>;
31
+ }
32
+ /** The same accounting `parts` declared each group with (`prototypeResidentBytes`), per model, so a group can be cut again. `null` when the data root has no index. */
33
+ export declare function readNearResidency(data: string): NearResidency | null;
34
+ export interface SplitReport {
35
+ groupsBefore: number;
36
+ groups: number;
37
+ cellM: number | null;
38
+ capMiB: number;
39
+ }
40
+ /**
41
+ * Cuts a part's near stream groups the way Granit's compose does: a stream group per `cellM` square of the map (nearest-first
42
+ * admission then fills the allowance in small steps and what does not fit stays on its HLOD), each cell cut again, recursively at the
43
+ * median of its longer axis, until it costs at most `capMiB`. The declared cost of a piece is what its distinct models cost resident.
44
+ * Each group's cell is the tight 3D box of its buildings (the bake leaves cells as wide as its source group, so the planner's
45
+ * distance was to a box far larger than the buildings it holds). A part whose cut would exceed `maxGroups` coarsens the grid
46
+ * (`cellM` x2 up to no grid), then raises the cost cap, so it still fits the document's group cap. HLOD clusters name the cells they
47
+ * cover: each is expanded to the pieces and its source hash re-derived.
48
+ */
49
+ export declare function splitNearGroups(part: HelixSceneV2, residency: NearResidency, options: {
50
+ capMiB: number;
51
+ cellM: number;
52
+ maxGroups?: number;
53
+ }): SplitReport;
15
54
  export declare const mountCommitmentMiB: (child: HelixSceneV2) => number;
16
55
  export declare function mountParts(base: HelixSceneV2, children: HelixSceneV2[], options: {
17
56
  id: string;
@@ -30,6 +69,10 @@ export interface ComposeOptions {
30
69
  /** Root scene id; omitted, the landscape's. */
31
70
  id?: string;
32
71
  title?: string;
72
+ /** Explicit values that win over `TIER_PROFILES[tier].compose` (CLI flags). */
73
+ overrides?: Partial<ComposeProfile>;
74
+ /** `eager` (default) mounts the parts with the root; `distance` makes `stream.city` a distance group, which perf rule P05 refuses. */
75
+ mountMode?: 'eager' | 'distance';
33
76
  log?: (line: string) => void;
34
77
  }
35
78
  export interface ComposeReport {
@@ -51,5 +94,17 @@ export interface ComposeReport {
51
94
  commitmentMiB: number;
52
95
  allowanceMiB: number;
53
96
  }>;
97
+ /** The policy actually applied (profile + overrides). */
98
+ policy: ComposeTier;
99
+ /** Per part: stream groups before and after the cell cut, the grid and cost cap the cut ended at. Empty when the data root has no prototype index. */
100
+ split: SplitReport[];
101
+ /** The shared atlas lowering: carriers lowered, bytes before and after (all parts). */
102
+ atlas: {
103
+ lowered: number;
104
+ bytesBefore: number;
105
+ bytesAfter: number;
106
+ dir: string | null;
107
+ };
54
108
  }
55
109
  export declare function composeWorld(options: ComposeOptions): ComposeReport;
110
+ export {};