@helix3/helix-cli 0.1.14-helix3.199 → 0.1.14-helix3.201
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/plugins/beamng-vehicle/README.md +5 -3
- package/plugins/beamng-vehicle/bin/__pycache__/beamng_expression.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/beamng_jbeam.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/beamng_materials.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/beamng-vehicle.mjs +183 -8
- package/plugins/beamng-vehicle/bin/beamng_expression.py +373 -0
- package/plugins/beamng-vehicle/bin/convert-beamng.py +1 -0
- package/plugins/beamng-vehicle/bin/lower-beamng.py +542 -16
- package/plugins/beamng-vehicle/bridge-plugin.json +2 -2
- package/plugins/beamng-vehicle/lib/material-semantics.mjs +300 -0
- package/plugins/beamng-vehicle/lib/vehicle-qa.mjs +30 -7
- package/plugins/beamng-vehicle/lib/vehicle-source.mjs +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@helix3/helix-cli",
|
|
3
|
-
"version": "0.1.14-helix3.
|
|
3
|
+
"version": "0.1.14-helix3.201",
|
|
4
4
|
"description": "helix — the HELIX creator CLI: scaffold, validate, and publish Instant Worlds",
|
|
5
5
|
"main": "dist/lib.js",
|
|
6
6
|
"types": "dist/lib.d.ts",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"@gltf-transform/extensions": "^4.4.0",
|
|
64
64
|
"@gltf-transform/functions": "^4.4.0",
|
|
65
65
|
"@hypersoniclabs/helix-manifest": "npm:@helix3/helix-manifest@0.3.25-helix3.68",
|
|
66
|
-
"@hypersoniclabs/helix-sdk": "npm:@helix3/helix-sdk@0.1.5-helix3.
|
|
66
|
+
"@hypersoniclabs/helix-sdk": "npm:@helix3/helix-sdk@0.1.5-helix3.99",
|
|
67
67
|
"commander": "^13.0.0",
|
|
68
68
|
"ktx2-encoder": "^0.5.3",
|
|
69
69
|
"meshoptimizer": "^0.22.0",
|
|
@@ -5,7 +5,7 @@ This plugin makes `helix bridge import <unpacked-mod>` the single preflight and
|
|
|
5
5
|
The importer performs two gates:
|
|
6
6
|
|
|
7
7
|
1. Candidate assessment selects exactly one `vehicles/<id>` root and requires JBeam, render geometry, source material bindings, an authored steering-wheel mesh, real cabin signals, rims **and authored tyre geometry**, four wheel signals, and recorded or source-referenced engine audio. JBeam power-steering text cannot satisfy the visual steering requirement, and a rim radius cannot satisfy the tyre requirement. Candidate-local `vehicles/common` dependencies win over any external common root.
|
|
8
|
-
2. Conversion resolves the strongest renderable JBeam slot/flexbody closure, ranking actual selected-DAE coverage and a complete authored cabin ahead of optional-part volume. It retains only selected common wheel dependencies, expands legacy single/axle-pair road-wheel geometry to four measured shell corners before validation, runs Blender headlessly, preserves authored maps and UVs, semantically projects every vehicle surface onto a required `helix-palette/3` input, and embeds `helix-material-binding/1`. One size-compatible authored tyre mesh is instanced at the four measured rim centres; all source and unselected tyre nodes are removed. Acceptance then tests dimensions, triangle budget, `+Z` forward semantics, exact-four tyre custody, 90% render-ready material coverage, engine placement, authored seat H-points and steering geometry, cockpit ergonomics, provenance, and audio evidence. Repairs remain recorded but cannot manufacture source eligibility. A rear-at-`+Z` export is converted again with a geometry-only front/back reflection; any other failed check blocks the import.
|
|
8
|
+
2. Conversion resolves the strongest renderable JBeam slot/flexbody closure, ranking actual selected-DAE coverage and a complete authored cabin ahead of optional-part volume. It retains only selected common wheel dependencies, expands legacy single/axle-pair road-wheel geometry to four measured shell corners before validation, runs Blender headlessly, preserves authored maps and UVs, semantically projects every vehicle surface onto a required `helix-palette/3` input, and embeds `helix-material-binding/1`. Before downstream material deduplication, reused source materials are split by node/primitive surface and stamped with `helix.vehicle-material-semantic/1`; family identity therefore survives optimization while authored atlases, UVs, and texture references remain shared. This metadata lets runtime consumers bypass name inference for new imports while retaining that fallback for older assets. One size-compatible authored tyre mesh is instanced at the four measured rim centres; all source and unselected tyre nodes are removed. Acceptance then tests dimensions, triangle budget, `+Z` forward semantics, exact-four tyre custody, 90% render-ready material coverage, engine placement, authored seat H-points and steering geometry, cockpit ergonomics, provenance, and audio evidence. Repairs remain recorded but cannot manufacture source eligibility. A rear-at-`+Z` export is converted again with a geometry-only front/back reflection; any other failed check blocks the import.
|
|
9
9
|
|
|
10
10
|
BeamNG vehicle archives commonly reference tyres and materials shipped by the base game instead of duplicating them in every mod. Index that exact dependency once, review it as the named `beamngCommon` Bridge input, and pass it beside the Palette:
|
|
11
11
|
|
|
@@ -41,8 +41,10 @@ External-plugin execution still requires the Bridge plan key and all permissions
|
|
|
41
41
|
|
|
42
42
|
The converter reads all four BeamNG material stages. Later authored stages win, while conflicts, declaration provenance, null sentinels, unknown maps, and dynamic `@...` render targets remain explicit diagnostics. Texture resolution is deterministic: selected vehicle files, then mod files, then the reviewed base-game common snapshot; same-tier ambiguity refuses the binding. This parser is packaged with the plugin and never imports engine-repository code at runtime.
|
|
43
43
|
|
|
44
|
-
Each successful terminal package emits
|
|
44
|
+
Each successful terminal package emits six assets: the canonical GLB, `<vehicle>.vehicle-source.json`, the versioned `<vehicle>.vehicle-lowering.json` diagnostic aggregate, `<vehicle>.vehicle-package.json`, `<vehicle>.vehicle-addons.json`, and `<vehicle>.vehicle-build.json`. The lowering aggregate makes source semantics, structural exclusions, runtime blockers, functional-domain status, and provenance available to CLI and agent consumers instead of discarding them after packaging. The source manifest remains embedded in GLB extras and records the exact configuration and slot closure, fingerprints, material-resolution provenance, and coverage ledger.
|
|
45
45
|
|
|
46
|
-
`lower-beamng.py` compiles the exact selected `.pc` and admitted JBeam closure into `helix.vehicle-package/2`, stable add-on records, and a source Build. Source literals cover the torque curve, idle/limiter, engine losses, gearbox and final drive, differentials, lights/electrics, audio event references, seats/cameras, and rigid openables when present.
|
|
46
|
+
`lower-beamng.py` compiles the exact selected `.pc` and admitted JBeam closure into `helix.vehicle-package/2`, stable add-on records, and a source Build. Source literals cover the torque curve, idle/limiter, engine losses, gearbox and final drive, differentials, selected front/rear `pressureWheels` brake and parking-brake values, lights/electrics, audio event references, seats/cameras, and rigid openables when present. Brake and suspension expressions use a bounded data-only resolver for selected numeric variables, arithmetic, comparisons, boolean conditionals, and `case()`; direct selected-variable references are supported, source code is never evaluated, and unknown variables, calls, or tokens remain explicitly unsupported. HELIX total brake torque is derived from BeamNG's per-wheel axle values and the exact `.pc` variable overrides take precedence over selected JBeam defaults.
|
|
47
|
+
|
|
48
|
+
Selected suspension evidence is lowered only from the chosen strut/coilover leaf part on each axle. Matching left/right spring, slow/fast bump, slow/fast rebound, velocity-split, precompression, and bound rows are retained with beam-space frequency and damping-ratio derivations. Structural parent/subframe beams and swaybar construction beams are explicitly excluded by part custody. Selected torsionbar stiffness is retained in `N.m/rad`, but the runtime suspension remains fail-closed: BeamNG element rates need motion-ratio geometry to become HELIX wheel rates, and the current package has one global damping triplet with no axle-specific fast/slow channels. The emitted HELIX suspension therefore remains an explicit fallback and `functionalDomains.suspension` remains false until those exact runtime mappings exist. Tyres, raw Lua/controller, and other unsupported expressions remain explicit provenance instead of being presented as literal runtime parity. Soft-body deformation, fluids, wear, and detailed damage remain out of scope. Rigid panel poses are semantic on every device, physical bodies begin at `medium`, and detachment remains disabled on every production tier while source break groups are retained as future eligibility. Simulation, authority, package identity, and Build selection are quality-invariant.
|
|
47
49
|
|
|
48
50
|
The engine consumer seam is `vehiclePackageToTuning(package, { landSolver: 'v2' })`. The source Build intentionally contains stable source identities rather than invented Continuum CIDs; after publication resolves immutable Package references, the existing `createPrivateBuildDraft` → `freezeBuildDraft` workflow is the canonical Build seam.
|
|
Binary file
|
|
Binary file
|
|
@@ -108,7 +108,19 @@ function vehiclePalette(path) {
|
|
|
108
108
|
throw new Error(`BeamNG Palette input has no valid reusable vehicle family '${id}'`);
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
|
-
return
|
|
111
|
+
return {
|
|
112
|
+
path: resolve(path),
|
|
113
|
+
familyVersions: Object.fromEntries(required.map((id) => [id, families.get(id).definitionVersion])),
|
|
114
|
+
familyDefaults: Object.fromEntries(required.map((id) => [id, {
|
|
115
|
+
roughness: families.get(id).shader.defaults.roughness,
|
|
116
|
+
metallic: families.get(id).shader.defaults.metallic,
|
|
117
|
+
}])),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function stampConversionMaterials(output, conversion, palette) {
|
|
122
|
+
injectVehicleMaterialBindings(output, conversion.importQuality?.materials?.bindings,
|
|
123
|
+
palette.familyVersions, palette.familyDefaults);
|
|
112
124
|
}
|
|
113
125
|
|
|
114
126
|
function assess(source, commonRoot = null) {
|
|
@@ -375,7 +387,6 @@ async function convert(source, candidate, output, palette, commonDependency = nu
|
|
|
375
387
|
}
|
|
376
388
|
const conversionReceipt = JSON.parse(readFileSync(receipt, 'utf8'));
|
|
377
389
|
injectVehicleReceipt(output, conversionReceipt);
|
|
378
|
-
injectVehicleMaterialBindings(output, conversionReceipt.HELIX_import_quality?.materials?.bindings);
|
|
379
390
|
const localDependencies = new Set(candidate.localWheelDependencies);
|
|
380
391
|
return { converter: 'blender-dae', sourceGeometry: daes.map((path) => path === candidate.tyreDependency || localDependencies.has(path)
|
|
381
392
|
? dependencyReceipt(path, source, commonDependency) : relative(candidate.root, path)),
|
|
@@ -503,6 +514,167 @@ function assertValidVehiclePackage(pkg, boundary) {
|
|
|
503
514
|
}
|
|
504
515
|
}
|
|
505
516
|
|
|
517
|
+
function record(value) {
|
|
518
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function assertValidFunctionalLowering(lowering, boundary) {
|
|
522
|
+
const semantics = lowering?.sourceSemantics;
|
|
523
|
+
const suspension = semantics?.suspension;
|
|
524
|
+
const coverage = lowering?.coverage;
|
|
525
|
+
const suspensionCounts = suspension?.counts;
|
|
526
|
+
const suspensionProvenance = coverage?.suspensionProvenance;
|
|
527
|
+
const requiredSuspensionCounts = ['selectedLeafParts', 'excludedStructuralParts', 'resolvedAxles', 'sourceRows'];
|
|
528
|
+
const requiredProvenanceCounts = ['source-literal', 'derived', 'defaulted', 'unsupported'];
|
|
529
|
+
const nonempty = (value) => typeof value === 'string' && value.length > 0;
|
|
530
|
+
const explicitUnsupported = (value) => record(value) && value.status === 'unsupported' && nonempty(value.reason);
|
|
531
|
+
const semanticReceipt = (value) => record(value)
|
|
532
|
+
&& ['literal', 'derived', 'unsupported'].includes(value.status) && nonempty(value.unit)
|
|
533
|
+
&& Array.isArray(value.variables)
|
|
534
|
+
&& (value.status === 'unsupported' ? nonempty(value.reason) : Number.isFinite(value.value));
|
|
535
|
+
const selectedReceipt = (value) => semanticReceipt(value) && (value.status === 'unsupported'
|
|
536
|
+
|| (Object.hasOwn(value, 'raw') && value.symmetricRows === 2));
|
|
537
|
+
const dampingReceipt = (value) => semanticReceipt(value) && (value.status === 'unsupported'
|
|
538
|
+
|| (Object.hasOwn(value, 'raw') && value.symmetricRows === 2
|
|
539
|
+
&& semanticReceipt(value.velocitySplit) && Object.hasOwn(value.velocitySplit, 'raw')
|
|
540
|
+
&& value.velocitySplit.symmetricRows === 2 && value.velocitySplit.unit === 'm/s'
|
|
541
|
+
&& value.velocitySplit.status !== 'unsupported' && value.velocitySplit.value >= 0));
|
|
542
|
+
const torsionReceipt = (value) => semanticReceipt(value)
|
|
543
|
+
&& (value.status === 'unsupported' || Object.hasOwn(value, 'raw'));
|
|
544
|
+
const suspensionAxle = (value) => {
|
|
545
|
+
if (explicitUnsupported(value) && Array.isArray(value.candidates)) return true;
|
|
546
|
+
if (!record(value) || !record(value.damping)) return false;
|
|
547
|
+
const receipts = [value.springRate, value.precompressionRange, value.bumpLimit,
|
|
548
|
+
...['slowBump', 'fastBump', 'slowRebound', 'fastRebound'].map((name) => value.damping[name])];
|
|
549
|
+
const receiptShapeValid = receipts.slice(0, 3).every(selectedReceipt)
|
|
550
|
+
&& receipts.slice(3).every(dampingReceipt);
|
|
551
|
+
const expectedStatus = receipts.every((receipt) => receipt?.status !== 'unsupported')
|
|
552
|
+
? 'resolved' : 'unsupported';
|
|
553
|
+
return value.status === expectedStatus
|
|
554
|
+
&& nonempty(value.slotType) && nonempty(value.partName) && nonempty(value.source)
|
|
555
|
+
&& receiptShapeValid && record(value.derived) && record(value.rowCounts)
|
|
556
|
+
&& ['spring', 'damper', 'travelLimit'].every((name) => Number.isInteger(value.rowCounts[name])
|
|
557
|
+
&& value.rowCounts[name] >= 0);
|
|
558
|
+
};
|
|
559
|
+
const antiRollAxle = (value) => explicitUnsupported(value) || (record(value)
|
|
560
|
+
&& nonempty(value.slotType) && nonempty(value.partName) && nonempty(value.source)
|
|
561
|
+
&& torsionReceipt(value.torsionSpring) && value.runtimeStatus === 'unsupported' && nonempty(value.reason));
|
|
562
|
+
const sourceExtractionComplete = suspension?.axles?.front?.status === 'resolved'
|
|
563
|
+
&& suspension?.axles?.rear?.status === 'resolved'
|
|
564
|
+
&& suspension?.antiRoll?.front?.torsionSpring?.status !== 'unsupported'
|
|
565
|
+
&& suspension?.antiRoll?.front?.torsionSpring !== undefined
|
|
566
|
+
&& suspension?.antiRoll?.rear?.torsionSpring?.status !== 'unsupported'
|
|
567
|
+
&& suspension?.antiRoll?.rear?.torsionSpring !== undefined;
|
|
568
|
+
const countedSuspensionProvenance = Object.fromEntries(requiredProvenanceCounts.map((disposition) => [
|
|
569
|
+
disposition,
|
|
570
|
+
Array.isArray(lowering?.provenance) ? lowering.provenance.filter((row) => record(row)
|
|
571
|
+
&& row.disposition === disposition
|
|
572
|
+
&& typeof row.target === 'string'
|
|
573
|
+
&& (row.target.startsWith('physics.suspension')
|
|
574
|
+
|| row.target.startsWith('sourceSemantics.suspension'))).length : -1,
|
|
575
|
+
]));
|
|
576
|
+
const requiredSemanticTargets = new Map();
|
|
577
|
+
const receiptDisposition = (receipt) => receipt?.status === 'literal' ? 'source-literal' : receipt?.status;
|
|
578
|
+
const expectedEvidence = (receipt, source) => ({ disposition: receiptDisposition(receipt), source });
|
|
579
|
+
for (const axle of ['front', 'rear']) {
|
|
580
|
+
const row = suspension?.axles?.[axle];
|
|
581
|
+
if (record(row) && nonempty(row.partName)) {
|
|
582
|
+
requiredSemanticTargets.set(`sourceSemantics.suspension.axles.${axle}.springRate`,
|
|
583
|
+
expectedEvidence(row.springRate, row.source));
|
|
584
|
+
requiredSemanticTargets.set(`sourceSemantics.suspension.axles.${axle}.precompressionRange`,
|
|
585
|
+
expectedEvidence(row.precompressionRange, row.source));
|
|
586
|
+
requiredSemanticTargets.set(`sourceSemantics.suspension.axles.${axle}.bumpLimit`,
|
|
587
|
+
expectedEvidence(row.bumpLimit, row.source));
|
|
588
|
+
for (const field of ['slowBump', 'fastBump', 'slowRebound', 'fastRebound']) {
|
|
589
|
+
requiredSemanticTargets.set(`sourceSemantics.suspension.axles.${axle}.damping.${field}`,
|
|
590
|
+
expectedEvidence(row.damping?.[field], row.source));
|
|
591
|
+
}
|
|
592
|
+
if (Object.keys(row.derived ?? {}).length > 0) {
|
|
593
|
+
requiredSemanticTargets.set(`sourceSemantics.suspension.axles.${axle}.derived`,
|
|
594
|
+
{ disposition: 'derived', source: row.source });
|
|
595
|
+
}
|
|
596
|
+
} else if (explicitUnsupported(row)) {
|
|
597
|
+
requiredSemanticTargets.set(`sourceSemantics.suspension.axles.${axle}`,
|
|
598
|
+
{ disposition: 'unsupported', source: 'selected JBeam part custody' });
|
|
599
|
+
}
|
|
600
|
+
if (record(suspension?.antiRoll?.[axle]) && nonempty(suspension.antiRoll[axle].partName)) {
|
|
601
|
+
requiredSemanticTargets.set(`sourceSemantics.suspension.antiRoll.${axle}.torsionSpring`,
|
|
602
|
+
expectedEvidence(suspension.antiRoll[axle].torsionSpring, suspension.antiRoll[axle].source));
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
const provenanceByTarget = new Map();
|
|
606
|
+
if (Array.isArray(lowering?.provenance)) {
|
|
607
|
+
for (const row of lowering.provenance) {
|
|
608
|
+
if (record(row) && typeof row.target === 'string') {
|
|
609
|
+
const rows = provenanceByTarget.get(row.target) ?? [];
|
|
610
|
+
rows.push(row);
|
|
611
|
+
provenanceByTarget.set(row.target, rows);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
const validSemanticTargets = [...requiredSemanticTargets].every(([target, expected]) => {
|
|
616
|
+
const rows = provenanceByTarget.get(target);
|
|
617
|
+
return rows?.length === 1 && rows[0].disposition === expected.disposition
|
|
618
|
+
&& rows[0].source === expected.source;
|
|
619
|
+
});
|
|
620
|
+
const axleRows = [suspension?.axles?.front, suspension?.axles?.rear];
|
|
621
|
+
const expectedSuspensionCounts = {
|
|
622
|
+
selectedLeafParts: axleRows.reduce((count, row) => count + (Array.isArray(row?.candidates)
|
|
623
|
+
? row.candidates.length : record(row) && nonempty(row.partName) ? 1 : 0), 0),
|
|
624
|
+
excludedStructuralParts: Array.isArray(suspension?.excludedStructuralParts)
|
|
625
|
+
? suspension.excludedStructuralParts.length : -1,
|
|
626
|
+
resolvedAxles: axleRows.filter((row) => row?.status === 'resolved').length,
|
|
627
|
+
sourceRows: axleRows.reduce((count, row) => count + (record(row?.rowCounts)
|
|
628
|
+
? Object.values(row.rowCounts).reduce((sum, value) => sum + (Number.isInteger(value) ? value : 0), 0) : 0), 0),
|
|
629
|
+
};
|
|
630
|
+
const validSemantics = record(semantics)
|
|
631
|
+
&& record(semantics.mass) && Number.isInteger(semantics.mass.uniqueNodes)
|
|
632
|
+
&& Number.isFinite(semantics.mass.selectedNodeMassKg)
|
|
633
|
+
&& Array.isArray(semantics.seats)
|
|
634
|
+
&& Array.isArray(semantics.cameras)
|
|
635
|
+
&& record(semantics.brakes) && record(semantics.brakes.axles)
|
|
636
|
+
&& record(suspension)
|
|
637
|
+
&& record(suspension.axles) && suspensionAxle(suspension.axles.front) && suspensionAxle(suspension.axles.rear)
|
|
638
|
+
&& record(suspension.antiRoll) && antiRollAxle(suspension.antiRoll.front) && antiRollAxle(suspension.antiRoll.rear)
|
|
639
|
+
&& Array.isArray(suspension.excludedStructuralParts)
|
|
640
|
+
&& Array.isArray(suspension.runtimeBlockers) && suspension.runtimeBlockers.length > 0
|
|
641
|
+
&& typeof suspension.extractionComplete === 'boolean'
|
|
642
|
+
&& suspension.extractionComplete === sourceExtractionComplete
|
|
643
|
+
&& record(suspensionCounts)
|
|
644
|
+
&& requiredSuspensionCounts.every((name) => Number.isInteger(suspensionCounts[name])
|
|
645
|
+
&& suspensionCounts[name] >= 0 && suspensionCounts[name] === expectedSuspensionCounts[name])
|
|
646
|
+
&& record(semantics.wheelsTyres) && Array.isArray(semantics.wheelsTyres.selectedParts)
|
|
647
|
+
&& Array.isArray(semantics.wheelsTyres.literalRadii)
|
|
648
|
+
&& Array.isArray(semantics.electrics)
|
|
649
|
+
&& Array.isArray(semantics.audioEvents)
|
|
650
|
+
&& Array.isArray(semantics.openableControllers);
|
|
651
|
+
const validCoverage = record(coverage)
|
|
652
|
+
&& record(coverage.functionalDomains)
|
|
653
|
+
&& coverage.functionalDomains.suspension === false
|
|
654
|
+
&& record(suspensionProvenance)
|
|
655
|
+
&& requiredProvenanceCounts.every((name) => Number.isInteger(suspensionProvenance[name])
|
|
656
|
+
&& suspensionProvenance[name] >= 0
|
|
657
|
+
&& suspensionProvenance[name] === countedSuspensionProvenance[name]);
|
|
658
|
+
const validCapabilities = Array.isArray(lowering?.capabilities) && lowering.capabilities.length > 0
|
|
659
|
+
&& lowering.capabilities.every((row) => record(row) && typeof row.capability === 'string'
|
|
660
|
+
&& typeof row.disposition === 'string' && Array.isArray(row.source) && typeof row.note === 'string');
|
|
661
|
+
const validProvenance = Array.isArray(lowering?.provenance) && lowering.provenance.length > 0
|
|
662
|
+
&& lowering.provenance.every((row) => record(row) && typeof row.target === 'string'
|
|
663
|
+
&& typeof row.source === 'string'
|
|
664
|
+
&& ['source-literal', 'derived', 'defaulted', 'unsupported', 'out-of-scope',
|
|
665
|
+
'missing-source', 'discovered-not-lowered'].includes(row.disposition)
|
|
666
|
+
&& typeof row.detail === 'string');
|
|
667
|
+
const invalid = [
|
|
668
|
+
[lowering?.schemaVersion === 'helix.beamng-functional-lowering/2', 'schema'],
|
|
669
|
+
[validSemantics, 'sourceSemantics'], [validCapabilities, 'capabilities'],
|
|
670
|
+
[validProvenance, 'provenance'], [validSemanticTargets, 'semanticProvenance'],
|
|
671
|
+
[validCoverage, 'coverage'],
|
|
672
|
+
].filter(([valid]) => !valid).map(([, name]) => name);
|
|
673
|
+
if (invalid.length > 0) {
|
|
674
|
+
throw new Error(`${boundary} has an incompatible schema or incomplete helix.beamng-functional-lowering/2 diagnostics: ${invalid.join(', ')}`);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
506
678
|
function lowerFunctionalVehicle({ source, candidate, sourceManifest, conversion, outputDir, commonRoot,
|
|
507
679
|
selectedPartIndexArtifact }) {
|
|
508
680
|
const configurationFile = conversion.importQuality?.configuration?.file;
|
|
@@ -532,8 +704,8 @@ function lowerFunctionalVehicle({ source, candidate, sourceManifest, conversion,
|
|
|
532
704
|
rmSync(sourcePath, { force: true });
|
|
533
705
|
rmSync(geometryPath, { force: true });
|
|
534
706
|
}
|
|
535
|
-
|
|
536
|
-
|
|
707
|
+
assertValidFunctionalLowering(bundle, 'functional BeamNG lowerer');
|
|
708
|
+
if (bundle?.vehiclePackage?.schemaVersion !== 'helix.vehicle-package/2'
|
|
537
709
|
|| bundle?.addOnPackages?.schemaVersion !== 'helix.vehicle-addon-packages/1'
|
|
538
710
|
|| bundle?.build?.schemaVersion !== 'helix.vehicle-build-source/1'
|
|
539
711
|
|| bundle?.identity?.candidateId !== candidate.id
|
|
@@ -552,8 +724,8 @@ function packageVehicleSource(request) {
|
|
|
552
724
|
}
|
|
553
725
|
const functional = input?.metadata?.functionalVehicle;
|
|
554
726
|
const candidateId = portableCandidateId(manifest?.vehicle?.candidateId);
|
|
555
|
-
|
|
556
|
-
|
|
727
|
+
assertValidFunctionalLowering(functional, 'BeamNG functional package metadata');
|
|
728
|
+
if (functional.vehiclePackage?.schemaVersion !== 'helix.vehicle-package/2'
|
|
557
729
|
|| functional.addOnPackages?.schemaVersion !== 'helix.vehicle-addon-packages/1'
|
|
558
730
|
|| functional.build?.schemaVersion !== 'helix.vehicle-build-source/1'
|
|
559
731
|
|| functional.identity?.candidateId !== manifest.vehicle.candidateId
|
|
@@ -606,6 +778,7 @@ function packageVehicleSource(request) {
|
|
|
606
778
|
vehicle: candidateId } },
|
|
607
779
|
];
|
|
608
780
|
for (const [suffix, value, role] of [
|
|
781
|
+
['vehicle-lowering', functional, 'vehicle-functional-lowering'],
|
|
609
782
|
['vehicle-package', functional.vehiclePackage, 'vehicle-package'],
|
|
610
783
|
['vehicle-addons', functional.addOnPackages, 'vehicle-add-on-packages'],
|
|
611
784
|
['vehicle-build', functional.build, 'vehicle-build-source'],
|
|
@@ -664,13 +837,15 @@ async function main() {
|
|
|
664
837
|
}
|
|
665
838
|
const indexIdentity = { bridgePlanKey: request.deterministicKey, sourceSha256: request.source.sha256,
|
|
666
839
|
commonClosureSha256: commonDependency?.closureSha256 ?? null };
|
|
667
|
-
conversion = await convert(source, selected, output, palette, commonDependency, false,
|
|
840
|
+
conversion = await convert(source, selected, output, palette.path, commonDependency, false,
|
|
668
841
|
selectedPartIndexPath, indexIdentity);
|
|
842
|
+
stampConversionMaterials(output, conversion, palette);
|
|
669
843
|
stampBridgeProvenance(output, source, selected);
|
|
670
844
|
qa = auditVehicleGlb(output, { sourceRoot: selected.root, hasAudio: selected.hasAudio });
|
|
671
845
|
if (qa.findings.some((row) => row.code === 'frame.forward-semantics' && row.status === 'error') && selected.glbs.length === 0) {
|
|
672
|
-
conversion = await convert(source, selected, output, palette, commonDependency, true,
|
|
846
|
+
conversion = await convert(source, selected, output, palette.path, commonDependency, true,
|
|
673
847
|
selectedPartIndexPath, indexIdentity);
|
|
848
|
+
stampConversionMaterials(output, conversion, palette);
|
|
674
849
|
stampBridgeProvenance(output, source, selected);
|
|
675
850
|
qa = auditVehicleGlb(output, { sourceRoot: selected.root, hasAudio: selected.hasAudio });
|
|
676
851
|
}
|