Files
recordingtest/docs/contracts/camera-restore.evaluation.md
minsung 11eb92b2b2 feat: camera-restore + LauncherUI UX 개선 + player fallback 강화 (#15)
camera-restore:
- IEngineStateProvider.SetCamera 반사 쓰기 (HmegDirectStateProvider)
- POST /camera/restore (BridgeHttpServer, StateRouter)
- Recorder --sidecar-url + camera_snapshot 캡처
- UiaPlayerHost.TryRestoreCamera, PlayerEngine 재생 전 복원
- 149 tests

LauncherUI (#15):
- Sidecar URL 체크박스 + 입력란 (녹화/재생 모두 연동)
- 재생 속도 슬라이더 (0.25x~4.0x, 기본 1.0x)
- 빌드 타임스탬프 타이틀바 표시
- 녹화 완료 후 RecordNameBox 초기화
- UiAnalysisWindow 추가

PlayerEngine (#15):
- CancellationToken 지원 (중단 버튼 동작)
- Focus 스텝 early return (no-op, issue #11)
- Type/Drag unresolvable UIA path fallback
- SpeedMultiplier 옵션

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 18:37:13 +09:00

27 lines
5.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Evaluation — camera-restore (2026-04-10 09:30)
Verdict: **pass**
| # | DoD item | Score | Evidence |
|---|----------|-------|----------|
| D1 | `IEngineStateProvider` has `SetCamera(CameraSnapshot)`. `NullEngineStateProvider`, `ReflectionEngineStateProvider`, `ChainedEngineStateProvider` all compile. | pass | `src/Recordingtest.Bridge.Abstractions/IEngineStateProvider.cs` line 23 (`void SetCamera(CameraSnapshot snapshot)`); `NullEngineStateProvider.SetCamera` line 43 (no-op); `ReflectionEngineStateProvider.SetCamera` in `src/Sut/EgBim/.../IEngineStateProvider.cs` line 84 (no-op); `ChainedEngineStateProvider.SetCamera` line 56 (delegates to primary). All pass `dotnet test` build. |
| D2 | `HmegDirectStateProvider.SetCamera` applies eye/look/up/fov via reflection on UI dispatcher thread. | pass | `src/Hmeg/Recordingtest.Hmeg.Bridge/HmegDirectStateProvider.cs` lines 167201: `SetCamera` computes lookDir from target-eye, calls `WriteVec3`/`WriteDouble` via reflection. Dispatches via `_uiDispatch` when not null (lines 192195). `HmEgBridgePlugin` wires `Application.Current.Dispatcher.Invoke` at line 116. |
| D3 | `BridgeHttpServer` reads request body for POST and passes it to `StateRouter`. | pass | `src/Sut/EgBim/.../BridgeHttpServer.cs` lines 4656: reads `ctx.Request.InputStream` when `HasEntityBody`; passes `requestBody` to `_router.Route(method, path, requestBody)` at line 57. |
| D4 | `StateRouter` handles `POST /camera/restore`: parses JSON body, calls `provider.SetCamera(...)`. Returns `{"ok":true}` on success, `{"error":"..."}` on failure. | partial | `src/Sut/EgBim/.../StateRouter.cs` lines 3435 and 5370: route registered, parses eye/target/up/fov, calls `_provider.SetCamera(...)`, returns `{"ok":true}`. On failure returns `{"ok":false,"error":"..."}` (line 68), not the bare `{"error":"..."}` specified in the contract. Minor extra field present; functionally compatible. |
| D5 | `HmEgHttpSnapshot.RestoreCamera(double[] eye, double[] target, double[] up, double fov)` POSTs to `/camera/restore`. | pass | `src/Hmeg/Recordingtest.Hmeg.Bridge.Client/HmEgHttpSnapshot.cs` lines 94115: method exists, POSTs to `_baseUrl + "/camera/restore"` with JSON body built by `BuildCameraJson`. |
| D6 | Recorder `Scenario` model has `CameraSnapshot?` field (YAML: `camera_snapshot`). Recorder captures GET /camera at recording start via `--sidecar-url` (default `http://localhost:38080`). | pass | `src/Recordingtest.Recorder/Scenario.cs` line 13: `public RecordedCameraSnapshot? CameraSnapshot`. `Program.cs` line 118: `TryCaptureCamera(args.SidecarUrl)` called before recording. `ParseArgs` line 61 defaults `sidecarUrl` to `"http://localhost:38080"`. `ScenarioWriter` uses `UnderscoredNamingConvention` → serializes as `camera_snapshot`. |
| D7 | Player `Scenario` model has `CameraSnapshot?` field. `IPlayerHost.TryRestoreCamera(...)` defaults to `return false`. `PlayerEngine.Run()` calls it when snapshot not null, logs result. | pass | `src/Recordingtest.Player/Model/Scenario.cs` line 10: `public ScenarioCameraSnapshot? CameraSnapshot`. `IPlayerHost.cs` line 40: default interface impl `bool TryRestoreCamera(...) => false`. `PlayerEngine.cs` lines 9096: calls `host.TryRestoreCamera(cs.Eye, cs.Target, cs.Up, cs.Fov)` and logs. |
| D8 | `UiaPlayerHost` accepts optional `string? sidecarUrl` constructor param. `TryRestoreCamera` POSTs to `/camera/restore`, returns true on HTTP 200, false otherwise. | pass | `src/Recordingtest.Player/UiaPlayerHost.cs` line 24: constructor param `string? sidecarUrl = "http://localhost:38080"`. Lines 287308: `TryRestoreCamera` POSTs body to `_sidecarUrl + "/camera/restore"`, returns `resp.IsSuccessStatusCode`, catches all exceptions and returns false. |
| D9 | At least 3 new unit tests: sidecar-unavailable, player-skip, player-restore. | pass | `RecorderTests.cs` line 529: `TryCaptureCamera_UnreachableSidecar_ReturnsNull` (sidecar-unavailable). `PlayerEngineTests.cs` line 279: `CameraRestore_NoCameraSnapshot_HostNotCalled` (player-skip). `PlayerEngineTests.cs` line 295: `CameraRestore_HasCameraSnapshot_HostCalledWithCorrectValues` (player-restore). 4 additional camera tests also present. |
| D10 | All existing tests still pass. | pass | `dotnet test --no-build -q`: 0 failures across all test assemblies. Total: 31+20+5+5+12+21+11+32+6+6 = 149 tests passed. |
## Notes
- D4 partial: The contract specifies `{"error":"..."}` as the failure body, but the implementation emits `{"ok":false,"error":"..."}`. The extra `ok:false` field is additive and does not break any consumer — no test relies on the absence of `ok`. Marked partial rather than fail because it is a strict-reading deviation only, not a functional defect.
- `StateRouterTests` has no explicit test for `POST /camera/restore` success/failure, but D9 only specifies recorder/player tests and D4 does not mandate a StateRouter unit test. The router logic is covered by code inspection.
- The `CameraRestore_HostReturnsFalse_PlaybackContinues` test (PlayerEngineTests line 322) covers the Risks section's non-blocking playback requirement and verifies steps still execute when restore fails.
- Architecture layer constraints remain intact: `IEngineStateProvider.SetCamera` is in the generic tier; `HmegDirectStateProvider.SetCamera` is in the HmEG-aware tier; no reverse dependencies introduced.