Files
recordingtest/docs/contracts/engine-bridge-v3.md
minsung f6b6e7449e 3-tier split (step 1) + engine-bridge v3 scaffold + HmegDirectStateProvider
Lays down the Generic / HmEG-aware / App-specific separation that lets us
target other HmEG-hosting WPF applications later, and lands the v3 engine
state provider on top of it.

Architecture rule (CLAUDE.md §8.1, new): every module belongs to exactly one
of three tiers — Generic / HmEG-aware / App-specific (e.g. EgBim). Dependency
direction is strictly App-specific → HmEG-aware → Generic. Generic must not
reference HmEG.dll; HmEG-aware must not reference any per-app assembly.

This commit is the first incremental step:

  + src/Recordingtest.Bridge.Abstractions/  (Generic, new csproj)
      IEngineStateProvider, CameraSnapshot, SceneSnapshot,
      NullEngineStateProvider — extracted from EgPlugin so the generic core
      owns the contract. Zero SUT references.

  + src/Hmeg/Recordingtest.Hmeg.Bridge/      (HmEG-aware, new csproj)
      HmegDirectStateProvider — IEngineStateProvider implemented against
      the HmEG public API (Space, HmEGViewport, ISelectable, ModelBase.Uid).
      Decoupled from any specific host app via Func<Space?>/Func<HmEGViewport?>
      lambdas; the EgBim plugin host supplies them. Reusable for any other
      WPF application that hosts HmEG.

      Selection traversal walks Space.Children and collects ModelBase.Uid
      for nodes whose ISelectable.IsSelected is true. We deliberately type
      nodes as object + late-bound Uid lookup to avoid pulling MemoryPack
      into the dependency graph.

  + tests/Hmeg/Recordingtest.Hmeg.Bridge.Tests/
      5 unit tests covering null lambdas, throwing lambdas, document path
      provider, and constructor null arg validation.

  + src/Recordingtest.EgPlugin/ChainedEngineStateProvider.cs
      Wraps two providers; falls back from Hmeg.Direct to the existing
      Reflection accessor when the primary returns empty/default. Lets us
      land the new wire-up before the EgBim adapter Q1~Q7 lookups are
      filled in. 7 new tests.

  + src/Recordingtest.EgPlugin/IAppManagerAccessor.cs
      Reflection accessor abstraction (preserved as the v3 fallback). Looks
      up Editor.AppManager.AppManager via well-known Instance/Current
      property names. Unit-testable through a fake.

  ~ src/Recordingtest.EgPlugin/IEngineStateProvider.cs
      Type definitions removed (now in Bridge.Abstractions); only the
      reflection-based provider remains. ReflectionEngineStateProvider
      delegates everything to IAppManagerAccessor.

  ~ src/Recordingtest.EgPlugin/HmEgBridgePlugin.cs
      BuildProvider() picks ChainedEngineStateProvider(Hmeg.Direct,
      Reflection). The HmEG-aware lambdas are stubs (return null) until the
      next step wires the EgBim adapter; the chain falls through to the
      reflection path so behaviour matches v2 for now.

  + docs/contracts/engine-bridge-v3.md       — Sprint Contract
  + docs/contracts/generic-sut-split.md      — Sprint Contract for the
      remaining mass-rename / folder move (step 2, deferred).
  + docs/hmeg-api-survey.md                  — Read-only survey of the HmEG
      public API (Space, ModelBase, HmEGViewport, IHmCamera, IPlugin) used
      to design HmegDirectStateProvider. Open Q1~Q7 listed.

Tests: 94 → 115 passing, 0 failing. The new HmEG-aware test project copies
HmEG.dll next to its output (Private=true) since it runs out-of-process.

Step 2 (deferred to next session): mass-rename
  src/Recordingtest.EgPlugin → src/Sut/EgBim/Recordingtest.Sut.EgBim.PluginHost + .Adapter
  src/Recordingtest.EngineBridge → src/Hmeg/Recordingtest.Hmeg.Catalog
  src/Recordingtest.EngineBridge.Client → split (Generic + Hmeg)
plus Recordingtest.Architecture.Tests to enforce the §8.1 dependency rule.

Ref: #10 follow-up, #14 follow-up.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 09:53:27 +09:00

4.0 KiB

Sprint Contract — engine-bridge-v3

Goal

Recordingtest.EgPlugin.ReflectionEngineStateProvider를 v2의 stub에서 진짜 reflection 기반 매핑으로 격상한다. SUT(EG-BIM Modeler)의 HmEG 엔진 내부 상태(카메라/선택/씬)를 in-process plugin에서 reflection으로 읽어 HTTP /state 응답에 실제 값을 채운다. 이 값이 골든파일 sidecar JSON으로 들어가 회귀 비교의 핵심 결정성 신호가 된다.

Background

  • v1: 정적 분석으로 HmEG 어셈블리 멤버 후보 8000+ 카탈로그 (docs/engine-catalog/hmeg-candidates.json).
  • v2: MEF plugin masquerade + HttpListener + 8 + 3 tests. ReflectionEngineStateProvider는 stub만.
  • v3: stub을 실매핑으로 교체. SUT 환경에서 라이브 검증 필요.

Definition of Done

각 항목은 객관적으로 검증 가능해야 한다.

D1. AppManager 발견

  • plugin이 SUT 프로세스 안에서 AppManager(또는 동등한 root) 인스턴스를 reflection으로 획득한다.
  • 실패 시 NullEngineStateProvider로 안전하게 폴백하고 stderr에 한 번만 경고 로그.

D2. CameraSnapshot 실매핑

  • GetCamera()가 활성 뷰포트의 카메라 eye/target/up/fov를 non-default 값으로 반환.
  • 검증: 라이브 SUT에서 카메라 이동 후 두 번 호출 → 적어도 한 필드가 달라짐.

D3. SceneSnapshot 실매핑

  • GetScene()가 현재 문서의 객체 수와 (열린 경우) 문서 경로를 반환.
  • 검증: Box 1개 생성 후 ObjectCount >= 1, 새 문서 만들면 0.

D4. SelectedIds 실매핑

  • GetSelectedIds()가 현재 선택된 객체의 ID 리스트를 반환 (HmEG 내부 ID 또는 GUID 문자열).
  • 검증: 객체 선택 → 비어있지 않은 리스트.

D5. 결정성 + 정규화

  • 응답 JSON은 normalizer가 처리 가능한 형태 (정렬된 키, 안정된 부동소수점 표현). normalizer 규칙은 기존 mask_volatile_settings / 부동소수점 epsilon으로 충분한지 확인하고 부족하면 신규 규칙 등록.

D6. 단위 테스트

  • ReflectionEngineStateProvider의 reflection 경로를 mockable한 IAppManagerAccessor 추상화 뒤로 격리.
  • Fake accessor로 각 D2/D3/D4를 단위 테스트화 (라이브 SUT 없이 CI 가능).
  • 최소 6 신규 테스트, 전체 suite green (현 94+ → 100+).

D7. 라이브 검증

  • 사용자 SUT 환경에서 plugin 로드 → /state GET 응답에 D2/D3/D4 실값 확인.
  • 결과는 docs/history/2026-04-08_engine-bridge-v3.md 에 캡처.

D8. 문서

  • docs/contracts/engine-bridge-v3.evaluation.md (Evaluator 산출물)
  • docs/guides/engine-bridge-deploy.md 업데이트 (v3 응답 스키마 변경분)

Out of scope

  • HmEG 내부 데이터 변경/쓰기 (read-only)
  • viewport 픽셀 캡처 (별개 모듈)
  • 새 HTTP 엔드포인트 (기존 /state 라우트만 채움)

Interfaces

// 새 추상화 (D6)
public interface IAppManagerAccessor
{
    object? GetAppManager();
    object? GetActiveDocument();
    object? GetActiveViewport();
}

// 기존 IEngineStateProvider 시그니처 유지 — 구현만 교체

Evaluation plan

  1. Evaluator는 /contract engine-bridge-v3.md 를 읽고 D1~D8을 차례로 채점.
  2. D2/D3/D4는 단위 테스트(D6)로 검증 가능 → CI에서 자동 grade.
  3. D7은 사용자 라이브 결과 첨부로 grade (orchestrator가 캡처 전달).
  4. fail 1회 → Generator 재작업. 누적 3회 → 자동 중단.

Risks

  • HmEG 내부 타입 이름이 obfuscation/난독화 가능 → reflection by-name이 깨질 수 있음. 완화: hmeg-candidates.json 카탈로그를 dictionary로 lookup, fallback 체인 다중화.
  • AppManager singleton 접근 패턴이 SUT 버전마다 다를 수 있음. 완화: D1에서 여러 후보 시도.
  • 카메라 좌표계가 right-handed/left-handed/up-axis 다양 → 정규화 규칙 필요.

Estimated complexity

중. 단위 테스트만으로는 D2/D3/D4의 실매핑이 옳은지 확인 불가 — 라이브 검증(D7)이 critical path. 1차 사이클은 발견(discovery)에 시간 쏠릴 가능성.