# Recorder — Evaluation (v2) - Generator commit: `56b7233` - Build: `dotnet build recordingtest.sln` → green (0 warnings, 0 errors) - Tests: `dotnet test tests/Recordingtest.Recorder.Tests` → 9 passed / 0 failed / 0 skipped - Evaluator: independent re-read of source + tests after Generator iteration 2 - Previous evaluation archived at `docs/contracts/recorder.evaluation.v1.md` ## Verdict table | # | DoD item | Verdict | Evidence | |---|---|---|---| | 1 | Console attach to SUT + 입력 캡처 시작 | pass (source) / untestable (live) | `Program.TryAttach` attaches by pid or by window-title scan via `Application.Attach`; never `Launch()`. `LowLevelHook` installs WH_KEYBOARD_LL + WH_MOUSE_LL on a dedicated STA thread. Cannot exercise against EG-BIM Modeler in this sandbox. | | 2 | 캡처 이벤트: 키 down/up, 클릭/드래그/휠, 포커스 변경 | pass | `LowLevelHook` emits `key_down/up`, `mouse_down_l/r/m`, `mouse_up_l`, `wheel`, `move`. `DragCollapser` is a real state machine: on `mouse_down_l` it stores the down event and tracks max distance through `move`s; on `mouse_up_l` it picks `drag` if `max(maxDistSq, finalDistSq) >= threshold²` else `click`. Right-click and key/wheel paths emit their own steps. `Program.cs` calls `automation.RegisterFocusChangedEvent(...)`, builds an UIA path inside the callback (try/catch-guarded) and pushes a synthetic `focus_change` RawEvent into the same channel; `DragCollapser` translates it to a `focus` ScenarioStep. | | 3 | Event shape `{ts, kind, uia_path, offset_norm, raw_coord, value}` | pass | `RawEvent` carries `TimestampMs, Kind, X, Y, Code, WheelDelta, FocusedElementPath`. `ScenarioStep` now exposes `Ts`, `RawCoord`, `EndOffset`, `EndRawCoord` plus existing `Kind/Target{UiaPath,Offset}/Value/WaitFor`. `DragCollapser` populates `Ts` and `RawCoord` (and end variants for drags) on every emitted step. | | 4 | 3D viewport `offset_norm ∈ [0..1]` | pass | `OffsetNormalizer.Normalize` clamps each axis to `[0,1]`; covered by `OffsetNormalizer_ClicksInsideElement_ReturnsZeroToOne`. | | 5 | Yaml schema 준수 | pass | `ScenarioWriter` uses `UnderscoredNamingConvention`; `ts` and `raw_coord` therefore serialize as snake_case. `ScenarioStep_YamlRoundtrip_PreservesTsAndRawCoord` asserts both `ts:` and `raw_coord` appear in the yaml and round-trip back to identical values. `YamlSerializer_RoundtripsScenario` covers click + masked-type. | | 6 | 비밀번호/토큰 마스킹 | pass | `MaskPolicy.Apply` returns `` for `IsPassword` or `ClassName == "PasswordBox"`. `DragCollapser` calls `MaskPolicy.IsMasked` on the resolved snapshot for both click and key paths and overrides `step.Value = MaskPolicy.MaskedValue`. Unit covered by `FocusedElementIsPassword_ReturnsMasked`. | | 7 | 60 FPS 영향 없음 | untestable | Requires running SUT + perf measurement; not possible in sandbox. Architecture (separate STA hook thread + unbounded `Channel`, UIA resolution moved out of the hook callback) is consistent with the requirement. Explicitly deferred. | | 8 | 종료 시 요약(이벤트 수, 소요 시간, 미결 건수) | pass | `Program.Run` writes `[recorder] done. events={count} elapsed={sw.Elapsed} unresolved_paths={unresolved}` on Ctrl+C exit. | ## Tests (9) 1. `ElementPathBuilder_WithNestedElements_ReturnsFullPath` 2. `OffsetNormalizer_ClicksInsideElement_ReturnsZeroToOne` 3. `FocusedElementIsPassword_ReturnsMasked` 4. `YamlSerializer_RoundtripsScenario` 5. `Cli_MissingAttach_ExitTwo` 6. `DragCollapser_DownMoveUp_BeyondThreshold_EmitsDrag` *(new — drag emit beyond threshold)* 7. `DragCollapser_DownUp_BelowThreshold_EmitsClick` *(new — click emit below threshold)* 8. `DragCollapser_FocusChangeEvent_EmitsFocusStep` *(new — focus_change → focus step)* 9. `ScenarioStep_YamlRoundtrip_PreservesTsAndRawCoord` *(new — yaml ts + raw_coord)* All four iteration-2 tests are present, meaningful, and assert the previously-missing behavior (state machine threshold, focus translation, snake_case persistence). ## Configurable threshold `DragCollapser` constructor: `public DragCollapser(int dragThresholdPx = 4)` and stored on `DragThresholdPx`. Default 4 px as required. ## Remaining items - DoD #1 live attach + DoD #7 perf: structurally untestable in this sandbox; deferred to manual smoke on a workstation with EG-BIM Modeler. Source-side wiring is correct. These are no longer "missing code" — they are environment-bound. - IME (한글 조합) handling: still not implemented; this is a contract Risk, not a DoD item. ## Overall verdict **pass** — all DoD items with code obligations are satisfied; the only non-`pass` cells (1 live, 7) are explicitly deferred as untestable in the sandbox, not missing code. v1 release gates (drag collapse, focus capture, ts+raw_coord persistence, drag-state-machine tests) are all closed.