summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/SomhairlesDream.Cli/Arguments.fs57
-rw-r--r--src/SomhairlesDream.Cli/Program.fs1
-rw-r--r--src/SomhairlesDream.Modeling/BlenderBridge.fs13
-rw-r--r--src/SomhairlesDream.Modeling/Definitions.fs65
-rw-r--r--src/SomhairlesDream.Modeling/Pipeline.fs13
-rw-r--r--src/SomhairlesDream.Server/ArtifactRunCoordinator.fs1
6 files changed, 128 insertions, 22 deletions
diff --git a/src/SomhairlesDream.Cli/Arguments.fs b/src/SomhairlesDream.Cli/Arguments.fs
index 2de96be..fa9087b 100644
--- a/src/SomhairlesDream.Cli/Arguments.fs
+++ b/src/SomhairlesDream.Cli/Arguments.fs
@@ -9,7 +9,8 @@ type RunOptions =
OutputRoot: string
StageRoot: string
Python: string
- Render: bool }
+ Render: bool
+ Design: string }
type Command =
| Run of RunOptions
@@ -19,22 +20,32 @@ type Command =
module Arguments =
let usage =
"usage: dotnet run --project src/SomhairlesDream.Cli -- run [options]\n"
- + " dotnet run --project src/SomhairlesDream.Cli -- verify <manifest.json>"
+ + " dotnet run --project src/SomhairlesDream.Cli -- verify <manifest.json>\n"
+ + "options: --project-id --run-id --base-id --target-id --output-root --stage-root --python --render --design heritage|probe"
let private defaultPython () =
let configured = Environment.GetEnvironmentVariable("SOMHAIRLES_BPYTHON")
if String.IsNullOrWhiteSpace(configured) then "python3" else configured
+ let private heritageIds =
+ { ProjectId = "heritage-001"
+ RunId = "run-001"
+ BaseId = "base-000"
+ TargetId = "target-003" }
+
+ let private probeIds =
+ { ProjectId = "probe-001"
+ RunId = "run-001"
+ BaseId = "probe-base-000"
+ TargetId = "probe-target-007" }
+
let private defaults () =
- { Ids =
- { ProjectId = "heritage-001"
- RunId = "run-001"
- BaseId = "base-000"
- TargetId = "target-003" }
+ { Ids = heritageIds
OutputRoot = ".artifacts"
StageRoot = "stage"
Python = defaultPython ()
- Render = false }
+ Render = false
+ Design = "heritage" }
let private requiredValue (args: string array) (index: int) (optionName: string) =
if index + 1 >= args.Length || String.IsNullOrWhiteSpace(args[index + 1]) then
@@ -51,14 +62,34 @@ module Arguments =
| "--output-root" -> Ok { options with OutputRoot = value }
| "--stage-root" -> Ok { options with StageRoot = value }
| "--python" -> Ok { options with Python = value }
+ | "--design" -> Ok { options with Design = value }
| _ -> Error $"unknown option: {optionName}"
+ let private resolveIds (options: RunOptions) =
+ if options.Design = "probe" then
+ let swap heritage probe value = if value = heritage then probe else value
+
+ let ids =
+ { ProjectId = swap heritageIds.ProjectId probeIds.ProjectId options.Ids.ProjectId
+ RunId = swap heritageIds.RunId probeIds.RunId options.Ids.RunId
+ BaseId = swap heritageIds.BaseId probeIds.BaseId options.Ids.BaseId
+ TargetId = swap heritageIds.TargetId probeIds.TargetId options.Ids.TargetId }
+
+ { options with Ids = ids }
+ else
+ options
+
let private validateRunOptions (options: RunOptions) =
- [| options.Ids.ProjectId; options.Ids.RunId; options.Ids.BaseId; options.Ids.TargetId |]
- |> Array.tryFind (Ids.validateResourceId >> Result.isError)
- |> function
- | Some value -> Error $"invalid resource id: {value}"
- | None -> Ok(Run options)
+ if options.Design <> "heritage" && options.Design <> "probe" then
+ Error $"invalid design: {options.Design}"
+ else
+ let resolved = resolveIds options
+
+ [| resolved.Ids.ProjectId; resolved.Ids.RunId; resolved.Ids.BaseId; resolved.Ids.TargetId |]
+ |> Array.tryFind (Ids.validateResourceId >> Result.isError)
+ |> function
+ | Some value -> Error $"invalid resource id: {value}"
+ | None -> Ok(Run resolved)
let private parseRun (args: string array) =
let rec loop (index: int) (options: RunOptions) =
diff --git a/src/SomhairlesDream.Cli/Program.fs b/src/SomhairlesDream.Cli/Program.fs
index 0d9ed1c..dd58ff0 100644
--- a/src/SomhairlesDream.Cli/Program.fs
+++ b/src/SomhairlesDream.Cli/Program.fs
@@ -14,6 +14,7 @@ module Program =
let pipelineOptions : PipelineOptions =
{ ArtifactRoot = options.OutputRoot
Ids = options.Ids
+ Steps = if options.Design = "probe" then Design.probeSteps else Design.steps
Render = options.Render
Bridge = bridge
Clock = fun () -> DateTimeOffset.UtcNow
diff --git a/src/SomhairlesDream.Modeling/BlenderBridge.fs b/src/SomhairlesDream.Modeling/BlenderBridge.fs
index c0795e9..c4215ae 100644
--- a/src/SomhairlesDream.Modeling/BlenderBridge.fs
+++ b/src/SomhairlesDream.Modeling/BlenderBridge.fs
@@ -101,8 +101,13 @@ type BlenderProcessBridge(python: string, stageRoot: string, ?timeout: TimeSpan)
File.WriteAllText(path, content)
- member private _.StartInfo(request: BridgeRequest) =
- let scriptPath = Path.GetFullPath(Path.Combine(stageRoot, "blender", "fixture.py"))
+ member private _.ScriptPath(stepId: string) =
+ let scriptName = if stepId.StartsWith("probe-") then "probe.py" else "fixture.py"
+
+ Path.GetFullPath(Path.Combine(stageRoot, "blender", scriptName))
+
+ member private this.StartInfo(request: BridgeRequest) =
+ let scriptPath = this.ScriptPath(request.Step.StepId)
let info = ProcessStartInfo()
info.FileName <- python
info.UseShellExecute <- false
@@ -128,10 +133,10 @@ type BlenderProcessBridge(python: string, stageRoot: string, ?timeout: TimeSpan)
interface IArtifactBridge with
member this.Export(request, heartbeat) =
try
- let scriptPath = Path.GetFullPath(Path.Combine(stageRoot, "blender", "fixture.py"))
+ let scriptPath = this.ScriptPath(request.Step.StepId)
if not (File.Exists(scriptPath)) then
- Error(bridgeError $"fixture is missing: {scriptPath}")
+ Error(bridgeError $"blender script is missing: {scriptPath}")
else
match Path.GetDirectoryName(request.GlbPath) with
| null -> ()
diff --git a/src/SomhairlesDream.Modeling/Definitions.fs b/src/SomhairlesDream.Modeling/Definitions.fs
index 64e6bc6..9a9256e 100644
--- a/src/SomhairlesDream.Modeling/Definitions.fs
+++ b/src/SomhairlesDream.Modeling/Definitions.fs
@@ -29,3 +29,68 @@ module Design =
Changes =
[| { ObjectId = "heritage.cabin"; Kind = "add"; Summary = "Add cabin volume" }
{ ObjectId = "heritage.crossbeam"; Kind = "add"; Summary = "Add cabin crossbeam" } |] } |]
+
+ // YJ-01 Voyager-class current-tech probe (source: interstellar-dual-track/current-tech-probe
+ // scripts/build_probe.py, line ranges cited per step). Object ids map the legacy
+ // UPPER_SNAKE names onto lowercase dotted ids via documented rule
+ // (probe.bus.bay.panel.00 <- BUS_BAY_PANEL_00, probe.rtg.01.fin.00 <- RTG_01_FIN_0).
+ // Display plinth, information card and MODEL_LABEL are intentionally excluded.
+ let probeSteps : DesignStep array =
+ let bus =
+ [| "probe.bus.electronics.decagon"; "probe.bus.ring.front"; "probe.bus.ring.rear"
+ "probe.bus.bay.panel.00"; "probe.bus.bay.panel.01"; "probe.bus.bay.panel.02"
+ "probe.bus.bay.panel.03"; "probe.bus.bay.panel.04"; "probe.bus.bay.panel.05"
+ "probe.bus.bay.panel.06"; "probe.bus.bay.panel.07"; "probe.bus.bay.panel.08"
+ "probe.bus.bay.panel.09"
+ "probe.bus.mli.top"; "probe.bus.mli.bottom"
+ "probe.bus.radiator.port"; "probe.bus.radiator.starboard"
+ "probe.aacs"
+ "probe.thruster.01"; "probe.thruster.02"; "probe.thruster.03"; "probe.thruster.04" |]
+ let booms =
+ [| "probe.rtg.boom"
+ "probe.mag.boom"; "probe.mag.head"; "probe.mag.sensor.a"; "probe.mag.sensor.b"
+ "probe.pws.antenna.left"; "probe.pws.antenna.right"
+ "probe.pws.tip.left"; "probe.pws.tip.right" |]
+ let rtgs =
+ [| "probe.rtg.01"; "probe.rtg.01.fin.00"; "probe.rtg.01.fin.01"; "probe.rtg.01.fin.02"
+ "probe.rtg.02"; "probe.rtg.02.fin.00"; "probe.rtg.02.fin.01"; "probe.rtg.02.fin.02"
+ "probe.rtg.03"; "probe.rtg.03.fin.00"; "probe.rtg.03.fin.01"; "probe.rtg.03.fin.02" |]
+ let hga =
+ [| "probe.hga.reflector"; "probe.hga.feed"
+ "probe.hga.strut.00"; "probe.hga.strut.01"; "probe.hga.strut.02"; "probe.hga.strut.03" |]
+ let instruments =
+ [| "probe.scan.pivot"; "probe.scan.platform"
+ "probe.crs"; "probe.plasma"; "probe.optical"; "probe.optical.aperture" |]
+ let mast = [| "probe.science.boom" |]
+ let prior (prefix: string array) (current: string array) =
+ Array.append prefix current
+
+ let busChanges = bus |> Array.map (fun id -> { ObjectId = id; Kind = "add"; Summary = "Add ten-sided bus with bay panels, MLI and radiators" })
+ let boomChanges = booms |> Array.map (fun id -> { ObjectId = id; Kind = "add"; Summary = "Add fields-and-particles and RTG support booms" })
+ let rtgChanges = rtgs |> Array.map (fun id -> { ObjectId = id; Kind = "add"; Summary = "Add three RTGs with heat-rejection fins" })
+ let hgaChanges = hga |> Array.map (fun id -> { ObjectId = id; Kind = "add"; Summary = "Add high-gain antenna, feed and struts" })
+ let instrumentChanges = instruments |> Array.map (fun id -> { ObjectId = id; Kind = "add"; Summary = "Add articulated scan platform with science instruments" })
+ let mastChanges = mast |> Array.map (fun id -> { ObjectId = id; Kind = "add"; Summary = "Add deployable science mast" })
+ let liveryChanges =
+ [| { ObjectId = "probe.hga.reflector"; Kind = "update"; Summary = "Livery pass: reflector white finish" }
+ { ObjectId = "probe.bus.mli.top"; Kind = "update"; Summary = "Livery pass: MLI gold foil finish" }
+ { ObjectId = "probe.bus.bay.panel.01"; Kind = "update"; Summary = "Livery pass: restrained red accents on bay panels" }
+ { ObjectId = "probe.pws.tip.left"; Kind = "update"; Summary = "Livery pass: red antenna tip markers" }
+ { ObjectId = "probe.bus.electronics.decagon"; Kind = "update"; Summary = "Livery pass: body blue basecoat" }
+ { ObjectId = "probe.rtg.01"; Kind = "update"; Summary = "Livery pass: graphite RTG housings" } |]
+
+ [| { StepId = "probe-01-bus"; Index = 0; ObjectIds = bus; Changes = busChanges }
+ { StepId = "probe-02-boom"; Index = 1; ObjectIds = prior bus booms; Changes = boomChanges }
+ { StepId = "probe-03-rtg"; Index = 2; ObjectIds = prior (prior bus booms) rtgs; Changes = rtgChanges }
+ { StepId = "probe-04-hga"; Index = 3; ObjectIds = prior (prior (prior bus booms) rtgs) hga; Changes = hgaChanges }
+ { StepId = "probe-05-instruments"; Index = 4
+ ObjectIds = prior (prior (prior (prior bus booms) rtgs) hga) instruments; Changes = instrumentChanges }
+ { StepId = "probe-06-mast"; Index = 5
+ ObjectIds = prior (prior (prior (prior (prior bus booms) rtgs) hga) instruments) mast
+ Changes = mastChanges }
+ { StepId = "probe-07-livery"; Index = 6
+ ObjectIds = prior (prior (prior (prior (prior (prior bus booms) rtgs) hga) instruments) mast) [||]
+ Changes = liveryChanges } |]
+
+ let forProject (projectId: string) : DesignStep array =
+ if projectId.StartsWith("probe-") then probeSteps else steps
diff --git a/src/SomhairlesDream.Modeling/Pipeline.fs b/src/SomhairlesDream.Modeling/Pipeline.fs
index 3f1f105..d9db5f6 100644
--- a/src/SomhairlesDream.Modeling/Pipeline.fs
+++ b/src/SomhairlesDream.Modeling/Pipeline.fs
@@ -33,6 +33,7 @@ type IArtifactBridge =
type PipelineOptions =
{ ArtifactRoot: string
Ids: RunIds
+ Steps: DesignStep array
Render: bool
Bridge: IArtifactBridge
Clock: unit -> DateTimeOffset
@@ -214,10 +215,12 @@ module Pipeline =
let emit event = appendEvent log options.OnEvent event
let ids = options.Ids
- emit (RunEvent.Start { Ids = ids; At = startedAt; TotalSteps = Design.steps.Length })
+ let steps = options.Steps
+
+ emit (RunEvent.Start { Ids = ids; At = startedAt; TotalSteps = steps.Length })
let artifacts =
- [| for step in Design.steps do
+ [| for step in steps do
currentStep <- Some step.StepId
emit (RunEvent.Heartbeat { Ids = ids; At = options.Clock(); Message = $"starting {step.StepId}" })
@@ -275,7 +278,7 @@ module Pipeline =
At = options.Clock()
StepId = step.StepId
StepIndex = step.Index
- TotalSteps = Design.steps.Length
+ TotalSteps = steps.Length
Message = "artifact verified"
ArtifactPath = relativePath }
@@ -424,12 +427,12 @@ module ArtifactVerifier =
|| Ids.validateResourceId manifest.BaseId |> Result.isError
|| Ids.validateResourceId manifest.TargetId |> Result.isError then
Error "manifest contains an invalid resource id"
- elif manifest.Steps.Length <> Design.steps.Length then
+ elif manifest.Steps.Length <> (Design.forProject manifest.ProjectId).Length then
Error "manifest step count does not match design"
else
let mutable failure: string option = None
- for expected, actual in Array.zip Design.steps manifest.Steps do
+ for expected, actual in Array.zip (Design.forProject manifest.ProjectId) manifest.Steps do
if failure.IsNone && actual.StepId <> expected.StepId then
failure <- Some $"manifest step id mismatch: {expected.StepId}"
elif failure.IsNone && actual.StepIndex <> expected.Index then
diff --git a/src/SomhairlesDream.Server/ArtifactRunCoordinator.fs b/src/SomhairlesDream.Server/ArtifactRunCoordinator.fs
index 022ac7d..07ac984 100644
--- a/src/SomhairlesDream.Server/ArtifactRunCoordinator.fs
+++ b/src/SomhairlesDream.Server/ArtifactRunCoordinator.fs
@@ -217,6 +217,7 @@ type ArtifactRunCoordinator(
let options : PipelineOptions =
{ ArtifactRoot = root
Ids = ids
+ Steps = Design.forProject ids.ProjectId
Render = render
Bridge = bridgeFactory ()
Clock = clock