# Glue Embedded Game Preview

> Glue Embedded Game Preview

- Skill: `vchelaru/glue-embedded-game-preview` (Agent Skill)
- Install (CLI): `npx skillmds@latest add vchelaru/glue-embedded-game-preview`
- Raw SKILL.md: https://api.skillmd.com/api/skills/vchelaru/glue-embedded-game-preview/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: vchelaru (https://skillmd.com/u/vchelaru)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/vchelaru/glue-embedded-game-preview

---


# Glue Embedded Game Preview

The "Game" tab does not render the game into a WPF surface. Glue launches the game as a real,
separate process and reparents its native window (`SetParent`, `GameHostView.xaml.cs::EmbedHwnd`)
into a WinForms `Panel` hosted by a `WindowsFormsHost` (`GameHostView.xaml` → `WinformsHost`).

## Resize flow

`WinformsHost_SizeChanged`/`MainGrid_SizeChanged` → `SetGameToEmbeddedGameWindow` sends a
`Runner_MoveWindow` command — not over the DTO socket, but as a raw `_eventCaller`/`ReactToPluginEvent`
string handled by `CompilerPlugin.HandleEvent` (`case "Runner_MoveWindow"`), which calls
`Runner.MoveWindow` → the real Win32 `MoveWindow` API directly on `gameHandle`. Every resize is the
*real game process's* actual OS window changing size/position for real, in-process — there's no visual
scaling on Glue's side, and whatever the game's own `AspectRatioBehavior`/`ResizeBehavior` (see below)
does with that size happens the same as it would for an end user.

`WinformsHost` and its underlying WinForms `Panel` (`winformsPanel`, `GameHostView` constructor) always
stay full-size, filling `MainGrid` (Stretch, no explicit size/alignment) — **even in fixed-size preview
mode.** Letterboxing is achieved by moving/sizing only the real embedded game window (via
`Runner_MoveWindow`'s X/Y/Width/Height) smaller than `winformsPanel`, whose own dark `BackColor`
(`FromArgb(30,30,30)`, set in the `GameHostView` constructor) shows through the margins as letterbox
bars.

**Landmine — do not center by resizing/repositioning `WinformsHost`/`winformsPanel` (the ANCESTOR)
instead of the game window itself.** An earlier version of fixed-size preview did exactly that
(`WinformsHost.HorizontalAlignment/VerticalAlignment = Center` + explicit `Width`/`Height`, always
passing `X=0,Y=0` to `Runner_MoveWindow`). It looked correct visually but broke rectangle-select and
zoom-around-cursor by exactly the centering offset: `gameHandle`'s position *relative to its own
immediate parent* (`winformsPanel`) never changed, so it never received a native move notification —
only its *ancestor* moved. MonoGame/SDL's cached window position (used to translate the cursor's
absolute screen position into client-relative mouse coordinates) went stale by that offset. The fix
was to always keep `winformsPanel` full-size/unmoved and instead pass the letterbox offset as
`Runner_MoveWindow`'s `X`/`Y` — since that's a real position change to `gameHandle` relative to its own
immediate parent, it generates the native move notification MonoGame/SDL needs.

## Landmine — "connected" does not mean the game can handle a DTO yet

`Runner_GameStarted` fires as soon as the game process has a `MainWindowHandle` (`Runner.cs`), which is
*earlier* than the game being able to act on anything. In generated `Game1.Initialize`, `GameConnectionManager`
is constructed (and connects) several statements before `GlueControlManager`, with `CameraSetup.SetupCamera`
between them. In that window the socket is live but `GlueControlManager.Self` is null, so nothing can
dispatch an incoming DTO. Anything sent on game startup therefore needs a retry budget that outlasts
graphics-device setup, not just the connection (`BorderlessRetryPolicy`). Waiting on
`GameCommunication_Connected` does **not** close this — connected is precisely the state where it fails.

**Retry on `Succeeded`, never on the reply's content.** The game answers that window with
`GameConnectionManager.NotReadyPayload` and Glue turns it into an unsuccessful `GeneralResponse`, so
`Succeeded` is the readiness signal. Most `HandleDto` overloads return `void` — `SetBorderlessDto` included —
and a healthy game answers those with an *empty* payload, so a "did it send anything back?" check never
becomes true no matter how long the budget is.

## Zoom / resolution status bar

`BottomStatusBar.xaml` (`ZoomControl` + resolution `TextBlock`) is fed by
`CurrentDisplayInfoDto`, pushed from `GlueControl.Editing.CameraLogic` (`Embedded/Editing/CameraLogic.cs`,
game process only — `PushZoomLevelToEditor`) and applied on the Glue side in
`CommandReceiving/CommandReceiver.cs::HandleDto(CurrentDisplayInfoDto)`, which writes
`CompilerViewModel.CurrentZoomLevelDisplay`/`ResolutionDisplayText`. Zoom itself (`ChangeZoomDto`,
`+`/`-`) is a separate concept from window size — it scales `Camera.Main.OrthogonalHeight` inside the
game and is independent of what size the embedded OS window actually is. None of this zoom state is
persisted to the project file; it resets every Glue launch.

## Landmine — `GameCommunicationPlugin.csproj` has no implicit globbing

`<EnableDefaultCompileItems>false</EnableDefaultCompileItems>` means a new `.cs` file under this
project (e.g. adding a class next to `GameHostView.xaml.cs`) silently compiles into nothing until
it's added to the `<Compile Include="GlueControl\Views\...` `<ItemGroup>` by hand - no error, the
type just doesn't exist for anything that references the assembly (`CS0103` in a consumer, not in
this project).

## Project's target resolution vs. live preview size

The project's configured target resolution/aspect ratio lives in
`DisplaySettingsViewModel`/`GlueCommon/SaveClasses/DisplaySettings.cs`
(`Glue/Plugins/EmbeddedPlugins/CameraPlugin/`) — edited via the Camera Settings panel
(`CameraSettingsControl.xaml`) — and normally only takes effect through codegen
(`CameraSetupCodeGenerator.cs`) for what a *built* game does at startup/on resize. By default the live
preview's `WinformsHost` panel ignores it and always stretches to fill the tab.

The "fixed-size preview" toggle (`BottomStatusBar`'s aspect-ratio icon, `CompilerViewModel.IsFixedSizePreview`,
issue #2035) is the one exception: when on, `GameHostView.SetGameToEmbeddedGameWindow` sizes/centers
the real embedded game window (not `WinformsHost` — see the resize-flow landmine above) to
`DisplaySettings.ResolutionWidth/Height` scaled to fit the panel
(`FixedSizePreviewCalculator`, unit-tested). **Landmine:** "100%" there is not the raw resolution —
it's `ResolutionWidth/Height * DisplaySettings.Scale / 100` (`FixedSizePreviewCalculator.GetEffectiveTargetResolution`),
since `Scale` (Camera Settings' desktop scale, e.g. 400%) is how a real launched window is already
upscaled from the project's internal resolution. Using the raw resolution previews the wrong size for
any project with a non-100% `Scale`.

## Landmine — editor zoom "100%" is not the same "100%" as a real launch

`CameraLogic`'s own zoom (`+`/`-`, `zoomLevels`) is an *editor-only* pan/zoom convenience: its 100%
means "1 world unit = 1 window pixel," computed purely from `Camera.Main.DestinationRectangle.Height`
(`UpdateCameraToZoomLevel`) — it has no idea what `Data.Scale` is. The real generated game
(`CameraSetupCodeGenerator.cs` ~line 696) instead sets
`Camera.Main.OrthogonalHeight = DestinationRectangle.Height / (Data.Scale / 100)`, which — at the
window's natural size (`ResolutionHeight * Scale / 100` pixels tall) — always works out to
`OrthogonalHeight == ResolutionHeight`, independent of `Scale`. `Scale` is a pure pixel-multiplier
(crisp upscaling), not a "show more/less world" zoom.

Fixed-size preview mode locks onto *that* value, not editor-zoom-100%: `CameraLogic.SetFixedSizePreviewLock`
pins `OrthogonalHeight` to a constant (`DisplaySettings.ResolutionHeight`, sent via
`SetFixedSizePreviewLockDto`) regardless of the embedded window's current pixel size — so when the Glue
panel is too small and the window gets letterbox-shrunk (`FixedSizePreviewCalculator`), the whole
picture optically shrinks uniformly (screenshot-thumbnail style) instead of revealing more world, which
is what using editor-zoom-100% would have done. `DoZoomPlus`/`DoZoomMinus` (mouse wheel, `Ctrl+/-`
hotkeys, and the Glue UI's `ChangeZoomDto` all route through these two) no-op while locked. The lock is
re-sent whenever `ResolutionChanged` fires (`MainCompilerPlugin`) but *not* `ScaleChanged` — Scale only
affects window pixel size (`SetGameToEmbeddedGameWindow`), never the locked `OrthogonalHeight` target.

## Landmine — editor zoom % *ratio* does match `Data.Scale`; don't assign `OrthogonalHeight` an absolute value

`DestinationRectangle.Height / OrthogonalHeight` is panel-size-invariant and equals `Data.Scale/100` at
the matching zoom level — only an absolute `OrthogonalHeight` assignment (vs. the divisor formula) breaks
that. See `UpdateCameraToZoomLevel`'s `forceToGameDefault` (issue #2044).

