- 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>
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.
4. Plugin masquerade via SUT's existing MEF pipeline (RECOMMENDED)
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