Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions docs/decisions.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,3 +224,64 @@ in the roadmap owns that PR. Until it lands, the Wave 2 JSON importer
falls back to structural sniff.

When this ADR is accepted, restate it as "Accepted" with the date.

## ADR-0007: Studio-local HTTP API rather than shared library or cloud service

**Status:** Accepted, 2026-05-14.

**Context.** Both Unity and Studio need to turn an OpenApparatus JSON
environment into a glTF binary. The Studio repo already has a
`GltfExporter`; Unity does not. Three delivery options:

1. **Shared library.** Multi-target `OpenApparatus.IO` to
netstandard2.1, ship the DLL with the Unity package, both consumers
link against the same code in-process.
2. **Cloud HTTP API.** Host the converter on a public endpoint; Unity
and Studio both POST JSON, download GLB.
3. **Studio-local HTTP API.** Studio embeds a tiny `HttpListener` bound
to `127.0.0.1`. Unity discovers the port via a JSON file Studio
writes at startup, then makes ordinary HTTP calls. Same machine,
loopback only.

**Decision.** Option 3 — Studio-local HTTP API. Contract documented in
[studio-api-contract.md](studio-api-contract.md).

**Why.** Research workstations are single-user, often air-gapped or on
flaky lab WiFi, frequently subject to IRB / DSGVO constraints that
prohibit uploading participant data to third-party services. The
natural workflow (export from Studio, import into Unity) implies
Studio is already running when Unity needs to call the converter, so
"Studio must be running" isn't a new requirement. Loopback transport
eliminates the cloud option's hosting cost, latency, auth, and privacy
costs without inheriting the shared-library option's multi-targeting
and DLL distribution burden.

**Why not option 1 (shared library).** Forces `OpenApparatus.IO` to
multi-target net8 and netstandard2.1 and audit every dependency for
netstandard2.1 compatibility. Tightly couples Unity package versions
to Core versions. Solves a real problem (in-process speed) but at a
high coordination cost across three repos.

**Why not option 2 (cloud).** Requires hosting infrastructure, auth,
rate limiting, monitoring; introduces a network dependency for a
fundamentally offline workflow; raises ethics-review questions about
participant data leaving the workstation. A cloud service can be
layered on top of the Studio-local API later if batch processing or
remote collaboration becomes a real need — both share the same
endpoint contract, only the transport address differs.

**Consequences.**
- Studio must implement a small HTTP server. Trivial via `HttpListener`
(no external dependencies).
- Unity must implement a discovery-file reader and HTTP client. Also
trivial via BCL `HttpClient`.
- When Studio isn't running, Unity falls back to a "please launch
Studio" prompt. Less elegant than in-process but matches the
workflow.
- The contract is the single source of truth across both repos.
Breaking changes require coordinated `/v1/` → `/v2/` migration.
- A hosted cloud variant of the same API is a future option, not a
blocker. The endpoint contract is identical; only the transport
address differs. Adding it would slot in as a `RemoteGltfConverter`
alongside today's `StudioGltfConverter` without breaking either
consumer.
79 changes: 66 additions & 13 deletions docs/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,22 @@ fully-componented environment into a scene.

## Wave structure

Three waves. Wave 1 is the foundation every importer depends on; Wave
2 runs as five parallel agents off the same base; Wave 3 follows once
the Wave 2 patterns settle.
Four waves. Wave 1 is the foundation every importer depends on; Wave
2 runs as parallel agents off the same base; Wave 3 follows once the
Wave 2 patterns settle. Wave 4 is the Studio-local API, additive and
optional — useful but not required for the import pipeline.

```
Wave 1 (foundation, parallel) Wave 2 (parallel) Wave 3
───────────────────────────── ──────────────── ──────

F1 Core DLL staging ──┐ A JSON importer ──┐
│ B glTF postproc ──┤
F2 Shared infra ──┼──▶ main ──▶ D Samples ─┼──▶ main ──▶ C .oapp importer
│ E Tests skeleton ─┤
F3 Render pipelines ──┘ G Studio JSON v4 ──┘ polish + integration tests
Wave 1 (foundation) Wave 2 (parallel) Wave 3 Wave 4 (cross-repo)
────────────────── ───────────────── ────── ───────────────────

F1 Core DLL ──┐ A JSON importer ──┐
│ B glTF postproc ──┤
F2 Shared ─┼─▶ main ─▶ D Samples ──┤ K Studio API server ─┐
│ E Tests ─┼─▶ main ─▶ C .oapp ─┤ ├─▶ main
F3 Pipelines ─┘ G Studio JSON v4 ──┤ H polish L Unity API client ─┘
I Colliders ──┤
J Prefab swap ──┘
```

Wave 1 tasks are independent of each other and can run in parallel
Expand All @@ -29,6 +32,23 @@ Wave 2 tasks are independent of each other and merge into `main` in
any order. Task C (`.oapp` importer) is deferred to Wave 3 so it can
mirror task A's settled patterns rather than diverge.

Wave 4 tasks (K and L) live in different repos (`openapparatus-studio`
and `openapparatus-unity`). Both depend only on
[studio-api-contract.md](studio-api-contract.md) being merged; they
can run as parallel agents in their respective repos.

Wave 4 file ownership:

- K → `openapparatus-studio/src/OpenApparatus.Studio/Api/*.cs`,
`openapparatus-studio/src/OpenApparatus.Studio/Settings/ApiSettings.cs`
- L → `Editor/Api/StudioApi.cs`, `Editor/Api/StudioDiscovery.cs`,
adds an "Export GLB via Studio" button to the existing
`Editor/Inspectors/MultiRoomEnvironmentAssetEditor.cs`

Cross-repo touch-point: [studio-api-contract.md](studio-api-contract.md)
in this repo, mirrored by reference from both implementers. Change
there first; propagate.

## Status

Update the box to `x` when the PR for a task is merged.
Expand All @@ -47,6 +67,8 @@ Update the box to `x` when the PR for a task is merged.
| J — Prefab substitution | 2 | | | [ ] |
| C — .oapp project importer | 3 | | | [ ] |
| H — Integration tests + polish | 3 | | | [ ] |
| K — Studio API server | 4 | | | [ ] |
| L — Unity Studio-API client | 4 | | | [ ] |

## Parallelisation guidance

Expand Down Expand Up @@ -96,6 +118,17 @@ Task C depends on task A's settled `JsonEnvironmentImporter` patterns.
Run sequentially. Task H runs after C and exercises every importer
against a real Studio export.

### Wave 4 — Studio-local API (cross-repo)

Tasks K (Studio server) and L (Unity client) implement the
[studio-api-contract.md](studio-api-contract.md) on either side of the
loopback transport. Both depend only on the contract being merged —
they can run in parallel agents in separate repos. End-to-end
verification needs both halves; the unit-level work doesn't.

The motivation and rejected alternatives (shared library, cloud API)
are in [decisions.md § ADR-0007](decisions.md#adr-0007-studio-local-http-api-rather-than-shared-library-or-cloud-service).

## Definition of done per wave

**Wave 1 done when:**
Expand Down Expand Up @@ -137,6 +170,23 @@ against a real Studio export.
- Integration test runs every importer against fixtures generated by
Studio CI (downloaded artifact, not committed to repo).

**Wave 4 done when:**

- Studio writes a valid discovery file on launch matching
[studio-api-contract.md](studio-api-contract.md) and deletes it on
graceful shutdown.
- `GET /v1/health` on the bound port returns the documented schema.
- `POST /v1/convert` with a valid JSON body returns a parseable
`.glb`; error responses match the documented codes (400 / 422 / 500).
- Unity's `MultiRoomEnvironmentAsset` inspector exposes an
**Export GLB via Studio** button.
- Clicking the button with Studio running produces a sibling `.glb`
that the existing glTF postprocessor picks up.
- Clicking the button with Studio not running surfaces a clear
user-facing dialog and no console exception.
- Stale discovery file is detected (PID liveness + health probe) and
deleted automatically.

## Estimated effort

Per task. Ballpark only; this is one developer's working-day estimate
Expand All @@ -156,13 +206,16 @@ based on the brief, not a contractual commitment.
| I | 0.5 day |
| J | 1 day |
| H | 1 day |
| K | 1 day (cross-repo, in openapparatus-studio) |
| L | 1 day |

Wave 1 wall-clock: ~1.5 days (with three parallel agents on the longest
task). Wave 2 wall-clock: ~2 days (A is still the critical path; I and
J are shorter and fit within it). Wave 3 wall-clock: ~3 days sequential.

Total wall-clock with full parallelisation: ~6–7 days. Total
agent-days of work: ~13.5.
Total wall-clock with full parallelisation: ~6–7 days through Wave 3.
Wave 4 adds ~1 day wall-clock (two parallel agents in two repos) and
~2 agent-days. Total agent-days of work: ~15.5.

## Cross-cutting concerns

Expand Down
Loading
Loading