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>
5.2 KiB
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 167–201: SetCamera computes lookDir from target-eye, calls WriteVec3/WriteDouble via reflection. Dispatches via _uiDispatch when not null (lines 192–195). 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 46–56: 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 34–35 and 53–70: 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 94–115: 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 90–96: 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 287–308: 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 extraok:falsefield is additive and does not break any consumer — no test relies on the absence ofok. Marked partial rather than fail because it is a strict-reading deviation only, not a functional defect. -
StateRouterTestshas no explicit test forPOST /camera/restoresuccess/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_PlaybackContinuestest (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.SetCamerais in the generic tier;HmegDirectStateProvider.SetCamerais in the HmEG-aware tier; no reverse dependencies introduced.