Files
recordingtest/docs/history/2026-04-10_camera-restore.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

3.7 KiB

2026-04-10 — camera-restore (Sprint Contract + Implementation)

Summary

Implemented camera state capture at recording start and restore at playback start via the engine-bridge HTTP sidecar. Replays are now viewport-position-independent.

Changes

New files

  • docs/contracts/camera-restore.md — Sprint Contract with 10 DoD items

Modified files

File Change
src/Recordingtest.Bridge.Abstractions/IEngineStateProvider.cs Added SetCamera(CameraSnapshot) to interface + NullEngineStateProvider no-op
src/Hmeg/Recordingtest.Hmeg.Bridge/HmegDirectStateProvider.cs Added SetCamera via reflection (WriteVec3/WriteDouble), optional uiDispatch: Action<Action> ctor param
src/Sut/EgBim/Recordingtest.Sut.EgBim.PluginHost/IEngineStateProvider.cs ReflectionEngineStateProvider.SetCamera no-op
src/Sut/EgBim/Recordingtest.Sut.EgBim.PluginHost/ChainedEngineStateProvider.cs SetCamera delegates to primary only
src/Sut/EgBim/Recordingtest.Sut.EgBim.PluginHost/BridgeHttpServer.cs Reads POST body (StreamReader) before calling Route
src/Sut/EgBim/Recordingtest.Sut.EgBim.PluginHost/StateRouter.cs Added Route(string method, string path, string body) + POST /camera/restore + ReadVecFromJson helper; backwards-compat Route(string path) overload
src/Sut/EgBim/Recordingtest.Sut.EgBim.PluginHost/HmEgBridgePlugin.cs Passes Application.Current.Dispatcher.Invoke as uiDispatch to HmegDirectStateProvider
src/Hmeg/Recordingtest.Hmeg.Bridge.Client/HmEgHttpSnapshot.cs Added RestoreCamera(eye,target,up,fov) POST method + BuildCameraJson helper
src/Recordingtest.Recorder/Scenario.cs Added RecordedCameraSnapshot class + CameraSnapshot? field on Scenario
src/Recordingtest.Recorder/Program.cs Added --sidecar-url CLI arg; TryCaptureCamera(url) internal helper (GET /camera, 2s timeout); stores snapshot in scenario
src/Recordingtest.Player/Model/Scenario.cs Added ScenarioCameraSnapshot class + CameraSnapshot? field
src/Recordingtest.Player/IPlayerHost.cs Added TryRestoreCamera(eye,target,up,fov) with default return false implementation
src/Recordingtest.Player/PlayerEngine.cs Calls host.TryRestoreCamera(...) before first step when scenario.CameraSnapshot != null
src/Recordingtest.Player/UiaPlayerHost.cs Optional sidecarUrl ctor param; TryRestoreCamera POSTs to /camera/restore
tests/Recordingtest.Player.Tests/FakePlayerHost.cs TryRestoreCamera override + CameraRestoreCalls tracking
tests/Recordingtest.Player.Tests/PlayerEngineTests.cs +5 camera tests (no-snapshot-skip, correct-values, false-return-continues, YAML parse, YAML null)
tests/Recordingtest.Recorder.Tests/RecorderTests.cs +3 camera tests (unreachable-null, roundtrip, null-field)
Various test fake providers Added SetCamera no-op to satisfy updated interface

Test counts

132 → 149 tests, all green.

Design decisions

  • SetCamera is write-only (no return); failures are silently swallowed — the SUT must never crash due to a record tool.
  • WriteVec3 uses ctor(double,double,double) or ctor(float,float,float) reflection to construct WPF/HmEG vector types without a compile-time reference.
  • Camera write must be dispatched to WPF UI thread: HmEgBridgePlugin captures Application.Current.Dispatcher.Invoke at construction time and passes it as Action<Action>.
  • Legacy scenarios (no camera_snapshot) work unchanged — field is nullable, engine skips restore when null.
  • UiaPlayerHost creates a fresh HttpClient per TryRestoreCamera call (short-lived; no pooling needed at this stage).

Context usage

~80K tokens