diff --git a/docs/PROJECT-STRUCTURE.md b/docs/PROJECT-STRUCTURE.md
index a7149b3..b897687 100644
--- a/docs/PROJECT-STRUCTURE.md
+++ b/docs/PROJECT-STRUCTURE.md
@@ -182,6 +182,9 @@ decoded pixels.
shared by VM presentation, graphics hosts, Godot, and tests.
`engine/Age.Engine/Model/InputContracts.cs` owns neutral ADV auto-wait state and fullwidth text-edit request/result
records shared by input hosts, timing policy, VM dispatch, Godot, and tests.
+`engine/Age.Engine/Model/AdvPresentationContracts.cs` owns neutral ADV wait-indicator configuration shared by VM
+dispatch, retained presentation, hosts, Godot, and tests. `engine/Age.Engine/Model/DiagnosticContracts.cs` owns
+the neutral synchronous diagnostic message transported between VM dispatch, diagnostic hosts, Godot, and tests.
`engine/Age.Engine/Model/GfxState.cs` retains cross-domain retained-graphics coordination.
`engine/Age.Engine/Model/GfxState.Contracts.cs` owns its public render, transition, diagnostic, persistence,
diff --git a/docs/remake-architecture-and-roadmap.md b/docs/remake-architecture-and-roadmap.md
index 04c65ff..d9e9aca 100644
--- a/docs/remake-architecture-and-roadmap.md
+++ b/docs/remake-architecture-and-roadmap.md
@@ -815,6 +815,13 @@ do not mix mechanical moves with semantic changes.
twenty-three ADV members, required implementations, overload/default chains, existing hosts, and transport
record locations remain behaviorally unchanged. Runtime validation remains green.
+ The eleventh bounded contract slice moved the unchanged `AdvWaitIndicatorConfig` and `DiagnosticMessage`
+ declarations from `Hosting` into domain-specific `Model` contract files. The retained wait-indicator presenter
+ no longer imports `Hosting`; two host-local consumers gained `Model` imports, and one test dropped its obsolete
+ fully qualified hosting name. Record shape, frame selection, diagnostic presentation, and runtime behavior
+ remain unchanged. This completes the planned runtime-contract split and neutral transport cleanup. Runtime
+ validation remains green.
+
4. **Make the build graph express source ownership.** Stop linking production `.cs` files from `godot/` and
`tools/movie-corpus-gate/` into `Age.Engine.Tests`. Extract the platform-neutral frontend/movie/diagnostic
code into a small production project referenced by Godot, tests, and the corpus gate. Retain both existing
@@ -1173,10 +1180,9 @@ layer's rendering diverges from ADV; save layout.
---
## 8. Immediate next step
-Continue step 3 of the **codebase consolidation** maintenance slice: clarify runtime contracts without changing
-behavior or the aggregate host accepted by the VM. With diagnostic, lifecycle, audio, and movie contracts
-established beneath `IHost`, shared graphics/input transport neutral, and graphics/input/ADV contracts now
-separated, move the unchanged `AdvWaitIndicatorConfig` and `DiagnosticMessage` declarations from `Hosting` to
-`Model` next. Preserve all consumers while removing the remaining transport declarations from interface files.
+Begin step 4 of the **codebase consolidation** maintenance slice: make the build graph express source ownership
+without changing behavior. Introduce the production project boundary for platform-neutral frontend helpers now
+linked directly from `godot/` into `Age.Engine.Tests`, starting with the diagnostic/options helper cluster. Replace
+source links with project references while retaining both existing solution files and Godot export behavior.
Concrete playthrough blockers may still preempt this bounded maintenance work; the consolidation effort does
not replace Phase B gameplay validation or the open cross-platform gates.
diff --git a/engine/Age.Engine.Tests/AdvTextOpsTests.cs b/engine/Age.Engine.Tests/AdvTextOpsTests.cs
index 82af3e5..bb3b323 100644
--- a/engine/Age.Engine.Tests/AdvTextOpsTests.cs
+++ b/engine/Age.Engine.Tests/AdvTextOpsTests.cs
@@ -384,7 +384,7 @@ public class AdvTextOpsTests
new VirtualMachine(script, table, host).Run();
- Assert.Equal(new Age.Engine.Hosting.AdvWaitIndicatorConfig(1, 385, 140, 12, 0, 0, 30, 27, 12, 48),
+ Assert.Equal(new AdvWaitIndicatorConfig(1, 385, 140, 12, 0, 0, 30, 27, 12, 48),
Assert.Single(host.WaitIndicators));
}
diff --git a/engine/Age.Engine/Hosting/CaptureHost.cs b/engine/Age.Engine/Hosting/CaptureHost.cs
index 1c953d1..817b54c 100644
--- a/engine/Age.Engine/Hosting/CaptureHost.cs
+++ b/engine/Age.Engine/Hosting/CaptureHost.cs
@@ -1,3 +1,5 @@
+using Age.Engine.Model;
+
namespace Age.Engine.Hosting;
public sealed class CaptureHost : IHost
{
diff --git a/engine/Age.Engine/Hosting/IAdvHost.cs b/engine/Age.Engine/Hosting/IAdvHost.cs
index 1835a3d..1d9d5c6 100644
--- a/engine/Age.Engine/Hosting/IAdvHost.cs
+++ b/engine/Age.Engine/Hosting/IAdvHost.cs
@@ -2,21 +2,6 @@ using Age.Engine.Model;
namespace Age.Engine.Hosting;
-public readonly record struct AdvWaitIndicatorConfig(
- int LayoutSlot, int X, int Y, int SurfaceSlot,
- int SourceX, int SourceY, int CellWidth, int CellHeight,
- int TerminalFrame, long FramePeriodMs)
-{
- /// Select the current atlas frame. TerminalFrame is the exclusive native upper bound,
- /// so SYSTEM4's value 12 addresses the twelve cells 0 through 11.
- public int FrameAt(long elapsedMs)
- {
- int frameCount = System.Math.Max(1, TerminalFrame);
- long period = System.Math.Max(1, FramePeriodMs);
- return (int)(System.Math.Max(0, elapsedMs) / period % frameCount);
- }
-}
-
public interface IAdvHost
{
void ShowText(int offset, string text);
diff --git a/engine/Age.Engine/Hosting/IDiagnosticHost.cs b/engine/Age.Engine/Hosting/IDiagnosticHost.cs
index 0a6d63a..85f327a 100644
--- a/engine/Age.Engine/Hosting/IDiagnosticHost.cs
+++ b/engine/Age.Engine/Hosting/IDiagnosticHost.cs
@@ -1,3 +1,5 @@
+using Age.Engine.Model;
+
namespace Age.Engine.Hosting;
public interface IDiagnosticHost
diff --git a/engine/Age.Engine/Hosting/IHost.cs b/engine/Age.Engine/Hosting/IHost.cs
index 5dc52f7..5619085 100644
--- a/engine/Age.Engine/Hosting/IHost.cs
+++ b/engine/Age.Engine/Hosting/IHost.cs
@@ -1,8 +1,5 @@
namespace Age.Engine.Hosting;
-/// A synchronous AGE-owned diagnostic prompt after native body/context formatting.
-public readonly record struct DiagnosticMessage(string Caption, string Text);
-
public interface IHost : IDiagnosticHost, ILifecycleHost, IAudioHost, IMovieHost, IGraphicsHost, IInputHost,
IAdvHost
{
diff --git a/engine/Age.Engine/Model/AdvPresentationContracts.cs b/engine/Age.Engine/Model/AdvPresentationContracts.cs
new file mode 100644
index 0000000..eb2c4d3
--- /dev/null
+++ b/engine/Age.Engine/Model/AdvPresentationContracts.cs
@@ -0,0 +1,16 @@
+namespace Age.Engine.Model;
+
+public readonly record struct AdvWaitIndicatorConfig(
+ int LayoutSlot, int X, int Y, int SurfaceSlot,
+ int SourceX, int SourceY, int CellWidth, int CellHeight,
+ int TerminalFrame, long FramePeriodMs)
+{
+ /// Select the current atlas frame. TerminalFrame is the exclusive native upper bound,
+ /// so SYSTEM4's value 12 addresses the twelve cells 0 through 11.
+ public int FrameAt(long elapsedMs)
+ {
+ int frameCount = System.Math.Max(1, TerminalFrame);
+ long period = System.Math.Max(1, FramePeriodMs);
+ return (int)(System.Math.Max(0, elapsedMs) / period % frameCount);
+ }
+}
diff --git a/engine/Age.Engine/Model/DiagnosticContracts.cs b/engine/Age.Engine/Model/DiagnosticContracts.cs
new file mode 100644
index 0000000..56ca2ba
--- /dev/null
+++ b/engine/Age.Engine/Model/DiagnosticContracts.cs
@@ -0,0 +1,4 @@
+namespace Age.Engine.Model;
+
+/// A synchronous AGE-owned diagnostic prompt after native body/context formatting.
+public readonly record struct DiagnosticMessage(string Caption, string Text);
diff --git a/engine/Age.Engine/Text/RetainedAdvWaitIndicatorPresentation.cs b/engine/Age.Engine/Text/RetainedAdvWaitIndicatorPresentation.cs
index a928df3..ff315d2 100644
--- a/engine/Age.Engine/Text/RetainedAdvWaitIndicatorPresentation.cs
+++ b/engine/Age.Engine/Text/RetainedAdvWaitIndicatorPresentation.cs
@@ -1,4 +1,3 @@
-using Age.Engine.Hosting;
using Age.Engine.Model;
namespace Age.Engine.Text;