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

5.2 KiB
Raw Permalink Blame History

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.