summaryrefslogtreecommitdiff
path: root/README.md
blob: ae59ca2e8dcaa3b3fa1f2077b03a727cf5c9cbd1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# somhairles-dream-fsharp

F# implementation of the heritage boat modeling pipeline: a Blender-driven
artifact pipeline (CLI), an ASP.NET server that runs and observes artifact
runs, and a Fable/Three.js frontend that follows run progress live.

Architecture boundary: F# owns the frontend, backend, and orchestration
(pipeline, artifacts, verification, serving). Python (`bpy`) owns geometry
generation inside `stage/blender/fixture.py`. There is no F# geometry DSL.

## Layout

- `src/SomhairlesDream.Shared` — domain, run state, replay, artifact contracts
- `src/SomhairlesDream.Modeling` — pipeline, Blender process bridge, verifier
- `src/SomhairlesDream.Cli` — `run` and `verify` commands
- `src/SomhairlesDream.Server` — run API, SSE, static frontend host
- `src/SomhairlesDream.Frontend` — Fable browser app compiled to `public/`
- `stage/blender/fixture.py` — deterministic bpy fixture (3 steps, 7 objects)
- `public/` — static frontend served by the server (index, styles, JS bundle)
- `tests/` — xUnit test projects per layer

## Build and test

Requires .NET SDK 8 (pinned by `global.json`). Warnings are errors.

```sh
dotnet build SomhairlesDream.sln
dotnet test SomhairlesDream.sln
```

## Frontend (Fable)

`src/SomhairlesDream.Frontend` compiles to `public/`. Regenerate after edits:

```sh
dotnet fable src/SomhairlesDream.Frontend/SomhairlesDream.Frontend.fsproj \
  --outDir public --noRestore
```

The server project copies `public/**` into its build output
(`CopyToOutputDirectory=PreserveNewest`), so rerun `dotnet build` on the server
after regenerating the bundle.

Three.js r160 and GLTFLoader are vendored under `public/vendor/` (import map
plus a `three-setup.js` shim exposing `window.THREE`/`window.GLTFLoader`).
They are MIT-licensed third-party dependencies, not project assets.

## Server

```sh
dotnet run --project src/SomhairlesDream.Server
```

Environment variables (all optional):

- `SOMHAIRLES_ARTIFACT_ROOT` — artifact output root (default `.artifacts`)
- `SOMHAIRLES_STAGE_ROOT` — stage directory containing `blender/fixture.py`
  (default `stage`)
- `SOMHAIRLES_BPYTHON` — Python interpreter used to run the fixture
  (default `python3`)

Routes used by the frontend: `POST /api/runs/start`,
`GET /api/runs/events?projectId=...&runId=...` (run-scoped SSE),
`GET /api/artifacts/manifest`, `GET /api/artifacts/file`, and
`GET /api/artifacts/steps?projectId=...&runId=...&path=steps/<step>.glb`
(serve a step GLB while a run is still active; `.glb` files under `steps/`
only, traversal-guarded). The server binds to the ASP.NET default (add
`--urls http://127.0.0.1:8099` to match the legacy port).

## CLI

```sh
dotnet run --project src/SomhairlesDream.Cli -- run [options]
dotnet run --project src/SomhairlesDream.Cli -- verify <manifest.json>
```

`run` options: `--project-id`, `--run-id`, `--base-id`, `--target-id`,
`--output-root`, `--stage-root`, `--python`, `--render`. `SOMHAIRLES_BPYTHON`
sets the default interpreter. Exit codes: `0` success, `2` usage, `3` bridge
failure, `4` pipeline/verify failure. On success it prints the manifest path.

A run produces, under `<output-root>/<projectId>/<runId>/`:

- `events.ndjson`, `manifest.json`
- `logs/01-foundation.log`, `02-frame.log`, `03-cabin.log`
- `steps/01-foundation.glb`, `02-frame.glb`, `03-cabin.glb`

`verify` recomputes hashes, checks log presence, path containment, object-ID
proofs, timestamps, and duplicate steps without needing Blender.

## Python interpreter for bpy

The fixture requires a Python that can `import bpy` (Blender as a module,
version >= 3.0.0). A system `python3` usually cannot. This project was
verified with a uv-managed environment at `/tmp/bpyenv/bin/python`
(`bpy 5.0.1`). Note that `/tmp` is ephemeral; recreate or point
`SOMHAIRLES_BPYTHON`/`--python` at any interpreter that provides `bpy`.

Example:

```sh
dotnet run --project src/SomhairlesDream.Cli -- run \
  --output-root .artifacts --python /tmp/bpyenv/bin/python
```

## Fixture limitations

- `stage/blender/fixture.py` is a deterministic placeholder geometry fixture
  (named cubes with `object_id` custom properties), not a production model.
- Bridge timeout is 120 seconds per step; the bridge requires the fixture to
  write its result JSON to `--result-output` (stdout/stderr are diagnostics
  only, retained as step logs).
- Renders are produced only with `--render` (512x512 PNG per step).
- Reimport verification counts objects (`2 -> 5 -> 7` across the three steps)
  and checks the `object_id` custom properties survive the GLB round-trip.

## Verification status

- Full solution test suite and Release build: clean (0 warnings/errors).
- Real CLI pipeline with `/tmp/bpyenv/bin/python`: 3 GLBs + logs + manifest,
  `verify` exit 0.
- Browser end-to-end (playwright + bundled chromium, `--enable-unsafe-swiftshader`),
  server on `:8099` with the real bpy interpreter, run started through the
  page form: pre-run page shows exactly `当前没有下一版正在开发` and status
  `待机`; during the run the SSE snapshot drives the viewport to load
  `steps/01-foundation.glb` (status `运行中`, note `管线几何 · …` — LIVE
  ingestion, not replay); at completion the viewport shows
  `steps/03-cabin.glb`, status `已完成`, manifest link exposed; the served
  `steps/03-cabin.glb` is byte-identical (sha256) to the CLI-produced
  reference run; a failure run (pre-existing run directory) shows status
  `失败` with the error surfaced in `run-message`; zero console errors on
  all pages.
- Frozen-viewport evidence (`?freeze=1` stops rotation): canvas screenshots
  of placeholder vs stage-1 vs stage-3 differ in pixel hashes, and the three
  stages are visually distinct cubes/frames/house. Evidence lives in a
  scratch directory and is not committed.
- NOT verified: production deployment of the old site remains untouched;
  `--render` PNG renders; replay button flow against a historical run.