Completes the Generic / HmEG-aware / App-specific separation started in
f6b6e74. The legacy EgPlugin / EngineBridge / EngineBridge.Client /
EngineBridge.Probe modules are moved into their proper tiers, namespaces
and csproj/sln entries are renamed, and the HmegDirectStateProvider
lambdas are finally populated with real handles from the EgBim plugin
host. A new Recordingtest.Architecture.Tests project enforces the tier
rule at build time.
Moves (git mv + csproj/RootNamespace/AssemblyName rename + sln):
src/Recordingtest.EgPlugin
-> src/Sut/EgBim/Recordingtest.Sut.EgBim.PluginHost
src/Recordingtest.EngineBridge
-> src/Hmeg/Recordingtest.Hmeg.Catalog
src/Recordingtest.EngineBridge.Client
-> src/Hmeg/Recordingtest.Hmeg.Bridge.Client
src/Recordingtest.EngineBridge.Probe
-> src/Hmeg/Recordingtest.Hmeg.Catalog.Probe
tests/Recordingtest.EgPlugin.Tests
-> tests/Sut/EgBim/Recordingtest.Sut.EgBim.PluginHost.Tests
tests/Recordingtest.EngineBridge.Tests
-> tests/Hmeg/Recordingtest.Hmeg.Catalog.Tests
tests/Recordingtest.EngineBridge.IntegrationTests
-> tests/Hmeg/Recordingtest.Hmeg.Catalog.IntegrationTests
Namespace rename applied across all .cs files and csproj RootNamespace:
Recordingtest.EgPlugin -> Recordingtest.Sut.EgBim.PluginHost
Recordingtest.EngineBridge -> Recordingtest.Hmeg.Catalog
Recordingtest.EngineBridge.Client -> Recordingtest.Hmeg.Bridge.Client
Recordingtest.EngineBridge.Probe -> Recordingtest.Hmeg.Catalog.Probe
New: tests/Recordingtest.Architecture.Tests/
DependencyGraphTests walks Assembly.GetReferencedAssemblies() for each
tier and fails if a forbidden reference leaks in:
- Generic modules must not reference HmEG or any app-specific DLL
- HmEG-aware modules must not reference app-specific DLLs
- Recordingtest.Hmeg.Bridge must reference HmEG (positive check)
11 tests, all passing. Prevents future drift from CLAUDE.md §8.1.
Engine-bridge v3 wire-up (HmEgBridgePlugin.BuildProvider):
Previously the HmegDirectStateProvider lambdas returned null and the
chain fell through to reflection. They now call directly into the
EditorPlugin base class that HmEgBridgePlugin inherits:
spaceProvider = () => RootSpace
// AppManager.ViewportManager.RootSpace
viewportProvider = () => View
// EGViewport : Control, HmEGViewport
documentPathProvider = () => AppManager?.FileManager?.CurrentFile
Every lambda is wrapped in try/catch so plugin construction still
cannot throw back into the SUT. Editor02.HmEGAppManager.dll added as
a reference on Recordingtest.Sut.EgBim.PluginHost.csproj — app-
specific tier, which is allowed by the architecture tests.
Entry points were confirmed from read-only review of the SUT sources at
D:\GiteaAll\EG-BIM_Modeler\EditorPluginInterface\EditorPlugin.cs
D:\GiteaAll\EG-BIM_Modeler\HmEGApplicationManagementLibrary\HmEGAppManager.cs
D:\GiteaAll\EG-BIM_Modeler\HmEGApplicationManagementLibrary\SubManager\FileManager.cs
closing out Q1/Q2/Q6/Q7 from docs/hmeg-api-survey.md.
Tests: 115 -> 126 (+11 Architecture), 0 failures.
Next step: live verification of /scene /camera /selection with a real
SUT session; any discrepancy in HmegDirectStateProvider reflection will
be tightened after observing real HmEG camera field names.
Ref: #10 follow-up, #14 follow-up, docs/contracts/generic-sut-split.md.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
First time box-v6.yaml (raw recorder output, 676 lines) replays end-to-end
and actually creates a Box in the SUT — no AI post-editing of target paths
or offsets required. This is the counterpart to #13's recorder-side fixes:
the player now absorbs the remaining record→replay gaps instead of demanding
a hand-cleaned scenario.
Changes (all in Recordingtest.Player):
- PlayerEngine: null-target fallbacks
- Type with null target → host.Type() against current focus
- Click with null target + raw_coord → click at screen-absolute raw_coord
- Other null targets still skipped
- PlayerEngine: strip leading alt+tab hotkey steps (recording-startup noise
that fights the player's own foreground switch)
- PlayerEngine: preserve recorded inter-step timing, clamped 150ms–3s,
routed through new IPlayerHost.Delay so the engine itself stays Sleep-free
(keeps the existing "no fixed sleep" DoD test passing)
- PlayerEngine: per-step console log for live debugging
- UiaPlayerHost: BringSutToForeground() — SetForeground + Focus + 600ms
settle, called from Program.cs before engine.Run
- Step model: add RawCoord (int[]) and Ts (long?) fields, auto-mapped from
YAML raw_coord / ts keys
Tests updated:
- PlayerEngine_NullTarget_SkipsWithoutCalling → _Fallback_Issue14
(verifies the new Click-with-raw_coord and Type behavior)
- FakePlayerHost (both player.tests and runner.tests) implement Delay
Live smoke: box-v6.yaml raw replay produced the expected Box geometry on
the 2nd attempt; 1st attempt dropped the initial "BOX" keystrokes, tracked
as a follow-up (foreground settle is still threshold-sensitive at 600ms).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Follow-ups to #4 normalizer PoC v2:
- Profile.float_decimals (default 6) flows into Rules.RoundFloatsInNode.
- mask_volatile_settings switches from name-only HashSet to a JSONPath-lite
allowlist ($.a.b.c) so same-named fields in unrelated subtrees stay intact.
- default.yaml migrated; 6 new tests including a regression trap for the
unrelated-subtree case. 16/16 normalizer tests, 77/77 solution tests.
Refs #2