From d2a4cb4e645979f20c22a5c46c1c767db460f54a Mon Sep 17 00:00:00 2001 From: "Somhairle H. Marisol" Date: Mon, 21 Sep 2026 15:16:23 +0800 Subject: feat(ship): HNX-01 crewed starship design branch (7-step ship.py, --design ship, 409 replays completed run history) --- src/SomhairlesDream.Cli/Arguments.fs | 32 +++++++--- src/SomhairlesDream.Cli/Program.fs | 2 +- src/SomhairlesDream.Frontend/App.fs | 91 ++++++++++++++------------- src/SomhairlesDream.Modeling/BlenderBridge.fs | 5 +- src/SomhairlesDream.Modeling/Definitions.fs | 86 ++++++++++++++++++++++++- 5 files changed, 161 insertions(+), 55 deletions(-) (limited to 'src') diff --git a/src/SomhairlesDream.Cli/Arguments.fs b/src/SomhairlesDream.Cli/Arguments.fs index fa9087b..c1c7bff 100644 --- a/src/SomhairlesDream.Cli/Arguments.fs +++ b/src/SomhairlesDream.Cli/Arguments.fs @@ -21,7 +21,7 @@ module Arguments = let usage = "usage: dotnet run --project src/SomhairlesDream.Cli -- run [options]\n" + " dotnet run --project src/SomhairlesDream.Cli -- verify \n" - + "options: --project-id --run-id --base-id --target-id --output-root --stage-root --python --render --design heritage|probe" + + "options: --project-id --run-id --base-id --target-id --output-root --stage-root --python --render --design heritage|probe|ship" let private defaultPython () = let configured = Environment.GetEnvironmentVariable("SOMHAIRLES_BPYTHON") @@ -39,6 +39,12 @@ module Arguments = BaseId = "probe-base-000" TargetId = "probe-target-007" } + let private shipIds = + { ProjectId = "ship-001" + RunId = "run-001" + BaseId = "ship-base-000" + TargetId = "ship-target-007" } + let private defaults () = { Ids = heritageIds OutputRoot = ".artifacts" @@ -66,21 +72,27 @@ module Arguments = | _ -> 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 + if options.Design = "heritage" then + options + else + let designIds = + match options.Design with + | "probe" -> probeIds + | "ship" -> shipIds + | _ -> heritageIds + + let swap heritage design value = if value = heritage then design 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 } + { ProjectId = swap heritageIds.ProjectId designIds.ProjectId options.Ids.ProjectId + RunId = swap heritageIds.RunId designIds.RunId options.Ids.RunId + BaseId = swap heritageIds.BaseId designIds.BaseId options.Ids.BaseId + TargetId = swap heritageIds.TargetId designIds.TargetId options.Ids.TargetId } { options with Ids = ids } - else - options let private validateRunOptions (options: RunOptions) = - if options.Design <> "heritage" && options.Design <> "probe" then + if options.Design <> "heritage" && options.Design <> "probe" && options.Design <> "ship" then Error $"invalid design: {options.Design}" else let resolved = resolveIds options diff --git a/src/SomhairlesDream.Cli/Program.fs b/src/SomhairlesDream.Cli/Program.fs index dd58ff0..7f33e1c 100644 --- a/src/SomhairlesDream.Cli/Program.fs +++ b/src/SomhairlesDream.Cli/Program.fs @@ -14,7 +14,7 @@ module Program = let pipelineOptions : PipelineOptions = { ArtifactRoot = options.OutputRoot Ids = options.Ids - Steps = if options.Design = "probe" then Design.probeSteps else Design.steps + Steps = Design.forProject options.Ids.ProjectId Render = options.Render Bridge = bridge Clock = fun () -> DateTimeOffset.UtcNow diff --git a/src/SomhairlesDream.Frontend/App.fs b/src/SomhairlesDream.Frontend/App.fs index e6edd51..0e6bcd4 100644 --- a/src/SomhairlesDream.Frontend/App.fs +++ b/src/SomhairlesDream.Frontend/App.fs @@ -707,48 +707,7 @@ let selectionForLiveStart (selection: RunSelection) = else selection -let startRun (selection: RunSelection) = - let requestBody = - createObj - [ "projectId" ==> selection.ProjectId - "runId" ==> selection.RunId - "baseId" ==> selection.BaseId - "targetId" ==> selection.TargetId - "render" ==> selection.Render ] - - let requestOptions = - createObj - [ "method" ==> "POST" - "headers" ==> createObj [ "Content-Type" ==> "application/json" ] - "body" ==> JS.JSON.stringify requestBody ] - - setText "run-message" "正在启动设计运行" - updateStreamState "实时链路 · 等待服务端确认" - - async { - try - let! response = fetch ("/api/runs/start", requestOptions) |> Async.AwaitPromise - - if response.ok then - let! value = response.json() |> Async.AwaitPromise - let payload = value :?> SnapshotPayload - updateState payload - connectToServer selection - updateStreamState "实时链路 · SSE 已连接" - else - setText "run-message" (sprintf "启动失败 · HTTP %d" response.status) - updateStreamState "实时链路 · 启动失败" - with _ -> - setText "run-message" "启动失败 · 无法连接服务端" - updateStreamState "实时链路 · 连接失败" - } - |> Async.StartImmediate - -/// Boot-time historical load: surface the pre-published probe build replay so -/// first-time visitors see the real vehicle instead of an empty placeholder. -/// Any failure leaves the honest placeholder idle state untouched. -let loadBootHistory () = - let selection = selectedRun () +let loadHistoryFor (selection: RunSelection) = let projectId = JS.encodeURIComponent selection.ProjectId let runId = JS.encodeURIComponent selection.RunId let url = sprintf "/api/artifacts/manifest?projectId=%s&runId=%s" projectId runId @@ -793,6 +752,54 @@ let loadBootHistory () = () } |> Async.StartImmediate +let startRun (selection: RunSelection) = + let requestBody = + createObj + [ "projectId" ==> selection.ProjectId + "runId" ==> selection.RunId + "baseId" ==> selection.BaseId + "targetId" ==> selection.TargetId + "render" ==> selection.Render ] + + let requestOptions = + createObj + [ "method" ==> "POST" + "headers" ==> createObj [ "Content-Type" ==> "application/json" ] + "body" ==> JS.JSON.stringify requestBody ] + + setText "run-message" "正在启动设计运行" + updateStreamState "实时链路 · 等待服务端确认" + + async { + try + let! response = fetch ("/api/runs/start", requestOptions) |> Async.AwaitPromise + + if response.ok then + let! value = response.json() |> Async.AwaitPromise + let payload = value :?> SnapshotPayload + updateState payload + connectToServer selection + updateStreamState "实时链路 · SSE 已连接" + elif response.status = 409 then + // The run id already exists on the server. If it completed + // earlier, replay its published history instead of treating + // the submit as a failure. + setText "run-message" "该运行已完成 · 载入历史回放" + loadHistoryFor selection + else + setText "run-message" (sprintf "启动失败 · HTTP %d" response.status) + updateStreamState "实时链路 · 启动失败" + with _ -> + setText "run-message" "启动失败 · 无法连接服务端" + updateStreamState "实时链路 · 连接失败" + } + |> Async.StartImmediate + +/// Boot-time historical load: surface the pre-published probe build replay so +/// first-time visitors see the real vehicle instead of an empty placeholder. +/// Any failure leaves the honest placeholder idle state untouched. +let loadBootHistory () = + loadHistoryFor (selectedRun ()) let boot () = showFrameMetadata frames[0] diff --git a/src/SomhairlesDream.Modeling/BlenderBridge.fs b/src/SomhairlesDream.Modeling/BlenderBridge.fs index c4215ae..0f605b0 100644 --- a/src/SomhairlesDream.Modeling/BlenderBridge.fs +++ b/src/SomhairlesDream.Modeling/BlenderBridge.fs @@ -102,7 +102,10 @@ type BlenderProcessBridge(python: string, stageRoot: string, ?timeout: TimeSpan) File.WriteAllText(path, content) member private _.ScriptPath(stepId: string) = - let scriptName = if stepId.StartsWith("probe-") then "probe.py" else "fixture.py" + let scriptName = + if stepId.StartsWith("probe-") then "probe.py" + elif stepId.StartsWith("ship-") then "ship.py" + else "fixture.py" Path.GetFullPath(Path.Combine(stageRoot, "blender", scriptName)) diff --git a/src/SomhairlesDream.Modeling/Definitions.fs b/src/SomhairlesDream.Modeling/Definitions.fs index 9a9256e..e1d2739 100644 --- a/src/SomhairlesDream.Modeling/Definitions.fs +++ b/src/SomhairlesDream.Modeling/Definitions.fs @@ -92,5 +92,89 @@ module Design = ObjectIds = prior (prior (prior (prior (prior (prior bus booms) rtgs) hga) instruments) mast) [||] Changes = liveryChanges } |] + // HNX-01 crewed interstellar starship (Concept A "Keel"): fusion-torch + // study with a spin-gravity habitat torus, water-shadow radiation + // shield and an axial keel separating crew from the reactor section. + // Display-scale visual research; no mass/thrust/shielding closure is + // claimed — the ship.py module header lists the deliberately open + // engineering problems. + let shipSteps : DesignStep array = + let keel = + [| "starship.keel.spine"; "starship.nose.cone"; "starship.dock.ring" |] + let hab = + [| "starship.hab.torus"; "starship.hub.core" + "starship.spoke.00"; "starship.spoke.01"; "starship.spoke.02" + "starship.spoke.03"; "starship.spoke.04"; "starship.spoke.05" |] + let radiation = + [| "starship.shadow.collar"; "starship.storm.cellar" |] + let reactor = + [| "starship.core.pressure"; "starship.core.shield.plug" + "starship.nozzle.magnetic" + "starship.radiator.00"; "starship.radiator.01"; "starship.radiator.02" + "starship.radiator.03"; "starship.radiator.04"; "starship.radiator.05" |] + let power = + [| "starship.power.truss"; "starship.capacitor.ring" + "starship.comm.dish"; "starship.comm.feed" |] + let cabin = + [| "starship.window.00"; "starship.window.01"; "starship.window.02" + "starship.window.03" + "starship.airlock.ring"; "starship.airlock.tunnel" + "starship.cargo.pod.00"; "starship.cargo.pod.01" |] + let prior (prefix: string array) (current: string array) = + Array.append prefix current + + let keelChanges = + [| { ObjectId = "starship.keel.spine"; Kind = "add"; Summary = "Add axial keel spine from bow to nozzle bay" } + { ObjectId = "starship.nose.cone"; Kind = "add"; Summary = "Add forward nose cone" } + { ObjectId = "starship.dock.ring"; Kind = "add"; Summary = "Add forward docking ring" } |] + let habChanges = + Array.append + [| { ObjectId = "starship.hab.torus"; Kind = "add"; Summary = "Add rotating spin-gravity habitat torus" } + { ObjectId = "starship.hub.core"; Kind = "add"; Summary = "Add zero-g hub with bearing core" } |] + (Array.init 6 (fun index -> + { ObjectId = sprintf "starship.spoke.%02d" index; Kind = "add"; Summary = "Add pressurized transfer spoke" })) + let radiationChanges = + [| { ObjectId = "starship.shadow.collar"; Kind = "add"; Summary = "Add water-shadow radiation collar" } + { ObjectId = "starship.storm.cellar"; Kind = "add"; Summary = "Add storm cellar shelter volume" } |] + let reactorChanges = + Array.append + [| { ObjectId = "starship.core.pressure"; Kind = "add"; Summary = "Add fusion core pressure vessel" } + { ObjectId = "starship.core.shield.plug"; Kind = "add"; Summary = "Add reactor shield plug" } + { ObjectId = "starship.nozzle.magnetic"; Kind = "add"; Summary = "Add magnetic exhaust nozzle" } |] + (Array.init 6 (fun index -> + { ObjectId = sprintf "starship.radiator.%02d" index; Kind = "add"; Summary = "Add waste-heat radiator panel" })) + let powerChanges = + [| { ObjectId = "starship.power.truss"; Kind = "add"; Summary = "Add power conditioning truss" } + { ObjectId = "starship.capacitor.ring"; Kind = "add"; Summary = "Add pulsed-power capacitor hoop" } + { ObjectId = "starship.comm.dish"; Kind = "add"; Summary = "Add high-gain communication dish" } + { ObjectId = "starship.comm.feed"; Kind = "add"; Summary = "Add communication feed horn" } |] + let cabinChanges = + Array.append + (Array.init 4 (fun index -> + { ObjectId = sprintf "starship.window.%02d" index; Kind = "add"; Summary = "Add habitat observation window" })) + [| { ObjectId = "starship.airlock.ring"; Kind = "add"; Summary = "Add forward airlock ring" } + { ObjectId = "starship.airlock.tunnel"; Kind = "add"; Summary = "Add pressurized airlock tunnel" } + { ObjectId = "starship.cargo.pod.00"; Kind = "add"; Summary = "Add port cargo pod" } + { ObjectId = "starship.cargo.pod.01"; Kind = "add"; Summary = "Add starboard cargo pod" } |] + let liveryChanges = + [| { ObjectId = "starship.keel.spine"; Kind = "update"; Summary = "Livery pass: bone-white hull structure" } + { ObjectId = "starship.hab.torus"; Kind = "update"; Summary = "Livery pass: habitat ring bone finish" } + { ObjectId = "starship.shadow.collar"; Kind = "update"; Summary = "Livery pass: ceramic shield darkening" } + { ObjectId = "starship.storm.cellar"; Kind = "update"; Summary = "Livery pass: ceramic cellar finish" } + { ObjectId = "starship.dock.ring"; Kind = "update"; Summary = "Livery pass: amber accent on dock ring" } + { ObjectId = "starship.capacitor.ring"; Kind = "update"; Summary = "Livery pass: amber capacitor hoop" } + { ObjectId = "starship.window.00"; Kind = "update"; Summary = "Livery pass: glass glazing" } + { ObjectId = "starship.nozzle.magnetic"; Kind = "update"; Summary = "Livery pass: nozzle coil graphite" } |] + + [| { StepId = "ship-01-keel"; Index = 0; ObjectIds = keel; Changes = keelChanges } + { StepId = "ship-02-hab"; Index = 1; ObjectIds = Array.append keel hab; Changes = habChanges } + { StepId = "ship-03-radiation"; Index = 2; ObjectIds = Array.append (Array.append keel hab) radiation; Changes = radiationChanges } + { StepId = "ship-04-reactor"; Index = 3; ObjectIds = Array.append (Array.append (Array.append keel hab) radiation) reactor; Changes = reactorChanges } + { StepId = "ship-05-power"; Index = 4; ObjectIds = Array.append (Array.append (Array.append (Array.append keel hab) radiation) reactor) power; Changes = powerChanges } + { StepId = "ship-06-cabin"; Index = 5; ObjectIds = Array.append (Array.append (Array.append (Array.append (Array.append keel hab) radiation) reactor) power) cabin; Changes = cabinChanges } + { StepId = "ship-07-livery"; Index = 6; ObjectIds = Array.append (Array.append (Array.append (Array.append (Array.append (Array.append keel hab) radiation) reactor) power) cabin) [||]; Changes = liveryChanges } |] + let forProject (projectId: string) : DesignStep array = - if projectId.StartsWith("probe-") then probeSteps else steps + if projectId.StartsWith("probe-") then probeSteps + elif projectId.StartsWith("ship-") then shipSteps + else steps -- cgit v1.2.3