Harden embedded browser runtime handshake

This commit is contained in:
axiomlogicnexus 2026-06-19 03:08:08 +00:00
parent e185c86d50
commit b2e158c8cc
10 changed files with 460 additions and 18 deletions

View file

@ -36,6 +36,7 @@ void UHyperTwistBrowserBridgeObject::NotifyState(const FString& StateJson)
void UHyperTwistBrowserBridgeObject::NotifyRuntimeReady(const FString& RuntimeReadyJson)
{
LastRuntimeReadyJson = RuntimeReadyJson;
OnRuntimeReady.Broadcast(RuntimeReadyJson);
}
void UHyperTwistBrowserBridgeObject::ResetReceivedMessages()

View file

@ -26,6 +26,7 @@ void UHyperTwistBrowserWidget::LoadBundledBrowserShell()
InitialUrl = ResolveBundledBrowserShellUrl();
if (BrowserWidget.IsValid())
{
ResetBrowserRuntimeHandshake();
BrowserWidget->LoadURL(InitialUrl);
}
}
@ -36,47 +37,47 @@ void UHyperTwistBrowserWidget::LoadBrowserUrl(const FString& NewUrl)
InitialUrl = NewUrl;
if (BrowserWidget.IsValid() && !NewUrl.IsEmpty())
{
ResetBrowserRuntimeHandshake();
BrowserWidget->LoadURL(NewUrl);
}
}
void UHyperTwistBrowserWidget::DispatchCommandJson(const FString& CommandJson)
{
if (!BrowserWidget.IsValid() || CommandJson.IsEmpty())
if (CommandJson.IsEmpty())
{
return;
}
const FString EscapedJson = EscapeForJavaScriptSingleQuotedString(CommandJson);
BrowserWidget->ExecuteJavascript(FString::Printf(
TEXT("window.HyperTwistBrowserRuntime && window.HyperTwistBrowserRuntime.receiveCommand(JSON.parse('%s'));"),
*EscapedJson
));
if (!IsBrowserRuntimeDispatchReady())
{
QueueCommandJson(CommandJson);
return;
}
ExecuteBrowserRuntimeScript(BuildCommandDispatchScript(CommandJson));
}
void UHyperTwistBrowserWidget::PushShellStateJson(const FString& StateJson)
{
if (!BrowserWidget.IsValid() || StateJson.IsEmpty())
if (StateJson.IsEmpty())
{
return;
}
const FString EscapedJson = EscapeForJavaScriptSingleQuotedString(StateJson);
BrowserWidget->ExecuteJavascript(FString::Printf(
TEXT("window.HyperTwistBrowserRuntime && window.HyperTwistBrowserRuntime.setShellState(JSON.parse('%s'));"),
*EscapedJson
));
if (!IsBrowserRuntimeDispatchReady())
{
QueueShellStateJson(StateJson);
return;
}
ExecuteBrowserRuntimeScript(BuildShellStateDispatchScript(StateJson));
}
bool UHyperTwistBrowserWidget::PushTilingStateEnvelope(
const FHyperTwistTrainingTilingStateEnvelope& StateEnvelope
)
{
if (!BrowserWidget.IsValid())
{
return false;
}
FString StateJson;
if (!UHyperTwistTrainingTilingLibrary::TrySerializeStateEnvelopeToJson(
StateEnvelope,
@ -104,6 +105,21 @@ FString UHyperTwistBrowserWidget::GetLastStateJson() const
return BrowserBridgeObject != nullptr ? BrowserBridgeObject->LastStateJson : FString();
}
int32 UHyperTwistBrowserWidget::GetPendingCommandCount() const
{
return PendingCommandJsonQueue.Num();
}
bool UHyperTwistBrowserWidget::HasPendingShellStateJson() const
{
return bHasPendingShellStateJson;
}
bool UHyperTwistBrowserWidget::IsBrowserRuntimeReady() const
{
return bBrowserRuntimeReady;
}
bool UHyperTwistBrowserWidget::TryGetLastTilingEnvelope(
FHyperTwistTrainingTilingStateEnvelope& OutStateEnvelope
) const
@ -142,9 +158,20 @@ TSharedRef<SWidget> UHyperTwistBrowserWidget::RebuildWidget()
}
else
{
if (RuntimeReadyDelegateHandle.IsValid())
{
BrowserBridgeObject->OnRuntimeReady.Remove(RuntimeReadyDelegateHandle);
RuntimeReadyDelegateHandle.Reset();
}
BrowserBridgeObject->ResetReceivedMessages();
}
RuntimeReadyDelegateHandle = BrowserBridgeObject->OnRuntimeReady.AddUObject(
this,
&UHyperTwistBrowserWidget::HandleBrowserRuntimeReady
);
ResetBrowserRuntimeHandshake();
BrowserWidget =
SNew(SWebBrowser)
.InitialURL(ResolveInitialUrl())
@ -158,6 +185,12 @@ void UHyperTwistBrowserWidget::ReleaseSlateResources(const bool bReleaseChildren
{
Super::ReleaseSlateResources(bReleaseChildren);
if (BrowserBridgeObject != nullptr && RuntimeReadyDelegateHandle.IsValid())
{
BrowserBridgeObject->OnRuntimeReady.Remove(RuntimeReadyDelegateHandle);
RuntimeReadyDelegateHandle.Reset();
}
if (BrowserWidget.IsValid() && BrowserBridgeObject != nullptr)
{
BrowserWidget->UnbindUObject(TEXT("hypertwist"), BrowserBridgeObject, true);
@ -176,11 +209,112 @@ void UHyperTwistBrowserWidget::SynchronizeProperties()
const FString ResolvedUrl = ResolveInitialUrl();
if (!ResolvedUrl.IsEmpty())
{
ResetBrowserRuntimeHandshake();
BrowserWidget->LoadURL(ResolvedUrl);
}
}
}
bool UHyperTwistBrowserWidget::IsBrowserRuntimeDispatchReady() const
{
#if WITH_AUTOMATION_TESTS
if (bForceBrowserRuntimeDispatchReadyForTesting && !BrowserWidget.IsValid())
{
return bBrowserRuntimeReady;
}
#endif
return BrowserWidget.IsValid() && bBrowserRuntimeReady;
}
void UHyperTwistBrowserWidget::ExecuteBrowserRuntimeScript(const FString& Script)
{
#if WITH_AUTOMATION_TESTS
if (bForceBrowserRuntimeDispatchReadyForTesting && !BrowserWidget.IsValid())
{
ExecutedBrowserScriptsForTesting.Add(Script);
return;
}
#endif
if (BrowserWidget.IsValid())
{
BrowserWidget->ExecuteJavascript(Script);
}
}
void UHyperTwistBrowserWidget::HandleBrowserRuntimeReady(const FString& RuntimeReadyJson)
{
if (RuntimeReadyJson.IsEmpty())
{
return;
}
bBrowserRuntimeReady = true;
FlushPendingBrowserMessages();
}
void UHyperTwistBrowserWidget::FlushPendingBrowserMessages()
{
if (!IsBrowserRuntimeDispatchReady())
{
return;
}
if (bHasPendingShellStateJson)
{
ExecuteBrowserRuntimeScript(BuildShellStateDispatchScript(PendingShellStateJson));
PendingShellStateJson.Reset();
bHasPendingShellStateJson = false;
}
for (const FString& CommandJson : PendingCommandJsonQueue)
{
ExecuteBrowserRuntimeScript(BuildCommandDispatchScript(CommandJson));
}
PendingCommandJsonQueue.Reset();
}
void UHyperTwistBrowserWidget::QueueCommandJson(const FString& CommandJson)
{
if (!CommandJson.IsEmpty())
{
PendingCommandJsonQueue.Add(CommandJson);
}
}
void UHyperTwistBrowserWidget::QueueShellStateJson(const FString& StateJson)
{
if (!StateJson.IsEmpty())
{
PendingShellStateJson = StateJson;
bHasPendingShellStateJson = true;
}
}
void UHyperTwistBrowserWidget::ResetBrowserRuntimeHandshake()
{
bBrowserRuntimeReady = false;
}
FString UHyperTwistBrowserWidget::BuildCommandDispatchScript(const FString& CommandJson) const
{
const FString EscapedJson = EscapeForJavaScriptSingleQuotedString(CommandJson);
return FString::Printf(
TEXT("window.HyperTwistBrowserRuntime && window.HyperTwistBrowserRuntime.receiveCommand(JSON.parse('%s'));"),
*EscapedJson
);
}
FString UHyperTwistBrowserWidget::BuildShellStateDispatchScript(const FString& StateJson) const
{
const FString EscapedJson = EscapeForJavaScriptSingleQuotedString(StateJson);
return FString::Printf(
TEXT("window.HyperTwistBrowserRuntime && window.HyperTwistBrowserRuntime.setShellState(JSON.parse('%s'));"),
*EscapedJson
);
}
FString UHyperTwistBrowserWidget::EscapeForJavaScriptSingleQuotedString(const FString& Input)
{
FString Escaped = Input;
@ -206,3 +340,21 @@ void UHyperTwistBrowserWidget::EnsureBridgeBound()
BrowserWidget->BindUObject(TEXT("hypertwist"), BrowserBridgeObject, true);
}
}
#if WITH_AUTOMATION_TESTS
void UHyperTwistBrowserWidget::SetBrowserRuntimeDispatchReadyForTesting(const bool bInDispatchReady)
{
bForceBrowserRuntimeDispatchReadyForTesting = bInDispatchReady;
ExecutedBrowserScriptsForTesting.Reset();
}
void UHyperTwistBrowserWidget::SimulateBrowserRuntimeReadyForTesting(const FString& RuntimeReadyJson)
{
HandleBrowserRuntimeReady(RuntimeReadyJson);
}
const TArray<FString>& UHyperTwistBrowserWidget::GetExecutedBrowserScriptsForTesting() const
{
return ExecutedBrowserScriptsForTesting;
}
#endif

View file

@ -5,6 +5,8 @@
#include "UObject/Object.h"
#include "HyperTwistBrowserBridgeObject.generated.h"
DECLARE_MULTICAST_DELEGATE_OneParam(FHyperTwistBrowserRuntimeReadyDelegate, const FString&);
UCLASS(BlueprintType)
class UNREALHYPERTWIST_API UHyperTwistBrowserBridgeObject : public UObject
{
@ -42,6 +44,8 @@ public:
UPROPERTY(BlueprintReadOnly, Category = "HyperTwist|Browser")
FString LastRuntimeReadyJson;
FHyperTwistBrowserRuntimeReadyDelegate OnRuntimeReady;
UPROPERTY(Transient)
FHyperTwistTrainingTilingStateEnvelope LastTilingEnvelope;

View file

@ -47,6 +47,15 @@ public:
UFUNCTION(BlueprintPure, Category = "HyperTwist|Browser")
FString GetLastStateJson() const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Browser")
int32 GetPendingCommandCount() const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Browser")
bool HasPendingShellStateJson() const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Browser")
bool IsBrowserRuntimeReady() const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Browser")
bool TryGetLastTilingEnvelope(
FHyperTwistTrainingTilingStateEnvelope& OutStateEnvelope
@ -67,8 +76,17 @@ protected:
virtual TSharedRef<SWidget> RebuildWidget() override;
virtual void ReleaseSlateResources(bool bReleaseChildren) override;
virtual void SynchronizeProperties() override;
virtual bool IsBrowserRuntimeDispatchReady() const;
virtual void ExecuteBrowserRuntimeScript(const FString& Script);
private:
void HandleBrowserRuntimeReady(const FString& RuntimeReadyJson);
void FlushPendingBrowserMessages();
void QueueCommandJson(const FString& CommandJson);
void QueueShellStateJson(const FString& StateJson);
void ResetBrowserRuntimeHandshake();
FString BuildCommandDispatchScript(const FString& CommandJson) const;
FString BuildShellStateDispatchScript(const FString& StateJson) const;
static FString EscapeForJavaScriptSingleQuotedString(const FString& Input);
FString ResolveInitialUrl() const;
void EnsureBridgeBound();
@ -76,5 +94,29 @@ private:
UPROPERTY(Transient)
TObjectPtr<UHyperTwistBrowserBridgeObject> BrowserBridgeObject = nullptr;
UPROPERTY(Transient)
TArray<FString> PendingCommandJsonQueue;
UPROPERTY(Transient)
FString PendingShellStateJson;
UPROPERTY(Transient)
bool bHasPendingShellStateJson = false;
UPROPERTY(Transient)
bool bBrowserRuntimeReady = false;
TSharedPtr<SWebBrowser> BrowserWidget;
FDelegateHandle RuntimeReadyDelegateHandle;
#if WITH_AUTOMATION_TESTS
public:
void SetBrowserRuntimeDispatchReadyForTesting(bool bInDispatchReady);
void SimulateBrowserRuntimeReadyForTesting(const FString& RuntimeReadyJson);
const TArray<FString>& GetExecutedBrowserScriptsForTesting() const;
private:
bool bForceBrowserRuntimeDispatchReadyForTesting = false;
TArray<FString> ExecutedBrowserScriptsForTesting;
#endif
};

View file

@ -84,6 +84,53 @@ bool FHyperTwistBrowserBridgeObjectNotificationTest::RunTest(const FString& Para
return true;
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
FHyperTwistBrowserBridgeObjectRuntimeReadyDelegateTest,
"HyperTwist.Browser.Bridge.RuntimeReadyDelegate",
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)
bool FHyperTwistBrowserBridgeObjectRuntimeReadyDelegateTest::RunTest(const FString& Parameters)
{
UHyperTwistBrowserBridgeObject* BridgeObject = NewObject<UHyperTwistBrowserBridgeObject>();
TestNotNull(TEXT("The browser bridge object must be constructible."), BridgeObject);
if (BridgeObject == nullptr)
{
return false;
}
int32 RuntimeReadyBroadcastCount = 0;
FString LastRuntimeReadyPayload;
const FDelegateHandle DelegateHandle = BridgeObject->OnRuntimeReady.AddLambda(
[&RuntimeReadyBroadcastCount, &LastRuntimeReadyPayload](const FString& RuntimeReadyJson)
{
++RuntimeReadyBroadcastCount;
LastRuntimeReadyPayload = RuntimeReadyJson;
}
);
BridgeObject->NotifyRuntimeReady(TEXT("{\"status\":\"ready\",\"mode\":\"fallback-js\"}"));
TestEqual(
TEXT("Runtime-ready notification must be retained on the bridge object."),
BridgeObject->LastRuntimeReadyJson,
TEXT("{\"status\":\"ready\",\"mode\":\"fallback-js\"}")
);
TestEqual(
TEXT("Runtime-ready notification must broadcast once to bridge listeners."),
RuntimeReadyBroadcastCount,
1
);
TestEqual(
TEXT("Runtime-ready notification must preserve the broadcast payload."),
LastRuntimeReadyPayload,
TEXT("{\"status\":\"ready\",\"mode\":\"fallback-js\"}")
);
BridgeObject->OnRuntimeReady.Remove(DelegateHandle);
return true;
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
FHyperTwistBrowserBridgeObjectTilingStateEnvelopeDecodeTest,
"HyperTwist.Browser.Bridge.TilingStateEnvelopeDecode",
@ -155,6 +202,81 @@ bool FHyperTwistBrowserBridgeObjectTilingStateEnvelopeDecodeTest::RunTest(
return true;
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
FHyperTwistBrowserWidgetQueuedOutboundMessagesTest,
"HyperTwist.Browser.Widget.QueuedOutboundMessages",
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)
bool FHyperTwistBrowserWidgetQueuedOutboundMessagesTest::RunTest(const FString& Parameters)
{
UHyperTwistBrowserWidget* BrowserWidget = NewObject<UHyperTwistBrowserWidget>();
TestNotNull(TEXT("The browser widget must be constructible."), BrowserWidget);
if (BrowserWidget == nullptr)
{
return false;
}
BrowserWidget->DispatchCommandJson(TEXT("{\"command\":\"alpha\"}"));
BrowserWidget->PushShellStateJson(TEXT("{\"phase\":\"7A\"}"));
BrowserWidget->DispatchCommandJson(TEXT("{\"command\":\"beta\"}"));
TestEqual(
TEXT("Command payloads must queue while the browser runtime is not ready."),
BrowserWidget->GetPendingCommandCount(),
2
);
TestTrue(
TEXT("The last shell-state payload must stay queued while the browser runtime is not ready."),
BrowserWidget->HasPendingShellStateJson()
);
TestFalse(
TEXT("A fresh browser widget must begin with runtime-ready false."),
BrowserWidget->IsBrowserRuntimeReady()
);
BrowserWidget->SetBrowserRuntimeDispatchReadyForTesting(true);
BrowserWidget->SimulateBrowserRuntimeReadyForTesting(TEXT("{\"status\":\"ready\"}"));
TestTrue(
TEXT("Runtime-ready simulation must mark the browser widget ready."),
BrowserWidget->IsBrowserRuntimeReady()
);
TestEqual(
TEXT("Queued command payloads must flush when runtime-ready arrives."),
BrowserWidget->GetPendingCommandCount(),
0
);
TestFalse(
TEXT("Queued shell-state payload must flush when runtime-ready arrives."),
BrowserWidget->HasPendingShellStateJson()
);
const TArray<FString>& ExecutedScripts = BrowserWidget->GetExecutedBrowserScriptsForTesting();
TestEqual(
TEXT("Flushing the queued outbound payloads must execute one shell-state script plus both queued commands."),
ExecutedScripts.Num(),
3
);
if (ExecutedScripts.Num() == 3)
{
TestTrue(
TEXT("Shell-state must flush before queued commands so the browser runtime sees the latest state first."),
ExecutedScripts[0].Contains(TEXT("setShellState"))
);
TestTrue(
TEXT("The first queued command must flush through receiveCommand."),
ExecutedScripts[1].Contains(TEXT("receiveCommand"))
);
TestTrue(
TEXT("The second queued command must also flush through receiveCommand."),
ExecutedScripts[2].Contains(TEXT("receiveCommand"))
);
}
return true;
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
FHyperTwistBrowserWidgetBundledShellUrlTest,
"HyperTwist.Browser.Widget.BundledShellUrl",

View file

@ -0,0 +1,107 @@
# HyperTwist Phase 8A embedded browser runtime hardening implementation packet
Created on `2026-06-19`
## Status
- first-party HyperTwist packet
- bounded first-party `Phase 8A` continuation slice
## Purpose
This packet hardens the already-landed embedded browser shipping lane without
widening into a browser-first topology change or any native renderer branch.
The landed slice is:
- queue outbound Unreal command payloads until the embedded browser runtime
reports ready
- retain the latest outbound shell-state payload until runtime-ready, then
flush that shell state before queued commands
- broadcast browser runtime-ready notifications through the first-party bridge
object so the widget can complete a deterministic handshake
- add focused automation proving the queue, flush order, and runtime-ready
bridge signal
It is not:
- a full-browser client implementation
- a new browser authority inversion over Unreal runtime state
- a native `MagicTile` renderer widening packet
## Current authority basis
This implementation packet stands on:
- `docs/ops/HYPERTWIST_COMPREHENSIVE_RECONSTRUCTION_ROADMAP_2026-06-10.md`
- `docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md`
- `docs/v6_5_deep_manual_pack/HyperTwist/ARCHITECTURE.md`
- `docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md`
- `docs/arch/HYPERTWIST_PHASE7C_MAGICTILE_NATIVE_BEHAVIOR_PROOF_IMPLEMENTATION_PACKET_2026-06-19.md`
- `docs/arch/HYPERTWIST_PHASE7D_MAGICTILE_RENDERER_PORT_DECISION_PACKET_2026-06-19.md`
- `docs/arch/HYPERTWIST_PHASE8B_OPTIONAL_FULL_BROWSER_CLIENT_HTTP_BACKEND_PREPARATION_PACKET_2026-06-19.md`
## Landed scope
Current code now hardens the live embedded-browser lane through:
- `UHyperTwistBrowserWidget` outbound queue ownership for:
- pending command JSON payloads
- latest pending shell-state JSON payload
- runtime-ready handshake state
- first-party flush ordering that applies shell state before queued commands
once the browser runtime is ready
- `UHyperTwistBrowserBridgeObject` runtime-ready delegate broadcast
- focused automation in `HyperTwistBrowserBridgeObjectTest.cpp` for:
- runtime-ready delegate retention/broadcast
- queued outbound browser messages and flush ordering
## Why this hardening mattered
The earlier browser-runtime bootstrap already queued messages once the browser
JavaScript runtime existed. The remaining deterministic gap was earlier in the
handshake: Unreal could still try to send command or shell-state payloads
before the embedded widget/runtime bridge was actually ready to receive them.
This packet closes that gap on the Unreal side while preserving the already
landed first-party browser shell and bridge topology.
## Validation
Windows build validation:
- `Build.bat UnrealHyperTwistEditor Win64 Development -Project='C:\HyperTwist_worktrees\phase10validate\UnrealHyperTwist\UnrealHyperTwist.uproject' -WaitMutex -NoHotReloadFromIDE -NoUba`
- result on `2026-06-19`: `Result: Succeeded` with UnrealBuildTool
`Total execution time: 96.96 seconds` on isolated worktree
`C:\HyperTwist_worktrees\phase10validate`
Focused browser validation:
- `Automation RunTests HyperTwist.Browser`
- result on `2026-06-19`: exported
`Saved\AutomationReports\Browser-Hardening-Verify` with all `6`
`HyperTwist.Browser.*` tests passing, including:
- `Bridge.RuntimeReadyDelegate`
- `Widget.QueuedOutboundMessages`
Adjacent regression validation:
- `Automation RunTests HyperTwist.FirstParty.MagicTile`
- `Automation RunTests HyperTwist.Permissive.MagicTile`
- result on `2026-06-19`: exported
`Saved\AutomationReports\Phase7-MagicTile-BrowserHardening` with all `7`
`HyperTwist.FirstParty.MagicTile.*` tests passing and
`Saved\AutomationReports\Phase7-Permissive-MagicTile-BrowserHardening` with
all `6` `HyperTwist.Permissive.MagicTile.*` tests passing
## Queue effect
This packet keeps the live embedded browser/CEF shipping lane intact, but it
raises its determinism and test coverage:
- Unreal-side browser startup is now tolerant of early outbound traffic before
runtime-ready
- browser bridge readiness is now a first-party signal rather than a silent
assumption
- current browser/MagicTile validation grows from earlier green proof into a
stronger shipping-lane hardening packet without reopening renderer decisions

View file

@ -420,6 +420,9 @@ Closure read:
- [x] Add `WebBrowserWidget` plugin to `.uproject`
- [x] Create `UHyperTwistBrowserWidget` wrapping `SWebBrowser`
- [x] Load the authoritative local browser shell in `Content/Browser/index.html`, with bundled-runtime upgrade plus clean-checkout plain-JS fallback
- [x] Queue outbound Unreal command and shell-state payloads until the embedded browser runtime reports ready, then flush shell state before queued commands
- Hardening continuation on `2026-06-19`: current code now keeps the live embedded browser lane deterministic above the already-landed shell/bootstrap path by retaining pending widget outbound traffic until the first-party runtime-ready bridge fires, then flushing the latest shell-state payload before queued commands through the browser runtime
- Validation evidence on `2026-06-19`: the primary reverse-SSH `localhost:22022` lane rebuilt isolated worktree `C:\HyperTwist_worktrees\phase10validate` with `Result: Succeeded` and UnrealBuildTool `Total execution time: 96.96 seconds`, then exported green automation reports for `6` `HyperTwist.Browser.*` tests at `Saved\AutomationReports\Browser-Hardening-Verify`, `7` `HyperTwist.FirstParty.MagicTile.*` tests at `Saved\AutomationReports\Phase7-MagicTile-BrowserHardening`, and `6` `HyperTwist.Permissive.MagicTile.*` tests at `Saved\AutomationReports\Phase7-Permissive-MagicTile-BrowserHardening`
### 8B — State Synchronization
- [x] Bridge UE cube state to browser via embedded Unreal bridge object plus JavaScript `postMessage` fallback

View file

@ -94,6 +94,11 @@ Current shipping browser posture remains the landed embedded browser/CEF shell
inside Unreal through `UHyperTwistBrowserWidget` and the authoritative
`Content/Browser/index.html` surface.
That live shipping lane was further hardened on `2026-06-19`: Unreal-side
outbound command and shell-state payloads now queue until the first-party
browser runtime-ready bridge arrives, then flush shell state before queued
commands so the embedded browser lane stays deterministic across startup.
The optional full-browser client path now exists only as a spec-defined
first-party branch:

View file

@ -144,7 +144,7 @@ Do not collapse those three tiers into one undifferentiated "features" voice.
| First-party provider-neutral custom-endpoint runtime routing | Implemented now | landed first-party `Phase 6R-AI` | First-party per-session provider-profile endpoint selection, runtime routing, fallback-health reflection, and bounded provider-backed transport-failure posture are now live above the landed provider-profile/BYOK and provider-routing seams. Provider-specific overlays, payment execution, provider-portal ownership, and payload shipping remain deferred. |
| Analytics/reporting surfaces | Implemented now | landed analytics/reporting packets | Reporting is real, but bounded to accepted retained slices. |
| Rewritten training analytics and report reference grounding | Implemented now | `apache/echarts` retained permissive lane + first-party current code | Current live `Training Analytics` reference side includes four rewritten first-party targets grounded in retained `apache/echarts`: session outcome and progress reporting, analytics data-view and export, timing-trend history and overview interaction, and the optional richer explainer or sidecar boundary. This does not displace the landed `Phase 3R-C` first-party analytics/reporting owner or elevate `ecomfe/echarts-gl` and `ecomfe/zrender` beyond support-only sidecars. |
| Browser/spatial/media adjunct surfaces | Implemented now | landed `three.js`, `react-three-fiber`, `xr`, `model-viewer`, `remotion` packets | These are implemented bounded families, and the current Phase 1 browser-runtime landing now includes a first-party authoritative `Content/Browser/index.html` shell, bundled-runtime upgrade path, committed clean-checkout plain-JS fallback runtime, and embedded `UHyperTwistBrowserWidget` bridge. This still is not proof of unlimited browser-shell parity. |
| Browser/spatial/media adjunct surfaces | Implemented now | landed `three.js`, `react-three-fiber`, `xr`, `model-viewer`, `remotion` packets | These are implemented bounded families, and the current Phase 1 browser-runtime landing now includes a first-party authoritative `Content/Browser/index.html` shell, bundled-runtime upgrade path, committed clean-checkout plain-JS fallback runtime, embedded `UHyperTwistBrowserWidget` bridge, and the `2026-06-19` runtime-ready queue hardening that retains outbound Unreal shell traffic until the browser runtime is ready. This still is not proof of unlimited browser-shell parity. |
| Optional full-browser client path above Unreal backend authority | Deep-source grounded retained | landed first-party `Phase 8B` preparation packet | This branch is now defined only as a spec-only first-party option: a browser-owned rendering and input shell may sit above explicit Unreal backend seams while Unreal remains authoritative for puzzle, training, timer, replay, and persistence state. The landed embedded browser/CEF shell remains the current shipping posture, and this branch stays separate from any native `MagicTile` behavior or renderer widening. |
| Rewritten browser spatial scene and renderer reference grounding | Implemented now | `mrdoob/three.js` retained permissive lane + first-party current code | Current live `Browser 3D and XR Support` reference side includes one rewritten first-party target grounded in retained `mrdoob/three.js`: the browser spatial scene and renderer contract. This does not displace the landed `Phase 3R-F` first-party browser spatial owner trio or absorb the adjacent `react-three-fiber` renderer-bridge and `xr` session slices. |
| Rewritten React-side browser renderer and event-bridge grounding | Implemented now | `pmndrs/react-three-fiber` retained permissive lane + first-party current code | Current live `Browser 3D and XR Support` reference side includes one rewritten first-party target grounded in retained `react-three-fiber`: the React scene renderer and event bridge. This does not displace the landed `Phase 3R-F` first-party browser spatial owner trio or absorb the adjacent `three.js` scene substrate and `xr` session slices. |

View file

@ -107,6 +107,12 @@ Current consolidated milestone snapshot:
scored against the gate and kept native renderer widening at explicit
`No-Go` because no real browser-host failure or bounded renderer target is
currently evidenced
- the current embedded browser shipping lane was also hardened on `2026-06-19`
so Unreal-side outbound browser traffic now queues until runtime-ready,
flushes shell state before commands, and remains live-validated on primary
reverse-SSH lane `localhost:22022` with `6` `HyperTwist.Browser.*`, `7`
`HyperTwist.FirstParty.MagicTile.*`, and `6`
`HyperTwist.Permissive.MagicTile.*` tests green
- classic-cube `Phase 9A` replay recording is now closed through first-party
runtime capture, `.json` replay persistence, local playback reconstruction,
schema-light replay normalization across save/load/viewer import, and live