Files
recordingtest/docs/engine-bridge-probe-design.md
minsung 2a4f1d3fa4 Implement engine-bridge PoC v1 (#9)
- Add Recordingtest.EngineBridge library (IEngineSnapshot, HmEgSnapshot
  skeleton, MetadataLoader, CandidateFinder, CatalogWriter).
- Add Recordingtest.EngineBridge.Probe console exe that dumps
  hmeg-types.json and hmeg-candidates.json to docs/engine-catalog.
- Add Recordingtest.EngineBridge.Tests (xUnit, 6 tests).
- Add probe design doc with plugin-masquerade recommendation.
- Static analysis only; SUT is never executed.

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

5.4 KiB

engine-bridge Probe Design (PoC v2 blueprint)

Related: issue #9, contract docs/contracts/engine-bridge.md.

Goal

Provide recordingtest with a reliable, low-latency read path into HmEG's live runtime state (selected object IDs, camera state, scene summary, render-complete flag) while the SUT (EG-BIM Modeler) is running, so golden-file scenarios can checkpoint internal state alongside saved .hmeg files.

PoC v1 (this sprint) delivers static catalogs and a skeleton API. PoC v2 must actually get into the process. The rest of this document surveys the options and picks one.

Options

1. AssemblyLoadContext side-load in the recordingtest process

Load HmEG.dll into a secondary AssemblyLoadContext inside the recordingtest test runner.

  • Pros: no native code, single process, trivial lifetime.
  • Cons: we would be reading our own copy of HmEG, not the SUT's. No live camera, no live selection. Any singleton the SUT maintains is unreachable.
  • Verdict: not viable for read-live-state; listed for completeness so reviewers don't propose it. Static metadata scanning (what PoC v1 already does) is the legitimate use of this family of APIs.

2. CLR profiling API (ICorProfilerCallback)

Ship a native profiler DLL registered via CORECLR_PROFILER* env vars. The profiler attaches at CLR startup, can SetILFunctionBody on HmEG methods, or hand managed code to a bootstrap assembly that installs hooks.

  • Pros: deepest reach, survives obfuscation, hooks JIT. Can intercept private methods.
  • Cons: native DLL (C/C++), must match CLR bitness and version, requires SUT restart with env vars set, complex failure modes, AV-sensitive.
  • Verdict: fallback for a zero-cooperation scenario. Too heavy for first attempt.

3. In-process managed injector (e.g. Ezez.CLRInjection, SharpMonoInjector style)

CreateRemoteThread + LoadLibrary on a small native bootstrap that starts a CLR host and runs our managed bridge DLL inside the SUT's AppDomain. Bridge uses ordinary reflection to reach HmEGAppManager and exposes state via named pipe or loopback HTTP.

  • Pros: attach at runtime, no SUT restart if the SUT is already running. Pure managed once bootstrapped. Symmetrical with how profilers like dotTrace attach on demand.
  • Cons: Windows-only, bitness must match (SUT is x64 WPF), AV alarms, fragile across .NET runtime versions, process isolation privileges required. CoreCLR hosting from a foreign thread is tricky compared to classic Framework.
  • Verdict: viable but operationally painful. Use only if option 4 is blocked.

The SUT already loads plugins from EG-BIM Modeler/Plugins/ using MEF (per CLAUDE.md §1 and docs/sut-catalog/plugins.md). Drop Recordingtest.EngineBridge.SutPlugin.dll into that folder. It exports the plugin contract the SUT expects, grabs a reference to HmEGAppManager (or whatever the static catalog shows is the canonical accessor), and publishes IEngineSnapshot over a localhost endpoint (named pipe recordingtest-bridge or HTTP on 127.0.0.1).

  • Pros: legitimate extension point, no native code, no process injection, no AV noise, CI friendly, single cooperating restart. Honors CLAUDE.md §5.7 ("SUT 침습 최소화 — 별도 어셈블리로 격리"). Integrates with dispatcher marshaling by design — the plugin runs on the WPF UI thread when called back.
  • Cons: requires SUT restart once to load the plugin; depends on MEF contract remaining stable; small IPC surface to keep deterministic.
  • Verdict: chosen path for PoC v2.

5. AutomationPeer (comparison baseline)

Add a custom UIA AutomationPeer on the 3D viewport that exposes selection via the standard SelectionPattern and camera via custom properties.

  • Pros: standard, debuggable with inspect.exe.
  • Cons: requires SUT source changes; cannot easily expose full scene graph or render-complete without contortions; limited to what UIA patterns can model.
  • Verdict: parallel track for exposing UI-shaped state. Not a replacement for the bridge.

Render-complete signal

Approach Latency (expected) Notes
Poll IsDirty-like property at ~16 ms ~1 frame simple, robust; costs a reflection call per tick
Subscribe to a FrameRendered/Invalidated event event-driven, ~0 ms best case; depends on such an event existing (the static catalog will tell us — look for render candidates with MemberKind=Event)
WPF CompositionTarget.Rendering from inside the plugin ~1 frame works even if HmEG has no event; UI-thread bound

Recommendation: try event subscription first via the candidates catalog; fall back to CompositionTarget.Rendering inside the MEF plugin because the plugin already runs on the UI thread.

Recommendation

PoC v2: implement option 4 (plugin masquerade) with a named-pipe JSON protocol that produces the IEngineSnapshot DTOs defined in this library. Option 2 (CLR profiler) is the documented fallback for a future "zero cooperation" scenario. Option 5 (AutomationPeer) is a parallel concern that belongs to sut-prober and recorder, not to engine-bridge.

Open questions (tracked, not blocking v1)

  • Exact MEF contract the SUT expects (see docs/sut-catalog/plugins.md — resolved before v2)
  • Whether HmEG exposes a FrameRendered-style event (answer will come from v1 catalog)
  • Selection identity: object GUID vs. transient index — needs a stable key for golden diffs