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

98 lines
5.4 KiB
Markdown

# 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