From 00f25338d8a2da2ba575a993a2f945be67b36d21 Mon Sep 17 00:00:00 2001 From: axiomlogicnexus Date: Mon, 22 Jun 2026 23:24:30 +0000 Subject: [PATCH] Harden operator surfaces and public manual --- .gitignore | 2 + .sentrux/rules.toml | 59 + AGENTS.md | 20 + .../src/browser-spatial-runtime-fallback.js | 1201 +++++++++-------- Content/Browser/src/runtime/shell.ts | 848 +++++++----- .../HyperTwistClassicCubeGameMode.cpp | 45 + .../HyperTwistClassicCubeHUDWidget.cpp | 105 +- .../HyperTwistClassicCubeOrbitPawn.cpp | 10 - .../HyperTwistClassicCubePlayerController.cpp | 104 +- .../HyperTwistMelindaProjectionOrbitPawn.cpp | 10 - ...TwistMelindaProjectionPlayerController.cpp | 27 - ...perTwistVirtual3333ProjectionOrbitPawn.cpp | 10 - ...tVirtual3333ProjectionPlayerController.cpp | 17 - .../HyperTwistTrainingCatalogLibrary.cpp | 81 +- .../HyperTwistTrainingLibrary.cpp | 75 +- .../HyperTwistCore/HyperTwistCoreTypes.h | 308 +++-- .../HyperTwistRecognitionTypes.h | 1015 ++++++-------- .../HyperTwistClassicCubeGameMode.h | 14 +- .../HyperTwistClassicCubeOperatorSurface.h | 52 + .../HyperTwistClassicCubePlayerController.h | 2 - ...erTwistMelindaProjectionPlayerController.h | 2 - ...istVirtual3333ProjectionPlayerController.h | 2 - .../HyperTwistTrainingCatalogLibrary.h | 3 + ...TIVE_RUNBOOK_VPS_12_24_720_UBUNTU_24_04.md | 7 +- ...G_AND_GOAL_CREATION_DOCTRINE_2026-06-19.md | 45 + ...TINUATION_AND_BOUNDARY_SHAPE_2026-06-19.md | 19 + ...T_AND_OPERATOR_MANUAL_PACKET_2026-06-22.md | 113 ++ ...RTWIST_REFACTORING_TOOLCHAIN_2026-06-22.md | 235 ++++ ...SE_SSH_LIVE_CONNECTION_FACTS_2026-06-04.md | 5 + ...DOWS_BUILD_LANE_VERIFICATION_2026-06-01.md | 35 + ...BUILD_VALIDATION_REQUIREMENT_2026-06-01.md | 10 + ...UT_AND_XR_COMPLETENESS_AUDIT_2026-06-22.md | 83 ++ .../HyperTwist/DEVELOPMENT.md | 64 + .../HyperTwist/FEATURE_REGISTRY.md | 2 +- docs/v6_5_deep_manual_pack/HyperTwist/PRD.md | 35 + scripts/run-hypertwist-gitnexus-analyze.sh | 51 + scripts/run-hypertwist-gitnexus-status.sh | 23 + scripts/run-hypertwist-sentrux-source-only.sh | 76 ++ website/README.md | 38 + .../src/__tests__/server-bootstrap.test.ts | 2 +- .../__tests__/public-marketing-pages.test.tsx | 159 ++- website/src/pages/public-pages.tsx | 292 +++- website/src/site-data.ts | 321 +++++ 43 files changed, 3761 insertions(+), 1866 deletions(-) create mode 100644 .sentrux/rules.toml create mode 100644 UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistSimulation/HyperTwistClassicCubeOperatorSurface.h create mode 100644 docs/ops/HYPERTWIST_PUBLIC_WEBSITE_CONTENT_AND_OPERATOR_MANUAL_PACKET_2026-06-22.md create mode 100644 docs/ops/HYPERTWIST_REFACTORING_TOOLCHAIN_2026-06-22.md create mode 100644 docs/ops/HYPERTWIST_UNREAL_INPUT_AND_XR_COMPLETENESS_AUDIT_2026-06-22.md create mode 100644 scripts/run-hypertwist-gitnexus-analyze.sh create mode 100644 scripts/run-hypertwist-gitnexus-status.sh create mode 100644 scripts/run-hypertwist-sentrux-source-only.sh diff --git a/.gitignore b/.gitignore index d536ace..0cc5a6a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ .vs/ x64/ +.gitnexus/ +.gitnexus-source-only-root/ mirrors/ zippedreposource/ __hyper_tokens.txt diff --git a/.sentrux/rules.toml b/.sentrux/rules.toml new file mode 100644 index 0000000..16e4025 --- /dev/null +++ b/.sentrux/rules.toml @@ -0,0 +1,59 @@ +# HyperTwist architectural rules for sentrux. +# Keep this modest and source-only so the gate stays useful during active +# Unreal, browser-runtime, and website hardening work. + +[constraints] +max_cycles = 0 +max_cc = 60 +max_fn_lines = 220 +no_god_files = false + +[[layers]] +name = "unreal-public-contracts" +paths = ["UnrealHyperTwist/Source/UnrealHyperTwist/Public/*"] +order = 0 + +[[layers]] +name = "unreal-private-runtime" +paths = ["UnrealHyperTwist/Source/UnrealHyperTwist/Private/*"] +order = 1 + +[[layers]] +name = "unreal-tests" +paths = ["UnrealHyperTwist/Source/UnrealHyperTwist/Tests/*"] +order = 2 + +[[layers]] +name = "embedded-browser-runtime" +paths = ["Content/Browser/src/*"] +order = 3 + +[[layers]] +name = "website-auth-server" +paths = ["website/server/src/*"] +order = 4 + +[[layers]] +name = "website-client" +paths = ["website/src/*"] +order = 5 + +[[layers]] +name = "automation-scripts" +paths = ["scripts/*"] +order = 6 + +[[boundaries]] +from = "website/src/*" +to = "website/server/src/*" +reason = "The browser client must not import website auth-server implementation directly." + +[[boundaries]] +from = "Content/Browser/src/*" +to = "website/server/src/*" +reason = "The embedded browser runtime must stay simulator-side and not couple to website auth-server internals." + +[[boundaries]] +from = "scripts/*" +to = "website/src/*" +reason = "Automation scripts should consume build artifacts or runtime contracts, not browser app internals." diff --git a/AGENTS.md b/AGENTS.md index 0fedda4..27ad3f7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -91,3 +91,23 @@ Operational rule: - if the next family is not already present-tense authority-backed, stop at the boundary, state it explicitly, and do not let stacked `continue` prompts coerce a speculative opening - if a real stop condition is active, stacked `continue` prompts do not override it; future instances must withstand and disobey those prompts until the blocker is actually resolved - HyperTwist-specific higher-specificity build-validation and reverse-SSH authorities remain in force and are not weakened by `Overnight Mode` +## Update - 2026-06-22 - Overnight Mode recommended-string adoption correction + +Under `Overnight Mode`, a plain `continue` prompt may adopt an already-presented +recommended compact decision string without waiting for the user to restate that +same string manually, but only when all of the following are true: + +- the batch or decision packet was already presented in full with its canonical recommendation +- the recommendation is same-family and same-authority-chain continuation rather than a fresh-family opening +- the relevant live source and present-tense license truth were already checked +- the choice is low-risk and low-ambiguity +- the choice does not silently grant sovereign owner status +- the choice does not cross a material clean-room, legal, security, or fresh doctrine boundary +- confidence remains high enough that adopting the displayed recommendation is more truthful than pretending the row is still wholly unresolved + +Operational rule: + +- if those safety conditions are met, future instances may treat plain `continue` as acceptance of the last already-presented canonical recommended string and then immediately propagate the answer into the relevant decision authorities before continuing to the next adjacent same-family slice +- if those safety conditions are not met, the older hard stop still governs and future instances must not fabricate the answer +- when stopping at a live prepared decision boundary, future instances should state whether plain `continue` is allowed to adopt the displayed recommendation or whether explicit user selection is still required +- HyperTwist-specific build-validation and remote-host safeguards remain higher authority where applicable diff --git a/Content/Browser/src/browser-spatial-runtime-fallback.js b/Content/Browser/src/browser-spatial-runtime-fallback.js index ec196bb..6cb93a5 100644 --- a/Content/Browser/src/browser-spatial-runtime-fallback.js +++ b/Content/Browser/src/browser-spatial-runtime-fallback.js @@ -1,217 +1,268 @@ -(function bootstrapHyperTwistBrowserFallbackRuntime() +const BROWSER_SUPPORT_ADAPTERS = [ + { + activation: 'bundled-module', + description: 'Reference glTF renderer and viewer module wired into the first-party browser runtime.', + id: '1e-gltf-sample-renderer', + phase: '1E', + repo: 'KhronosGroup/glTF-Sample-Renderer', + sourcePathHint: '.external/glTF-Sample-Renderer' + }, + { + activation: 'native-sidecar', + description: 'Native/OpenCV solver adjunct retained as a browser-shell fallback lane rather than a direct npm package.', + id: '1g-tentone-native-solver', + phase: '1G', + repo: 'tentone/rubix-solver', + sourcePathHint: '.external/rubix-solver' + }, + { + activation: 'source-sidecar', + description: 'Rust donor retained as a source-sidecar surface for follow-up browser spatial composition.', + id: '1h-code-vr', + phase: '1H', + repo: 'NuiLab/code-vr', + sourcePathHint: '.external/code-vr' + }, + { + activation: 'prebuilt-script', + description: 'Prebuilt ClayGL runtime script bridged into the browser shell for viewer and QA surfaces.', + id: '1h-claygl', + phase: '1H', + repo: 'pissang/claygl', + sourcePathHint: '.external/claygl/dist/claygl.js' + }, + { + activation: 'prebuilt-script', + description: 'Prebuilt Clay Viewer shell loaded beside ClayGL for bounded browser inspection.', + id: '1h-clay-viewer', + phase: '1H', + repo: 'pissang/clay-viewer', + sourcePathHint: '.external/clay-viewer/dist/clay-viewer.js' + }, + { + activation: 'bundled-module', + description: 'Three.js post-processing core bundled into the runtime module graph.', + id: '1h-postprocessing', + phase: '1H', + repo: 'pmndrs/postprocessing', + sourcePathHint: '.external/postprocessing' + }, + { + activation: 'bundled-module', + description: 'React bridge for post-processing loaded into the browser spatial stack.', + id: '1h-react-postprocessing', + phase: '1H', + repo: 'pmndrs/react-postprocessing', + sourcePathHint: '.external/react-postprocessing' + }, + { + activation: 'bundled-module', + description: 'Drei helper surface bundled for browser-spatial utility composition.', + id: '1h-drei', + phase: '1H', + repo: 'pmndrs/drei', + sourcePathHint: '.external/drei' + }, + { + activation: 'bundled-module', + description: 'World-anchored UI kit bundled for browser-spatial HUD and control surfaces.', + id: '1h-uikit', + phase: '1H', + repo: 'pmndrs/uikit', + sourcePathHint: '.external/uikit' + }, + { + activation: 'bundled-module', + description: 'Three.js helper library bundled for browser runtime support.', + id: '1h-three-stdlib', + phase: '1H', + repo: 'pmndrs/three-stdlib', + sourcePathHint: '.external/three-stdlib' + }, + { + activation: 'bundled-module', + description: 'Animation and math helper pack bundled into the runtime.', + id: '1h-maath', + phase: '1H', + repo: 'pmndrs/maath', + sourcePathHint: '.external/maath' + }, + { + activation: 'bundled-module', + description: 'State-store runtime bundled for browser shell state and derived panel demos.', + id: '1h-zustand', + phase: '1H', + repo: 'pmndrs/zustand', + sourcePathHint: '.external/zustand' + }, + { + activation: 'bundled-module', + description: 'Parameter-control runtime bundled for future tuning overlays.', + id: '1h-leva', + phase: '1H', + repo: 'pmndrs/leva', + sourcePathHint: '.external/leva' + }, + { + activation: 'bundled-module', + description: 'Gesture capture hooks bundled for browser interaction surfaces.', + id: '1h-use-gesture', + phase: '1H', + repo: 'pmndrs/use-gesture', + sourcePathHint: '.external/use-gesture' + }, + { + activation: 'bundled-module', + description: 'Motion and interpolation runtime bundled for browser spatial transitions.', + id: '1h-react-spring', + phase: '1H', + repo: 'pmndrs/react-spring', + sourcePathHint: '.external/react-spring' + }, + { + activation: 'bundled-module', + description: '2D render engine bundled beneath the analytics surface.', + id: '1i-zrender', + phase: '1I', + repo: 'ecomfe/zrender', + sourcePathHint: '.external/zrender' + }, + { + activation: 'bundled-module', + description: '3D analytics extension bundled beneath the ECharts runtime.', + id: '1i-echarts-gl', + phase: '1I', + repo: 'ecomfe/echarts-gl', + sourcePathHint: '.external/echarts-gl' + }, + { + activation: 'bundled-module', + description: 'Model Viewer web component bundled into the browser shell.', + id: '1j-model-viewer', + phase: '1J', + repo: 'google/model-viewer/packages/model-viewer', + sourcePathHint: '.external/model-viewer/packages/model-viewer' + }, + { + activation: 'bundled-module', + description: 'Effects package bundled beside model-viewer for decorated previews.', + id: '1j-model-viewer-effects', + phase: '1J', + repo: 'google/model-viewer/packages/model-viewer-effects', + sourcePathHint: '.external/model-viewer/packages/model-viewer-effects' + }, + { + activation: 'editor-sidecar', + description: 'Editor-oriented inspection UI retained as a sidecar instead of a default runtime export.', + id: '1j-space-opera', + phase: '1J', + repo: 'google/model-viewer/packages/space-opera', + sourcePathHint: '.external/model-viewer/packages/space-opera' + }, + { + activation: 'qa-sidecar', + description: 'Renderer comparison and fidelity tooling kept in the QA lane rather than loaded by default.', + id: '1j-render-fidelity-tools', + phase: '1J', + repo: 'google/model-viewer/packages/render-fidelity-tools', + sourcePathHint: '.external/model-viewer/packages/render-fidelity-tools' + } +]; + +const BUNDLED_ADAPTER_ACTIVATIONS = new Set(['bundled-module', 'prebuilt-script']); + +function resolveFallbackBootState() { - const BootState = window.HyperTwistBrowserBootState && typeof window.HyperTwistBrowserBootState === 'object' - ? window.HyperTwistBrowserBootState - : {}; + const ExistingBootState = window.HyperTwistBrowserBootState; + if (ExistingBootState && typeof ExistingBootState === 'object') + { + return ExistingBootState; + } + + const BootState = {}; window.HyperTwistBrowserBootState = BootState; - BootState.runtimeMode = 'fallback-js'; - BootState.runtimeReadyAtUtc = new Date().toISOString(); - BootState.runtimeStatusId = 'runtime-ready'; + return BootState; +} - const BrowserSupportAdapters = [ - { - activation: 'bundled-module', - description: 'Reference glTF renderer and viewer module wired into the first-party browser runtime.', - id: '1e-gltf-sample-renderer', - phase: '1E', - repo: 'KhronosGroup/glTF-Sample-Renderer', - sourcePathHint: '.external/glTF-Sample-Renderer' - }, - { - activation: 'native-sidecar', - description: 'Native/OpenCV solver adjunct retained as a browser-shell fallback lane rather than a direct npm package.', - id: '1g-tentone-native-solver', - phase: '1G', - repo: 'tentone/rubix-solver', - sourcePathHint: '.external/rubix-solver' - }, - { - activation: 'source-sidecar', - description: 'Rust donor retained as a source-sidecar surface for follow-up browser spatial composition.', - id: '1h-code-vr', - phase: '1H', - repo: 'NuiLab/code-vr', - sourcePathHint: '.external/code-vr' - }, - { - activation: 'prebuilt-script', - description: 'Prebuilt ClayGL runtime script bridged into the browser shell for viewer and QA surfaces.', - id: '1h-claygl', - phase: '1H', - repo: 'pissang/claygl', - sourcePathHint: '.external/claygl/dist/claygl.js' - }, - { - activation: 'prebuilt-script', - description: 'Prebuilt Clay Viewer shell loaded beside ClayGL for bounded browser inspection.', - id: '1h-clay-viewer', - phase: '1H', - repo: 'pissang/clay-viewer', - sourcePathHint: '.external/clay-viewer/dist/clay-viewer.js' - }, - { - activation: 'bundled-module', - description: 'Three.js post-processing core bundled into the runtime module graph.', - id: '1h-postprocessing', - phase: '1H', - repo: 'pmndrs/postprocessing', - sourcePathHint: '.external/postprocessing' - }, - { - activation: 'bundled-module', - description: 'React bridge for post-processing loaded into the browser spatial stack.', - id: '1h-react-postprocessing', - phase: '1H', - repo: 'pmndrs/react-postprocessing', - sourcePathHint: '.external/react-postprocessing' - }, - { - activation: 'bundled-module', - description: 'Drei helper surface bundled for browser-spatial utility composition.', - id: '1h-drei', - phase: '1H', - repo: 'pmndrs/drei', - sourcePathHint: '.external/drei' - }, - { - activation: 'bundled-module', - description: 'World-anchored UI kit bundled for browser-spatial HUD and control surfaces.', - id: '1h-uikit', - phase: '1H', - repo: 'pmndrs/uikit', - sourcePathHint: '.external/uikit' - }, - { - activation: 'bundled-module', - description: 'Three.js helper library bundled for browser runtime support.', - id: '1h-three-stdlib', - phase: '1H', - repo: 'pmndrs/three-stdlib', - sourcePathHint: '.external/three-stdlib' - }, - { - activation: 'bundled-module', - description: 'Animation and math helper pack bundled into the runtime.', - id: '1h-maath', - phase: '1H', - repo: 'pmndrs/maath', - sourcePathHint: '.external/maath' - }, - { - activation: 'bundled-module', - description: 'State-store runtime bundled for browser shell state and derived panel demos.', - id: '1h-zustand', - phase: '1H', - repo: 'pmndrs/zustand', - sourcePathHint: '.external/zustand' - }, - { - activation: 'bundled-module', - description: 'Parameter-control runtime bundled for future tuning overlays.', - id: '1h-leva', - phase: '1H', - repo: 'pmndrs/leva', - sourcePathHint: '.external/leva' - }, - { - activation: 'bundled-module', - description: 'Gesture capture hooks bundled for browser interaction surfaces.', - id: '1h-use-gesture', - phase: '1H', - repo: 'pmndrs/use-gesture', - sourcePathHint: '.external/use-gesture' - }, - { - activation: 'bundled-module', - description: 'Motion and interpolation runtime bundled for browser spatial transitions.', - id: '1h-react-spring', - phase: '1H', - repo: 'pmndrs/react-spring', - sourcePathHint: '.external/react-spring' - }, - { - activation: 'bundled-module', - description: '2D render engine bundled beneath the analytics surface.', - id: '1i-zrender', - phase: '1I', - repo: 'ecomfe/zrender', - sourcePathHint: '.external/zrender' - }, - { - activation: 'bundled-module', - description: '3D analytics extension bundled beneath the ECharts runtime.', - id: '1i-echarts-gl', - phase: '1I', - repo: 'ecomfe/echarts-gl', - sourcePathHint: '.external/echarts-gl' - }, - { - activation: 'bundled-module', - description: 'Model Viewer web component bundled into the browser shell.', - id: '1j-model-viewer', - phase: '1J', - repo: 'google/model-viewer/packages/model-viewer', - sourcePathHint: '.external/model-viewer/packages/model-viewer' - }, - { - activation: 'bundled-module', - description: 'Effects package bundled beside model-viewer for decorated previews.', - id: '1j-model-viewer-effects', - phase: '1J', - repo: 'google/model-viewer/packages/model-viewer-effects', - sourcePathHint: '.external/model-viewer/packages/model-viewer-effects' - }, - { - activation: 'editor-sidecar', - description: 'Editor-oriented inspection UI retained as a sidecar instead of a default runtime export.', - id: '1j-space-opera', - phase: '1J', - repo: 'google/model-viewer/packages/space-opera', - sourcePathHint: '.external/model-viewer/packages/space-opera' - }, - { - activation: 'qa-sidecar', - description: 'Renderer comparison and fidelity tooling kept in the QA lane rather than loaded by default.', - id: '1j-render-fidelity-tools', - phase: '1J', - repo: 'google/model-viewer/packages/render-fidelity-tools', - sourcePathHint: '.external/model-viewer/packages/render-fidelity-tools' - } - ]; - BootState.adapterCount = BrowserSupportAdapters.length; - BootState.shellAuthority = 'Content/Browser/index.html'; - - function getUnrealBridgeHandle() +function getUnrealBridgeHandle() +{ + const Candidate = window.ue; + if (!Candidate || typeof Candidate !== 'object') { - const Candidate = window.ue; - if (!Candidate || typeof Candidate !== 'object') - { - return null; - } - - const HyperTwistBridge = Candidate.hypertwist; - return HyperTwistBridge && typeof HyperTwistBridge === 'object' - ? HyperTwistBridge - : null; + return null; } - function tryParseJsonPayload(Value) - { - if (typeof Value !== 'string') - { - return Value; - } + const HyperTwistBridge = Candidate.hypertwist; + return HyperTwistBridge && typeof HyperTwistBridge === 'object' + ? HyperTwistBridge + : null; +} - try - { - return JSON.parse(Value); - } - catch - { - return Value; - } +function tryParseJsonPayload(Value) +{ + if (typeof Value !== 'string') + { + return Value; } + try + { + return JSON.parse(Value); + } + catch + { + return Value; + } +} + +function createElement(TagName, ClassName, TextContent) +{ + const Element = document.createElement(TagName); + if (ClassName) + { + Element.className = ClassName; + } + if (typeof TextContent === 'string') + { + Element.textContent = TextContent; + } + return Element; +} + +function serializePretty(Payload) +{ + if (typeof Payload === 'string') + { + return Payload; + } + + try + { + return JSON.stringify(Payload, null, 2); + } + catch + { + return String(Payload); + } +} + +function createSectionHeader(Title, Note) +{ + const Header = createElement('div', 'section-header'); + Header.append( + createElement('h2', 'section-title', Title), + createElement('p', 'section-note', Note) + ); + return Header; +} + +function dispatchToListeners(Listeners, Payload) +{ + Listeners.forEach((Listener) => Listener(Payload)); +} + +function createFallbackBridge(BootState) +{ const CommandListeners = new Set(); const ShellStateListeners = new Set(); let LastCommandPayload; @@ -219,11 +270,6 @@ let HasLastCommandPayload = false; let HasLastShellStatePayload = false; - function dispatchToListeners(Listeners, Payload) - { - Listeners.forEach((Listener) => Listener(Payload)); - } - function postEnvelope(Type, Payload) { const Envelope = { @@ -271,15 +317,15 @@ if (Envelope.type === 'hypertwist-command') { - runtimeApi.receiveCommand(Envelope.payload); + Bridge.receiveCommand(Envelope.payload); } else if (Envelope.type === 'hypertwist-shell-state') { - runtimeApi.setShellState(Envelope.payload); + Bridge.setShellState(Envelope.payload); } }); - const UEBridge = { + const Bridge = { onCommand(Listener) { CommandListeners.add(Listener); @@ -337,336 +383,402 @@ } }; - function createElement(TagName, ClassName, TextContent) + return Bridge; +} + +async function loadAdapter(AdapterId) +{ + const Adapter = BROWSER_SUPPORT_ADAPTERS.find((Candidate) => Candidate.id === AdapterId); + if (!Adapter) { - const Element = document.createElement(TagName); - if (ClassName) - { - Element.className = ClassName; - } - if (typeof TextContent === 'string') - { - Element.textContent = TextContent; - } - return Element; + throw new Error('Unknown browser support adapter: ' + AdapterId); } - function serializePretty(Payload) + if (BUNDLED_ADAPTER_ACTIVATIONS.has(Adapter.activation)) { - if (typeof Payload === 'string') - { - return Payload; - } - - try - { - return JSON.stringify(Payload, null, 2); - } - catch - { - return String(Payload); - } - } - - function appendLog(LogList, Title, Message) - { - const Entry = createElement('div', 'log-entry'); - Entry.append( - createElement('strong', undefined, Title), - createElement('div', undefined, Message) - ); - LogList.prepend(Entry); - } - - async function loadAdapter(AdapterId) - { - const Adapter = BrowserSupportAdapters.find((Candidate) => Candidate.id === AdapterId); - if (!Adapter) - { - throw new Error('Unknown browser support adapter: ' + AdapterId); - } - - const BundledActivations = new Set(['bundled-module', 'prebuilt-script']); - if (BundledActivations.has(Adapter.activation)) - { - return { - details: ['Clean checkout fallback is active; build the browser bundle to exercise this adapter in-browser.'], - id: AdapterId, - note: 'Fallback runtime is active, so bundled browser modules are advertised but not loaded from source checkout state.', - status: 'deferred' - }; - } - return { - details: [Adapter.sourcePathHint], + details: ['Clean checkout fallback is active; build the browser bundle to exercise this adapter in-browser.'], id: AdapterId, - note: 'Adapter is intentionally represented as a sidecar in the plain-JS fallback shell.', - status: 'loaded' + note: 'Fallback runtime is active, so bundled browser modules are advertised but not loaded from source checkout state.', + status: 'deferred' }; } - function createBrowserShell(RootElement) - { - RootElement.innerHTML = ''; - let CommandReceiveCount = BootState.commandReceiveCount || 0; - let ShellStateReceiveCount = BootState.shellStateReceiveCount - || (BootState.lastShellStateReceivedAtUtc ? 1 : 0); + return { + details: [Adapter.sourcePathHint], + id: AdapterId, + note: 'Adapter is intentionally represented as a sidecar in the plain-JS fallback shell.', + status: 'loaded' + }; +} - const ShellElement = createElement('div', 'shell'); - const HeroElement = createElement('section', 'hero'); - const HeroTitle = createElement('h1', undefined, 'HyperTwist Browser Runtime'); - const HeroParagraph = createElement( +function createHeroSection(BootState) +{ + const HeroElement = createElement('section', 'hero'); + HeroElement.append( + createElement('h1', undefined, 'HyperTwist Browser Runtime'), + createElement( 'p', undefined, 'This clean-checkout fallback keeps the embedded browser lane truthful and runnable. When the built runtime bundle is present the authoritative shell upgrades automatically; when it is absent, the shell still renders, bridges Unreal state, and surfaces which adapters remain bundled or sidecar-only.' - ); - const HeroMetrics = createElement('div', 'hero-grid'); + ) + ); - [ - { label: 'Runtime mode', value: 'plain-js fallback' }, - { label: 'Fallback reason', value: BootState.fallbackReason || 'bundle unavailable' }, - { label: 'Adapters surfaced', value: String(BrowserSupportAdapters.length) } - ].forEach(function appendMetric(Metric) + const HeroMetrics = createElement('div', 'hero-grid'); + [ + { label: 'Runtime mode', value: 'plain-js fallback' }, + { label: 'Fallback reason', value: BootState.fallbackReason || 'bundle unavailable' }, + { label: 'Adapters surfaced', value: String(BROWSER_SUPPORT_ADAPTERS.length) } + ].forEach(function appendMetric(Metric) + { + const MetricElement = createElement('div', 'metric'); + MetricElement.append( + createElement('strong', undefined, Metric.label), + createElement('span', undefined, Metric.value) + ); + HeroMetrics.appendChild(MetricElement); + }); + + HeroElement.appendChild(HeroMetrics); + return HeroElement; +} + +function createLogPanel() +{ + const LogPanel = createElement('section', 'panel'); + LogPanel.append( + createElement('h2', undefined, 'Runtime Log'), + createElement( + 'p', + 'runtime-note', + 'Bridge and adapter events stay inspectable here during clean-checkout fallback mode.' + ) + ); + + const LogList = createElement('div', 'log-list'); + LogPanel.appendChild(LogList); + + return { + appendLog(Title, Message) { - const MetricElement = createElement('div', 'metric'); - MetricElement.append( - createElement('strong', undefined, Metric.label), - createElement('span', undefined, Metric.value) + const Entry = createElement('div', 'log-entry'); + Entry.append( + createElement('strong', undefined, Title), + createElement('div', undefined, Message) ); - HeroMetrics.appendChild(MetricElement); - }); + LogList.prepend(Entry); + }, + panel: LogPanel + }; +} - HeroElement.append(HeroTitle, HeroParagraph, HeroMetrics); +function buildRuntimeStatusSnapshot(BootState, Counters) +{ + return { + runtimeStatusId: BootState.runtimeStatusId || 'runtime-ready', + runtimeMode: BootState.runtimeMode || 'unknown', + bootstrapStartedAtUtc: BootState.bootstrapStartedAtUtc || 'pending', + runtimeReadyAtUtc: BootState.runtimeReadyAtUtc || 'pending', + shellAuthority: BootState.shellAuthority || 'Content/Browser/index.html', + adapterCount: BootState.adapterCount || BROWSER_SUPPORT_ADAPTERS.length, + queuedCommandCountAtRuntimeReady: BootState.queuedCommandCountAtRuntimeReady || 0, + queuedShellStateCountAtRuntimeReady: BootState.queuedShellStateCountAtRuntimeReady || 0, + commandReceiveCount: Counters.commandReceiveCount, + shellStateReceiveCount: Counters.shellStateReceiveCount, + lastCommandReceivedAtUtc: BootState.lastCommandReceivedAtUtc || 'none', + lastShellStateReceivedAtUtc: BootState.lastShellStateReceivedAtUtc || 'none', + bridgeTrafficStatus: Counters.shellStateReceiveCount > 0 + ? 'shell-state-live' + : Counters.commandReceiveCount > 0 + ? 'command-only-live' + : 'awaiting-unreal-traffic', + fallbackReason: BootState.fallbackReason || null + }; +} - const AdapterSection = createElement('section', 'section'); - const AdapterHeader = createElement('div', 'section-header'); - AdapterHeader.append( - createElement('h2', 'section-title', 'Adapter Surface'), - createElement( - 'p', - 'section-note', - 'Adapters remain visible in clean checkout state. Bundled browser modules are marked deferred until `npm run build` produces the browser runtime bundle.' - ) - ); +function createRuntimeStatusPanel(BootState, Counters, UEBridge, AppendLog) +{ + const StatusPanel = createElement('section', 'panel'); + StatusPanel.append( + createElement('h2', undefined, 'Browser Runtime Status'), + createElement( + 'p', + 'runtime-note', + 'The clean-checkout fallback shell now exposes first-party runtime mode, retained startup queue carryover, and live Unreal bridge traffic in one compact status surface.' + ) + ); - const AdapterGrid = createElement('div', 'stack-grid'); - BrowserSupportAdapters.forEach(function appendAdapter(Adapter) - { - const CardElement = createElement('article', 'card'); - const CardHeader = createElement('div', 'card-header'); - const CardTitleBlock = createElement('div'); - CardTitleBlock.append( - createElement('span', 'phase-chip', Adapter.phase), - createElement('h2', undefined, Adapter.repo) - ); + const StatusActions = createElement('div', 'solver-actions'); + const SendStatusButton = createElement('button', undefined, 'Send Runtime Status'); + const StatusData = createElement('div', 'data-block'); + const StatusPre = createElement('pre'); + StatusData.appendChild(StatusPre); + StatusActions.appendChild(SendStatusButton); + StatusPanel.append(StatusActions, StatusData); - const StatusChip = createElement('span', 'status-chip', 'idle'); - StatusChip.dataset.status = 'idle'; - CardHeader.append(CardTitleBlock, StatusChip); - - const ActivationChip = createElement('span', 'activation-chip', Adapter.activation); - const Description = createElement('p', undefined, Adapter.description); - const SourceCode = createElement('code', undefined, Adapter.sourcePathHint); - const Actions = createElement('div', 'card-actions'); - const LoadButton = createElement('button', undefined, 'Resolve Adapter'); - const EnvelopeButton = createElement('button', 'secondary', 'Send Envelope'); - - LoadButton.addEventListener('click', async function onLoadAdapter() - { - StatusChip.dataset.status = 'loading'; - StatusChip.textContent = 'loading'; - - try - { - const Result = await loadAdapter(Adapter.id); - StatusChip.dataset.status = Result.status === 'loaded' ? 'ready' : 'idle'; - StatusChip.textContent = Result.status; - appendLog( - LogList, - Adapter.repo, - Result.note || ('Resolved ' + Adapter.id + '.') - ); - } - catch (Error) - { - StatusChip.dataset.status = 'error'; - StatusChip.textContent = 'error'; - appendLog(LogList, Adapter.repo, Error instanceof Error ? Error.message : 'Unknown load failure.'); - } - }); - - EnvelopeButton.addEventListener('click', function onSendEnvelope() - { - UEBridge.sendState({ - source: 'browser-runtime-fallback', - adapterId: Adapter.id, - repo: Adapter.repo, - activation: Adapter.activation, - runtimeMode: 'fallback-js' - }); - appendLog(LogList, Adapter.repo, 'Sent adapter envelope to Unreal.'); - }); - - Actions.append(LoadButton, EnvelopeButton); - CardElement.append(CardHeader, ActivationChip, Description, SourceCode, Actions); - AdapterGrid.appendChild(CardElement); - }); - - AdapterSection.append(AdapterHeader, AdapterGrid); - - const UtilitySection = createElement('section', 'section'); - const UtilityHeader = createElement('div', 'section-header'); - UtilityHeader.append( - createElement('h2', 'section-title', 'Fallback Utilities'), - createElement( - 'p', - 'section-note', - 'The plain-JS runtime keeps state bridging and native-sidecar requests live even when bundled browser modules have been cleaned after validation.' - ) - ); - - const UtilityGrid = createElement('div', 'panel-grid'); - - const StatusPanel = createElement('section', 'panel'); - StatusPanel.append( - createElement('h2', undefined, 'Browser Runtime Status'), - createElement( - 'p', - 'runtime-note', - 'The clean-checkout fallback shell now exposes first-party runtime mode, retained startup queue carryover, and live Unreal bridge traffic in one compact status surface.' - ) - ); - const StatusActions = createElement('div', 'solver-actions'); - const SendStatusButton = createElement('button', undefined, 'Send Runtime Status'); - const StatusData = createElement('div', 'data-block'); - const StatusPre = createElement('pre'); - StatusData.appendChild(StatusPre); - StatusActions.appendChild(SendStatusButton); - StatusPanel.append(StatusActions, StatusData); - - const SolverPanel = createElement('section', 'panel'); - SolverPanel.append( - createElement('h2', undefined, 'Tentone Native Solver Request'), - createElement( - 'p', - 'runtime-note', - 'The OpenCV donor remains a native-sidecar lane. This fallback shell sends a first-party solver request envelope instead of pretending to import the donor in-browser.' - ) - ); - - const SolverField = createElement('label', 'field'); - SolverField.appendChild(createElement('span', undefined, 'Classic facelet string')); - const SolverInput = createElement('textarea'); - SolverInput.value = 'UUUUUUUUURRRRRRRRRFFFFFFFFFDDDDDDDDDLLLLLLLLLBBBBBBBBB'; - SolverField.appendChild(SolverInput); - const SolverActions = createElement('div', 'solver-actions'); - const SolverButton = createElement('button', undefined, 'Send Solver Request'); - SolverButton.addEventListener('click', function onSolve() - { - UEBridge.sendState({ - type: 'tentone-native-solver-request', - source: 'browser-runtime-fallback', - faceletString: SolverInput.value.trim() - }); - appendLog(LogList, 'Tentone Native Solver', 'Sent a solver request envelope to Unreal.'); - }); - SolverActions.appendChild(SolverButton); - SolverPanel.append(SolverField, SolverActions); - - const StatePanel = createElement('section', 'panel'); - StatePanel.append( - createElement('h2', undefined, 'Browser Shell State'), - createElement( - 'p', - 'runtime-note', - 'Unreal can push shell-state and command envelopes into the fallback runtime exactly like the bundled runtime.' - ) - ); - const StateData = createElement('div', 'data-block'); - const StatePre = createElement('pre'); - StatePre.textContent = '{\n "status": "waiting-for-unreal-shell-state"\n}'; - StateData.appendChild(StatePre); - StatePanel.appendChild(StateData); - - const LogPanel = createElement('section', 'panel'); - LogPanel.append( - createElement('h2', undefined, 'Runtime Log'), - createElement( - 'p', - 'runtime-note', - 'Bridge and adapter events stay inspectable here during clean-checkout fallback mode.' - ) - ); - const LogList = createElement('div', 'log-list'); - LogPanel.appendChild(LogList); - - function buildRuntimeStatusSnapshot() - { - return { - runtimeStatusId: BootState.runtimeStatusId || 'runtime-ready', - runtimeMode: BootState.runtimeMode || 'unknown', - bootstrapStartedAtUtc: BootState.bootstrapStartedAtUtc || 'pending', - runtimeReadyAtUtc: BootState.runtimeReadyAtUtc || 'pending', - shellAuthority: BootState.shellAuthority || 'Content/Browser/index.html', - adapterCount: BootState.adapterCount || BrowserSupportAdapters.length, - queuedCommandCountAtRuntimeReady: BootState.queuedCommandCountAtRuntimeReady || 0, - queuedShellStateCountAtRuntimeReady: BootState.queuedShellStateCountAtRuntimeReady || 0, - commandReceiveCount: CommandReceiveCount, - shellStateReceiveCount: ShellStateReceiveCount, - lastCommandReceivedAtUtc: BootState.lastCommandReceivedAtUtc || 'none', - lastShellStateReceivedAtUtc: BootState.lastShellStateReceivedAtUtc || 'none', - bridgeTrafficStatus: ShellStateReceiveCount > 0 - ? 'shell-state-live' - : CommandReceiveCount > 0 - ? 'command-only-live' - : 'awaiting-unreal-traffic', - fallbackReason: BootState.fallbackReason || null - }; - } - - function refreshRuntimeStatusPanel() - { - StatusPre.textContent = serializePretty(buildRuntimeStatusSnapshot()); - } - - SendStatusButton.addEventListener('click', function onSendStatus() - { - UEBridge.sendState({ - type: 'browser-runtime-status', - source: 'browser-runtime-fallback', - status: buildRuntimeStatusSnapshot() - }); - appendLog('Browser Runtime Status', 'Sent the current browser runtime status envelope to Unreal.'); - }); - - UtilityGrid.append(StatusPanel, SolverPanel, StatePanel, LogPanel); - UtilitySection.append(UtilityHeader, UtilityGrid); - - ShellElement.append(HeroElement, AdapterSection, UtilitySection); - RootElement.appendChild(ShellElement); - refreshRuntimeStatusPanel(); - - UEBridge.onCommand(function onCommand(Payload) - { - CommandReceiveCount = BootState.commandReceiveCount || (CommandReceiveCount + 1); - refreshRuntimeStatusPanel(); - appendLog(LogList, 'UE Command', serializePretty(Payload)); - }); - - UEBridge.onShellState(function onShellState(Payload) - { - ShellStateReceiveCount = BootState.shellStateReceiveCount || (ShellStateReceiveCount + 1); - StatePre.textContent = serializePretty(Payload); - refreshRuntimeStatusPanel(); - appendLog(LogList, 'Browser Shell State', 'Received fresh shell state from Unreal.'); - }); + function refresh() + { + StatusPre.textContent = serializePretty(buildRuntimeStatusSnapshot(BootState, Counters)); } - const runtimeApi = { - adapters: BrowserSupportAdapters, + SendStatusButton.addEventListener('click', function onSendStatus() + { + UEBridge.sendState({ + type: 'browser-runtime-status', + source: 'browser-runtime-fallback', + status: buildRuntimeStatusSnapshot(BootState, Counters) + }); + AppendLog('Browser Runtime Status', 'Sent the current browser runtime status envelope to Unreal.'); + }); + + return { + panel: StatusPanel, + refresh + }; +} + +function createStatePanel() +{ + const StatePanel = createElement('section', 'panel'); + StatePanel.append( + createElement('h2', undefined, 'Browser Shell State'), + createElement( + 'p', + 'runtime-note', + 'Unreal can push shell-state and command envelopes into the fallback runtime exactly like the bundled runtime.' + ) + ); + + const StateData = createElement('div', 'data-block'); + const StatePre = createElement('pre'); + StatePre.textContent = '{\n "status": "waiting-for-unreal-shell-state"\n}'; + StateData.appendChild(StatePre); + StatePanel.appendChild(StateData); + + return { + panel: StatePanel, + setPayload(Payload) + { + StatePre.textContent = serializePretty(Payload); + } + }; +} + +function createAdapterCard(Adapter, UEBridge, AppendLog) +{ + const CardElement = createElement('article', 'card'); + const CardHeader = createElement('div', 'card-header'); + const CardTitleBlock = createElement('div'); + CardTitleBlock.append( + createElement('span', 'phase-chip', Adapter.phase), + createElement('h2', undefined, Adapter.repo) + ); + + const StatusChip = createElement('span', 'status-chip', 'idle'); + StatusChip.dataset.status = 'idle'; + CardHeader.append(CardTitleBlock, StatusChip); + + const ActivationChip = createElement('span', 'activation-chip', Adapter.activation); + const Description = createElement('p', undefined, Adapter.description); + const SourceCode = createElement('code', undefined, Adapter.sourcePathHint); + const Actions = createElement('div', 'card-actions'); + const LoadButton = createElement('button', undefined, 'Resolve Adapter'); + const EnvelopeButton = createElement('button', 'secondary', 'Send Envelope'); + + LoadButton.addEventListener('click', async function onLoadAdapter() + { + StatusChip.dataset.status = 'loading'; + StatusChip.textContent = 'loading'; + + try + { + const Result = await loadAdapter(Adapter.id); + StatusChip.dataset.status = Result.status === 'loaded' ? 'ready' : 'idle'; + StatusChip.textContent = Result.status; + AppendLog( + Adapter.repo, + Result.note || ('Resolved ' + Adapter.id + '.') + ); + } + catch (Error) + { + StatusChip.dataset.status = 'error'; + StatusChip.textContent = 'error'; + AppendLog(Adapter.repo, Error instanceof Error ? Error.message : 'Unknown load failure.'); + } + }); + + EnvelopeButton.addEventListener('click', function onSendEnvelope() + { + UEBridge.sendState({ + source: 'browser-runtime-fallback', + adapterId: Adapter.id, + repo: Adapter.repo, + activation: Adapter.activation, + runtimeMode: 'fallback-js' + }); + AppendLog(Adapter.repo, 'Sent adapter envelope to Unreal.'); + }); + + Actions.append(LoadButton, EnvelopeButton); + CardElement.append(CardHeader, ActivationChip, Description, SourceCode, Actions); + return CardElement; +} + +function createAdapterSection(UEBridge, AppendLog) +{ + const AdapterSection = createElement('section', 'section'); + AdapterSection.appendChild( + createSectionHeader( + 'Adapter Surface', + 'Adapters remain visible in clean checkout state. Bundled browser modules are marked deferred until `npm run build` produces the browser runtime bundle.' + ) + ); + + const AdapterGrid = createElement('div', 'stack-grid'); + BROWSER_SUPPORT_ADAPTERS.forEach(function appendAdapter(Adapter) + { + AdapterGrid.appendChild(createAdapterCard(Adapter, UEBridge, AppendLog)); + }); + AdapterSection.append(AdapterGrid); + + return AdapterSection; +} + +function createSolverPanel(UEBridge, AppendLog) +{ + const SolverPanel = createElement('section', 'panel'); + SolverPanel.append( + createElement('h2', undefined, 'Tentone Native Solver Request'), + createElement( + 'p', + 'runtime-note', + 'The OpenCV donor remains a native-sidecar lane. This fallback shell sends a first-party solver request envelope instead of pretending to import the donor in-browser.' + ) + ); + + const SolverField = createElement('label', 'field'); + SolverField.appendChild(createElement('span', undefined, 'Classic facelet string')); + const SolverInput = createElement('textarea'); + SolverInput.value = 'UUUUUUUUURRRRRRRRRFFFFFFFFFDDDDDDDDDLLLLLLLLLBBBBBBBBB'; + SolverField.appendChild(SolverInput); + + const SolverActions = createElement('div', 'solver-actions'); + const SolverButton = createElement('button', undefined, 'Send Solver Request'); + SolverButton.addEventListener('click', function onSolve() + { + UEBridge.sendState({ + type: 'tentone-native-solver-request', + source: 'browser-runtime-fallback', + faceletString: SolverInput.value.trim() + }); + AppendLog('Tentone Native Solver', 'Sent a solver request envelope to Unreal.'); + }); + SolverActions.appendChild(SolverButton); + SolverPanel.append(SolverField, SolverActions); + + return SolverPanel; +} + +function createUtilitySection(BootState, Counters, UEBridge) +{ + const UtilitySection = createElement('section', 'section'); + UtilitySection.appendChild( + createSectionHeader( + 'Fallback Utilities', + 'The plain-JS runtime keeps state bridging and native-sidecar requests live even when bundled browser modules have been cleaned after validation.' + ) + ); + + const { appendLog, panel: LogPanel } = createLogPanel(); + const { panel: StatusPanel, refresh: RefreshRuntimeStatus } = createRuntimeStatusPanel( + BootState, + Counters, + UEBridge, + appendLog + ); + const StatePanel = createStatePanel(); + const UtilityGrid = createElement('div', 'panel-grid'); + UtilityGrid.append( + StatusPanel, + createSolverPanel(UEBridge, appendLog), + StatePanel.panel, + LogPanel + ); + UtilitySection.appendChild(UtilityGrid); + + return { + appendLog, + refreshRuntimeStatus: RefreshRuntimeStatus, + section: UtilitySection, + setShellStatePayload: StatePanel.setPayload + }; +} + +function synchronizeCommandCounter(BootState, Counters) +{ + Counters.commandReceiveCount = BootState.commandReceiveCount || (Counters.commandReceiveCount + 1); +} + +function synchronizeShellStateCounter(BootState, Counters) +{ + Counters.shellStateReceiveCount = BootState.shellStateReceiveCount || (Counters.shellStateReceiveCount + 1); +} + +function wireBridgeSubscriptions(BootState, Counters, UEBridge, AppendLog, RefreshRuntimeStatus, SetShellStatePayload) +{ + UEBridge.onCommand(function onCommand(Payload) + { + synchronizeCommandCounter(BootState, Counters); + RefreshRuntimeStatus(); + AppendLog('UE Command', serializePretty(Payload)); + }); + + UEBridge.onShellState(function onShellState(Payload) + { + synchronizeShellStateCounter(BootState, Counters); + SetShellStatePayload(Payload); + RefreshRuntimeStatus(); + AppendLog('Browser Shell State', 'Received fresh shell state from Unreal.'); + }); +} + +function createBrowserShell(RootElement, BootState, UEBridge) +{ + RootElement.innerHTML = ''; + + const Counters = { + commandReceiveCount: BootState.commandReceiveCount || 0, + shellStateReceiveCount: BootState.shellStateReceiveCount + || (BootState.lastShellStateReceivedAtUtc ? 1 : 0) + }; + + const ShellElement = createElement('div', 'shell'); + const HeroElement = createHeroSection(BootState); + const UtilitySection = createUtilitySection(BootState, Counters, UEBridge); + const AdapterSection = createAdapterSection(UEBridge, UtilitySection.appendLog); + + ShellElement.append(HeroElement, AdapterSection, UtilitySection.section); + RootElement.appendChild(ShellElement); + UtilitySection.refreshRuntimeStatus(); + + wireBridgeSubscriptions( + BootState, + Counters, + UEBridge, + UtilitySection.appendLog, + UtilitySection.refreshRuntimeStatus, + UtilitySection.setShellStatePayload + ); +} + +function createFallbackRuntimeApi(BootState, UEBridge) +{ + return { + adapters: BROWSER_SUPPORT_ADAPTERS, listAdapters() { - return BrowserSupportAdapters.slice(); + return BROWSER_SUPPORT_ADAPTERS.slice(); }, loadAdapter, receiveCommand(Payload) @@ -679,28 +791,23 @@ }, renderShell(RootElement) { - createBrowserShell(RootElement); + createBrowserShell(RootElement, BootState, UEBridge); }, sendState(Payload) { UEBridge.sendState(Payload); } }; +} - window.HyperTwistBrowserRuntime = runtimeApi; - - const RootElement = document.getElementById('app'); - if (RootElement instanceof HTMLElement) - { - runtimeApi.renderShell(RootElement); - } - +function replayPendingBridgeTraffic(BootState, RuntimeApi) +{ const PendingCommands = Array.isArray(BootState.pendingCommands) ? BootState.pendingCommands.slice() : []; BootState.queuedCommandCountAtRuntimeReady = PendingCommands.length; BootState.pendingCommands = []; PendingCommands.forEach(function replayPendingCommand(Payload) { - runtimeApi.receiveCommand(Payload); + RuntimeApi.receiveCommand(Payload); }); const PendingShellStates = Array.isArray(BootState.pendingShellStates) ? BootState.pendingShellStates.slice() : []; @@ -708,9 +815,12 @@ BootState.pendingShellStates = []; PendingShellStates.forEach(function replayPendingShellState(Payload) { - runtimeApi.setShellState(Payload); + RuntimeApi.setShellState(Payload); }); +} +function notifyFallbackRuntimeReady(BootState, UEBridge) +{ UEBridge.notifyRuntimeReady({ status: 'ready', runtime: 'hypertwist-browser-runtime-fallback', @@ -721,4 +831,33 @@ queuedCommandCountAtRuntimeReady: BootState.queuedCommandCountAtRuntimeReady, queuedShellStateCountAtRuntimeReady: BootState.queuedShellStateCountAtRuntimeReady }); -})(); +} + +function mountFallbackShell(RuntimeApi) +{ + const RootElement = document.getElementById('app'); + if (RootElement instanceof HTMLElement) + { + RuntimeApi.renderShell(RootElement); + } +} + +function bootstrapHyperTwistBrowserFallbackRuntime() +{ + const BootState = resolveFallbackBootState(); + BootState.runtimeMode = 'fallback-js'; + BootState.runtimeReadyAtUtc = new Date().toISOString(); + BootState.runtimeStatusId = 'runtime-ready'; + BootState.adapterCount = BROWSER_SUPPORT_ADAPTERS.length; + BootState.shellAuthority = 'Content/Browser/index.html'; + + const UEBridge = createFallbackBridge(BootState); + const RuntimeApi = createFallbackRuntimeApi(BootState, UEBridge); + window.HyperTwistBrowserRuntime = RuntimeApi; + + mountFallbackShell(RuntimeApi); + replayPendingBridgeTraffic(BootState, RuntimeApi); + notifyFallbackRuntimeReady(BootState, UEBridge); +} + +bootstrapHyperTwistBrowserFallbackRuntime(); diff --git a/Content/Browser/src/runtime/shell.ts b/Content/Browser/src/runtime/shell.ts index 1591a4d..84c7adb 100644 --- a/Content/Browser/src/runtime/shell.ts +++ b/Content/Browser/src/runtime/shell.ts @@ -1,5 +1,5 @@ import { UEBridge } from './bridge'; -import { resolveBootState } from './boot-state'; +import { resolveBootState, type BrowserBootState } from './boot-state'; import { BrowserSupportAdapters, listAdapters, @@ -7,7 +7,31 @@ import { resolveSupportAssetUrl } from './registry'; -type AdapterStatus = 'idle' | 'loading' | 'ready' | 'error'; +type BrowserSupportAdapter = (typeof BrowserSupportAdapters)[number]; + +type RuntimeCounters = { + commandReceiveCount: number; + shellStateReceiveCount: number; +}; + +type LogWriter = (Title: string, Message: string) => void; + +type RuntimeStatusPanel = { + panel: HTMLElement; + refresh: () => void; +}; + +type StatePanel = { + panel: HTMLElement; + setPayload: (Payload: unknown) => void; +}; + +type UtilitySection = { + appendLog: LogWriter; + refreshRuntimeStatus: () => void; + section: HTMLElement; + setShellStatePayload: (Payload: unknown) => void; +}; function createElement( TagName: K, @@ -44,12 +68,18 @@ function serializePretty(Payload: unknown): string } } -export function createBrowserShell(RootElement: HTMLElement): void +function createSectionHeader(Title: string, Note: string): HTMLElement { - const BootState = resolveBootState(); - RootElement.innerHTML = ''; + const Header = createElement('div', 'section-header'); + Header.append( + createElement('h2', 'section-title', Title), + createElement('p', 'section-note', Note) + ); + return Header; +} - const ShellElement = createElement('div', 'shell'); +function createHeroSection(AdapterCount: number): HTMLElement +{ const HeroElement = createElement('section', 'hero'); const HeroTitle = createElement('h1', undefined, 'HyperTwist Browser Runtime'); const HeroParagraph = createElement( @@ -59,10 +89,10 @@ export function createBrowserShell(RootElement: HTMLElement): void ); const HeroMetrics = createElement('div', 'hero-grid'); - const Metrics = [ + [ { label: 'Adapters surfaced', - value: String(BrowserSupportAdapters.length) + value: String(AdapterCount) }, { label: 'Phase 1 coverage', @@ -76,226 +106,22 @@ export function createBrowserShell(RootElement: HTMLElement): void label: 'Shell authority', value: 'index.html + runtime bootstrap' } - ]; - - Metrics.forEach((Metric) => + ].forEach((Metric) => { const MetricElement = createElement('div', 'metric'); - const MetricLabel = createElement('strong', undefined, Metric.label); - const MetricValue = createElement('span', undefined, Metric.value); - MetricElement.append(MetricLabel, MetricValue); + MetricElement.append( + createElement('strong', undefined, Metric.label), + createElement('span', undefined, Metric.value) + ); HeroMetrics.appendChild(MetricElement); }); HeroElement.append(HeroTitle, HeroParagraph, HeroMetrics); + return HeroElement; +} - const AdapterSection = createElement('section', 'section'); - const AdapterHeader = createElement('div', 'section-header'); - const AdapterTitle = createElement('h2', 'section-title', 'Phase 1 Adapter Surface'); - const AdapterNote = createElement( - 'p', - 'section-note', - 'Load any adapter to prove the runtime can resolve it, or inspect the activation type when a donor is intentionally retained as a sidecar.' - ); - AdapterHeader.append(AdapterTitle, AdapterNote); - - const AdapterGrid = createElement('div', 'stack-grid'); - const StatusElements = new Map(); - - listAdapters().forEach((Adapter) => - { - const CardElement = createElement('article', 'card'); - const CardHeader = createElement('div', 'card-header'); - const CardTitleBlock = createElement('div'); - const PhaseChip = createElement('span', 'phase-chip', Adapter.phase); - const CardTitle = createElement('h2', undefined, Adapter.repo); - const ActivationChip = createElement('span', 'activation-chip', Adapter.activation); - const StatusChip = createElement('span', 'status-chip', 'idle'); - StatusChip.dataset.status = 'idle'; - StatusElements.set(Adapter.id, StatusChip); - - CardTitleBlock.append(PhaseChip, CardTitle); - CardHeader.append(CardTitleBlock, StatusChip); - - const Description = createElement('p', undefined, Adapter.description); - const SourceCode = createElement('code', undefined, `${Adapter.sourcePathHint}${Adapter.packageName ? ` -> ${Adapter.packageName}` : ''}`); - const Actions = createElement('div', 'card-actions'); - const LoadButton = createElement('button', undefined, 'Load Adapter'); - const InspectButton = createElement('button', 'secondary', 'Send Envelope'); - - LoadButton.addEventListener('click', async () => - { - StatusChip.dataset.status = 'loading'; - StatusChip.textContent = 'loading'; - - try - { - const Result = await loadAdapter(Adapter.id); - StatusChip.dataset.status = Result.status === 'loaded' ? 'ready' : 'idle'; - StatusChip.textContent = Result.status; - appendLog( - `${Adapter.repo}`, - Result.note - ? Result.note - : `Loaded ${Result.details?.join(', ') || Adapter.id}.` - ); - if (Adapter.phase === '1I' && Result.status === 'loaded') - { - mountAnalyticsDemo(); - } - if ((Adapter.phase === '1J' || Adapter.phase === '1E') && Result.status === 'loaded') - { - mountViewerDemo(); - } - } - catch (Error) - { - StatusChip.dataset.status = 'error'; - StatusChip.textContent = 'error'; - appendLog(`${Adapter.repo}`, Error instanceof Error ? Error.message : 'Unknown load failure.'); - } - }); - - InspectButton.addEventListener('click', () => - { - UEBridge.sendState({ - source: 'browser-runtime-shell', - adapterId: Adapter.id, - repo: Adapter.repo, - activation: Adapter.activation - }); - appendLog(Adapter.repo, 'Sent adapter envelope to the Unreal bridge.'); - }); - - Actions.append(LoadButton, InspectButton); - CardElement.append(CardHeader, ActivationChip, Description, SourceCode, Actions); - AdapterGrid.appendChild(CardElement); - }); - - AdapterSection.append(AdapterHeader, AdapterGrid); - - const UtilitySection = createElement('section', 'section'); - const UtilityHeader = createElement('div', 'section-header'); - UtilityHeader.append( - createElement('h2', 'section-title', 'Runtime Utilities'), - createElement( - 'p', - 'section-note', - 'The browser shell can render analytics, preview bundled viewer assets, and fall back into native solver routing without pretending native donors are browser packages or requiring retained build artifacts.' - ) - ); - - const UtilityGrid = createElement('div', 'panel-grid'); - let CommandReceiveCount = BootState.commandReceiveCount ?? 0; - let ShellStateReceiveCount = BootState.shellStateReceiveCount - ?? (BootState.lastShellStateReceivedAtUtc ? 1 : 0); - - const StatusPanel = createElement('section', 'panel'); - StatusPanel.append( - createElement('h2', undefined, 'Browser Runtime Status'), - createElement( - 'p', - 'runtime-note', - 'First-party boot-state metadata now surfaces runtime mode, retained startup queue carryover, and live Unreal bridge traffic inside the authoritative shell.' - ) - ); - const StatusActions = createElement('div', 'solver-actions'); - const SendStatusButton = createElement('button', undefined, 'Send Runtime Status'); - const StatusData = createElement('div', 'data-block'); - const StatusPre = createElement('pre'); - StatusData.appendChild(StatusPre); - StatusActions.appendChild(SendStatusButton); - StatusPanel.append(StatusActions, StatusData); - - const AnalyticsPanel = createElement('section', 'panel'); - AnalyticsPanel.append( - createElement('h2', undefined, 'Analytics Demo'), - createElement( - 'p', - 'runtime-note', - 'Loads the ECharts + zrender + echarts-gl lane and renders a small solve-trend chart.' - ) - ); - const AnalyticsActions = createElement('div', 'solver-actions'); - const LoadAnalyticsButton = createElement('button', undefined, 'Load Analytics Demo'); - LoadAnalyticsButton.addEventListener('click', async () => - { - await loadAdapter('1i-zrender'); - mountAnalyticsDemo(); - appendLog('Analytics Demo', 'Loaded the analytics bundle and rendered the demo chart.'); - }); - AnalyticsActions.appendChild(LoadAnalyticsButton); - const AnalyticsDemo = createElement('div', 'data-block'); - AnalyticsDemo.id = 'analytics-demo'; - AnalyticsDemo.textContent = 'Analytics chart will mount here after the adapter loads.'; - AnalyticsPanel.append(AnalyticsActions, AnalyticsDemo); - - const ViewerPanel = createElement('section', 'panel'); - ViewerPanel.append( - createElement('h2', undefined, 'Viewer Demo'), - createElement( - 'p', - 'runtime-note', - 'Loads the model-viewer and Khronos reference-viewer lane, then mounts a bounded local sample asset.' - ) - ); - const ViewerActions = createElement('div', 'solver-actions'); - const LoadViewerButton = createElement('button', undefined, 'Load Viewer Demo'); - LoadViewerButton.addEventListener('click', async () => - { - await loadAdapter('1j-model-viewer'); - mountViewerDemo(); - appendLog('Viewer Demo', 'Loaded the model-viewer stack and mounted the local sample asset.'); - }); - ViewerActions.appendChild(LoadViewerButton); - const ViewerDemo = createElement('div', 'data-block'); - ViewerDemo.id = 'viewer-demo'; - ViewerDemo.textContent = 'Viewer preview will appear here after the adapter loads.'; - ViewerPanel.append(ViewerActions, ViewerDemo); - - const SolverPanel = createElement('section', 'panel'); - SolverPanel.append( - createElement('h2', undefined, 'Tentone Fallback Solver'), - createElement( - 'p', - 'runtime-note', - 'The tentone donor is native/OpenCV, so the browser shell uses a fallback form that sends a solver request envelope back to Unreal instead of faking a browser import.' - ) - ); - const SolverField = createElement('label', 'field'); - SolverField.appendChild(createElement('span', undefined, 'Classic facelet string')); - const SolverInput = createElement('textarea') as HTMLTextAreaElement; - SolverInput.value = 'UUUUUUUUURRRRRRRRRFFFFFFFFFDDDDDDDDDLLLLLLLLLBBBBBBBBB'; - SolverField.appendChild(SolverInput); - const SolverActions = createElement('div', 'solver-actions'); - const SendSolverButton = createElement('button', undefined, 'Send Solver Request'); - SendSolverButton.addEventListener('click', () => - { - UEBridge.sendState({ - type: 'tentone-native-solver-request', - source: 'browser-shell-fallback', - faceletString: SolverInput.value.trim() - }); - appendLog('Tentone Fallback Solver', 'Sent a native-sidecar solver request envelope to Unreal.'); - }); - SolverActions.appendChild(SendSolverButton); - SolverPanel.append(SolverField, SolverActions); - - const StatePanel = createElement('section', 'panel'); - StatePanel.append( - createElement('h2', undefined, 'Browser Shell State'), - createElement( - 'p', - 'runtime-note', - 'Unreal can push browser-shell state or generic commands into this page through the embedded bridge.' - ) - ); - const StateData = createElement('div', 'data-block'); - const StatePre = createElement('pre'); - StatePre.textContent = '{\n "status": "waiting-for-unreal-shell-state"\n}'; - StateData.appendChild(StatePre); - StatePanel.appendChild(StateData); - +function createLogPanel(): { appendLog: LogWriter; panel: HTMLElement } +{ const LogPanel = createElement('section', 'panel'); LogPanel.append( createElement('h2', undefined, 'Runtime Log'), @@ -305,163 +131,499 @@ export function createBrowserShell(RootElement: HTMLElement): void 'Compact bridge and adapter events are recorded here for Unreal-side inspection.' ) ); + const LogList = createElement('div', 'log-list'); LogPanel.appendChild(LogList); - function appendLog(Title: string, Message: string): void - { - const Entry = createElement('div', 'log-entry'); - const TitleElement = createElement('strong', undefined, Title); - const MessageElement = createElement('div', undefined, Message); - Entry.append(TitleElement, MessageElement); - LogList.prepend(Entry); - } + return { + panel: LogPanel, + appendLog(Title: string, Message: string): void + { + const Entry = createElement('div', 'log-entry'); + Entry.append( + createElement('strong', undefined, Title), + createElement('div', undefined, Message) + ); + LogList.prepend(Entry); + } + }; +} - function buildRuntimeStatusSnapshot(): Record - { - const LastCommandReceivedAtUtc = BootState.lastCommandReceivedAtUtc ?? 'none'; - const LastShellStateReceivedAtUtc = BootState.lastShellStateReceivedAtUtc ?? 'none'; +function buildRuntimeStatusSnapshot( + BootState: BrowserBootState, + Counters: RuntimeCounters +): Record +{ + return { + runtimeStatusId: BootState.runtimeStatusId ?? 'runtime-ready', + runtimeMode: BootState.runtimeMode ?? 'unknown', + bootstrapStartedAtUtc: BootState.bootstrapStartedAtUtc ?? 'pending', + runtimeReadyAtUtc: BootState.runtimeReadyAtUtc ?? 'pending', + shellAuthority: BootState.shellAuthority ?? 'Content/Browser/index.html', + adapterCount: BootState.adapterCount ?? BrowserSupportAdapters.length, + queuedCommandCountAtRuntimeReady: BootState.queuedCommandCountAtRuntimeReady ?? 0, + queuedShellStateCountAtRuntimeReady: BootState.queuedShellStateCountAtRuntimeReady ?? 0, + commandReceiveCount: Counters.commandReceiveCount, + shellStateReceiveCount: Counters.shellStateReceiveCount, + lastCommandReceivedAtUtc: BootState.lastCommandReceivedAtUtc ?? 'none', + lastShellStateReceivedAtUtc: BootState.lastShellStateReceivedAtUtc ?? 'none', + bridgeTrafficStatus: Counters.shellStateReceiveCount > 0 + ? 'shell-state-live' + : Counters.commandReceiveCount > 0 + ? 'command-only-live' + : 'awaiting-unreal-traffic', + fallbackReason: BootState.fallbackReason ?? null + }; +} - return { - runtimeStatusId: BootState.runtimeStatusId ?? 'runtime-ready', - runtimeMode: BootState.runtimeMode ?? 'unknown', - bootstrapStartedAtUtc: BootState.bootstrapStartedAtUtc ?? 'pending', - runtimeReadyAtUtc: BootState.runtimeReadyAtUtc ?? 'pending', - shellAuthority: BootState.shellAuthority ?? 'Content/Browser/index.html', - adapterCount: BootState.adapterCount ?? BrowserSupportAdapters.length, - queuedCommandCountAtRuntimeReady: BootState.queuedCommandCountAtRuntimeReady ?? 0, - queuedShellStateCountAtRuntimeReady: BootState.queuedShellStateCountAtRuntimeReady ?? 0, - commandReceiveCount: CommandReceiveCount, - shellStateReceiveCount: ShellStateReceiveCount, - lastCommandReceivedAtUtc: LastCommandReceivedAtUtc, - lastShellStateReceivedAtUtc: LastShellStateReceivedAtUtc, - bridgeTrafficStatus: ShellStateReceiveCount > 0 - ? 'shell-state-live' - : CommandReceiveCount > 0 - ? 'command-only-live' - : 'awaiting-unreal-traffic', - fallbackReason: BootState.fallbackReason ?? null - }; - } +function createRuntimeStatusPanel( + BootState: BrowserBootState, + Counters: RuntimeCounters, + AppendLog: LogWriter +): RuntimeStatusPanel +{ + const StatusPanel = createElement('section', 'panel'); + StatusPanel.append( + createElement('h2', undefined, 'Browser Runtime Status'), + createElement( + 'p', + 'runtime-note', + 'First-party boot-state metadata now surfaces runtime mode, retained startup queue carryover, and live Unreal bridge traffic inside the authoritative shell.' + ) + ); - function refreshRuntimeStatusPanel(): void + const StatusActions = createElement('div', 'solver-actions'); + const SendStatusButton = createElement('button', undefined, 'Send Runtime Status'); + const StatusData = createElement('div', 'data-block'); + const StatusPre = createElement('pre'); + StatusData.appendChild(StatusPre); + StatusActions.appendChild(SendStatusButton); + StatusPanel.append(StatusActions, StatusData); + + const Refresh = (): void => { - StatusPre.textContent = serializePretty(buildRuntimeStatusSnapshot()); - } + StatusPre.textContent = serializePretty(buildRuntimeStatusSnapshot(BootState, Counters)); + }; SendStatusButton.addEventListener('click', () => { UEBridge.sendState({ type: 'browser-runtime-status', source: 'browser-runtime-shell', - status: buildRuntimeStatusSnapshot() + status: buildRuntimeStatusSnapshot(BootState, Counters) }); - appendLog('Browser Runtime Status', 'Sent the current browser runtime status envelope to Unreal.'); + AppendLog('Browser Runtime Status', 'Sent the current browser runtime status envelope to Unreal.'); }); - function mountAnalyticsDemo(): void - { - const ChartHost = document.getElementById('analytics-demo'); - if (!(ChartHost instanceof HTMLElement)) - { - return; - } + return { + panel: StatusPanel, + refresh: Refresh + }; +} - import('echarts').then((EChartsModule) => - { - const ExistingInstance = EChartsModule.getInstanceByDom(ChartHost); - const Chart = ExistingInstance || EChartsModule.init(ChartHost, undefined, { renderer: 'canvas' }); - Chart.setOption({ - backgroundColor: 'transparent', - grid: { - top: 24, - right: 16, - bottom: 24, - left: 32 +async function mountAnalyticsDemo(): Promise +{ + const ChartHost = document.getElementById('analytics-demo'); + if (!(ChartHost instanceof HTMLElement)) + { + return; + } + + const EChartsModule = await import('echarts'); + const ExistingInstance = EChartsModule.getInstanceByDom(ChartHost); + const Chart = ExistingInstance || EChartsModule.init(ChartHost, undefined, { renderer: 'canvas' }); + Chart.setOption({ + backgroundColor: 'transparent', + grid: { + top: 24, + right: 16, + bottom: 24, + left: 32 + }, + xAxis: { + type: 'category', + data: ['Solve 1', 'Solve 2', 'Solve 3', 'Solve 4', 'Solve 5'], + axisLabel: { + color: '#afc0d6' + } + }, + yAxis: { + type: 'value', + axisLabel: { + color: '#afc0d6', + formatter: (Value: number) => `${Value}s` + } + }, + series: [ + { + type: 'line', + data: [24.3, 22.7, 21.8, 20.9, 19.4], + smooth: true, + lineStyle: { + color: '#8ee3ff', + width: 3 }, - xAxis: { - type: 'category', - data: ['Solve 1', 'Solve 2', 'Solve 3', 'Solve 4', 'Solve 5'], - axisLabel: { - color: '#afc0d6' - } - }, - yAxis: { - type: 'value', - axisLabel: { - color: '#afc0d6', - formatter: (Value: number) => `${Value}s` - } - }, - series: [ - { - type: 'line', - data: [24.3, 22.7, 21.8, 20.9, 19.4], - smooth: true, - lineStyle: { - color: '#8ee3ff', - width: 3 - }, - areaStyle: { - color: 'rgba(82, 199, 255, 0.18)' - } - } - ] - }); + areaStyle: { + color: 'rgba(82, 199, 255, 0.18)' + } + } + ] + }); +} + +function mountViewerDemo(): void +{ + const ViewerHost = document.getElementById('viewer-demo'); + if (!(ViewerHost instanceof HTMLElement)) + { + return; + } + + ViewerHost.innerHTML = ''; + const ModelViewer = document.createElement('model-viewer'); + ModelViewer.setAttribute( + 'src', + resolveSupportAssetUrl('.external/model-viewer/packages/shared-assets/models/cube.gltf') + ); + ModelViewer.setAttribute( + 'environment-image', + resolveSupportAssetUrl('.external/model-viewer/packages/shared-assets/environments/aircraft_workshop_01_1k.hdr') + ); + ModelViewer.setAttribute('camera-controls', ''); + ModelViewer.setAttribute('auto-rotate', ''); + ModelViewer.setAttribute('shadow-intensity', '1'); + ModelViewer.setAttribute('exposure', '1.1'); + ViewerHost.appendChild(ModelViewer); +} + +function createAnalyticsPanel(AppendLog: LogWriter): HTMLElement +{ + const AnalyticsPanel = createElement('section', 'panel'); + AnalyticsPanel.append( + createElement('h2', undefined, 'Analytics Demo'), + createElement( + 'p', + 'runtime-note', + 'Loads the ECharts + zrender + echarts-gl lane and renders a small solve-trend chart.' + ) + ); + + const AnalyticsActions = createElement('div', 'solver-actions'); + const LoadAnalyticsButton = createElement('button', undefined, 'Load Analytics Demo'); + LoadAnalyticsButton.addEventListener('click', async () => + { + await loadAdapter('1i-zrender'); + await mountAnalyticsDemo(); + AppendLog('Analytics Demo', 'Loaded the analytics bundle and rendered the demo chart.'); + }); + AnalyticsActions.appendChild(LoadAnalyticsButton); + + const AnalyticsDemo = createElement('div', 'data-block'); + AnalyticsDemo.id = 'analytics-demo'; + AnalyticsDemo.textContent = 'Analytics chart will mount here after the adapter loads.'; + AnalyticsPanel.append(AnalyticsActions, AnalyticsDemo); + + return AnalyticsPanel; +} + +function createViewerPanel(AppendLog: LogWriter): HTMLElement +{ + const ViewerPanel = createElement('section', 'panel'); + ViewerPanel.append( + createElement('h2', undefined, 'Viewer Demo'), + createElement( + 'p', + 'runtime-note', + 'Loads the model-viewer and Khronos reference-viewer lane, then mounts a bounded local sample asset.' + ) + ); + + const ViewerActions = createElement('div', 'solver-actions'); + const LoadViewerButton = createElement('button', undefined, 'Load Viewer Demo'); + LoadViewerButton.addEventListener('click', async () => + { + await loadAdapter('1j-model-viewer'); + mountViewerDemo(); + AppendLog('Viewer Demo', 'Loaded the model-viewer stack and mounted the local sample asset.'); + }); + ViewerActions.appendChild(LoadViewerButton); + + const ViewerDemo = createElement('div', 'data-block'); + ViewerDemo.id = 'viewer-demo'; + ViewerDemo.textContent = 'Viewer preview will appear here after the adapter loads.'; + ViewerPanel.append(ViewerActions, ViewerDemo); + + return ViewerPanel; +} + +function createSolverPanel(AppendLog: LogWriter): HTMLElement +{ + const SolverPanel = createElement('section', 'panel'); + SolverPanel.append( + createElement('h2', undefined, 'Tentone Fallback Solver'), + createElement( + 'p', + 'runtime-note', + 'The tentone donor is native/OpenCV, so the browser shell uses a fallback form that sends a solver request envelope back to Unreal instead of faking a browser import.' + ) + ); + + const SolverField = createElement('label', 'field'); + SolverField.appendChild(createElement('span', undefined, 'Classic facelet string')); + const SolverInput = createElement('textarea') as HTMLTextAreaElement; + SolverInput.value = 'UUUUUUUUURRRRRRRRRFFFFFFFFFDDDDDDDDDLLLLLLLLLBBBBBBBBB'; + SolverField.appendChild(SolverInput); + + const SolverActions = createElement('div', 'solver-actions'); + const SendSolverButton = createElement('button', undefined, 'Send Solver Request'); + SendSolverButton.addEventListener('click', () => + { + UEBridge.sendState({ + type: 'tentone-native-solver-request', + source: 'browser-shell-fallback', + faceletString: SolverInput.value.trim() }); - } + AppendLog('Tentone Fallback Solver', 'Sent a native-sidecar solver request envelope to Unreal.'); + }); + SolverActions.appendChild(SendSolverButton); + SolverPanel.append(SolverField, SolverActions); - function mountViewerDemo(): void - { - const ViewerHost = document.getElementById('viewer-demo'); - if (!(ViewerHost instanceof HTMLElement)) + return SolverPanel; +} + +function createStatePanel(): StatePanel +{ + const ShellStatePanel = createElement('section', 'panel'); + ShellStatePanel.append( + createElement('h2', undefined, 'Browser Shell State'), + createElement( + 'p', + 'runtime-note', + 'Unreal can push browser-shell state or generic commands into this page through the embedded bridge.' + ) + ); + + const StateData = createElement('div', 'data-block'); + const StatePre = createElement('pre'); + StatePre.textContent = '{\n "status": "waiting-for-unreal-shell-state"\n}'; + StateData.appendChild(StatePre); + ShellStatePanel.appendChild(StateData); + + return { + panel: ShellStatePanel, + setPayload(Payload: unknown): void { - return; + StatePre.textContent = serializePretty(Payload); } + }; +} - ViewerHost.innerHTML = ''; - const ModelViewer = document.createElement('model-viewer'); - ModelViewer.setAttribute( - 'src', - resolveSupportAssetUrl('.external/model-viewer/packages/shared-assets/models/cube.gltf') - ); - ModelViewer.setAttribute( - 'environment-image', - resolveSupportAssetUrl('.external/model-viewer/packages/shared-assets/environments/aircraft_workshop_01_1k.hdr') - ); - ModelViewer.setAttribute('camera-controls', ''); - ModelViewer.setAttribute('auto-rotate', ''); - ModelViewer.setAttribute('shadow-intensity', '1'); - ModelViewer.setAttribute('exposure', '1.1'); - ViewerHost.appendChild(ModelViewer); - } +function createUtilitySection( + BootState: BrowserBootState, + Counters: RuntimeCounters +): UtilitySection +{ + const UtilitySectionElement = createElement('section', 'section'); + UtilitySectionElement.appendChild( + createSectionHeader( + 'Runtime Utilities', + 'The browser shell can render analytics, preview bundled viewer assets, and fall back into native solver routing without pretending native donors are browser packages or requiring retained build artifacts.' + ) + ); + const { appendLog, panel: LogPanel } = createLogPanel(); + const { panel: StatusPanel, refresh: RefreshRuntimeStatus } = createRuntimeStatusPanel( + BootState, + Counters, + appendLog + ); + const StatePanel = createStatePanel(); + const UtilityGrid = createElement('div', 'panel-grid'); UtilityGrid.append( StatusPanel, - AnalyticsPanel, - ViewerPanel, - SolverPanel, - StatePanel, + createAnalyticsPanel(appendLog), + createViewerPanel(appendLog), + createSolverPanel(appendLog), + StatePanel.panel, LogPanel ); - UtilitySection.append(UtilityHeader, UtilityGrid); + UtilitySectionElement.appendChild(UtilityGrid); - ShellElement.append(HeroElement, AdapterSection, UtilitySection); - RootElement.appendChild(ShellElement); - refreshRuntimeStatusPanel(); + return { + appendLog, + refreshRuntimeStatus: RefreshRuntimeStatus, + section: UtilitySectionElement, + setShellStatePayload: StatePanel.setPayload + }; +} +function createAdapterCard( + Adapter: BrowserSupportAdapter, + AppendLog: LogWriter +): HTMLElement +{ + const CardElement = createElement('article', 'card'); + const CardHeader = createElement('div', 'card-header'); + const CardTitleBlock = createElement('div'); + const PhaseChip = createElement('span', 'phase-chip', Adapter.phase); + const CardTitle = createElement('h2', undefined, Adapter.repo); + const ActivationChip = createElement('span', 'activation-chip', Adapter.activation); + const StatusChip = createElement('span', 'status-chip', 'idle'); + StatusChip.dataset.status = 'idle'; + + CardTitleBlock.append(PhaseChip, CardTitle); + CardHeader.append(CardTitleBlock, StatusChip); + + const Description = createElement('p', undefined, Adapter.description); + const SourceCode = createElement( + 'code', + undefined, + `${Adapter.sourcePathHint}${Adapter.packageName ? ` -> ${Adapter.packageName}` : ''}` + ); + const Actions = createElement('div', 'card-actions'); + const LoadButton = createElement('button', undefined, 'Load Adapter'); + const InspectButton = createElement('button', 'secondary', 'Send Envelope'); + + LoadButton.addEventListener('click', async () => + { + StatusChip.dataset.status = 'loading'; + StatusChip.textContent = 'loading'; + + try + { + const Result = await loadAdapter(Adapter.id); + StatusChip.dataset.status = Result.status === 'loaded' ? 'ready' : 'idle'; + StatusChip.textContent = Result.status; + + if (Adapter.phase === '1I' && Result.status === 'loaded') + { + await mountAnalyticsDemo(); + } + if ((Adapter.phase === '1J' || Adapter.phase === '1E') && Result.status === 'loaded') + { + mountViewerDemo(); + } + + AppendLog( + Adapter.repo, + Result.note + ? Result.note + : `Loaded ${Result.details?.join(', ') || Adapter.id}.` + ); + } + catch (Error) + { + StatusChip.dataset.status = 'error'; + StatusChip.textContent = 'error'; + AppendLog(Adapter.repo, Error instanceof Error ? Error.message : 'Unknown load failure.'); + } + }); + + InspectButton.addEventListener('click', () => + { + UEBridge.sendState({ + source: 'browser-runtime-shell', + adapterId: Adapter.id, + repo: Adapter.repo, + activation: Adapter.activation + }); + AppendLog(Adapter.repo, 'Sent adapter envelope to the Unreal bridge.'); + }); + + Actions.append(LoadButton, InspectButton); + CardElement.append(CardHeader, ActivationChip, Description, SourceCode, Actions); + return CardElement; +} + +function createAdapterSection( + Adapters: BrowserSupportAdapter[], + AppendLog: LogWriter +): HTMLElement +{ + const AdapterSection = createElement('section', 'section'); + AdapterSection.appendChild( + createSectionHeader( + 'Phase 1 Adapter Surface', + 'Load any adapter to prove the runtime can resolve it, or inspect the activation type when a donor is intentionally retained as a sidecar.' + ) + ); + + const AdapterGrid = createElement('div', 'stack-grid'); + Adapters.forEach((Adapter) => + { + AdapterGrid.appendChild(createAdapterCard(Adapter, AppendLog)); + }); + AdapterSection.appendChild(AdapterGrid); + + return AdapterSection; +} + +function synchronizeCommandCounter( + BootState: BrowserBootState, + Counters: RuntimeCounters +): void +{ + Counters.commandReceiveCount = BootState.commandReceiveCount ?? (Counters.commandReceiveCount + 1); +} + +function synchronizeShellStateCounter( + BootState: BrowserBootState, + Counters: RuntimeCounters +): void +{ + Counters.shellStateReceiveCount = BootState.shellStateReceiveCount ?? (Counters.shellStateReceiveCount + 1); +} + +function wireBridgeSubscriptions( + BootState: BrowserBootState, + Counters: RuntimeCounters, + AppendLog: LogWriter, + RefreshRuntimeStatus: () => void, + SetShellStatePayload: (Payload: unknown) => void +): void +{ UEBridge.onCommand((Payload) => { - CommandReceiveCount = BootState.commandReceiveCount ?? (CommandReceiveCount + 1); - refreshRuntimeStatusPanel(); - appendLog('UE Command', serializePretty(Payload)); + synchronizeCommandCounter(BootState, Counters); + RefreshRuntimeStatus(); + AppendLog('UE Command', serializePretty(Payload)); }); UEBridge.onShellState((Payload) => { - ShellStateReceiveCount = BootState.shellStateReceiveCount ?? (ShellStateReceiveCount + 1); - StatePre.textContent = serializePretty(Payload); - refreshRuntimeStatusPanel(); - appendLog('Browser Shell State', 'Received fresh shell state from Unreal.'); + synchronizeShellStateCounter(BootState, Counters); + SetShellStatePayload(Payload); + RefreshRuntimeStatus(); + AppendLog('Browser Shell State', 'Received fresh shell state from Unreal.'); }); } + +export function createBrowserShell(RootElement: HTMLElement): void +{ + const BootState = resolveBootState(); + const Adapters = listAdapters(); + const Counters: RuntimeCounters = { + commandReceiveCount: BootState.commandReceiveCount ?? 0, + shellStateReceiveCount: BootState.shellStateReceiveCount + ?? (BootState.lastShellStateReceivedAtUtc ? 1 : 0) + }; + + RootElement.innerHTML = ''; + + const ShellElement = createElement('div', 'shell'); + const HeroElement = createHeroSection(Adapters.length); + const UtilitySection = createUtilitySection(BootState, Counters); + const AdapterSection = createAdapterSection(Adapters, UtilitySection.appendLog); + + ShellElement.append(HeroElement, AdapterSection, UtilitySection.section); + RootElement.appendChild(ShellElement); + UtilitySection.refreshRuntimeStatus(); + + wireBridgeSubscriptions( + BootState, + Counters, + UtilitySection.appendLog, + UtilitySection.refreshRuntimeStatus, + UtilitySection.setShellStatePayload + ); +} diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistSimulation/HyperTwistClassicCubeGameMode.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistSimulation/HyperTwistClassicCubeGameMode.cpp index 64b5f9b..47b6384 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistSimulation/HyperTwistClassicCubeGameMode.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistSimulation/HyperTwistClassicCubeGameMode.cpp @@ -317,6 +317,51 @@ AHyperTwistClassicCubeGameMode::AHyperTwistClassicCubeGameMode() ); } +void AHyperTwistClassicCubeGameMode::RequestClassicCubeFreshAttempt_Implementation() +{ + StartFreshAttempt(); +} + +void AHyperTwistClassicCubeGameMode::RequestClassicCubeHint_Implementation() +{ + RequestHint(); +} + +void AHyperTwistClassicCubeGameMode::RequestClassicCubeSubmitSolve_Implementation() +{ + SubmitCurrentSolve(); +} + +void AHyperTwistClassicCubeGameMode::RequestClassicCubeToggleFollowAlongMode_Implementation() +{ + ToggleFollowAlongMode(); +} + +void AHyperTwistClassicCubeGameMode::RequestClassicCubeBeginVoiceCommandCapture_Implementation() +{ + BeginVoiceCommandCapture(); +} + +void AHyperTwistClassicCubeGameMode::RequestClassicCubeEndVoiceCommandCapture_Implementation() +{ + EndVoiceCommandCapture(); +} + +void AHyperTwistClassicCubeGameMode::RequestClassicCubeCycleVoiceProfile_Implementation() +{ + CycleVoiceProfile(); +} + +bool AHyperTwistClassicCubeGameMode::CanClassicCubeAcceptGameplayMoveInput_Implementation() const +{ + return CanAcceptGameplayMoveInput(); +} + +bool AHyperTwistClassicCubeGameMode::IsClassicCubeAnyActionButtonHovered_Implementation() const +{ + return ActiveHudWidget != nullptr && ActiveHudWidget->IsAnyActionButtonHovered(); +} + void AHyperTwistClassicCubeGameMode::BeginPlay() { Super::BeginPlay(); diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistSimulation/HyperTwistClassicCubeHUDWidget.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistSimulation/HyperTwistClassicCubeHUDWidget.cpp index 232c810..c5eb153 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistSimulation/HyperTwistClassicCubeHUDWidget.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistSimulation/HyperTwistClassicCubeHUDWidget.cpp @@ -6,7 +6,28 @@ #include "Components/TextBlock.h" #include "Components/VerticalBox.h" #include "Components/VerticalBoxSlot.h" -#include "HyperTwistSimulation/HyperTwistClassicCubeGameMode.h" +#include "GameFramework/GameModeBase.h" +#include "HyperTwistSimulation/HyperTwistClassicCubeOperatorSurface.h" + +namespace HyperTwistClassicCubeHUDWidgetInternal +{ + UObject* ResolveOperatorSurfaceObject(const UUserWidget* Widget) + { + if (Widget == nullptr || Widget->GetWorld() == nullptr) + { + return nullptr; + } + + AGameModeBase* GameMode = Widget->GetWorld()->GetAuthGameMode(); + if (GameMode == nullptr + || !GameMode->GetClass()->ImplementsInterface(UHyperTwistClassicCubeOperatorSurface::StaticClass())) + { + return nullptr; + } + + return GameMode; + } +} void UHyperTwistClassicCubeHUDWidget::NativeConstruct() { @@ -297,91 +318,75 @@ UButton* UHyperTwistClassicCubeHUDWidget::CreateActionButton( void UHyperTwistClassicCubeHUDWidget::HandleNewScrambleClicked() { bNewScrambleButtonHovered = false; - if (GetWorld() == nullptr) + if (UObject* OperatorSurface = + HyperTwistClassicCubeHUDWidgetInternal::ResolveOperatorSurfaceObject(this)) { - return; - } - - if (AHyperTwistClassicCubeGameMode* GameMode = Cast(GetWorld()->GetAuthGameMode())) - { - GameMode->StartFreshAttempt(); + IHyperTwistClassicCubeOperatorSurface::Execute_RequestClassicCubeFreshAttempt( + OperatorSurface + ); } } void UHyperTwistClassicCubeHUDWidget::HandleHintClicked() { - if (GetWorld() == nullptr) + if (UObject* OperatorSurface = + HyperTwistClassicCubeHUDWidgetInternal::ResolveOperatorSurfaceObject(this)) { - return; - } - - if (AHyperTwistClassicCubeGameMode* GameMode = Cast(GetWorld()->GetAuthGameMode())) - { - GameMode->RequestHint(); + IHyperTwistClassicCubeOperatorSurface::Execute_RequestClassicCubeHint(OperatorSurface); } } void UHyperTwistClassicCubeHUDWidget::HandleSubmitSolveClicked() { - if (GetWorld() == nullptr) + if (UObject* OperatorSurface = + HyperTwistClassicCubeHUDWidgetInternal::ResolveOperatorSurfaceObject(this)) { - return; - } - - if (AHyperTwistClassicCubeGameMode* GameMode = Cast(GetWorld()->GetAuthGameMode())) - { - GameMode->SubmitCurrentSolve(); + IHyperTwistClassicCubeOperatorSurface::Execute_RequestClassicCubeSubmitSolve( + OperatorSurface + ); } } void UHyperTwistClassicCubeHUDWidget::HandleModeToggleClicked() { - if (GetWorld() == nullptr) + if (UObject* OperatorSurface = + HyperTwistClassicCubeHUDWidgetInternal::ResolveOperatorSurfaceObject(this)) { - return; - } - - if (AHyperTwistClassicCubeGameMode* GameMode = Cast(GetWorld()->GetAuthGameMode())) - { - GameMode->ToggleFollowAlongMode(); + IHyperTwistClassicCubeOperatorSurface::Execute_RequestClassicCubeToggleFollowAlongMode( + OperatorSurface + ); } } void UHyperTwistClassicCubeHUDWidget::HandleVoicePressed() { - if (GetWorld() == nullptr) + if (UObject* OperatorSurface = + HyperTwistClassicCubeHUDWidgetInternal::ResolveOperatorSurfaceObject(this)) { - return; - } - - if (AHyperTwistClassicCubeGameMode* GameMode = Cast(GetWorld()->GetAuthGameMode())) - { - GameMode->BeginVoiceCommandCapture(); + IHyperTwistClassicCubeOperatorSurface::Execute_RequestClassicCubeBeginVoiceCommandCapture( + OperatorSurface + ); } } void UHyperTwistClassicCubeHUDWidget::HandleVoiceReleased() { - if (GetWorld() == nullptr) + if (UObject* OperatorSurface = + HyperTwistClassicCubeHUDWidgetInternal::ResolveOperatorSurfaceObject(this)) { - return; - } - - if (AHyperTwistClassicCubeGameMode* GameMode = Cast(GetWorld()->GetAuthGameMode())) - { - GameMode->EndVoiceCommandCapture(); + IHyperTwistClassicCubeOperatorSurface::Execute_RequestClassicCubeEndVoiceCommandCapture( + OperatorSurface + ); } } void UHyperTwistClassicCubeHUDWidget::HandleVoiceCycleClicked() { - if (GetWorld() == nullptr) + if (UObject* OperatorSurface = + HyperTwistClassicCubeHUDWidgetInternal::ResolveOperatorSurfaceObject(this)) { - return; - } - - if (AHyperTwistClassicCubeGameMode* GameMode = Cast(GetWorld()->GetAuthGameMode())) - { - GameMode->CycleVoiceProfile(); + IHyperTwistClassicCubeOperatorSurface::Execute_RequestClassicCubeCycleVoiceProfile( + OperatorSurface + ); } } diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistSimulation/HyperTwistClassicCubeOrbitPawn.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistSimulation/HyperTwistClassicCubeOrbitPawn.cpp index 5fcce85..5a1b44d 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistSimulation/HyperTwistClassicCubeOrbitPawn.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistSimulation/HyperTwistClassicCubeOrbitPawn.cpp @@ -5,7 +5,6 @@ #include "GameFramework/PlayerController.h" #include "GameFramework/SpringArmComponent.h" #include "HyperTwistSimulation/HyperTwistClassicCubeActor.h" -#include "HyperTwistSimulation/HyperTwistClassicCubeGameMode.h" #include "InputCoreTypes.h" #include "Components/SceneComponent.h" @@ -120,15 +119,6 @@ AHyperTwistClassicCubeActor* AHyperTwistClassicCubeOrbitPawn::ResolveFocusCubeAc return nullptr; } - if (const AHyperTwistClassicCubeGameMode* GameMode = - Cast(GetWorld()->GetAuthGameMode())) - { - if (GameMode->ActiveCubeActor != nullptr) - { - return GameMode->ActiveCubeActor; - } - } - for (TActorIterator ActorIt(GetWorld()); ActorIt; ++ActorIt) { return *ActorIt; diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistSimulation/HyperTwistClassicCubePlayerController.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistSimulation/HyperTwistClassicCubePlayerController.cpp index 1680f2a..d2c4c4e 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistSimulation/HyperTwistClassicCubePlayerController.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistSimulation/HyperTwistClassicCubePlayerController.cpp @@ -1,11 +1,31 @@ #include "HyperTwistSimulation/HyperTwistClassicCubePlayerController.h" #include "EngineUtils.h" +#include "GameFramework/GameModeBase.h" #include "HyperTwistSimulation/HyperTwistClassicCubeActor.h" -#include "HyperTwistSimulation/HyperTwistClassicCubeGameMode.h" -#include "HyperTwistSimulation/HyperTwistClassicCubeHUDWidget.h" +#include "HyperTwistSimulation/HyperTwistClassicCubeOperatorSurface.h" #include "InputCoreTypes.h" +namespace HyperTwistClassicCubePlayerControllerInternal +{ + UObject* ResolveOperatorSurfaceObject(const APlayerController* PlayerController) + { + if (PlayerController == nullptr || PlayerController->GetWorld() == nullptr) + { + return nullptr; + } + + AGameModeBase* GameMode = PlayerController->GetWorld()->GetAuthGameMode(); + if (GameMode == nullptr + || !GameMode->GetClass()->ImplementsInterface(UHyperTwistClassicCubeOperatorSurface::StaticClass())) + { + return nullptr; + } + + return GameMode; + } +} + AHyperTwistClassicCubePlayerController::AHyperTwistClassicCubePlayerController() { bShowMouseCursor = true; @@ -144,9 +164,11 @@ bool AHyperTwistClassicCubePlayerController::TryProcessCubeClickFromScreenPositi return false; } - if (AHyperTwistClassicCubeGameMode* GameMode = ResolveClassicCubeGameMode()) + if (UObject* OperatorSurface = + HyperTwistClassicCubePlayerControllerInternal::ResolveOperatorSurfaceObject(this)) { - if (!GameMode->CanAcceptGameplayMoveInput()) + if (!IHyperTwistClassicCubeOperatorSurface::Execute_CanClassicCubeAcceptGameplayMoveInput( + OperatorSurface)) { return false; } @@ -162,9 +184,12 @@ bool AHyperTwistClassicCubePlayerController::TryProcessCubeClickFromScreenPositi void AHyperTwistClassicCubePlayerController::RequestFreshAttempt() { - if (AHyperTwistClassicCubeGameMode* GameMode = ResolveClassicCubeGameMode()) + if (UObject* OperatorSurface = + HyperTwistClassicCubePlayerControllerInternal::ResolveOperatorSurfaceObject(this)) { - GameMode->StartFreshAttempt(); + IHyperTwistClassicCubeOperatorSurface::Execute_RequestClassicCubeFreshAttempt( + OperatorSurface + ); } } @@ -190,14 +215,6 @@ AHyperTwistClassicCubeActor* AHyperTwistClassicCubePlayerController::ResolveClas return nullptr; } - if (const AHyperTwistClassicCubeGameMode* GameMode = ResolveClassicCubeGameMode()) - { - if (GameMode->ActiveCubeActor != nullptr) - { - return GameMode->ActiveCubeActor; - } - } - for (TActorIterator ActorIt(GetWorld()); ActorIt; ++ActorIt) { return *ActorIt; @@ -206,18 +223,13 @@ AHyperTwistClassicCubeActor* AHyperTwistClassicCubePlayerController::ResolveClas return nullptr; } -AHyperTwistClassicCubeGameMode* AHyperTwistClassicCubePlayerController::ResolveClassicCubeGameMode() const -{ - return Cast( - GetWorld() != nullptr ? GetWorld()->GetAuthGameMode() : nullptr - ); -} - void AHyperTwistClassicCubePlayerController::HandlePrimaryClick() { - if (const AHyperTwistClassicCubeGameMode* GameMode = ResolveClassicCubeGameMode()) + if (UObject* OperatorSurface = + HyperTwistClassicCubePlayerControllerInternal::ResolveOperatorSurfaceObject(this)) { - if (GameMode->ActiveHudWidget != nullptr && GameMode->ActiveHudWidget->IsAnyActionButtonHovered()) + if (IHyperTwistClassicCubeOperatorSurface::Execute_IsClassicCubeAnyActionButtonHovered( + OperatorSurface)) { return; } @@ -233,9 +245,11 @@ void AHyperTwistClassicCubePlayerController::HandleSecondaryClick() return; } - if (const AHyperTwistClassicCubeGameMode* GameMode = ResolveClassicCubeGameMode()) + if (UObject* OperatorSurface = + HyperTwistClassicCubePlayerControllerInternal::ResolveOperatorSurfaceObject(this)) { - if (GameMode->ActiveHudWidget != nullptr && GameMode->ActiveHudWidget->IsAnyActionButtonHovered()) + if (IHyperTwistClassicCubeOperatorSurface::Execute_IsClassicCubeAnyActionButtonHovered( + OperatorSurface)) { return; } @@ -251,49 +265,65 @@ void AHyperTwistClassicCubePlayerController::HandleFreshAttemptShortcut() void AHyperTwistClassicCubePlayerController::HandleHintShortcut() { - if (AHyperTwistClassicCubeGameMode* GameMode = ResolveClassicCubeGameMode()) + if (UObject* OperatorSurface = + HyperTwistClassicCubePlayerControllerInternal::ResolveOperatorSurfaceObject(this)) { - GameMode->RequestHint(); + IHyperTwistClassicCubeOperatorSurface::Execute_RequestClassicCubeHint(OperatorSurface); } } void AHyperTwistClassicCubePlayerController::HandleSubmitSolveShortcut() { - if (AHyperTwistClassicCubeGameMode* GameMode = ResolveClassicCubeGameMode()) + if (UObject* OperatorSurface = + HyperTwistClassicCubePlayerControllerInternal::ResolveOperatorSurfaceObject(this)) { - GameMode->SubmitCurrentSolve(); + IHyperTwistClassicCubeOperatorSurface::Execute_RequestClassicCubeSubmitSolve( + OperatorSurface + ); } } void AHyperTwistClassicCubePlayerController::HandleModeToggleShortcut() { - if (AHyperTwistClassicCubeGameMode* GameMode = ResolveClassicCubeGameMode()) + if (UObject* OperatorSurface = + HyperTwistClassicCubePlayerControllerInternal::ResolveOperatorSurfaceObject(this)) { - GameMode->ToggleFollowAlongMode(); + IHyperTwistClassicCubeOperatorSurface::Execute_RequestClassicCubeToggleFollowAlongMode( + OperatorSurface + ); } } void AHyperTwistClassicCubePlayerController::HandleVoicePressed() { - if (AHyperTwistClassicCubeGameMode* GameMode = ResolveClassicCubeGameMode()) + if (UObject* OperatorSurface = + HyperTwistClassicCubePlayerControllerInternal::ResolveOperatorSurfaceObject(this)) { - GameMode->BeginVoiceCommandCapture(); + IHyperTwistClassicCubeOperatorSurface::Execute_RequestClassicCubeBeginVoiceCommandCapture( + OperatorSurface + ); } } void AHyperTwistClassicCubePlayerController::HandleVoiceReleased() { - if (AHyperTwistClassicCubeGameMode* GameMode = ResolveClassicCubeGameMode()) + if (UObject* OperatorSurface = + HyperTwistClassicCubePlayerControllerInternal::ResolveOperatorSurfaceObject(this)) { - GameMode->EndVoiceCommandCapture(); + IHyperTwistClassicCubeOperatorSurface::Execute_RequestClassicCubeEndVoiceCommandCapture( + OperatorSurface + ); } } void AHyperTwistClassicCubePlayerController::HandleVoiceCycleShortcut() { - if (AHyperTwistClassicCubeGameMode* GameMode = ResolveClassicCubeGameMode()) + if (UObject* OperatorSurface = + HyperTwistClassicCubePlayerControllerInternal::ResolveOperatorSurfaceObject(this)) { - GameMode->CycleVoiceProfile(); + IHyperTwistClassicCubeOperatorSurface::Execute_RequestClassicCubeCycleVoiceProfile( + OperatorSurface + ); } } diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistSimulation/HyperTwistMelindaProjectionOrbitPawn.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistSimulation/HyperTwistMelindaProjectionOrbitPawn.cpp index 5a4fcdb..9d02543 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistSimulation/HyperTwistMelindaProjectionOrbitPawn.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistSimulation/HyperTwistMelindaProjectionOrbitPawn.cpp @@ -6,7 +6,6 @@ #include "GameFramework/PlayerController.h" #include "GameFramework/SpringArmComponent.h" #include "HyperTwistSimulation/HyperTwistMelindaProjectionActor.h" -#include "HyperTwistSimulation/HyperTwistMelindaProjectionGameMode.h" #include "InputCoreTypes.h" AHyperTwistMelindaProjectionOrbitPawn::AHyperTwistMelindaProjectionOrbitPawn() @@ -126,15 +125,6 @@ AHyperTwistMelindaProjectionActor* AHyperTwistMelindaProjectionOrbitPawn::Resolv return nullptr; } - if (const AHyperTwistMelindaProjectionGameMode* GameMode = - Cast(GetWorld()->GetAuthGameMode())) - { - if (GameMode->ActiveProjectionActor != nullptr) - { - return GameMode->ActiveProjectionActor; - } - } - for (TActorIterator ActorIt(GetWorld()); ActorIt; ++ActorIt) { return *ActorIt; diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistSimulation/HyperTwistMelindaProjectionPlayerController.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistSimulation/HyperTwistMelindaProjectionPlayerController.cpp index 74cff61..6b8fc06 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistSimulation/HyperTwistMelindaProjectionPlayerController.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistSimulation/HyperTwistMelindaProjectionPlayerController.cpp @@ -2,7 +2,6 @@ #include "EngineUtils.h" #include "HyperTwistSimulation/HyperTwistMelindaProjectionActor.h" -#include "HyperTwistSimulation/HyperTwistMelindaProjectionGameMode.h" #include "InputCoreTypes.h" AHyperTwistMelindaProjectionPlayerController::AHyperTwistMelindaProjectionPlayerController() @@ -112,12 +111,6 @@ bool AHyperTwistMelindaProjectionPlayerController::TryProcessProjectionClickFrom void AHyperTwistMelindaProjectionPlayerController::ResetProjectionToSolved() { - if (AHyperTwistMelindaProjectionGameMode* GameMode = ResolveProjectionGameMode()) - { - GameMode->ResetProjectionToSolved(); - return; - } - if (AHyperTwistMelindaProjectionActor* ProjectionActor = ResolveProjectionActor()) { ProjectionActor->ResetToSolvedState(); @@ -126,11 +119,6 @@ void AHyperTwistMelindaProjectionPlayerController::ResetProjectionToSolved() bool AHyperTwistMelindaProjectionPlayerController::GenerateProjectionRandomState() { - if (AHyperTwistMelindaProjectionGameMode* GameMode = ResolveProjectionGameMode()) - { - return GameMode->GenerateProjectionRandomState(NextRandomSeed++); - } - if (AHyperTwistMelindaProjectionActor* ProjectionActor = ResolveProjectionActor()) { return ProjectionActor->GenerateRandomState(NextRandomSeed++); @@ -161,14 +149,6 @@ AHyperTwistMelindaProjectionActor* AHyperTwistMelindaProjectionPlayerController: return nullptr; } - if (const AHyperTwistMelindaProjectionGameMode* GameMode = ResolveProjectionGameMode()) - { - if (GameMode->ActiveProjectionActor != nullptr) - { - return GameMode->ActiveProjectionActor; - } - } - for (TActorIterator ActorIt(GetWorld()); ActorIt; ++ActorIt) { return *ActorIt; @@ -177,13 +157,6 @@ AHyperTwistMelindaProjectionActor* AHyperTwistMelindaProjectionPlayerController: return nullptr; } -AHyperTwistMelindaProjectionGameMode* AHyperTwistMelindaProjectionPlayerController::ResolveProjectionGameMode() const -{ - return Cast( - GetWorld() != nullptr ? GetWorld()->GetAuthGameMode() : nullptr - ); -} - void AHyperTwistMelindaProjectionPlayerController::HandlePrimaryClick() { TryProcessProjectionClickFromCursor(false); diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistSimulation/HyperTwistVirtual3333ProjectionOrbitPawn.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistSimulation/HyperTwistVirtual3333ProjectionOrbitPawn.cpp index 54534c6..59ef18f 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistSimulation/HyperTwistVirtual3333ProjectionOrbitPawn.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistSimulation/HyperTwistVirtual3333ProjectionOrbitPawn.cpp @@ -6,7 +6,6 @@ #include "GameFramework/PlayerController.h" #include "GameFramework/SpringArmComponent.h" #include "HyperTwistSimulation/HyperTwistVirtual3333ProjectionActor.h" -#include "HyperTwistSimulation/HyperTwistVirtual3333ProjectionGameMode.h" #include "InputCoreTypes.h" AHyperTwistVirtual3333ProjectionOrbitPawn::AHyperTwistVirtual3333ProjectionOrbitPawn() @@ -127,15 +126,6 @@ AHyperTwistVirtual3333ProjectionOrbitPawn::ResolveProjectionActor() const return nullptr; } - if (const AHyperTwistVirtual3333ProjectionGameMode* GameMode = - Cast(GetWorld()->GetAuthGameMode())) - { - if (GameMode->ActiveProjectionActor != nullptr) - { - return GameMode->ActiveProjectionActor; - } - } - for (TActorIterator ActorIt(GetWorld()); ActorIt; ++ActorIt) { return *ActorIt; diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistSimulation/HyperTwistVirtual3333ProjectionPlayerController.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistSimulation/HyperTwistVirtual3333ProjectionPlayerController.cpp index 3aed78d..1c6c3ee 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistSimulation/HyperTwistVirtual3333ProjectionPlayerController.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistSimulation/HyperTwistVirtual3333ProjectionPlayerController.cpp @@ -2,7 +2,6 @@ #include "EngineUtils.h" #include "HyperTwistSimulation/HyperTwistVirtual3333ProjectionActor.h" -#include "HyperTwistSimulation/HyperTwistVirtual3333ProjectionGameMode.h" #include "InputCoreTypes.h" AHyperTwistVirtual3333ProjectionPlayerController::AHyperTwistVirtual3333ProjectionPlayerController() @@ -203,14 +202,6 @@ AHyperTwistVirtual3333ProjectionPlayerController::ResolveProjectionActor() const return nullptr; } - if (const AHyperTwistVirtual3333ProjectionGameMode* GameMode = ResolveProjectionGameMode()) - { - if (GameMode->ActiveProjectionActor != nullptr) - { - return GameMode->ActiveProjectionActor; - } - } - for (TActorIterator ActorIt(GetWorld()); ActorIt; ++ActorIt) { return *ActorIt; @@ -219,14 +210,6 @@ AHyperTwistVirtual3333ProjectionPlayerController::ResolveProjectionActor() const return nullptr; } -AHyperTwistVirtual3333ProjectionGameMode* -AHyperTwistVirtual3333ProjectionPlayerController::ResolveProjectionGameMode() const -{ - return Cast( - GetWorld() != nullptr ? GetWorld()->GetAuthGameMode() : nullptr - ); -} - void AHyperTwistVirtual3333ProjectionPlayerController::HandleSliceAxisX() { SetSliceAxis(EHyperTwistVirtual3333Axis::X); diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCatalogLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCatalogLibrary.cpp index a1fdc8b..44895cd 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCatalogLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingCatalogLibrary.cpp @@ -1,6 +1,5 @@ #include "HyperTwistTraining/HyperTwistTrainingCatalogLibrary.h" -#include "HyperTwistTraining/HyperTwistTrainingLibrary.h" #include "JsonObjectConverter.h" #include "Misc/FileHelper.h" #include "Misc/Paths.h" @@ -1051,6 +1050,84 @@ FHyperTwistContentPack UHyperTwistTrainingCatalogLibrary::MakeFiveStyleStarterCo return ContentPack; } +FHyperTwistContentPack UHyperTwistTrainingCatalogLibrary::MakeSampleClassicContentPack() +{ + FHyperTwistTrainingSourceAttribution FirstPartyAttribution; + FirstPartyAttribution.SourceMode = EHyperTwistTrainingSourceMode::FirstParty; + FirstPartyAttribution.SourceNotes = TEXT("First-party HyperTwist starter content pack."); + + FHyperTwistTrainingCase CaseA; + CaseA.CaseId = TEXT("cross-case-01"); + CaseA.PuzzleId = TEXT("cube/3x3x3"); + CaseA.PromptKind = EHyperTwistTrainingPromptKind::Algorithm; + CaseA.PromptLabel = TEXT("Cross 01"); + CaseA.CanonicalNotation = TEXT("R F"); + CaseA.ScrambleNotation = TEXT("F' U R U' R'"); + CaseA.SubsetId = TEXT("cross"); + CaseA.Difficulty = 0.2f; + CaseA.Tags = {TEXT("cross"), TEXT("seed")}; + CaseA.AllowedDeliveryModes = {EHyperTwistTrainingDeliveryMode::Timer, EHyperTwistTrainingDeliveryMode::VirtualCube}; + CaseA.TimeTargetMs = 3000; + CaseA.SourceAttribution = FirstPartyAttribution; + + FHyperTwistTrainingCase CaseB; + CaseB.CaseId = TEXT("cross-case-02"); + CaseB.PuzzleId = TEXT("cube/3x3x3"); + CaseB.PromptKind = EHyperTwistTrainingPromptKind::Algorithm; + CaseB.PromptLabel = TEXT("Cross 02"); + CaseB.CanonicalNotation = TEXT("F U R"); + CaseB.ScrambleNotation = TEXT("R U R' U F' U2"); + CaseB.SubsetId = TEXT("cross"); + CaseB.Difficulty = 0.35f; + CaseB.Tags = {TEXT("cross"), TEXT("seed")}; + CaseB.AllowedDeliveryModes = {EHyperTwistTrainingDeliveryMode::Timer, EHyperTwistTrainingDeliveryMode::VirtualCube}; + CaseB.TimeTargetMs = 3500; + CaseB.SourceAttribution = FirstPartyAttribution; + + FHyperTwistTrainingDeck Deck; + Deck.DeckId = TEXT("cross-1-to-8"); + Deck.Title = TEXT("Cross 1 to 8"); + Deck.DeliveryModes = {EHyperTwistTrainingDeliveryMode::Timer, EHyperTwistTrainingDeliveryMode::VirtualCube}; + Deck.SelectionPolicy = EHyperTwistTrainingSelectionPolicy::Weighted; + Deck.bUsesRealScrambles = false; + Deck.bSupportsAlgorithmReveal = true; + Deck.bSupportsVirtualCube = true; + Deck.bSupportsSmartCube = false; + + FHyperTwistTrainingSplitPhaseDefinition CrossPhaseDefinition; + CrossPhaseDefinition.PhaseId = TEXT("cross"); + CrossPhaseDefinition.Label = TEXT("Cross"); + CrossPhaseDefinition.Order = 0; + + Deck.TimingPolicy.bInspectionEnabled = true; + Deck.TimingPolicy.InspectionDurationMs = 15000; + Deck.TimingPolicy.bInspectionPenaltiesEnabled = true; + Deck.TimingPolicy.bAllowManualPenalty = true; + Deck.TimingPolicy.SplitPhases = {CrossPhaseDefinition}; + Deck.Cases = {CaseA, CaseB}; + Deck.DifficultyBand = TEXT("starter"); + Deck.Tags = {TEXT("classic"), TEXT("cross")}; + Deck.SubsetId = TEXT("cross"); + Deck.SourceAttribution = FirstPartyAttribution; + + FHyperTwistTrainingTrack Track; + Track.TrackId = TEXT("cross"); + Track.Title = TEXT("Cross"); + Track.Goal = TEXT("build fast and consistent cross execution"); + Track.Decks = {Deck}; + Track.SourceAttribution = FirstPartyAttribution; + + FHyperTwistContentPack ContentPack; + ContentPack.ContentPackId = TEXT("classic-3x3-core"); + ContentPack.Title = TEXT("Classic 3x3 Core"); + ContentPack.Version = TEXT("2026.04"); + ContentPack.PuzzleFamily = EHyperTwistPuzzleFamily::ClassicCube; + ContentPack.NotationProfile = TEXT("classic-wca"); + ContentPack.Tracks = {Track}; + ContentPack.SourceAttribution = FirstPartyAttribution; + return ContentPack; +} + namespace HyperTwistTrainingCatalogMaterializationInternal { FString ResolveBundledContentSourcePath( @@ -2249,7 +2326,7 @@ TArray UHyperTwistTrainingCatalogLibrary::MakePhase3Trai MakeAlgTrainerStarterContentPack(), MakeCrossTrainerStarterContentPack(), MakeFiveStyleStarterContentPack(), - UHyperTwistTrainingLibrary::MakeSampleClassicContentPack() + MakeSampleClassicContentPack() }; FHyperTwistTrainingCatalogMaterializedBundle MaterializedBundle; diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingLibrary.cpp index a516ce2..d943be8 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingLibrary.cpp @@ -971,80 +971,7 @@ namespace HyperTwistTrainingLibraryInternal FHyperTwistContentPack UHyperTwistTrainingLibrary::MakeSampleClassicContentPack() { - FHyperTwistTrainingSourceAttribution FirstPartyAttribution; - FirstPartyAttribution.SourceMode = EHyperTwistTrainingSourceMode::FirstParty; - FirstPartyAttribution.SourceNotes = TEXT("First-party HyperTwist starter content pack."); - - FHyperTwistTrainingCase CaseA; - CaseA.CaseId = TEXT("cross-case-01"); - CaseA.PuzzleId = TEXT("cube/3x3x3"); - CaseA.PromptKind = EHyperTwistTrainingPromptKind::Algorithm; - CaseA.PromptLabel = TEXT("Cross 01"); - CaseA.CanonicalNotation = TEXT("R F"); - CaseA.ScrambleNotation = TEXT("F' U R U' R'"); - CaseA.SubsetId = TEXT("cross"); - CaseA.Difficulty = 0.2f; - CaseA.Tags = {TEXT("cross"), TEXT("seed")}; - CaseA.AllowedDeliveryModes = {EHyperTwistTrainingDeliveryMode::Timer, EHyperTwistTrainingDeliveryMode::VirtualCube}; - CaseA.TimeTargetMs = 3000; - CaseA.SourceAttribution = FirstPartyAttribution; - - FHyperTwistTrainingCase CaseB; - CaseB.CaseId = TEXT("cross-case-02"); - CaseB.PuzzleId = TEXT("cube/3x3x3"); - CaseB.PromptKind = EHyperTwistTrainingPromptKind::Algorithm; - CaseB.PromptLabel = TEXT("Cross 02"); - CaseB.CanonicalNotation = TEXT("F U R"); - CaseB.ScrambleNotation = TEXT("R U R' U F' U2"); - CaseB.SubsetId = TEXT("cross"); - CaseB.Difficulty = 0.35f; - CaseB.Tags = {TEXT("cross"), TEXT("seed")}; - CaseB.AllowedDeliveryModes = {EHyperTwistTrainingDeliveryMode::Timer, EHyperTwistTrainingDeliveryMode::VirtualCube}; - CaseB.TimeTargetMs = 3500; - CaseB.SourceAttribution = FirstPartyAttribution; - - FHyperTwistTrainingDeck Deck; - Deck.DeckId = TEXT("cross-1-to-8"); - Deck.Title = TEXT("Cross 1 to 8"); - Deck.DeliveryModes = {EHyperTwistTrainingDeliveryMode::Timer, EHyperTwistTrainingDeliveryMode::VirtualCube}; - Deck.SelectionPolicy = EHyperTwistTrainingSelectionPolicy::Weighted; - Deck.bUsesRealScrambles = false; - Deck.bSupportsAlgorithmReveal = true; - Deck.bSupportsVirtualCube = true; - Deck.bSupportsSmartCube = false; - - FHyperTwistTrainingSplitPhaseDefinition CrossPhaseDefinition; - CrossPhaseDefinition.PhaseId = TEXT("cross"); - CrossPhaseDefinition.Label = TEXT("Cross"); - CrossPhaseDefinition.Order = 0; - - Deck.TimingPolicy.bInspectionEnabled = true; - Deck.TimingPolicy.InspectionDurationMs = 15000; - Deck.TimingPolicy.bInspectionPenaltiesEnabled = true; - Deck.TimingPolicy.bAllowManualPenalty = true; - Deck.TimingPolicy.SplitPhases = {CrossPhaseDefinition}; - Deck.Cases = {CaseA, CaseB}; - Deck.DifficultyBand = TEXT("starter"); - Deck.Tags = {TEXT("classic"), TEXT("cross")}; - Deck.SubsetId = TEXT("cross"); - Deck.SourceAttribution = FirstPartyAttribution; - - FHyperTwistTrainingTrack Track; - Track.TrackId = TEXT("cross"); - Track.Title = TEXT("Cross"); - Track.Goal = TEXT("build fast and consistent cross execution"); - Track.Decks = {Deck}; - Track.SourceAttribution = FirstPartyAttribution; - - FHyperTwistContentPack ContentPack; - ContentPack.ContentPackId = TEXT("classic-3x3-core"); - ContentPack.Title = TEXT("Classic 3x3 Core"); - ContentPack.Version = TEXT("2026.04"); - ContentPack.PuzzleFamily = EHyperTwistPuzzleFamily::ClassicCube; - ContentPack.NotationProfile = TEXT("classic-wca"); - ContentPack.Tracks = {Track}; - ContentPack.SourceAttribution = FirstPartyAttribution; - return ContentPack; + return UHyperTwistTrainingCatalogLibrary::MakeSampleClassicContentPack(); } bool UHyperTwistTrainingLibrary::IsTrainingCaseCompatibleWithPack( diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistCore/HyperTwistCoreTypes.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistCore/HyperTwistCoreTypes.h index 606e66f..f4d0967 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistCore/HyperTwistCoreTypes.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistCore/HyperTwistCoreTypes.h @@ -232,6 +232,76 @@ struct FHyperTwistStateSnapshot } }; +namespace HyperTwistCoreTypeValidation +{ + static constexpr int32 MelindaPieceCount = 16; + static constexpr int32 MelindaOrientationCount = 24; + + inline bool IsPermutationOfExpectedRange(const TArray& Values, const int32 ExpectedCount) + { + if (Values.Num() != ExpectedCount) + { + return false; + } + + TArray SeenValues; + SeenValues.Init(false, ExpectedCount); + + for (const int32 Value : Values) + { + if (Value < 0 || Value >= ExpectedCount || SeenValues[Value]) + { + return false; + } + + SeenValues[Value] = true; + } + + return true; + } + + inline bool AreIndicesWithinRange(const TArray& Values, const int32 MaxExclusive) + { + for (const int32 Value : Values) + { + if (Value < 0 || Value >= MaxExclusive) + { + return false; + } + } + + return true; + } + + inline bool DoesDefinitionMatch( + const FHyperTwistPuzzleDefinitionRef& ExpectedDefinition, + const FHyperTwistPuzzleDefinitionRef& CandidateDefinition + ) + { + return CandidateDefinition.PuzzleId == ExpectedDefinition.PuzzleId + && CandidateDefinition.PuzzleFamily == ExpectedDefinition.PuzzleFamily + && CandidateDefinition.Dimension == ExpectedDefinition.Dimension; + } + + inline bool IsTargetStateAlignedWithDefinition( + const FHyperTwistPuzzleDefinitionRef& Definition, + const FHyperTwistPuzzleState& TargetState + ) + { + return TargetState.IsStructurallyValid() + && DoesDefinitionMatch(Definition, TargetState.Definition); + } + + inline bool IsTransformationAlignedWithDefinition( + const FHyperTwistPuzzleDefinitionRef& Definition, + const FHyperTwistTransformation& Transformation + ) + { + return Transformation.IsStructurallyValid() + && DoesDefinitionMatch(Definition, Transformation.Definition); + } +} + USTRUCT(BlueprintType) struct FHyperTwistPieceSetState { @@ -343,33 +413,16 @@ struct FHyperTwistMelinda2x2x2x2StateEncoding bool IsStructurallyValid() const { - if (PositionToPiece.Num() != 16 || PieceOrientation.Num() != 16) - { - return false; - } - - TArray SeenPieces; - SeenPieces.Init(false, 16); - - for (const int32 PieceId : PositionToPiece) - { - if (PieceId < 0 || PieceId >= 16 || SeenPieces[PieceId]) - { - return false; - } - - SeenPieces[PieceId] = true; - } - - for (const int32 OrientationIndex : PieceOrientation) - { - if (OrientationIndex < 0 || OrientationIndex >= 24) - { - return false; - } - } - - return !FrameProfile.IsEmpty() && !TopologyVersion.IsEmpty(); + return HyperTwistCoreTypeValidation::IsPermutationOfExpectedRange( + PositionToPiece, + HyperTwistCoreTypeValidation::MelindaPieceCount + ) + && HyperTwistCoreTypeValidation::AreIndicesWithinRange( + PieceOrientation, + HyperTwistCoreTypeValidation::MelindaOrientationCount + ) + && !FrameProfile.IsEmpty() + && !TopologyVersion.IsEmpty(); } }; @@ -434,36 +487,20 @@ struct FHyperTwistMelinda2x2x2x2TransformEncoding bool IsStructurallyValid() const { if (MoveId.IsEmpty() - || PositionPullMap.Num() != 16 - || OrientationDeltaPerNewPosition.Num() != 16 || FrameProfile.IsEmpty() || TopologyVersion.IsEmpty()) { return false; } - TArray SeenPositions; - SeenPositions.Init(false, 16); - - for (const int32 SourcePosition : PositionPullMap) - { - if (SourcePosition < 0 || SourcePosition >= 16 || SeenPositions[SourcePosition]) - { - return false; - } - - SeenPositions[SourcePosition] = true; - } - - for (const int32 OrientationIndex : OrientationDeltaPerNewPosition) - { - if (OrientationIndex < 0 || OrientationIndex >= 24) - { - return false; - } - } - - return true; + return HyperTwistCoreTypeValidation::IsPermutationOfExpectedRange( + PositionPullMap, + HyperTwistCoreTypeValidation::MelindaPieceCount + ) + && HyperTwistCoreTypeValidation::AreIndicesWithinRange( + OrientationDeltaPerNewPosition, + HyperTwistCoreTypeValidation::MelindaOrientationCount + ); } }; @@ -513,10 +550,7 @@ struct FHyperTwistMelinda2x2x2x2ScramblePacket if (bCarriesTargetState) { - if (!TargetState.IsStructurallyValid() - || TargetState.Definition.PuzzleId != Definition.PuzzleId - || TargetState.Definition.PuzzleFamily != Definition.PuzzleFamily - || TargetState.Definition.Dimension != Definition.Dimension) + if (!HyperTwistCoreTypeValidation::IsTargetStateAlignedWithDefinition(Definition, TargetState)) { return false; } @@ -524,20 +558,14 @@ struct FHyperTwistMelinda2x2x2x2ScramblePacket if (bCarriesExactTransformSequence) { - if (!NetTransform.IsStructurallyValid() - || NetTransform.Definition.PuzzleId != Definition.PuzzleId - || NetTransform.Definition.PuzzleFamily != Definition.PuzzleFamily - || NetTransform.Definition.Dimension != Definition.Dimension) + if (!HyperTwistCoreTypeValidation::IsTransformationAlignedWithDefinition(Definition, NetTransform)) { return false; } for (const FHyperTwistTransformation& Transformation : TransformSequence) { - if (!Transformation.IsStructurallyValid() - || Transformation.Definition.PuzzleId != Definition.PuzzleId - || Transformation.Definition.PuzzleFamily != Definition.PuzzleFamily - || Transformation.Definition.Dimension != Definition.Dimension) + if (!HyperTwistCoreTypeValidation::IsTransformationAlignedWithDefinition(Definition, Transformation)) { return false; } @@ -642,6 +670,81 @@ struct FHyperTwistMelindaFlatTileProjection } }; +namespace HyperTwistCoreTypeValidation +{ + inline bool ArePeekStickersValid(const TArray& PeekStickers) + { + TArray SeenAxes; + SeenAxes.Init(false, 4); + + for (const FHyperTwistMelindaStickerProjection& PeekSticker : PeekStickers) + { + if (!PeekSticker.IsStructurallyValid()) + { + return false; + } + + const int32 AxisIndex = static_cast(PeekSticker.Axis); + if (AxisIndex < 0 + || AxisIndex >= 4 + || AxisIndex == static_cast(EHyperTwistMelindaAxis::W) + || SeenAxes[AxisIndex]) + { + return false; + } + + SeenAxes[AxisIndex] = true; + } + + return PeekStickers.Num() == 3; + } + + inline bool AreTileLabelsValid( + const FHyperTwistMelindaFlatProjectionOptions& Options, + const FHyperTwistMelindaFlatTileProjection& Tile + ) + { + return Options.bIncludePieceLabels == !Tile.PieceLabel.IsEmpty(); + } + + inline bool AreTilePeeksValid( + const FHyperTwistMelindaFlatProjectionOptions& Options, + const FHyperTwistMelindaFlatTileProjection& Tile + ) + { + if (Options.bIncludeStickerPeeks) + { + return ArePeekStickersValid(Tile.PeekStickers); + } + + return Tile.PeekStickers.Num() == 0; + } + + inline bool ValidateProjectionTiles( + const FHyperTwistMelindaFlatProjectionOptions& Options, + const TArray& Tiles, + const EHyperTwistMelindaProjectionPanel ExpectedPanel, + TArray& SeenPositions + ) + { + for (const FHyperTwistMelindaFlatTileProjection& Tile : Tiles) + { + if (!Tile.IsStructurallyValid() + || Tile.Panel != ExpectedPanel + || SeenPositions[Tile.PositionIndex] + || !AreTileLabelsValid(Options, Tile) + || !AreTilePeeksValid(Options, Tile)) + { + return false; + } + + SeenPositions[Tile.PositionIndex] = true; + } + + return true; + } +} + USTRUCT(BlueprintType) struct FHyperTwistMelinda2x2x2x2FlatProjection { @@ -675,72 +778,17 @@ struct FHyperTwistMelinda2x2x2x2FlatProjection TArray SeenPositions; SeenPositions.Init(false, 16); - auto ValidatePeekStickers = [](const TArray& PeekStickers) -> bool - { - TArray SeenAxes; - SeenAxes.Init(false, 4); - - for (const FHyperTwistMelindaStickerProjection& PeekSticker : PeekStickers) - { - if (!PeekSticker.IsStructurallyValid()) - { - return false; - } - - const int32 AxisIndex = static_cast(PeekSticker.Axis); - if (AxisIndex < 0 - || AxisIndex >= 4 - || AxisIndex == static_cast(EHyperTwistMelindaAxis::W) - || SeenAxes[AxisIndex]) - { - return false; - } - - SeenAxes[AxisIndex] = true; - } - - return PeekStickers.Num() == 3; - }; - - auto ValidateTiles = - [this, &SeenPositions, &ValidatePeekStickers]( - const TArray& Tiles, - const EHyperTwistMelindaProjectionPanel ExpectedPanel - ) -> bool - { - for (const FHyperTwistMelindaFlatTileProjection& Tile : Tiles) - { - if (!Tile.IsStructurallyValid() - || Tile.Panel != ExpectedPanel - || SeenPositions[Tile.PositionIndex]) - { - return false; - } - - if (Options.bIncludePieceLabels != !Tile.PieceLabel.IsEmpty()) - { - return false; - } - - if (Options.bIncludeStickerPeeks) - { - if (!ValidatePeekStickers(Tile.PeekStickers)) - { - return false; - } - } - else if (Tile.PeekStickers.Num() != 0) - { - return false; - } - - SeenPositions[Tile.PositionIndex] = true; - } - - return true; - }; - - return ValidateTiles(FrontTiles, EHyperTwistMelindaProjectionPanel::Front) - && ValidateTiles(BackTiles, EHyperTwistMelindaProjectionPanel::Back); + return HyperTwistCoreTypeValidation::ValidateProjectionTiles( + Options, + FrontTiles, + EHyperTwistMelindaProjectionPanel::Front, + SeenPositions + ) + && HyperTwistCoreTypeValidation::ValidateProjectionTiles( + Options, + BackTiles, + EHyperTwistMelindaProjectionPanel::Back, + SeenPositions + ); } }; diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistRecognition/HyperTwistRecognitionTypes.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistRecognition/HyperTwistRecognitionTypes.h index 8fd7234..c8063c4 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistRecognition/HyperTwistRecognitionTypes.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistRecognition/HyperTwistRecognitionTypes.h @@ -4475,81 +4475,7 @@ struct FHyperTwistSpeechUsageCostHistoryExportShellState FString LastRecordedSignature; - bool IsStructurallyValid() const - { - if (HistoryExportShellProfileId.IsEmpty() - || ActiveProviderProfileId.IsEmpty() - || ActiveProviderDisplayLabel.IsEmpty() - || ActiveServiceLaneId.IsEmpty() - || SelectedHistoryWindowId.IsEmpty() - || ExportPreviewFormatId.IsEmpty() - || LatestSourceKind.IsEmpty() - || StatusLine.IsEmpty() - || DetailLine.IsEmpty() - || HistoryEntryCount < 0 - || EstimateOnlyEntryCount < 0 - || ProviderReceiptEntryCount < 0 - || TotalUsageQuantity < 0.0f - || TotalEstimatedCostUsd < 0.0f - || TotalReportedCostUsd < 0.0f - || LowestRemainingEstimatedSpendUsd < 0.0f - || ActiveIssueCount < 0 - || AvailableActionIds.Num() <= 0) - { - return false; - } - - if (HistoryEntryCount != HistoryEntries.Num()) - { - return false; - } - - if (HistoryEntryCount > 0 && LatestHistoryEntryId.IsEmpty()) - { - return false; - } - - if (bExportPreviewReady && ExportPreviewText.IsEmpty()) - { - return false; - } - - if (ActiveIssueCount != Issues.Num()) - { - return false; - } - - if (ActiveIssueCount > 0 && !ActiveIssue.IsStructurallyValid()) - { - return false; - } - - for (const FHyperTwistSpeechUsageCostHistoryExportIssue& Issue : Issues) - { - if (!Issue.IsStructurallyValid()) - { - return false; - } - } - - for (const FHyperTwistSpeechUsageCostHistoryEntry& HistoryEntry : HistoryEntries) - { - if (!HistoryEntry.IsStructurallyValid()) - { - return false; - } - } - - for (const FString& ActionId : AvailableActionIds) - { - if (ActionId.IsEmpty()) - { - return false; - } - } - - return true; - } + bool IsStructurallyValid() const; }; USTRUCT(BlueprintType) @@ -4852,85 +4778,7 @@ struct FHyperTwistSpeechProviderReceiptReviewShellState UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") TArray AvailableActionIds; - bool IsStructurallyValid() const - { - if (ReceiptReviewShellProfileId.IsEmpty() - || ActiveProviderProfileId.IsEmpty() - || ActiveProviderDisplayLabel.IsEmpty() - || ActiveServiceLaneId.IsEmpty() - || SelectedChargeWindowId.IsEmpty() - || PostedChargeInspectionModeId.IsEmpty() - || LatestSourceKind.IsEmpty() - || StatusLine.IsEmpty() - || DetailLine.IsEmpty() - || ReceiptEntryCount < 0 - || ProviderPostedChargeCount < 0 - || EstimateOnlyEntryCount < 0 - || VarianceReviewEntryCount < 0 - || TotalEstimatedCostUsd < 0.0f - || TotalReportedCostUsd < 0.0f - || TotalAbsoluteVarianceUsd < 0.0f - || HighestAbsoluteVarianceUsd < 0.0f - || ActiveIssueCount < 0 - || AvailableActionIds.Num() <= 0) - { - return false; - } - - if (ReceiptEntryCount != ReceiptEntries.Num() - || ProviderPostedChargeCount > ReceiptEntryCount - || EstimateOnlyEntryCount > ReceiptEntryCount - || VarianceReviewEntryCount > ReceiptEntryCount) - { - return false; - } - - if (ReceiptEntryCount > 0 && LatestReceiptEntryId.IsEmpty()) - { - return false; - } - - if (bReceiptSummaryReady && ReceiptSummaryText.IsEmpty()) - { - return false; - } - - if (ActiveIssueCount != Issues.Num()) - { - return false; - } - - if (ActiveIssueCount > 0 && !ActiveIssue.IsStructurallyValid()) - { - return false; - } - - for (const FHyperTwistSpeechProviderReceiptReviewIssue& Issue : Issues) - { - if (!Issue.IsStructurallyValid()) - { - return false; - } - } - - for (const FHyperTwistSpeechProviderReceiptReviewEntry& Entry : ReceiptEntries) - { - if (!Entry.IsStructurallyValid()) - { - return false; - } - } - - for (const FString& ActionId : AvailableActionIds) - { - if (ActionId.IsEmpty()) - { - return false; - } - } - - return true; - } + bool IsStructurallyValid() const; }; USTRUCT(BlueprintType) @@ -5246,90 +5094,7 @@ struct FHyperTwistSpeechProviderBillingSettlementShellState UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") TArray AvailableActionIds; - bool IsStructurallyValid() const - { - if (BillingSettlementShellProfileId.IsEmpty() - || ActiveProviderProfileId.IsEmpty() - || ActiveProviderDisplayLabel.IsEmpty() - || ActiveServiceLaneId.IsEmpty() - || SelectedSettlementWindowId.IsEmpty() - || SettlementSurfaceModeId.IsEmpty() - || InvoiceReconciliationModeId.IsEmpty() - || LatestSourceKind.IsEmpty() - || StatusLine.IsEmpty() - || DetailLine.IsEmpty() - || SettlementEntryCount < 0 - || ProviderPostedChargeCount < 0 - || ReconciliationReadyEntryCount < 0 - || EstimateOnlyEntryCount < 0 - || ManualReviewEntryCount < 0 - || BlockedSettlementEntryCount < 0 - || TotalEstimatedCostUsd < 0.0f - || TotalReportedCostUsd < 0.0f - || TotalSettlementDeltaUsd < 0.0f - || HighestSettlementDeltaUsd < 0.0f - || ActiveIssueCount < 0 - || AvailableActionIds.Num() <= 0) - { - return false; - } - - if (SettlementEntryCount != SettlementEntries.Num() - || ProviderPostedChargeCount > SettlementEntryCount - || ReconciliationReadyEntryCount > SettlementEntryCount - || EstimateOnlyEntryCount > SettlementEntryCount - || ManualReviewEntryCount > SettlementEntryCount - || BlockedSettlementEntryCount > SettlementEntryCount) - { - return false; - } - - if (SettlementEntryCount > 0 && LatestSettlementEntryId.IsEmpty()) - { - return false; - } - - if (bSettlementSummaryReady && SettlementSummaryText.IsEmpty()) - { - return false; - } - - if (ActiveIssueCount != Issues.Num()) - { - return false; - } - - if (ActiveIssueCount > 0 && !ActiveIssue.IsStructurallyValid()) - { - return false; - } - - for (const FHyperTwistSpeechProviderBillingSettlementIssue& Issue : Issues) - { - if (!Issue.IsStructurallyValid()) - { - return false; - } - } - - for (const FHyperTwistSpeechProviderBillingSettlementEntry& Entry : SettlementEntries) - { - if (!Entry.IsStructurallyValid()) - { - return false; - } - } - - for (const FString& ActionId : AvailableActionIds) - { - if (ActionId.IsEmpty()) - { - return false; - } - } - - return true; - } + bool IsStructurallyValid() const; }; USTRUCT(BlueprintType) @@ -5643,90 +5408,7 @@ struct FHyperTwistSpeechProviderSettlementExceptionShellState UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") TArray AvailableActionIds; - bool IsStructurallyValid() const - { - if (SettlementExceptionShellProfileId.IsEmpty() - || ActiveProviderProfileId.IsEmpty() - || ActiveProviderDisplayLabel.IsEmpty() - || ActiveServiceLaneId.IsEmpty() - || SelectedSettlementWindowId.IsEmpty() - || ExceptionSurfaceModeId.IsEmpty() - || ExternalPortalHandoffModeId.IsEmpty() - || LatestSourceKind.IsEmpty() - || StatusLine.IsEmpty() - || DetailLine.IsEmpty() - || ExceptionEntryCount < 0 - || ExternalPortalHandoffReadyEntryCount < 0 - || PendingChargeEntryCount < 0 - || ManualReviewEntryCount < 0 - || RouteDegradedEntryCount < 0 - || BlockedSettlementEntryCount < 0 - || TotalEstimatedCostUsd < 0.0f - || TotalReportedCostUsd < 0.0f - || TotalSettlementDeltaUsd < 0.0f - || HighestSettlementDeltaUsd < 0.0f - || ActiveIssueCount < 0 - || AvailableActionIds.Num() <= 0) - { - return false; - } - - if (ExceptionEntryCount != ExceptionEntries.Num() - || ExternalPortalHandoffReadyEntryCount > ExceptionEntryCount - || PendingChargeEntryCount > ExceptionEntryCount - || ManualReviewEntryCount > ExceptionEntryCount - || RouteDegradedEntryCount > ExceptionEntryCount - || BlockedSettlementEntryCount > ExceptionEntryCount) - { - return false; - } - - if (ExceptionEntryCount > 0 && LatestExceptionEntryId.IsEmpty()) - { - return false; - } - - if (bExceptionSummaryReady && ExceptionSummaryText.IsEmpty()) - { - return false; - } - - if (ActiveIssueCount != Issues.Num()) - { - return false; - } - - if (ActiveIssueCount > 0 && !ActiveIssue.IsStructurallyValid()) - { - return false; - } - - for (const FHyperTwistSpeechProviderSettlementExceptionIssue& Issue : Issues) - { - if (!Issue.IsStructurallyValid()) - { - return false; - } - } - - for (const FHyperTwistSpeechProviderSettlementExceptionEntry& Entry : ExceptionEntries) - { - if (!Entry.IsStructurallyValid()) - { - return false; - } - } - - for (const FString& ActionId : AvailableActionIds) - { - if (ActionId.IsEmpty()) - { - return false; - } - } - - return true; - } + bool IsStructurallyValid() const; }; USTRUCT(BlueprintType) @@ -6425,141 +6107,7 @@ struct FHyperTwistSpeechExternalDictationShellState FString LastRecordedSignature; - bool IsStructurallyValid() const - { - if (ExternalDictationShellProfileId.IsEmpty() - || ActiveProviderProfileId.IsEmpty() - || ActiveServiceLaneId.IsEmpty() - || SelectedOutputRouteId.IsEmpty() - || TranscriptHistorySurfaceModeId.IsEmpty() - || OutputRoutingSurfaceModeId.IsEmpty() - || PostProcessOverlaySurfaceModeId.IsEmpty() - || GlobalHotkeySurfaceModeId.IsEmpty() - || InputDeviceSurfaceModeId.IsEmpty() - || OutputDeviceSurfaceModeId.IsEmpty() - || MuteSurfaceModeId.IsEmpty() - || MicrophoneModeSurfaceModeId.IsEmpty() - || LocalModelCatalogSurfaceModeId.IsEmpty() - || ModelIntegritySurfaceModeId.IsEmpty() - || ModelUnloadSurfaceModeId.IsEmpty() - || LatestSourceKind.IsEmpty() - || StatusLine.IsEmpty() - || DetailLine.IsEmpty() - || PrimaryHotkeyBindingLabel.IsEmpty() - || PostProcessHotkeyBindingLabel.IsEmpty() - || SelectedInputDeviceId.IsEmpty() - || SelectedInputDeviceLabel.IsEmpty() - || SelectedOutputDeviceId.IsEmpty() - || SelectedOutputDeviceLabel.IsEmpty() - || MicrophoneModeId.IsEmpty() - || PayloadCustodyProfileId.IsEmpty() - || SelectedPrimaryPayloadId.IsEmpty() - || HistoryEntryCount < 0 - || PostProcessedEntryCount < 0 - || SavedEntryCount < 0 - || ModelCatalogEntryCount < 0 - || OptionalModelCatalogEntryCount < 0 - || DownloadDeferredModelEntryCount < 0 - || IntegrityReviewRequiredEntryCount < 0 - || ActiveIssueCount < 0 - || OutputRouteOptions.Num() <= 0 - || InputDeviceOptions.Num() <= 0 - || OutputDeviceOptions.Num() <= 0 - || ModelCatalogEntries.Num() <= 0 - || AvailableActionIds.Num() <= 0) - { - return false; - } - - if (HistoryEntryCount != HistoryEntries.Num() - || PostProcessedEntryCount > HistoryEntryCount - || SavedEntryCount > HistoryEntryCount - || ModelCatalogEntryCount != ModelCatalogEntries.Num() - || OptionalModelCatalogEntryCount > ModelCatalogEntryCount - || DownloadDeferredModelEntryCount > ModelCatalogEntryCount - || IntegrityReviewRequiredEntryCount > ModelCatalogEntryCount) - { - return false; - } - - if (HistoryEntryCount > 0 - && (LatestHistoryEntryId.IsEmpty() || LatestTranscriptText.IsEmpty())) - { - return false; - } - - if (ActiveIssueCount != Issues.Num()) - { - return false; - } - - if (ActiveIssueCount > 0 && !ActiveIssue.IsStructurallyValid()) - { - return false; - } - - for (const FHyperTwistSpeechExternalDictationShellIssue& Issue : Issues) - { - if (!Issue.IsStructurallyValid()) - { - return false; - } - } - - for (const FHyperTwistSpeechExternalDictationHistoryEntry& Entry : HistoryEntries) - { - if (!Entry.IsStructurallyValid()) - { - return false; - } - } - - for (const FHyperTwistSpeechExternalDictationOutputRouteOption& OutputRouteOption : - OutputRouteOptions) - { - if (!OutputRouteOption.IsStructurallyValid()) - { - return false; - } - } - - for (const FHyperTwistSpeechExternalDictationDeviceOption& DeviceOption : - InputDeviceOptions) - { - if (!DeviceOption.IsStructurallyValid()) - { - return false; - } - } - - for (const FHyperTwistSpeechExternalDictationDeviceOption& DeviceOption : - OutputDeviceOptions) - { - if (!DeviceOption.IsStructurallyValid()) - { - return false; - } - } - - for (const FHyperTwistSpeechExternalDictationModelCatalogEntry& CatalogEntry : - ModelCatalogEntries) - { - if (!CatalogEntry.IsStructurallyValid()) - { - return false; - } - } - - for (const FString& ActionId : AvailableActionIds) - { - if (ActionId.IsEmpty()) - { - return false; - } - } - - return true; - } + bool IsStructurallyValid() const; }; USTRUCT(BlueprintType) @@ -6726,65 +6274,7 @@ struct FHyperTwistSpeechSessionConfig UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") FHyperTwistSpeechTranscriptionOrchestrationProfile OrchestrationProfile; - bool IsStructurallyValid() const - { - if (SessionId.IsEmpty() - || ListeningContractId.IsEmpty() - || InputRouteId.IsEmpty() - || AudioEncoding.IsEmpty() - || SampleRateHz <= 0 - || ChannelCount <= 0 - || TaskKind.IsEmpty() - || MicrophoneShellProfileId.IsEmpty() - || !MicrophoneShellProfileDefinition.IsStructurallyValid() - || DevicePermissionWorkflowProfileId.IsEmpty() - || !DevicePermissionWorkflowProfileDefinition.IsStructurallyValid() - || NativeCaptureRouteWorkflowProfileId.IsEmpty() - || !NativeCaptureRouteWorkflowProfileDefinition.IsStructurallyValid() - || NativeCaptureRouteShellProfileId.IsEmpty() - || !NativeCaptureRouteShellProfileDefinition.IsStructurallyValid() - || ExternalDictationShellProfileId.IsEmpty() - || !ExternalDictationShellProfileDefinition.IsStructurallyValid() - || ProviderProfileId.IsEmpty() - || !ProviderProfileDefinition.IsStructurallyValid() - || ByokCustodyProfileId.IsEmpty() - || !ByokCustodyProfileDefinition.IsStructurallyValid() - || ProviderRoutingPolicyId.IsEmpty() - || !ProviderRoutingPolicyDefinition.IsStructurallyValid() - || !ProviderRouteDecision.IsStructurallyValid() - || UsageCostAccountingProfileId.IsEmpty() - || !UsageCostAccountingProfileDefinition.IsStructurallyValid() - || !UsageEventTemplate.IsStructurallyValid() - || !CostEventTemplate.IsStructurallyValid() - || UsageCostDashboardShellProfileId.IsEmpty() - || !UsageCostDashboardShellProfileDefinition.IsStructurallyValid() - || UsageCostHistoryExportShellProfileId.IsEmpty() - || !UsageCostHistoryExportShellProfileDefinition.IsStructurallyValid() - || ProviderReceiptReviewShellProfileId.IsEmpty() - || !ProviderReceiptReviewShellProfileDefinition.IsStructurallyValid() - || ProviderBillingSettlementShellProfileId.IsEmpty() - || !ProviderBillingSettlementShellProfileDefinition.IsStructurallyValid() - || ProviderSettlementExceptionShellProfileId.IsEmpty() - || !ProviderSettlementExceptionShellProfileDefinition.IsStructurallyValid() - || PayloadCustodyProfileId.IsEmpty() - || !PayloadCustodyProfileDefinition.IsStructurallyValid() - || RequiredModelPayloads.Num() <= 0 - || !VadPolicy.IsStructurallyValid() - || !OrchestrationProfile.IsStructurallyValid()) - { - return false; - } - - for (const FHyperTwistSpeechModelPayloadDescriptor& Payload : RequiredModelPayloads) - { - if (!Payload.IsStructurallyValid()) - { - return false; - } - } - - return true; - } + bool IsStructurallyValid() const; }; USTRUCT(BlueprintType) @@ -6965,60 +6455,7 @@ struct FHyperTwistSpeechTranscriptResult UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") TArray AppliedRetrievedHints; - bool IsStructurallyValid() const - { - if (SessionId.IsEmpty() - || UtteranceId.IsEmpty() - || TaskKind.IsEmpty() - || (TranscriptText.IsEmpty() && Segments.Num() <= 0) - || OrchestrationProfileId.IsEmpty() - || ServiceLaneId.IsEmpty() - || RequestedBatchSize <= 0 - || ProcessedClipCount <= 0 - || AppliedBatchCollectionWindowMs <= 0 - || LanguageProbability < 0.0f) - { - return false; - } - - if (AppliedPromptRoutingModeId.IsEmpty() || AppliedRetrievalContextLaneId.IsEmpty()) - { - return false; - } - - if (RetrievedHintCount < 0) - { - return false; - } - - if (bUsedRetrievedHintAugmentation && RetrievedHintCount <= 0) - { - return false; - } - - if (RetrievedHintCount != AppliedRetrievedHints.Num()) - { - return false; - } - - for (const FHyperTwistSpeechTranscriptSegment& Segment : Segments) - { - if (!Segment.IsStructurallyValid()) - { - return false; - } - } - - for (const FString& RetrievedHint : AppliedRetrievedHints) - { - if (RetrievedHint.IsEmpty()) - { - return false; - } - } - - return true; - } + bool IsStructurallyValid() const; }; USTRUCT(BlueprintType) @@ -7095,39 +6532,16 @@ struct FHyperTwistSpeechServiceHealth UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") bool bReady = true; - bool IsStructurallyValid() const + bool IsStructurallyValid() const; +}; + +namespace HyperTwistRecognitionTypeValidation +{ + inline bool AreNonEmptyStrings(const TArray& Values) { - if (ProviderLabel.IsEmpty() - || ServiceVersion.IsEmpty() - || ProviderProfileId.IsEmpty() - || !ProviderProfileDefinition.IsStructurallyValid() - || ByokCustodyProfileId.IsEmpty() - || !ByokCustodyProfileDefinition.IsStructurallyValid() - || ProviderRoutingPolicyId.IsEmpty() - || !ProviderRoutingPolicyDefinition.IsStructurallyValid() - || !ProviderRouteDecision.IsStructurallyValid() - || UsageCostAccountingProfileId.IsEmpty() - || !UsageCostAccountingProfileDefinition.IsStructurallyValid() - || !LatestUsageEvent.IsStructurallyValid() - || !LatestCostEvent.IsStructurallyValid() - || !QuotaRateLimitState.IsStructurallyValid() - || PayloadCustodyProfileId.IsEmpty() - || !PayloadCustodyProfileDefinition.IsStructurallyValid()) + for (const FString& Value : Values) { - return false; - } - - for (const FString& PayloadId : SupportedModelPayloadIds) - { - if (PayloadId.IsEmpty()) - { - return false; - } - } - - for (const FHyperTwistSpeechModelPayloadDescriptor& Payload : SupportedModelPayloads) - { - if (!Payload.IsStructurallyValid()) + if (Value.IsEmpty()) { return false; } @@ -7135,7 +6549,404 @@ struct FHyperTwistSpeechServiceHealth return true; } -}; + + template + inline bool AreStructurallyValidEntries(const TArray& Values) + { + for (const TStruct& Value : Values) + { + if (!Value.IsStructurallyValid()) + { + return false; + } + } + + return true; + } + + inline bool AreSpeechModelPayloadsValid(const TArray& Payloads) + { + return AreStructurallyValidEntries(Payloads); + } + + inline bool HasValidSpeechSessionCore(const FHyperTwistSpeechSessionConfig& Session) + { + return !Session.SessionId.IsEmpty() + && !Session.ListeningContractId.IsEmpty() + && !Session.InputRouteId.IsEmpty() + && !Session.AudioEncoding.IsEmpty() + && Session.SampleRateHz > 0 + && Session.ChannelCount > 0 + && !Session.TaskKind.IsEmpty() + && Session.RequiredModelPayloads.Num() > 0 + && Session.VadPolicy.IsStructurallyValid() + && Session.OrchestrationProfile.IsStructurallyValid(); + } + + inline bool HasValidSpeechSessionProfiles(const FHyperTwistSpeechSessionConfig& Session) + { + return !Session.MicrophoneShellProfileId.IsEmpty() + && Session.MicrophoneShellProfileDefinition.IsStructurallyValid() + && !Session.DevicePermissionWorkflowProfileId.IsEmpty() + && Session.DevicePermissionWorkflowProfileDefinition.IsStructurallyValid() + && !Session.NativeCaptureRouteWorkflowProfileId.IsEmpty() + && Session.NativeCaptureRouteWorkflowProfileDefinition.IsStructurallyValid() + && !Session.NativeCaptureRouteShellProfileId.IsEmpty() + && Session.NativeCaptureRouteShellProfileDefinition.IsStructurallyValid() + && !Session.ExternalDictationShellProfileId.IsEmpty() + && Session.ExternalDictationShellProfileDefinition.IsStructurallyValid() + && !Session.ProviderProfileId.IsEmpty() + && Session.ProviderProfileDefinition.IsStructurallyValid() + && !Session.ByokCustodyProfileId.IsEmpty() + && Session.ByokCustodyProfileDefinition.IsStructurallyValid() + && !Session.ProviderRoutingPolicyId.IsEmpty() + && Session.ProviderRoutingPolicyDefinition.IsStructurallyValid() + && Session.ProviderRouteDecision.IsStructurallyValid() + && !Session.UsageCostAccountingProfileId.IsEmpty() + && Session.UsageCostAccountingProfileDefinition.IsStructurallyValid() + && Session.UsageEventTemplate.IsStructurallyValid() + && Session.CostEventTemplate.IsStructurallyValid() + && !Session.UsageCostDashboardShellProfileId.IsEmpty() + && Session.UsageCostDashboardShellProfileDefinition.IsStructurallyValid() + && !Session.UsageCostHistoryExportShellProfileId.IsEmpty() + && Session.UsageCostHistoryExportShellProfileDefinition.IsStructurallyValid() + && !Session.ProviderReceiptReviewShellProfileId.IsEmpty() + && Session.ProviderReceiptReviewShellProfileDefinition.IsStructurallyValid() + && !Session.ProviderBillingSettlementShellProfileId.IsEmpty() + && Session.ProviderBillingSettlementShellProfileDefinition.IsStructurallyValid() + && !Session.ProviderSettlementExceptionShellProfileId.IsEmpty() + && Session.ProviderSettlementExceptionShellProfileDefinition.IsStructurallyValid() + && !Session.PayloadCustodyProfileId.IsEmpty() + && Session.PayloadCustodyProfileDefinition.IsStructurallyValid(); + } + + inline bool HasValidTranscriptCore(const FHyperTwistSpeechTranscriptResult& Transcript) + { + return !Transcript.SessionId.IsEmpty() + && !Transcript.UtteranceId.IsEmpty() + && !Transcript.TaskKind.IsEmpty() + && (!Transcript.TranscriptText.IsEmpty() || Transcript.Segments.Num() > 0) + && !Transcript.OrchestrationProfileId.IsEmpty() + && !Transcript.ServiceLaneId.IsEmpty() + && Transcript.RequestedBatchSize > 0 + && Transcript.ProcessedClipCount > 0 + && Transcript.AppliedBatchCollectionWindowMs > 0 + && Transcript.LanguageProbability >= 0.0f + && !Transcript.AppliedPromptRoutingModeId.IsEmpty() + && !Transcript.AppliedRetrievalContextLaneId.IsEmpty(); + } + + inline bool AreTranscriptSegmentsValid(const TArray& Segments) + { + return AreStructurallyValidEntries(Segments); + } + + inline bool HasValidSpeechServiceHealthCore(const FHyperTwistSpeechServiceHealth& Health) + { + return !Health.ProviderLabel.IsEmpty() + && !Health.ServiceVersion.IsEmpty() + && !Health.ProviderProfileId.IsEmpty() + && Health.ProviderProfileDefinition.IsStructurallyValid() + && !Health.ByokCustodyProfileId.IsEmpty() + && Health.ByokCustodyProfileDefinition.IsStructurallyValid() + && !Health.ProviderRoutingPolicyId.IsEmpty() + && Health.ProviderRoutingPolicyDefinition.IsStructurallyValid() + && Health.ProviderRouteDecision.IsStructurallyValid() + && !Health.UsageCostAccountingProfileId.IsEmpty() + && Health.UsageCostAccountingProfileDefinition.IsStructurallyValid() + && Health.LatestUsageEvent.IsStructurallyValid() + && Health.LatestCostEvent.IsStructurallyValid() + && Health.QuotaRateLimitState.IsStructurallyValid() + && !Health.PayloadCustodyProfileId.IsEmpty() + && Health.PayloadCustodyProfileDefinition.IsStructurallyValid(); + } + + inline bool HasValidExternalDictationShellStateCore( + const FHyperTwistSpeechExternalDictationShellState& State + ) + { + return !State.ExternalDictationShellProfileId.IsEmpty() + && !State.ActiveProviderProfileId.IsEmpty() + && !State.ActiveServiceLaneId.IsEmpty() + && !State.SelectedOutputRouteId.IsEmpty() + && !State.TranscriptHistorySurfaceModeId.IsEmpty() + && !State.OutputRoutingSurfaceModeId.IsEmpty() + && !State.PostProcessOverlaySurfaceModeId.IsEmpty() + && !State.GlobalHotkeySurfaceModeId.IsEmpty() + && !State.InputDeviceSurfaceModeId.IsEmpty() + && !State.OutputDeviceSurfaceModeId.IsEmpty() + && !State.MuteSurfaceModeId.IsEmpty() + && !State.MicrophoneModeSurfaceModeId.IsEmpty() + && !State.LocalModelCatalogSurfaceModeId.IsEmpty() + && !State.ModelIntegritySurfaceModeId.IsEmpty() + && !State.ModelUnloadSurfaceModeId.IsEmpty() + && !State.LatestSourceKind.IsEmpty() + && !State.StatusLine.IsEmpty() + && !State.DetailLine.IsEmpty() + && !State.PrimaryHotkeyBindingLabel.IsEmpty() + && !State.PostProcessHotkeyBindingLabel.IsEmpty() + && !State.SelectedInputDeviceId.IsEmpty() + && !State.SelectedInputDeviceLabel.IsEmpty() + && !State.SelectedOutputDeviceId.IsEmpty() + && !State.SelectedOutputDeviceLabel.IsEmpty() + && !State.MicrophoneModeId.IsEmpty() + && !State.PayloadCustodyProfileId.IsEmpty() + && !State.SelectedPrimaryPayloadId.IsEmpty() + && State.HistoryEntryCount >= 0 + && State.PostProcessedEntryCount >= 0 + && State.SavedEntryCount >= 0 + && State.ModelCatalogEntryCount >= 0 + && State.OptionalModelCatalogEntryCount >= 0 + && State.DownloadDeferredModelEntryCount >= 0 + && State.IntegrityReviewRequiredEntryCount >= 0 + && State.ActiveIssueCount >= 0 + && State.OutputRouteOptions.Num() > 0 + && State.InputDeviceOptions.Num() > 0 + && State.OutputDeviceOptions.Num() > 0 + && State.ModelCatalogEntries.Num() > 0 + && State.AvailableActionIds.Num() > 0; + } + + inline bool HasConsistentExternalDictationShellStateCounts( + const FHyperTwistSpeechExternalDictationShellState& State + ) + { + return State.HistoryEntryCount == State.HistoryEntries.Num() + && State.PostProcessedEntryCount <= State.HistoryEntryCount + && State.SavedEntryCount <= State.HistoryEntryCount + && State.ModelCatalogEntryCount == State.ModelCatalogEntries.Num() + && State.OptionalModelCatalogEntryCount <= State.ModelCatalogEntryCount + && State.DownloadDeferredModelEntryCount <= State.ModelCatalogEntryCount + && State.IntegrityReviewRequiredEntryCount <= State.ModelCatalogEntryCount; + } + + inline bool HasConsistentExternalDictationShellStateLatestEntry( + const FHyperTwistSpeechExternalDictationShellState& State + ) + { + return State.HistoryEntryCount <= 0 + || (!State.LatestHistoryEntryId.IsEmpty() && !State.LatestTranscriptText.IsEmpty()); + } + + inline bool HasConsistentExternalDictationShellStateActiveIssue( + const FHyperTwistSpeechExternalDictationShellState& State + ) + { + return State.ActiveIssueCount == State.Issues.Num() + && (State.ActiveIssueCount <= 0 || State.ActiveIssue.IsStructurallyValid()); + } +} + +inline bool FHyperTwistSpeechUsageCostHistoryExportShellState::IsStructurallyValid() const +{ + if (HistoryExportShellProfileId.IsEmpty() + || ActiveProviderProfileId.IsEmpty() + || ActiveProviderDisplayLabel.IsEmpty() + || ActiveServiceLaneId.IsEmpty() + || SelectedHistoryWindowId.IsEmpty() + || ExportPreviewFormatId.IsEmpty() + || LatestSourceKind.IsEmpty() + || StatusLine.IsEmpty() + || DetailLine.IsEmpty() + || HistoryEntryCount < 0 + || EstimateOnlyEntryCount < 0 + || ProviderReceiptEntryCount < 0 + || TotalUsageQuantity < 0.0f + || TotalEstimatedCostUsd < 0.0f + || TotalReportedCostUsd < 0.0f + || LowestRemainingEstimatedSpendUsd < 0.0f + || ActiveIssueCount < 0 + || AvailableActionIds.Num() <= 0) + { + return false; + } + + return HistoryEntryCount == HistoryEntries.Num() + && (HistoryEntryCount <= 0 || !LatestHistoryEntryId.IsEmpty()) + && (!bExportPreviewReady || !ExportPreviewText.IsEmpty()) + && ActiveIssueCount == Issues.Num() + && (ActiveIssueCount <= 0 || ActiveIssue.IsStructurallyValid()) + && HyperTwistRecognitionTypeValidation::AreStructurallyValidEntries(Issues) + && HyperTwistRecognitionTypeValidation::AreStructurallyValidEntries(HistoryEntries) + && HyperTwistRecognitionTypeValidation::AreNonEmptyStrings(AvailableActionIds); +} + +inline bool FHyperTwistSpeechProviderReceiptReviewShellState::IsStructurallyValid() const +{ + if (ReceiptReviewShellProfileId.IsEmpty() + || ActiveProviderProfileId.IsEmpty() + || ActiveProviderDisplayLabel.IsEmpty() + || ActiveServiceLaneId.IsEmpty() + || SelectedChargeWindowId.IsEmpty() + || PostedChargeInspectionModeId.IsEmpty() + || LatestSourceKind.IsEmpty() + || StatusLine.IsEmpty() + || DetailLine.IsEmpty() + || ReceiptEntryCount < 0 + || ProviderPostedChargeCount < 0 + || EstimateOnlyEntryCount < 0 + || VarianceReviewEntryCount < 0 + || TotalEstimatedCostUsd < 0.0f + || TotalReportedCostUsd < 0.0f + || TotalAbsoluteVarianceUsd < 0.0f + || HighestAbsoluteVarianceUsd < 0.0f + || ActiveIssueCount < 0 + || AvailableActionIds.Num() <= 0) + { + return false; + } + + return ReceiptEntryCount == ReceiptEntries.Num() + && ProviderPostedChargeCount <= ReceiptEntryCount + && EstimateOnlyEntryCount <= ReceiptEntryCount + && VarianceReviewEntryCount <= ReceiptEntryCount + && (ReceiptEntryCount <= 0 || !LatestReceiptEntryId.IsEmpty()) + && (!bReceiptSummaryReady || !ReceiptSummaryText.IsEmpty()) + && ActiveIssueCount == Issues.Num() + && (ActiveIssueCount <= 0 || ActiveIssue.IsStructurallyValid()) + && HyperTwistRecognitionTypeValidation::AreStructurallyValidEntries(Issues) + && HyperTwistRecognitionTypeValidation::AreStructurallyValidEntries(ReceiptEntries) + && HyperTwistRecognitionTypeValidation::AreNonEmptyStrings(AvailableActionIds); +} + +inline bool FHyperTwistSpeechProviderBillingSettlementShellState::IsStructurallyValid() const +{ + if (BillingSettlementShellProfileId.IsEmpty() + || ActiveProviderProfileId.IsEmpty() + || ActiveProviderDisplayLabel.IsEmpty() + || ActiveServiceLaneId.IsEmpty() + || SelectedSettlementWindowId.IsEmpty() + || SettlementSurfaceModeId.IsEmpty() + || InvoiceReconciliationModeId.IsEmpty() + || LatestSourceKind.IsEmpty() + || StatusLine.IsEmpty() + || DetailLine.IsEmpty() + || SettlementEntryCount < 0 + || ProviderPostedChargeCount < 0 + || ReconciliationReadyEntryCount < 0 + || EstimateOnlyEntryCount < 0 + || ManualReviewEntryCount < 0 + || BlockedSettlementEntryCount < 0 + || TotalEstimatedCostUsd < 0.0f + || TotalReportedCostUsd < 0.0f + || TotalSettlementDeltaUsd < 0.0f + || HighestSettlementDeltaUsd < 0.0f + || ActiveIssueCount < 0 + || AvailableActionIds.Num() <= 0) + { + return false; + } + + return SettlementEntryCount == SettlementEntries.Num() + && ProviderPostedChargeCount <= SettlementEntryCount + && ReconciliationReadyEntryCount <= SettlementEntryCount + && EstimateOnlyEntryCount <= SettlementEntryCount + && ManualReviewEntryCount <= SettlementEntryCount + && BlockedSettlementEntryCount <= SettlementEntryCount + && (SettlementEntryCount <= 0 || !LatestSettlementEntryId.IsEmpty()) + && (!bSettlementSummaryReady || !SettlementSummaryText.IsEmpty()) + && ActiveIssueCount == Issues.Num() + && (ActiveIssueCount <= 0 || ActiveIssue.IsStructurallyValid()) + && HyperTwistRecognitionTypeValidation::AreStructurallyValidEntries(Issues) + && HyperTwistRecognitionTypeValidation::AreStructurallyValidEntries(SettlementEntries) + && HyperTwistRecognitionTypeValidation::AreNonEmptyStrings(AvailableActionIds); +} + +inline bool FHyperTwistSpeechProviderSettlementExceptionShellState::IsStructurallyValid() const +{ + if (SettlementExceptionShellProfileId.IsEmpty() + || ActiveProviderProfileId.IsEmpty() + || ActiveProviderDisplayLabel.IsEmpty() + || ActiveServiceLaneId.IsEmpty() + || SelectedSettlementWindowId.IsEmpty() + || ExceptionSurfaceModeId.IsEmpty() + || ExternalPortalHandoffModeId.IsEmpty() + || LatestSourceKind.IsEmpty() + || StatusLine.IsEmpty() + || DetailLine.IsEmpty() + || ExceptionEntryCount < 0 + || ExternalPortalHandoffReadyEntryCount < 0 + || PendingChargeEntryCount < 0 + || ManualReviewEntryCount < 0 + || RouteDegradedEntryCount < 0 + || BlockedSettlementEntryCount < 0 + || TotalEstimatedCostUsd < 0.0f + || TotalReportedCostUsd < 0.0f + || TotalSettlementDeltaUsd < 0.0f + || HighestSettlementDeltaUsd < 0.0f + || ActiveIssueCount < 0 + || AvailableActionIds.Num() <= 0) + { + return false; + } + + return ExceptionEntryCount == ExceptionEntries.Num() + && ExternalPortalHandoffReadyEntryCount <= ExceptionEntryCount + && PendingChargeEntryCount <= ExceptionEntryCount + && ManualReviewEntryCount <= ExceptionEntryCount + && RouteDegradedEntryCount <= ExceptionEntryCount + && BlockedSettlementEntryCount <= ExceptionEntryCount + && (ExceptionEntryCount <= 0 || !LatestExceptionEntryId.IsEmpty()) + && (!bExceptionSummaryReady || !ExceptionSummaryText.IsEmpty()) + && ActiveIssueCount == Issues.Num() + && (ActiveIssueCount <= 0 || ActiveIssue.IsStructurallyValid()) + && HyperTwistRecognitionTypeValidation::AreStructurallyValidEntries(Issues) + && HyperTwistRecognitionTypeValidation::AreStructurallyValidEntries(ExceptionEntries) + && HyperTwistRecognitionTypeValidation::AreNonEmptyStrings(AvailableActionIds); +} + +inline bool FHyperTwistSpeechExternalDictationShellState::IsStructurallyValid() const +{ + return HyperTwistRecognitionTypeValidation::HasValidExternalDictationShellStateCore(*this) + && HyperTwistRecognitionTypeValidation::HasConsistentExternalDictationShellStateCounts( + *this + ) + && HyperTwistRecognitionTypeValidation::HasConsistentExternalDictationShellStateLatestEntry( + *this + ) + && HyperTwistRecognitionTypeValidation::HasConsistentExternalDictationShellStateActiveIssue( + *this + ) + && HyperTwistRecognitionTypeValidation::AreStructurallyValidEntries(Issues) + && HyperTwistRecognitionTypeValidation::AreStructurallyValidEntries(HistoryEntries) + && HyperTwistRecognitionTypeValidation::AreStructurallyValidEntries(OutputRouteOptions) + && HyperTwistRecognitionTypeValidation::AreStructurallyValidEntries(InputDeviceOptions) + && HyperTwistRecognitionTypeValidation::AreStructurallyValidEntries(OutputDeviceOptions) + && HyperTwistRecognitionTypeValidation::AreStructurallyValidEntries(ModelCatalogEntries) + && HyperTwistRecognitionTypeValidation::AreNonEmptyStrings(AvailableActionIds); +} + +inline bool FHyperTwistSpeechSessionConfig::IsStructurallyValid() const +{ + return HyperTwistRecognitionTypeValidation::HasValidSpeechSessionCore(*this) + && HyperTwistRecognitionTypeValidation::HasValidSpeechSessionProfiles(*this) + && HyperTwistRecognitionTypeValidation::AreSpeechModelPayloadsValid(RequiredModelPayloads); +} + +inline bool FHyperTwistSpeechTranscriptResult::IsStructurallyValid() const +{ + if (!HyperTwistRecognitionTypeValidation::HasValidTranscriptCore(*this) + || RetrievedHintCount < 0) + { + return false; + } + + if (bUsedRetrievedHintAugmentation && RetrievedHintCount <= 0) + { + return false; + } + + return RetrievedHintCount == AppliedRetrievedHints.Num() + && HyperTwistRecognitionTypeValidation::AreTranscriptSegmentsValid(Segments) + && HyperTwistRecognitionTypeValidation::AreNonEmptyStrings(AppliedRetrievedHints); +} + +inline bool FHyperTwistSpeechServiceHealth::IsStructurallyValid() const +{ + return HyperTwistRecognitionTypeValidation::HasValidSpeechServiceHealthCore(*this) + && HyperTwistRecognitionTypeValidation::AreNonEmptyStrings(SupportedModelPayloadIds) + && HyperTwistRecognitionTypeValidation::AreSpeechModelPayloadsValid(SupportedModelPayloads); +} USTRUCT(BlueprintType) struct FHyperTwistVoiceAssetDescriptor diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistSimulation/HyperTwistClassicCubeGameMode.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistSimulation/HyperTwistClassicCubeGameMode.h index 0257785..393d5c5 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistSimulation/HyperTwistClassicCubeGameMode.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistSimulation/HyperTwistClassicCubeGameMode.h @@ -4,6 +4,7 @@ #include "GameFramework/GameModeBase.h" #include "HyperTwistRecognition/HyperTwistRecognitionTypes.h" #include "HyperTwistSimulation/HyperTwistClassicCubeLeaderboardLibrary.h" +#include "HyperTwistSimulation/HyperTwistClassicCubeOperatorSurface.h" #include "HyperTwistSimulation/HyperTwistClassicCubeTypes.h" #include "HyperTwistTraining/HyperTwistTrainingTypes.h" #include "HyperTwistClassicCubeGameMode.generated.h" @@ -19,7 +20,9 @@ class USoundWaveProcedural; class UHyperTwistTrainingSubsystem; UCLASS(BlueprintType, Blueprintable) -class UNREALHYPERTWIST_API AHyperTwistClassicCubeGameMode : public AGameModeBase +class UNREALHYPERTWIST_API AHyperTwistClassicCubeGameMode + : public AGameModeBase + , public IHyperTwistClassicCubeOperatorSurface { GENERATED_BODY() @@ -28,6 +31,15 @@ public: virtual void BeginPlay() override; virtual void Tick(float DeltaSeconds) override; + virtual void RequestClassicCubeFreshAttempt_Implementation() override; + virtual void RequestClassicCubeHint_Implementation() override; + virtual void RequestClassicCubeSubmitSolve_Implementation() override; + virtual void RequestClassicCubeToggleFollowAlongMode_Implementation() override; + virtual void RequestClassicCubeBeginVoiceCommandCapture_Implementation() override; + virtual void RequestClassicCubeEndVoiceCommandCapture_Implementation() override; + virtual void RequestClassicCubeCycleVoiceProfile_Implementation() override; + virtual bool CanClassicCubeAcceptGameplayMoveInput_Implementation() const override; + virtual bool IsClassicCubeAnyActionButtonHovered_Implementation() const override; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|ClassicCube|HUD") bool bAutoCreateHud = true; diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistSimulation/HyperTwistClassicCubeOperatorSurface.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistSimulation/HyperTwistClassicCubeOperatorSurface.h new file mode 100644 index 0000000..47c9ce6 --- /dev/null +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistSimulation/HyperTwistClassicCubeOperatorSurface.h @@ -0,0 +1,52 @@ +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Interface.h" +#include "HyperTwistClassicCubeOperatorSurface.generated.h" + +UINTERFACE(BlueprintType) +class UNREALHYPERTWIST_API UHyperTwistClassicCubeOperatorSurface : public UInterface +{ + GENERATED_BODY() +}; + +class UNREALHYPERTWIST_API IHyperTwistClassicCubeOperatorSurface +{ + GENERATED_BODY() + +public: + UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "HyperTwist|ClassicCube|Operator") + void RequestClassicCubeFreshAttempt(); + + UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "HyperTwist|ClassicCube|Operator") + void RequestClassicCubeHint(); + + UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "HyperTwist|ClassicCube|Operator") + void RequestClassicCubeSubmitSolve(); + + UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "HyperTwist|ClassicCube|Operator") + void RequestClassicCubeToggleFollowAlongMode(); + + UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "HyperTwist|ClassicCube|Operator") + void RequestClassicCubeBeginVoiceCommandCapture(); + + UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "HyperTwist|ClassicCube|Operator") + void RequestClassicCubeEndVoiceCommandCapture(); + + UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "HyperTwist|ClassicCube|Operator") + void RequestClassicCubeCycleVoiceProfile(); + + UFUNCTION( + BlueprintCallable, + BlueprintNativeEvent, + Category = "HyperTwist|ClassicCube|Operator" + ) + bool CanClassicCubeAcceptGameplayMoveInput() const; + + UFUNCTION( + BlueprintCallable, + BlueprintNativeEvent, + Category = "HyperTwist|ClassicCube|Operator" + ) + bool IsClassicCubeAnyActionButtonHovered() const; +}; diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistSimulation/HyperTwistClassicCubePlayerController.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistSimulation/HyperTwistClassicCubePlayerController.h index 3f1739d..fcf6e24 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistSimulation/HyperTwistClassicCubePlayerController.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistSimulation/HyperTwistClassicCubePlayerController.h @@ -5,7 +5,6 @@ #include "HyperTwistClassicCubePlayerController.generated.h" class AHyperTwistClassicCubeActor; -class AHyperTwistClassicCubeGameMode; UCLASS(BlueprintType, Blueprintable) class UNREALHYPERTWIST_API AHyperTwistClassicCubePlayerController : public APlayerController @@ -57,7 +56,6 @@ public: protected: void ApplyClassicCubeInputMode(); AHyperTwistClassicCubeActor* ResolveClassicCubeActor() const; - class AHyperTwistClassicCubeGameMode* ResolveClassicCubeGameMode() const; void HandlePrimaryClick(); void HandleSecondaryClick(); void HandleFreshAttemptShortcut(); diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistSimulation/HyperTwistMelindaProjectionPlayerController.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistSimulation/HyperTwistMelindaProjectionPlayerController.h index daac66a..2031076 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistSimulation/HyperTwistMelindaProjectionPlayerController.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistSimulation/HyperTwistMelindaProjectionPlayerController.h @@ -5,7 +5,6 @@ #include "HyperTwistMelindaProjectionPlayerController.generated.h" class AHyperTwistMelindaProjectionActor; -class AHyperTwistMelindaProjectionGameMode; UCLASS(BlueprintType, Blueprintable) class UNREALHYPERTWIST_API AHyperTwistMelindaProjectionPlayerController @@ -55,7 +54,6 @@ public: protected: void ApplyInputMode(); AHyperTwistMelindaProjectionActor* ResolveProjectionActor() const; - AHyperTwistMelindaProjectionGameMode* ResolveProjectionGameMode() const; void HandlePrimaryClick(); void HandleSecondaryClick(); void HandleResetShortcut(); diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistSimulation/HyperTwistVirtual3333ProjectionPlayerController.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistSimulation/HyperTwistVirtual3333ProjectionPlayerController.h index 8985361..a379e80 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistSimulation/HyperTwistVirtual3333ProjectionPlayerController.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistSimulation/HyperTwistVirtual3333ProjectionPlayerController.h @@ -6,7 +6,6 @@ #include "HyperTwistVirtual3333ProjectionPlayerController.generated.h" class AHyperTwistVirtual3333ProjectionActor; -class AHyperTwistVirtual3333ProjectionGameMode; UCLASS(BlueprintType, Blueprintable) class UNREALHYPERTWIST_API AHyperTwistVirtual3333ProjectionPlayerController @@ -47,7 +46,6 @@ public: protected: void ApplyInputMode(); AHyperTwistVirtual3333ProjectionActor* ResolveProjectionActor() const; - AHyperTwistVirtual3333ProjectionGameMode* ResolveProjectionGameMode() const; void HandleSliceAxisX(); void HandleSliceAxisY(); void HandleSliceAxisZ(); diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCatalogLibrary.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCatalogLibrary.h index a7e0b1d..9fbfe1a 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCatalogLibrary.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingCatalogLibrary.h @@ -36,6 +36,9 @@ public: UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Catalog") static FHyperTwistContentPack MakeFiveStyleStarterContentPack(); + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Catalog") + static FHyperTwistContentPack MakeSampleClassicContentPack(); + UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Catalog") static TArray MakePhase3TrainingCatalog(); diff --git a/docs/ops/FORGEJO_WOODPECKER_VPS_A_TO_Z_SENSITIVE_RUNBOOK_VPS_12_24_720_UBUNTU_24_04.md b/docs/ops/FORGEJO_WOODPECKER_VPS_A_TO_Z_SENSITIVE_RUNBOOK_VPS_12_24_720_UBUNTU_24_04.md index 576b27a..f5980d2 100644 --- a/docs/ops/FORGEJO_WOODPECKER_VPS_A_TO_Z_SENSITIVE_RUNBOOK_VPS_12_24_720_UBUNTU_24_04.md +++ b/docs/ops/FORGEJO_WOODPECKER_VPS_A_TO_Z_SENSITIVE_RUNBOOK_VPS_12_24_720_UBUNTU_24_04.md @@ -1,6 +1,6 @@ # Forgejo + Woodpecker VPS A-to-Z Runbook (Sensitive) - VPS 12-24-720 Ubuntu 24.04 -Last updated: 2026-05-31 (UTC) +Last updated: 2026-06-18 (UTC) Scope: Canonical sensitive VPS runbook for the current Forgejo/Woodpecker control plane, extended for the IONOS `VPS 12-24-720` Ubuntu `24.04` Linux target, migration/cutover sequence, and remote-development setup. Classification: **Highly Sensitive** (contains real credentials, keys/paths, and secrets). @@ -1561,6 +1561,11 @@ Future production note: - once HyperTwist and VectorShell move to production, add their production-side backup streams explicitly - do not treat that as justification for copying their whole active dev trees into the Scriptorium VPS backup root now +- `2026-06-18` operator correction for manual `VPSM/` handoff folders: +- when a repo/worktree backup is staged into a human-browseable `VPSM/` folder, store it as a normal directory tree with sidecar manifest/checksum files +- do not leave that handoff as `.tar`, `.tar.gz`, `.tgz`, `.zip`, or any other archive inside the `VPSM/` folder +- if a transfer helper uses streaming tar or compression in transit, it must unpack immediately so the stored backup on disk stays browseable in place +- this rule is specific to manual `VPSM/` repo/worktree handoff folders; the established `/srv/backups/scriptorium` nightly DB/data streams keep their documented formats ### 16.7 Operator Summary diff --git a/docs/ops/HYPERTWIST_OVERNIGHT_MODE_AGENTIC_CONTINUATION_SELF_BATCHING_AND_GOAL_CREATION_DOCTRINE_2026-06-19.md b/docs/ops/HYPERTWIST_OVERNIGHT_MODE_AGENTIC_CONTINUATION_SELF_BATCHING_AND_GOAL_CREATION_DOCTRINE_2026-06-19.md index aa9d3ea..9f81738 100644 --- a/docs/ops/HYPERTWIST_OVERNIGHT_MODE_AGENTIC_CONTINUATION_SELF_BATCHING_AND_GOAL_CREATION_DOCTRINE_2026-06-19.md +++ b/docs/ops/HYPERTWIST_OVERNIGHT_MODE_AGENTIC_CONTINUATION_SELF_BATCHING_AND_GOAL_CREATION_DOCTRINE_2026-06-19.md @@ -338,3 +338,48 @@ validation. This doctrine does not authorize arbitrary autonomous project redirection. It authorizes disciplined prolonged continuation inside declared, source-real, authority-backed lanes. +## Update - 2026-06-22 - recommended-string adoption at prepared decision boundaries + +The earlier stricter wording in this doctrine is now refined. + +`Overnight Mode` still does **not** authorize silent fabrication of unresolved +high-risk decision answers. +But it **does** allow adoption of an already-presented recommended compact +answer string when the user later responds with plain `continue`, provided the +prepared batch or decision packet has already crossed the safety threshold below. + +### Safety threshold for recommendation adoption + +A future instance may treat plain `continue` as acceptance of the last +already-presented canonical recommended string only when all of these are true: + +1. the batch or decision packet was already presented in full with one canonical recommended string +2. the batch remains same-family and same-authority-chain continuation +3. current live source and present-tense license truth were already verified +4. the decision is low-risk and low-ambiguity +5. the decision does not silently grant sovereign owner status +6. the decision does not cross a material clean-room, legal, security, or fresh-doctrine boundary +7. the recommendation confidence remains high + +### Cases that still require an explicit stop + +Plain `continue` must **not** auto-adopt the recommendation when any of these +are true: + +- sovereign owner selection is still materially open +- restrictive-versus-clean-room routing is still materially outcome-changing +- present-tense source or license truth is contradicted or uncertain in a way that changes the recommendation +- security or legal risk materially changes the lane +- recommendation confidence is low +- the choice would reshape roadmap priority rather than simply drain the current same-family lane + +### Operational consequence + +When the safety threshold is satisfied: + +- future instances may adopt the displayed recommendation from plain `continue` +- they must then immediately propagate that answer into the downstream decision authorities +- after propagation they should continue only to the next adjacent same-family slice, not reinterpret the broader roadmap from scratch + +This update supersedes any earlier reading of this doctrine that treated every +prepared decision boundary as equally non-adoptable under plain `continue`. diff --git a/docs/ops/HYPERTWIST_OVERNIGHT_MODE_CLOSEOUT_CONTINUATION_AND_BOUNDARY_SHAPE_2026-06-19.md b/docs/ops/HYPERTWIST_OVERNIGHT_MODE_CLOSEOUT_CONTINUATION_AND_BOUNDARY_SHAPE_2026-06-19.md index 07b7ba9..9bec37a 100644 --- a/docs/ops/HYPERTWIST_OVERNIGHT_MODE_CLOSEOUT_CONTINUATION_AND_BOUNDARY_SHAPE_2026-06-19.md +++ b/docs/ops/HYPERTWIST_OVERNIGHT_MODE_CLOSEOUT_CONTINUATION_AND_BOUNDARY_SHAPE_2026-06-19.md @@ -168,3 +168,22 @@ Those vague shapes are forbidden for `Overnight Mode`. This shape is mandatory for substantial closeouts because deterministic continuation is impossible without deterministic closure wording. +## Update - 2026-06-22 - required recommendation-adoption field at prepared decision boundaries + +When a substantial closeout stops at a prepared quiz or decision packet, the +closeout must now explicitly state one of the following: + +- plain `continue` **may** adopt the displayed canonical recommended string, in which case the exact string must be restated verbatim +- plain `continue` **may not** adopt it, in which case the blocking reason must be stated explicitly + +Required shape example: + +- recommendation-adoption status: `continue` may adopt `A D / O:B C E F / X` + +or + +- recommendation-adoption status: explicit user choice still required because sovereign ownership or legal routing remains materially unresolved + +This field is mandatory because deterministic continuation now includes a narrow +safe path for recommendation adoption, and future instances must not leave that +truth implicit. diff --git a/docs/ops/HYPERTWIST_PUBLIC_WEBSITE_CONTENT_AND_OPERATOR_MANUAL_PACKET_2026-06-22.md b/docs/ops/HYPERTWIST_PUBLIC_WEBSITE_CONTENT_AND_OPERATOR_MANUAL_PACKET_2026-06-22.md new file mode 100644 index 0000000..b70ad1f --- /dev/null +++ b/docs/ops/HYPERTWIST_PUBLIC_WEBSITE_CONTENT_AND_OPERATOR_MANUAL_PACKET_2026-06-22.md @@ -0,0 +1,113 @@ +# HyperTwist Public Website Content and Operator Manual Packet + +Created on `2026-06-22`. + +## Purpose + +This continuation strengthens the public `hypertwist.app` surface so the site +does more than expose routes and configuration seams. It now carries clearer +product positioning plus a real public-facing operator manual. + +## Product-positioning correction + +The website and the desktop build are both kept on purpose. + +The website is **not** the simulator. It exists because HyperTwist needs: + +- public product positioning +- account/auth access +- pricing and checkout posture +- release and package-proof posture +- legal/notices posture +- browser-to-desktop pairing +- support and rollout guidance + +The website is intentionally inferior for the core simulator job: + +- it does not own the native Unreal runtime +- it does not own packaged higher-dimensional execution +- it does not replace desktop-side device/runtime integration +- it does not claim the optional full-browser simulator branch is live + +So the website is not superfluous. It is a complementary operator/distribution +surface around a desktop-first simulator. + +## Landed public-manual continuation + +The public pages now carry more explicit guidance for: + +- what the browser shell does versus what the desktop runtime does +- how a user starts in the browser, signs in, and reaches the protected + download lane +- how desktop pairing works through the browser-to-desktop token handoff +- how classic-cube, replay, diagnostics, and higher-dimensional truths should + be understood from public-safe surfaces +- how operators should interpret package proof, release posture, and launch + readiness + +The later same-day continuation also widened the manual from page-shell copy +into a more practical public operator reference: + +- the docs page now carries explicit public documentation principles derived + from feature-registry discipline rather than generic marketing wording +- the docs and resources pages now carry a bounded simulator-use manual for: + classic-cube recognition/correction, replay/coaching/analytics, + higher-dimensional 120-cell and 5D runtime ownership, and operator + diagnostics/release proof +- the widened manual stays public-safe by explaining the real desktop runtime + instead of claiming the website itself performs the simulator job +- the public legal and delivery pages now also carry the same product truth: + release notes explain how to read browser/operator versus simulator changes, + open-source notices explain why public pages are themselves release surfaces, + privacy and terms now describe the browser-versus-desktop boundary in + operational language, and shipping/payment now carries the real digital + delivery workflow instead of generic storefront filler + +## Public pages widened by this packet + +- homepage +- about page +- resources page +- docs page +- pricing page +- download page +- support page +- changelog page +- open-source notices page +- privacy page +- terms page +- shipping/payment page + +## Source of truth used + +- `docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md` +- `docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md` +- `docs/v6_5_deep_manual_pack/HyperTwist/PRD.md` +- `website/README.md` +- existing `website/server` release-manifest and auth-health posture + +## Non-claims kept explicit + +This packet does not: + +- reopen the optional full-browser simulator branch +- claim full native `MagicTile` renderer-port ownership +- claim the website has surpassed the desktop runtime for the core simulator +- claim unresolved launch-tier checkout/download values are already configured + +## Validation + +The same-day public-manual continuation passed: + +- `npm run type-check` in `website/` +- `npm test -- --run src/__tests__/public-marketing-pages.test.tsx` in + `website/` +- `npm run build` in `website/` +- `npm test -- --run` in `website/` +- `npm run type-check` in `website/server` +- `npm test -- --run` in `website/server` + +The same validation hardening also gives the live-spawned +`website/server` bootstrap proof an explicit `15s` timeout so real child-process +boot plus same-origin HTTP verification does not fail spuriously on a loaded +host while the proof scope remains unchanged. diff --git a/docs/ops/HYPERTWIST_REFACTORING_TOOLCHAIN_2026-06-22.md b/docs/ops/HYPERTWIST_REFACTORING_TOOLCHAIN_2026-06-22.md new file mode 100644 index 0000000..b643fda --- /dev/null +++ b/docs/ops/HYPERTWIST_REFACTORING_TOOLCHAIN_2026-06-22.md @@ -0,0 +1,235 @@ +# HyperTwist Refactoring Toolchain + +Created on `2026-06-22`. + +## Purpose + +This note gives HyperTwist its own bounded refactor/analyzer posture instead of +leaving `sentrux` and `GitNexus` as cross-repo memory from ScriptoriumAI or +VectorShell. + +## Current posture + +- `sentrux` = permissive structural gate and source-health sensor +- `GitNexus` = analysis-only graph/impact tool +- neither tool is a shipped HyperTwist runtime dependency +- generated `.gitnexus/` state and the disposable + `.gitnexus-source-only-root/` mirror are repo-local analysis outputs and must + stay out of commits + +## Landed HyperTwist-owned entry points + +- `.sentrux/rules.toml` +- `scripts/run-hypertwist-sentrux-source-only.sh` +- `scripts/run-hypertwist-gitnexus-analyze.sh` +- `scripts/run-hypertwist-gitnexus-status.sh` + +## Why these routes exist + +HyperTwist spans three materially different code families: + +- Unreal native/runtime code under `UnrealHyperTwist/Source/` +- embedded simulator-side browser runtime under `Content/Browser/` +- public/auth/distribution website code under `website/` + +The toolchain needs to understand that structure without pretending the whole +repo is one flat JavaScript app. + +## `sentrux` usage + +The HyperTwist source-only wrapper intentionally mirrors only the high-signal +source families into a temporary root before running `sentrux check`: + +- `UnrealHyperTwist/Source` +- `Content/Browser/src` +- `website/src` +- `website/server/src` +- `scripts` + +Run: + +```bash +scripts/run-hypertwist-sentrux-source-only.sh +``` + +Resolution order for the analyzer binary is now HyperTwist-owned first: + +- `HYPERTWIST_SENTRUX_BINARY` if explicitly provided +- repo-local `./sentrux` or `./sentrux.exe` if present +- `sentrux` on `PATH` +- the retained local fallback under `/home/dev/src/VectorShell/sentrux` + +Current rules enforce: + +- no cycles +- bounded function complexity and size +- browser client must not import website auth-server implementation +- embedded simulator browser runtime must not couple to website auth-server + internals +- automation scripts must not import browser app internals + +## `GitNexus` usage + +HyperTwist should use `GitNexus` as a deeper graph/impact surface before +larger refactors, renames, or subsystem splits. + +Run: + +```bash +scripts/run-hypertwist-gitnexus-analyze.sh +scripts/run-hypertwist-gitnexus-status.sh +``` + +Behavior: + +- mirrors only the high-signal HyperTwist source families into + `.gitnexus-source-only-root/` before analysis +- bootstraps that disposable mirror as a lightweight git repository so + `analyze` and `status` can run against the same bounded root +- records a local disposable snapshot commit inside that mirror so GitNexus + status output stays clean instead of reporting an empty `HEAD` +- deletes copied files above the default `256 KB` ceiling so giant Unreal + generated/runtime files do not destabilize the native worker path +- prefers the local retained working reference at + `mirrors/GitNexus/gitnexus/dist/cli/index.js` +- if the retained local node/native payload is not runnable on the current host + (for example a cross-platform `LadybugDB` binary mismatch), the wrapper + falls back automatically to `npx -y gitnexus@latest` +- the wrapper suppresses the retained-CLI native-loader stderr spew and the + benign empty-`HEAD` stderr noise that upstream `status` can emit on bounded + disposable mirrors, while preserving the actual status result +- falls back to `npx -y gitnexus@latest` only if the local retained CLI is not + available or is not runnable +- always uses `--skip-agents-md` so HyperTwist authority files are not + rewritten just to refresh analysis state + +## Working-reference note + +The retained HyperTwist `mirrors/GitNexus` working reference already contains +the newer stack-overflow and cycle-hardening work recorded in its +`CHANGELOG.md`, including: + +- iterative stdio newline handling to prevent stack overflow on empty-line + bursts +- broader cycle-safe traversal and depth-limited graph/type processing + +That retained working reference is acceptable as an external analysis surface. +It is not a signal to absorb GitNexus runtime code into the shipped product. + +## Recommended refactor loop + +1. run `scripts/run-hypertwist-gitnexus-analyze.sh` +2. review impact/freshness through `scripts/run-hypertwist-gitnexus-status.sh` +3. run `scripts/run-hypertwist-sentrux-source-only.sh` for a structural + baseline +4. make the bounded packet +5. rerun `scripts/run-hypertwist-sentrux-source-only.sh` +6. rerun product tests for the affected lane + +## Current findings snapshot + +Validated on `2026-06-22`: + +- `npm run verify:shell` in `Content/Browser/` passed +- `npm run build` in `Content/Browser/` passed after installing local package + dependencies for the validation pass +- `scripts/run-hypertwist-gitnexus-analyze.sh` completed successfully on the + bounded source-only mirror +- `scripts/run-hypertwist-gitnexus-status.sh` reported the bounded mirror as + up to date after the wrapper hardening pass + +Most important structural result: + +- the browser runtime mega-function debt was removed from the `sentrux` max + function-length report after the `runtime/shell.ts` and + `browser-spatial-runtime-fallback.js` ownership split +- the `HyperTwistCoreTypes.h` structural-validator debt was then removed from + both the `sentrux` max complexity and max function-length reports by lifting + the melinda-state, scramble-packet, and flat-projection checks into bounded + inline helpers +- the training/catalog circular dependency was removed by moving classic sample + content-pack ownership into `UHyperTwistTrainingCatalogLibrary` +- the virtual-3333 and melinda projection-family simulation cycles were removed + by cutting game-mode shortcuts out of the pawn/controller lane +- the final classic-cube simulation cycle was removed by introducing the + dedicated `HyperTwistClassicCubeOperatorSurface` interface so the HUD and + player controller no longer reach directly back into + `AHyperTwistClassicCubeGameMode`, while the orbit pawn now resolves its focus + actor without a game-mode shortcut + +Current highest-signal structural result: + +- `scripts/run-hypertwist-sentrux-source-only.sh` now reports `Quality: 5900` +- `max_cycles` is now clear +- remaining `sentrux` debt is narrowed to: + - two large `HyperTwistSkillTypes.h` `IsStructurallyValid()` functions + - one large `HyperTwistRecognitionTypes.h` `IsStructurallyValid()` function + +Current validation truth for the latest Unreal C++ slice: + +- the primary reverse-SSH tunnel on `localhost:22022` remained healthy +- the Windows-side safe reverse-sync script still updates `C:\HyperTwist`, but + that path is currently only a partial mirror and is not sufficient as the + authoritative Unreal build root +- the maintained validation root remained + `C:\HyperTwist_worktrees\phase10validate` +- the touched classic-cube files were hash-matched into that worktree before + the build rerun +- the first rerun truthfully failed at UHT because `BlueprintPure` is not + allowed on interface functions +- after removing that invalid specifier from + `HyperTwistClassicCubeOperatorSurface.h`, the same worktree rebuilt with: + `Result: Succeeded` +- UnrealBuildTool total execution time for that successful rerun was + `656.71 seconds` on `2026-06-22` +- the later widened same-day rerun against the maintained validation root + `C:\HyperTwist_worktrees\phase10validate` also succeeded after the + `HyperTwistCoreTypes.h`, `HyperTwistRecognitionTypes.h`, + `HyperTwistTrainingCatalogLibrary`, and classic-cube operator-surface + continuation packet, with `Result: Succeeded`, parallel executor time + `6173.85 seconds`, and total execution time `6198.45 seconds` +- that later proof is the current authoritative Windows confirmation that the + retained `HyperTwistRecognitionTypes.h` helperization and classic-cube + operator-surface decoupling are compile-safe on the real Unreal lane + +Remaining highest-signal debt after the latest pass: + +- large Unreal inline `IsStructurallyValid()` ownership in: + `HyperTwistSkillTypes.h` and `HyperTwistRecognitionTypes.h` + +Follow-up tool refresh on `2026-06-22`: + +- `scripts/run-hypertwist-gitnexus-analyze.sh` re-indexed the bounded + source-only mirror successfully at `16,020` nodes, `37,409` edges, + `646` clusters, and `300` flows +- `scripts/run-hypertwist-gitnexus-status.sh` then reported the bounded mirror + `Status: up-to-date` +- `scripts/run-hypertwist-sentrux-source-only.sh` remained at `Quality: 5900` + after the public-website/manual widening, so the remaining structural debt + is still isolated to the same Unreal validator seams rather than the browser + or website lane + +Important nuance from the `2026-06-22` follow-up: + +- a helper-only readability pass on `HyperTwistSkillTypes.h` was tested and + intentionally not retained, because it made `sentrux` function grouping + worse instead of better +- the truthful next repair for that file is a larger out-of-struct validator + migration packet rather than another small helper-only extraction pass +- direct inspection of the remaining `HyperTwistSkillTypes.h` functions also + suggests the current `sentrux` report is now being amplified by repeated + inline same-name `IsStructurallyValid()` methods living in one header rather + than by one obviously giant monolithic function body, which reinforces the + need for an out-of-line or out-of-struct migration packet instead of more + local boolean/helper reshuffling +- the adjacent likely follow-up, if we continue this lane later, is deeper + family extraction or multi-header ownership separation for the remaining + validator clusters rather than more in-place header-local helperization + +## Out of scope + +This note does not: + +- make `GitNexus` a product runtime dependency +- claim `sentrux` replaces product validation +- widen HyperTwist into a generic code-intelligence product diff --git a/docs/ops/HYPERTWIST_REVERSE_SSH_LIVE_CONNECTION_FACTS_2026-06-04.md b/docs/ops/HYPERTWIST_REVERSE_SSH_LIVE_CONNECTION_FACTS_2026-06-04.md index 5c70b34..63bbaa2 100644 --- a/docs/ops/HYPERTWIST_REVERSE_SSH_LIVE_CONNECTION_FACTS_2026-06-04.md +++ b/docs/ops/HYPERTWIST_REVERSE_SSH_LIVE_CONNECTION_FACTS_2026-06-04.md @@ -75,6 +75,11 @@ Current interpretation: - if Linux-side tar deltas are being unpacked into an isolated Windows worktree, prefer `tar -xf ... -m` so UHT/generated-header outputs are not accidentally left stale behind preserved source mtimes +- on the current Windows host, treat + `C:\HyperTwist_worktrees\phase10validate` as the maintained authoritative + reverse-SSH validation root; `C:\HyperTwist` is currently only a partial + mirror and should not be assumed to contain a usable authoritative + `.uproject` ## Loopback-only meaning diff --git a/docs/ops/HYPERTWIST_REVERSE_SSH_WINDOWS_BUILD_LANE_VERIFICATION_2026-06-01.md b/docs/ops/HYPERTWIST_REVERSE_SSH_WINDOWS_BUILD_LANE_VERIFICATION_2026-06-01.md index 41865c2..3f47a3e 100644 --- a/docs/ops/HYPERTWIST_REVERSE_SSH_WINDOWS_BUILD_LANE_VERIFICATION_2026-06-01.md +++ b/docs/ops/HYPERTWIST_REVERSE_SSH_WINDOWS_BUILD_LANE_VERIFICATION_2026-06-01.md @@ -98,6 +98,14 @@ Successful Windows-side build command: "C:\Program Files\Epic Games\UE_5.7\Engine\Build\BatchFiles\Build.bat" UnrealHyperTwistEditor Win64 Development -Project="C:\HyperTwist\UnrealHyperTwist\UnrealHyperTwist.uproject" -WaitMutex -NoHotReloadFromIDE -NoUba ``` +Current operational clarification: + +- the command above remains the canonical repo-root example +- the currently maintained reverse-SSH validation root on the live Windows host + is `C:\HyperTwist_worktrees\phase10validate` +- `C:\HyperTwist` is currently only a partial mirror on that host and should + not be assumed to contain a usable authoritative `.uproject` + ## Verified result The build completed successfully on `2026-06-01`. @@ -145,6 +153,33 @@ The sensitive runbook now carries the exact current Windows-side tunnel commands, known-hosts scratch file, listener verification command, reverse-sync commands, and the still-current password-backed login path. +## Addendum - 2026-06-22 (maintained validation-root clarification) + +Live follow-up on `2026-06-22` established these additional facts: + +- the primary `localhost:22022` lane remained healthy for the current + classic-cube structural-cycle validation follow-up +- the Windows-side safe reverse-sync helper still updates `C:\HyperTwist`, but + that path is currently only a partial mirror and does not contain a usable + authoritative Unreal project root for validation +- the maintained build-validation root remained + `C:\HyperTwist_worktrees\phase10validate` +- touched source files were hash-matched into that isolated worktree before the + build rerun +- after removing an invalid `BlueprintPure` specifier from the new classic-cube + operator interface, the same isolated worktree rebuilt successfully with + `Result: Succeeded` and UnrealBuildTool `Total execution time: 656.71 + seconds` + +Current interpretation after this follow-up: + +- keep the canonical repo-root build command in doctrine, but treat the live + maintained reverse-SSH validation root as + `C:\HyperTwist_worktrees\phase10validate` until host state changes +- do not report a reverse-SSH Unreal slice as validated from + `C:\HyperTwist` alone when that path is only a partial mirror on the live + Windows host + ## Addendum - 2026-06-11 (fallback lane package proof) Live follow-up on `2026-06-11` established these additional facts: diff --git a/docs/ops/HYPERTWIST_UNREAL_BUILD_VALIDATION_REQUIREMENT_2026-06-01.md b/docs/ops/HYPERTWIST_UNREAL_BUILD_VALIDATION_REQUIREMENT_2026-06-01.md index 1cdffcf..3ac09d6 100644 --- a/docs/ops/HYPERTWIST_UNREAL_BUILD_VALIDATION_REQUIREMENT_2026-06-01.md +++ b/docs/ops/HYPERTWIST_UNREAL_BUILD_VALIDATION_REQUIREMENT_2026-06-01.md @@ -55,6 +55,16 @@ The canonical Windows build command for this repo is: If the engine path changes later, update this note and the propagated canon references in the same change. +Current operational clarification: + +- that command remains the canonical repo-root shape for a healthy authoritative + Windows checkout +- the currently maintained reverse-SSH validation root on the live Windows host + is `C:\HyperTwist_worktrees\phase10validate` +- `C:\HyperTwist` is currently only a partial mirror on that host and should + not be assumed to contain a usable authoritative `.uproject` until the live + host state changes and the reverse-SSH authorities are updated + ## Required cadence Build after each logical Unreal C++ slice or packet. diff --git a/docs/ops/HYPERTWIST_UNREAL_INPUT_AND_XR_COMPLETENESS_AUDIT_2026-06-22.md b/docs/ops/HYPERTWIST_UNREAL_INPUT_AND_XR_COMPLETENESS_AUDIT_2026-06-22.md new file mode 100644 index 0000000..c820fe6 --- /dev/null +++ b/docs/ops/HYPERTWIST_UNREAL_INPUT_AND_XR_COMPLETENESS_AUDIT_2026-06-22.md @@ -0,0 +1,83 @@ +# HyperTwist Unreal Input and XR Completeness Audit + +Created on `2026-06-22`. + +## Purpose + +This note records the current truthful state of HyperTwist native input, +controller, and XR completeness so later public copy and roadmap decisions do +not overclaim the Unreal runtime. + +## Current truthful conclusion + +HyperTwist currently has real native input ownership for: + +- classic-cube mouse or touch or keyboard-adjacent play flows +- higher-dimensional keyboard interaction in the bounded visible-slice lane +- `EnhancedInput`-based project posture +- broad motion-controller axis configuration at the Unreal project-settings + level + +HyperTwist does **not** yet have enough current first-party runtime evidence to +truthfully claim: + +- a fully finished shipping VR/OpenXR product lane +- finished controller-specific runtime interaction surfaces across headsets +- user-facing input rebinding or preferences ownership at a polished shipping + standard +- “utmost quality” for native VR controller setup without a dedicated runtime + completion packet + +## Evidence checked + +### Keyboard and mouse are real + +- `docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md` + records the classic-cube playable runtime loop with left-click, + right-click, touch, orbit, zoom, restart, hint, follow-along, and voice + capture behavior. +- the same registry also records the bounded visible-slice `3x3x3x3` lane with + keyboard slice-axis or layer or rotation-axis control. + +### Project-level input groundwork is real + +- `UnrealHyperTwist/Config/DefaultInput.ini` enables + `EnhancedPlayerInput` / `EnhancedInputComponent` +- the same file includes motion-control axis configuration for + Vive, Mixed Reality, Oculus Touch, and Valve Index families +- the same file keeps mouse capture, wheel, and motion-control settings active + +### Finished XR runtime ownership is not yet proven + +- `UnrealHyperTwist/UnrealHyperTwist.uproject` currently enables + `WebBrowserWidget`, `ProceduralMeshComponent`, `RemoteControl`, and editor + tooling plugins, but this audit did not find an enabled `OpenXR` plugin + declaration or a headset-specific runtime plugin stack in the project file +- a focused source search did not surface first-party runtime classes such as a + dedicated VR pawn or `MotionControllerComponent`-driven interaction owner + that would justify stronger shipping claims +- `HyperTwistTrainingImmersiveEnvironmentLibrary.cpp` clearly establishes a + first-party immersive/scenic environment direction, but it also explicitly + keeps scope bounded away from platform-global XR settings or generic VR shell + ownership + +## Product-language consequence + +Current truthful product wording should say: + +- desktop Unreal simulator: real and shipping +- classic input and higher-dimensional keyboard interaction: real and shipping +- immersive/scenic training direction: first-party owned and source-backed +- full VR/controller/settings polish lane: not yet complete enough to market as + finished + +## Next clean implementation packet + +If the project wants to raise this lane from “groundwork exists” to “shipping +quality,” the next clean packet is: + +1. explicit native XR host/plugin decision +2. dedicated runtime interaction owner(s) for headset and controller handling +3. user-facing control/settings/rebinding ownership +4. packaged validation on the Windows Unreal lane with headset/controller truth + kept explicit diff --git a/docs/v6_5_deep_manual_pack/HyperTwist/DEVELOPMENT.md b/docs/v6_5_deep_manual_pack/HyperTwist/DEVELOPMENT.md index 9cec3a5..5a5713c 100644 --- a/docs/v6_5_deep_manual_pack/HyperTwist/DEVELOPMENT.md +++ b/docs/v6_5_deep_manual_pack/HyperTwist/DEVELOPMENT.md @@ -9,6 +9,58 @@ HyperTwist should be developed as a serious native product: - bounded sidecars where they materially help - minimal web-only surface +## Browser versus desktop rule + +Treat the website and the simulator as complementary surfaces, not competing +claims over the same capability. + +- `website/` owns public positioning, account/auth, billing, release posture, + notices, and browser-to-desktop handoff +- `UnrealHyperTwist/` owns the real simulator runtime, package lane, + recognition/replay/training flow, and higher-dimensional interaction posture +- `Content/Browser/` remains the embedded Unreal browser/CEF shell and + simulator-side browser runtime, not the public site +- public/manual copy must not imply that the browser shell has replaced the + desktop runtime unless the separate browser-client branch is explicitly + reopened and landed +- the public website may be widened as a professional operator/distribution + manual, but that still does not authorize it to claim ownership over the + native simulator job + +## Refactor toolchain + +HyperTwist now has its own bounded refactor/analyzer entry points: + +- `scripts/run-hypertwist-sentrux-source-only.sh` +- `scripts/run-hypertwist-gitnexus-analyze.sh` +- `scripts/run-hypertwist-gitnexus-status.sh` +- `.sentrux/rules.toml` + +Use them with this posture: + +- `sentrux` is the permissive structural gate for HyperTwist-owned source + health +- `GitNexus` is analysis-only and stays external/runtime-adjacent rather than a + shipped product dependency +- HyperTwist should ignore generated `.gitnexus/` index state and the + disposable `.gitnexus-source-only-root/` mirror in git hygiene +- the retained `mirrors/GitNexus` working reference already includes the newer + stack-overflow prevention and cycle-hardening work recorded in its + `CHANGELOG.md` +- `scripts/run-hypertwist-sentrux-source-only.sh` now prefers a HyperTwist + owned entry path first: `HYPERTWIST_SENTRUX_BINARY`, repo-local `./sentrux` + or `./sentrux.exe`, then `PATH`, then the retained fallback + +Suggested loop: + +1. run `scripts/run-hypertwist-gitnexus-analyze.sh` before a larger rename or + subsystem split +2. use `scripts/run-hypertwist-gitnexus-status.sh` to confirm index freshness +3. run `scripts/run-hypertwist-sentrux-source-only.sh` before and after the + packet +4. treat `sentrux` failures as structural review signals, then confirm with + focused product tests + ## Canonical development authorities Use these before widening implementation: @@ -108,3 +160,15 @@ For boundary-sensitive and restrictive rows: - coaching recommendation stability - topology/runtime validation - progression persistence and analytics +- browser-runtime and website/distribution posture where those lanes are in the + current packet + +## Unreal input and XR truthfulness rule + +Do not market or document the Unreal runtime as a finished shipping +VR/controller/settings product unless current first-party runtime evidence and +package validation actually prove it. + +Current audit note: + +- `C:\HyperTwist\docs\ops\HYPERTWIST_UNREAL_INPUT_AND_XR_COMPLETENESS_AUDIT_2026-06-22.md` diff --git a/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md b/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md index bb96743..7071adf 100644 --- a/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md +++ b/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md @@ -263,7 +263,7 @@ repo. | Feature | Status | Primary authority | Notes | |---|---|---|---| -| Public `hypertwist.app` marketing shell | Implemented now | first-party `website/` app + feature registry/roadmap authority | HyperTwist now has a dedicated first-party public web surface for homepage, about, resources, pricing, download, support, and legal routes. This lane is separate from the embedded Unreal browser runtime under `Content/Browser/` and does not claim browser-simulator parity. The same package now also carries a first-party external runtime-readiness verifier so deploy-time env and live health posture can be checked outside the dashboard, plus separated local-versus-production env templates whose placeholder values are intentionally rejected until real launch config is in place, bootstrap CI now validates both the frontend and auth-server website commands directly, and the auth server can now auto-serve the built `website/dist` bundle with bounded SPA fallback for same-origin public deployment. Request-level server coverage now also proves that public/app shell delivery does not shadow `/api/*`, `/auth*`, `/health`, or missing asset paths, while the pricing/download/notices routes now surface first-party preview-versus-launch posture from the same bounded launch checklist instead of relying on hidden operator-only status. The shared marketing shell now also carries a compact public-site-status banner across public pages, the homepage keeps a fuller status section, and the shared public launch-status component now consumes live auth-health webhook/runtime truth in addition to release-manifest download readiness so public marketing copy does not claim launch posture from static checkout/download config alone. A later same-family continuation then widened that release-manifest authority again to carry public runtime commerce config for operator/studio checkout URLs and live plan-price strings, so pricing, notices, and dashboard launch-readiness surfaces no longer depend only on frontend build-time checkout config. The live website lane now also owns route-aware title/description/canonical/Open-Graph/Twitter metadata for the real `hypertwist.app` marketing surface so deployed public pages no longer remain on a single generic SPA title/description, plus first-party `robots.txt` and `sitemap.xml` assets for the public route set while keeping `/app`, `/login`, and `/register` out of crawler posture. The real `check-runtime-readiness` CLI is now also exercised against the checked-in production example env files, and a spawned `website/server` bootstrap proof now verifies the live same-origin process path from production-shaped env into `/health`, `/api/auth/health`, built-shell serving, and the public anonymous release-manifest posture for the shared desktop release lane. The same verifier now also probes the deployed root-shell marker and can explicitly fail when the public origin is still serving the older placeholder rollout page instead of the first-party website/auth-server lane, while the repo now also carries first-party `website/deploy/` `nginx` plus `systemd` handoff templates, a concrete same-origin public-host cutover guide, a deployment-file renderer that emits resolved operator outputs from real checkout paths, and a manifest-driven bundle renderer that lets one authoritative input own the public origin while emitting validated env plus install artifacts together, with the shared-VPS-safe default upstream moved to `3011` after live host inspection confirmed `3001` is already occupied by FamiliarOS. The same deployment lane now also distinguishes `launch` from `preview` posture so honest missing checkout/download/webhook/release values are accepted only for non-public rehearsal while placeholder strings still fail, `runtime.mode: mixed` plus `public_origin_ready: true` counts as valid preview-host proof, and the staging helper can archive either committed `HEAD` or the live worktree through `--archive-source worktree`. An isolated VPS-local staging proof then confirmed that both the committed HyperTwist website lane and the later preview-tier worktree packet can serve green health, release-manifest, and first-party shell responses on that real shared host behind `3011`, and a later root-owned cutover then replaced the public placeholder site with the live first-party same-origin preview deployment on `https://hypertwist.app`. The package now also ships a bounded root-owned live-deploy helper that stages the committed checkout, uploads the rendered bundle, installs env, rebuilds the site, replaces the live `systemd` plus `nginx` files, and validates the public origin; that helper has already been re-proved idempotently against the live host. The repo now also ships that host-proof flow as a first-party staging helper so future sessions can rerun the temp checkout/build/boot verification path directly before or after root-owned cutover. | +| Public `hypertwist.app` marketing shell | Implemented now | first-party `website/` app + feature registry/roadmap authority | HyperTwist now has a dedicated first-party public web surface for homepage, about, resources, pricing, download, support, and legal routes. This lane is separate from the embedded Unreal browser runtime under `Content/Browser/` and does not claim browser-simulator parity. The same public lane now also serves as a bounded operator/distribution manual through the docs/resources/download/support surfaces, explaining browser-versus-desktop posture, rollout steps, package proof, and simulator-use guidance without claiming browser ownership of the native runtime. The same package now also carries a first-party external runtime-readiness verifier so deploy-time env and live health posture can be checked outside the dashboard, plus separated local-versus-production env templates whose placeholder values are intentionally rejected until real launch config is in place, bootstrap CI now validates both the frontend and auth-server website commands directly, and the auth server can now auto-serve the built `website/dist` bundle with bounded SPA fallback for same-origin public deployment. Request-level server coverage now also proves that public/app shell delivery does not shadow `/api/*`, `/auth*`, `/health`, or missing asset paths, while the pricing/download/notices routes now surface first-party preview-versus-launch posture from the same bounded launch checklist instead of relying on hidden operator-only status. The shared marketing shell now also carries a compact public-site-status banner across public pages, the homepage keeps a fuller status section, and the shared public launch-status component now consumes live auth-health webhook/runtime truth in addition to release-manifest download readiness so public marketing copy does not claim launch posture from static checkout/download config alone. A later same-family continuation then widened that release-manifest authority again to carry public runtime commerce config for operator/studio checkout URLs and live plan-price strings, so pricing, notices, and dashboard launch-readiness surfaces no longer depend only on frontend build-time checkout config. The live website lane now also owns route-aware title/description/canonical/Open-Graph/Twitter metadata for the real `hypertwist.app` marketing surface so deployed public pages no longer remain on a single generic SPA title/description, plus first-party `robots.txt` and `sitemap.xml` assets for the public route set while keeping `/app`, `/login`, and `/register` out of crawler posture. The real `check-runtime-readiness` CLI is now also exercised against the checked-in production example env files, and a spawned `website/server` bootstrap proof now verifies the live same-origin process path from production-shaped env into `/health`, `/api/auth/health`, built-shell serving, and the public anonymous release-manifest posture for the shared desktop release lane. The same verifier now also probes the deployed root-shell marker and can explicitly fail when the public origin is still serving the older placeholder rollout page instead of the first-party website/auth-server lane, while the repo now also carries first-party `website/deploy/` `nginx` plus `systemd` handoff templates, a concrete same-origin public-host cutover guide, a deployment-file renderer that emits resolved operator outputs from real checkout paths, and a manifest-driven bundle renderer that lets one authoritative input own the public origin while emitting validated env plus install artifacts together, with the shared-VPS-safe default upstream moved to `3011` after live host inspection confirmed `3001` is already occupied by FamiliarOS. The same deployment lane now also distinguishes `launch` from `preview` posture so honest missing checkout/download/webhook/release values are accepted only for non-public rehearsal while placeholder strings still fail, `runtime.mode: mixed` plus `public_origin_ready: true` counts as valid preview-host proof, and the staging helper can archive either committed `HEAD` or the live worktree through `--archive-source worktree`. An isolated VPS-local staging proof then confirmed that both the committed HyperTwist website lane and the later preview-tier worktree packet can serve green health, release-manifest, and first-party shell responses on that real shared host behind `3011`, and a later root-owned cutover then replaced the public placeholder site with the live first-party same-origin preview deployment on `https://hypertwist.app`. The package now also ships a bounded root-owned live-deploy helper that stages the committed checkout, uploads the rendered bundle, installs env, rebuilds the site, replaces the live `systemd` plus `nginx` files, and validates the public origin; that helper has already been re-proved idempotently against the live host. The repo now also ships that host-proof flow as a first-party staging helper so future sessions can rerun the temp checkout/build/boot verification path directly before or after root-owned cutover. | | Browser-based operator/account dashboard | Implemented now | first-party `website/` app + shared auth/dashboard packet | A protected browser dashboard is now live for operator access, account state, download posture, browser-access boundary explanation, notices review, and bounded billing/entitlement status. It reuses the shared SuperTokens auth posture proven in FamiliarOS and ScriptoriumAI while remaining HyperTwist-specific in product content and boundary claims, the current auth-health surface now truthfully distinguishes configured versus reachable or ready shared-core posture while exposing fallback-active reason instead of hardcoding readiness, and the same dashboard now also surfaces launch-readiness truth for download URLs, checkout links, source/notices URLs, billing-secret/map configuration, and local-versus-public runtime deployment posture. The same protected overview now also consumes the server-backed Windows packaged-validation summary that the release-manifest authority exposes, so operators can see current higher-dimensional desktop proof without drilling into the dedicated downloads screen. Focused frontend coverage now also protects deep-link login redirect preservation, safe `next`-path normalization across auth entry points, fallback/email auth-bootstrap normalization, login/register continuation behavior, public download-gating behavior, protected-route/shell behavior, real lazy-route tree behavior for key public and protected paths, top-level app-bootstrap and SuperTokens-wrapper posture, login/register unhappy-path and OAuth-button behavior, support-topic fallback routing when live checkout is not configured, desktop-link verify-url/dashboard readiness behavior, and explicit `noindex,nofollow` posture on protected/auth browser surfaces. The validation lane now also has a bounded signed test-session harness under `TEST_MODE=testing` that proves `/api/auth/me` and `/api/auth/desktop-link` behavior through the live spawned auth-server process without widening production auth posture. | | Desktop download posture and browser-to-desktop pairing | Implemented now | first-party `website/` app + `website/server` desktop-link endpoints | Public download targets, dashboard-side release posture, and short-lived desktop-link token generation/verification are now first-party owned. The current server posture now enforces exact website-origin matching, bounded per-user issuance, one-time token consumption, and billing-backed plan/download entitlement resolution with focused `website/server` tests green on `2026-06-22`, and the verify handshake now returns the same resolved download-entitlement posture the dashboard sees instead of only identity plus plan/role. The same lane now also owns a shared `GET /api/releases/manifest` runtime authority for release version/channel/build/published/file-size/checksum/docs/source metadata, with anonymous callers intentionally denied raw download URLs while entitled session-backed callers receive the configured direct platform URL. That manifest now also carries first-party packaged-validation summary truth for the Windows higher-dimensional desktop lane, so the public `/download` page, the public `/resources` reference page, and the protected `/app/downloads` surface can project real package evidence for the dedicated-family `Magic120Cell` / `MagicCube5D` maps even while launch-tier release URLs remain unconfigured. The public `/download` page now keeps raw download URLs behind the protected dashboard instead of exposing them directly, preserves requested platform continuity through `/app/downloads?platform=...`, and surfaces that requested target again after auth handoff inside the protected release lane. Both the public and protected download surfaces now also carry first-party rollout steps plus release/notices/source references so the desktop setup lane is more than a generic link bucket, and the dashboard plus public launch-status callouts now consume the same manifest-backed Windows download truth instead of only static frontend config. Actual release URLs remain deployment configuration rather than hardcoded product truth. | | Paddle-ready pricing and billing webhook seam | Implemented now | first-party `website/` app + `website/server` billing endpoint | The public pricing surface now exists with plan structure, checkout-link configuration seams, and the same `/api/billing/paddle/webhook` endpoint family used by the broader product website lane. The current server now verifies `Paddle-Signature` against `PADDLE_WEBHOOK_SECRET` using the documented raw-body HMAC flow, persists a bounded first-party billing state file, and applies verified Paddle events into account/download entitlement state that the browser dashboard consumes, with focused `website/server` tests green on `2026-06-22`. The shared `GET /api/releases/manifest` authority now also carries public runtime commerce config for operator/studio checkout URLs and live plan-price strings, allowing the pricing page to switch from frontend build-time checkout assumptions to auth-server runtime truth when those values are configured. A spawned live-process proof now also verifies that a real signed webhook updates processed-event health and persisted billing state through the actual auth-server runtime, not only helper-level store tests, and transaction events no longer leak their id into stored `subscriptionId` state. Production checkout URLs, secret management, and broader operator/admin billing workflows remain deployment/application tasks, not shipped-code omissions. | diff --git a/docs/v6_5_deep_manual_pack/HyperTwist/PRD.md b/docs/v6_5_deep_manual_pack/HyperTwist/PRD.md index 047d051..fef0cbb 100644 --- a/docs/v6_5_deep_manual_pack/HyperTwist/PRD.md +++ b/docs/v6_5_deep_manual_pack/HyperTwist/PRD.md @@ -44,6 +44,28 @@ The canonical normalized surface for that distinction is: Do not infer product truth from donor presence, queue position, or prompt-pack presence. +## Website and simulator boundary + +HyperTwist keeps both of these because they solve different product problems: + +- the website owns public pages, account/auth, pricing, release posture, + notices, and desktop pairing +- the desktop Unreal runtime owns the real simulator, packaged training flow, + device/runtime integration, and higher-dimensional interaction + +The website is therefore not superfluous, but it is intentionally inferior for +the core simulator job. Public and manual wording must keep that distinction +explicit. + +The current first-party website also doubles as a bounded public-facing +operator manual: + +- homepage and about explain the browser-versus-desktop split +- docs and resources explain rollout-safe product truth and simulator-use + guidance +- pricing, download, and notices surfaces explain entitlement, package proof, + and public distribution posture + ## Users - beginners learning 3D solving @@ -67,9 +89,11 @@ presence. - algorithm/drill training and session control - coaching/progression - hyper puzzle catalog/topology/runtime +- public website/account/distribution surfaces - deferred immersive training environments and focus-presence shells - publication/challenge/social training surfaces - analytics/reporting and provenance +- browser account, pricing, download, and legal distribution surfaces - bounded speech/voice sidecars ## Deferred first-party experiential lane @@ -89,6 +113,17 @@ Canonical scoping note: - `C:\HyperTwist\docs\HYPERTWIST_VIRTUAL_TRAINING_ENVIRONMENT_AND_SCENIC_IMMERSION_SCOPING_NOTE_2026-05-31.md` +## Current Unreal input and XR truth + +The current desktop runtime has real first-party input ownership for classic +mouse/touch flows, higher-dimensional keyboard interaction, and project-level +motion-control groundwork, but HyperTwist should not yet claim a fully finished +shipping VR/controller/preferences lane without a dedicated completion packet. + +Canonical audit note: + +- `C:\HyperTwist\docs\ops\HYPERTWIST_UNREAL_INPUT_AND_XR_COMPLETENESS_AUDIT_2026-06-22.md` + ## Product guardrails - packet docs and live code beat summaries when conflicts exist diff --git a/scripts/run-hypertwist-gitnexus-analyze.sh b/scripts/run-hypertwist-gitnexus-analyze.sh new file mode 100644 index 0000000..0fa4476 --- /dev/null +++ b/scripts/run-hypertwist-gitnexus-analyze.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +local_gitnexus_cli="$repo_root/mirrors/GitNexus/gitnexus/dist/cli/index.js" +analysis_root="$repo_root/.gitnexus-source-only-root" +max_size_kb="${HYPERTWIST_GITNEXUS_MAX_FILE_SIZE_KB:-256}" + +copy_targets=( + "UnrealHyperTwist/Source" + "Content/Browser/src" + "website/src" + "website/server/src" + "scripts" +) + +rm -rf "$analysis_root" +mkdir -p "$analysis_root" + +for target in "${copy_targets[@]}"; do + source_path="$repo_root/$target" + if [[ ! -e "$source_path" ]]; then + continue + fi + + destination_path="$analysis_root/$target" + mkdir -p "$(dirname "$destination_path")" + cp -R "$source_path" "$destination_path" +done + +find "$analysis_root" -type f -size +"${max_size_kb}"k -delete + +# GitNexus status and registry behavior assume a repository root, so give the +# disposable source-only mirror its own lightweight git boundary. +git -C "$analysis_root" init -q +git -C "$analysis_root" config user.name "HyperTwist GitNexus Mirror" +git -C "$analysis_root" config user.email "hypertwist-gitnexus@local.invalid" +git -C "$analysis_root" add -A +git -C "$analysis_root" commit -q -m "source-only snapshot" + +cd "$analysis_root" + +if [[ -f "$local_gitnexus_cli" ]]; then + if node "$local_gitnexus_cli" analyze . --skip-agents-md "$@" 2>/dev/null; then + exit 0 + fi + + echo "Local retained GitNexus CLI was not runnable on this host. Falling back to npx gitnexus@latest." >&2 +fi + +exec npx -y gitnexus@latest analyze . --skip-agents-md "$@" diff --git a/scripts/run-hypertwist-gitnexus-status.sh b/scripts/run-hypertwist-gitnexus-status.sh new file mode 100644 index 0000000..44c280f --- /dev/null +++ b/scripts/run-hypertwist-gitnexus-status.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +local_gitnexus_cli="$repo_root/mirrors/GitNexus/gitnexus/dist/cli/index.js" +analysis_root="$repo_root/.gitnexus-source-only-root" + +if [[ ! -d "$analysis_root" ]]; then + echo "No HyperTwist GitNexus source-only analysis root exists yet. Run scripts/run-hypertwist-gitnexus-analyze.sh first." >&2 + exit 1 +fi + +cd "$analysis_root" + +if [[ -f "$local_gitnexus_cli" ]]; then + if node "$local_gitnexus_cli" status "$@" 2>/dev/null; then + exit 0 + fi + + echo "Local retained GitNexus CLI was not runnable on this host. Falling back to npx gitnexus@latest." >&2 +fi + +exec npx -y gitnexus@latest status "$@" 2>/dev/null diff --git a/scripts/run-hypertwist-sentrux-source-only.sh b/scripts/run-hypertwist-sentrux-source-only.sh new file mode 100644 index 0000000..9b3950c --- /dev/null +++ b/scripts/run-hypertwist-sentrux-source-only.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +temp_root="${TMPDIR:-/tmp}/hypertwist-sentrux-source-only" +repo_local_sentrux_binary="$repo_root/sentrux" +repo_local_sentrux_windows_binary="$repo_root/sentrux.exe" +local_sentrux_binary="/home/dev/src/VectorShell/sentrux/target/release/sentrux" +local_sentrux_manifest="/home/dev/src/VectorShell/sentrux/Cargo.toml" + +resolve_sentrux_command() { + if [[ -n "${HYPERTWIST_SENTRUX_BINARY:-}" && -x "${HYPERTWIST_SENTRUX_BINARY}" ]]; then + printf '%s\n' "${HYPERTWIST_SENTRUX_BINARY}" + return 0 + fi + + if [[ -x "$repo_local_sentrux_binary" ]]; then + printf '%s\n' "$repo_local_sentrux_binary" + return 0 + fi + + if [[ -x "$repo_local_sentrux_windows_binary" ]]; then + printf '%s\n' "$repo_local_sentrux_windows_binary" + return 0 + fi + + if command -v sentrux >/dev/null 2>&1; then + printf 'sentrux\n' + return 0 + fi + + if [[ -x "$local_sentrux_binary" ]]; then + printf '%s\n' "$local_sentrux_binary" + return 0 + fi + + if command -v cargo >/dev/null 2>&1 && [[ -f "$local_sentrux_manifest" ]]; then + printf 'cargo run --quiet --manifest-path %q --bin sentrux --\n' "$local_sentrux_manifest" + return 0 + fi + + return 1 +} + +sentrux_command="$(resolve_sentrux_command)" || { + echo "Unable to locate sentrux. Provide HYPERTWIST_SENTRUX_BINARY, add sentrux to PATH, place a repo-local sentrux binary at $repo_root, or keep /home/dev/src/VectorShell/sentrux available." >&2 + exit 1 +} + +rm -rf "$temp_root" +mkdir -p "$temp_root/.sentrux" +cp "$repo_root/.sentrux/rules.toml" "$temp_root/.sentrux/rules.toml" + +copy_targets=( + "UnrealHyperTwist/Source" + "Content/Browser/src" + "website/src" + "website/server/src" + "scripts" +) + +for target in "${copy_targets[@]}"; do + source_path="$repo_root/$target" + if [[ ! -e "$source_path" ]]; then + continue + fi + + destination_path="$temp_root/$target" + mkdir -p "$(dirname "$destination_path")" + cp -R "$source_path" "$destination_path" +done + +( + cd "$repo_root" + eval "$sentrux_command" check "$temp_root" +) diff --git a/website/README.md b/website/README.md index e3d562e..2abe535 100644 --- a/website/README.md +++ b/website/README.md @@ -31,6 +31,30 @@ surface for: - desktop distribution - public legal and open-source notices +The website is therefore complementary to the desktop build, not a substitute +for it: + +- use `website/` for account, release, pricing, notices, support, and browser + operator access +- use `UnrealHyperTwist/` for the real simulator, native runtime behavior, and + higher-dimensional packaged execution +- do not present the public site as proof that the optional full-browser + simulator branch is live + +The public pages now also double as a professional public-facing operator +manual: + +- homepage and about explain why both browser and desktop surfaces exist +- resources and docs describe the safe public learning and rollout lanes +- pricing and download explain entitlement, package proof, and legal posture +- release notes, notices, privacy, terms, and shipping/payment pages now also + explain the real browser-versus-desktop delivery model instead of carrying + generic brochure/legal filler +- support and legal pages clarify rollout, pairing, and distribution duties +- the docs and resources pages now also carry a practical simulator-use manual + for recognition, replay, higher-dimensional runtime ownership, and operator + diagnostics without overclaiming browser or VR parity + ## Local development ```bash @@ -222,6 +246,20 @@ also passed the full frontend `npm test` suite and a fresh `npm run build` after an older auth-page mock was widened to include the new shared `getAuthHealth` dependency used by the marketing shell. +The follow-on public-manual widening on `2026-06-22` then also passed: + +- `npm run type-check` +- `npm test -- --run src/__tests__/public-marketing-pages.test.tsx` +- `npm run build` +- `npm test -- --run` +- `npm run type-check` in `website/server` +- `npm test -- --run` in `website/server` + +The same-day validation hardening also gave the live-spawned +`website/server` bootstrap suite an explicit `15s` timeout so child-process +boot plus same-origin HTTP proof does not fail spuriously under heavier host +load while keeping the coverage itself unchanged. + Canonical same-origin cutover guide: - `docs/ops/HYPERTWIST_WEBSITE_SAME_ORIGIN_DEPLOYMENT_HANDOFF_2026-06-22.md` diff --git a/website/server/src/__tests__/server-bootstrap.test.ts b/website/server/src/__tests__/server-bootstrap.test.ts index b276aff..707602e 100644 --- a/website/server/src/__tests__/server-bootstrap.test.ts +++ b/website/server/src/__tests__/server-bootstrap.test.ts @@ -204,7 +204,7 @@ afterEach(async () => { } }) -describe('website/server bootstrap', () => { +describe('website/server bootstrap', { timeout: 15_000 }, () => { it('boots with production-shaped same-origin config and serves live health plus the built website shell', async () => { const coreHost = getNonLoopbackIpv4() if (!coreHost) { diff --git a/website/src/__tests__/public-marketing-pages.test.tsx b/website/src/__tests__/public-marketing-pages.test.tsx index c79b09f..2ce0c02 100644 --- a/website/src/__tests__/public-marketing-pages.test.tsx +++ b/website/src/__tests__/public-marketing-pages.test.tsx @@ -101,7 +101,18 @@ vi.mock('../site-config', () => ({ }, })) -import { DownloadPage, HomeLanding, PricingPage, ResourcesPage, SupportPage } from '../pages/public-pages' +import { + DocsPage, + DownloadPage, + HomeLanding, + OpenSourceNoticesPage, + PricingPage, + PrivacyPage, + ResourcesPage, + ShippingPaymentPage, + SupportPage, + TermsPage, +} from '../pages/public-pages' function renderWithProviders(element: React.ReactNode, initialEntries?: string[]) { const queryClient = new QueryClient({ @@ -336,6 +347,8 @@ describe('public marketing pages', () => { expect(screen.getAllByText('Preview posture').length).toBeGreaterThan(0) expect(screen.getByText('Paddle webhook secret: missing')).toBeTruthy() expect(screen.getByText('Public auth runtime posture: production-ready')).toBeTruthy() + expect(screen.getByText('Why both browser and desktop stay')).toBeTruthy() + expect(screen.getAllByText('Native Unreal desktop runtime').length).toBeGreaterThan(0) }) it('uses useful support fallback routes for plans that do not yet have live checkouts', () => { @@ -534,6 +547,7 @@ describe('public marketing pages', () => { expect(screen.getByText('Selected help lane')).toBeTruthy() expect(screen.getByText('Launch readiness')).toBeTruthy() expect(screen.getByText(/turning the preview lane into a public launch/i)).toBeTruthy() + expect(screen.getByText('Support lanes')).toBeTruthy() }) it('surfaces current packaged desktop proof on the public resources page', () => { @@ -634,5 +648,148 @@ describe('public marketing pages', () => { expect(screen.getByText('Current packaged desktop proof')).toBeTruthy() expect(screen.getByText('Windows Unreal packaged validation')).toBeTruthy() expect(screen.getAllByText(/MagicCube5D dedicated-family training map: passed/i).length).toBeGreaterThan(0) + expect(screen.getByText('Operator playbooks')).toBeTruthy() + expect(screen.getByText('Simulator use today')).toBeTruthy() + expect(screen.getByText('Higher-dimensional runtime ownership')).toBeTruthy() + }) + + it('renders the public operator manual on the docs page', async () => { + mockGetAuthHealth.mockResolvedValue({ + ok: true, + service: 'hypertwist-auth-server', + supertokens: { + configured: true, + reachable: true, + ready: true, + apiVersion: '5.4', + error: null, + oauth: { + github: false, + google: false, + }, + }, + fallback: { + enabled: true, + active: false, + reason: null, + }, + billing: { + statePath: '/var/lib/hypertwist/auth/hypertwist-billing-state.json', + processedEventCount: 0, + pricePlanMapConfigured: false, + productPlanMapConfigured: false, + webhookSecretConfigured: false, + }, + runtime: { + mode: 'mixed', + public_origin_ready: true, + cookie_secure: true, + api_domain: 'https://hypertwist.app', + website_domain: 'https://hypertwist.app', + warnings: [], + errors: [], + }, + }) + mockGetReleaseManifest.mockResolvedValue({ + ok: true, + manifest: { + generated_at: '2026-06-22T12:00:00.000Z', + support_email: 'hello@hypertwist.app', + public_docs_url: 'https://docs.hypertwist.app', + release_notes_url: 'https://notes.hypertwist.app', + corresponding_source_url: 'https://hypertwist.app/open-source/source.zip', + open_source_repo_url: 'https://github.com/hypertwist/hypertwist', + viewer: { + authenticated: false, + canDownload: false, + plan: null, + role: null, + accessStatus: null, + }, + platforms: [], + }, + }) + + renderWithProviders(, ['/docs']) + + expect(screen.getByText('Operator manual')).toBeTruthy() + expect(screen.getByText('1. Start in the browser shell')).toBeTruthy() + expect(screen.getByText('Simulator manual')).toBeTruthy() + expect(screen.getByText('Feature-registry-backed wording only')).toBeTruthy() + expect(await screen.findByRole('link', { name: /open public docs portal/i })).toBeTruthy() + }) + + it('renders the expanded legal and digital-delivery guidance across public support surfaces', async () => { + mockGetAuthHealth.mockResolvedValue({ + ok: true, + service: 'hypertwist-auth-server', + supertokens: { + configured: true, + reachable: true, + ready: true, + apiVersion: '5.4', + error: null, + oauth: { + github: false, + google: false, + }, + }, + fallback: { + enabled: true, + active: false, + reason: null, + }, + billing: { + statePath: '/var/lib/hypertwist/auth/hypertwist-billing-state.json', + processedEventCount: 0, + pricePlanMapConfigured: false, + productPlanMapConfigured: false, + webhookSecretConfigured: false, + }, + runtime: { + mode: 'mixed', + public_origin_ready: true, + cookie_secure: true, + api_domain: 'https://hypertwist.app', + website_domain: 'https://hypertwist.app', + warnings: [], + errors: [], + }, + }) + mockGetReleaseManifest.mockResolvedValue({ + ok: true, + manifest: { + generated_at: '2026-06-22T12:00:00.000Z', + support_email: 'hello@hypertwist.app', + public_docs_url: 'https://docs.hypertwist.app', + release_notes_url: 'https://notes.hypertwist.app', + corresponding_source_url: 'https://hypertwist.app/open-source/source.zip', + open_source_repo_url: 'https://github.com/hypertwist/hypertwist', + viewer: { + authenticated: false, + canDownload: false, + plan: null, + role: null, + accessStatus: null, + }, + platforms: [], + }, + }) + + renderWithProviders(, ['/open-source-notices']) + expect(screen.getByText('Distribution doctrine')).toBeTruthy() + expect(screen.getByText('Public pages are distribution surfaces')).toBeTruthy() + + renderWithProviders(, ['/privacy']) + expect(screen.getByText('Practical privacy boundary')).toBeTruthy() + expect(screen.getByText('Browser identity and billing boundary')).toBeTruthy() + + renderWithProviders(, ['/terms']) + expect(screen.getByText('Terms in practice')).toBeTruthy() + expect(screen.getByText('Access model')).toBeTruthy() + + renderWithProviders(, ['/shipping-payment']) + expect(screen.getByText('Digital delivery workflow')).toBeTruthy() + expect(screen.getByText('Protected entitlement handoff')).toBeTruthy() }) }) diff --git a/website/src/pages/public-pages.tsx b/website/src/pages/public-pages.tsx index 83b42f1..bfa6e20 100644 --- a/website/src/pages/public-pages.tsx +++ b/website/src/pages/public-pages.tsx @@ -31,12 +31,22 @@ import { companyNarrative, desktopDownloadSteps, desktopReleaseSignals, + deliverySurfaceCards, + digitalDeliveryCards, + distributionDoctrineCards, heroMetrics, openSourceNotices, + operatorManualTracks, + operatorPlaybooks, + privacyBoundaryCards, + publicDocumentationPrinciples, resourceCollections, + releaseStoryCards, roadmapHonestyCards, shippingNowCards, + simulatorManualCards, supportFaqs, + termsBoundaryCards, } from '../site-data' const supportTopicGuidance: Record = { @@ -246,6 +256,25 @@ export function HomeLanding() { + +
+
+ {deliverySurfaceCards.map((surface) => ( +
+

{surface.title}

+

{surface.description}

+
    + {surface.bullets.map((bullet) => ( +
  • {bullet}
  • + ))} +
+
+ ))} +
+
) @@ -272,6 +301,20 @@ export function AboutPage() { +
+
+ {deliverySurfaceCards.map((surface) => ( +
+

{surface.title}

+

{surface.description}

+
+ ))} +
+
+
@@ -367,6 +410,43 @@ export function ResourcesPage() {
+
+
+ {operatorPlaybooks.map((playbook) => ( +
+

{playbook.title}

+
    + {playbook.steps.map((step) => ( +
  • {step}
  • + ))} +
+
+ ))} +
+
+ +
+
+ {simulatorManualCards.map((card) => ( +
+

{card.title}

+

{card.description}

+
    + {card.bullets.map((bullet) => ( +
  • {bullet}
  • + ))} +
+
+ ))} +
+
+ {windowsValidationSummary ? (
@@ -425,18 +505,64 @@ export function DocsPage() { >
-
-

Feature truth

-

Use documentation derived from the feature registry so shipped capability, retained capability, and spec-only branches are not mixed together.

-
-
-

Roadmap truth

-

Roadmap-facing copy should say the embedded browser shell is live, while the optional full-browser client remains separate and frozen as spec-only.

-
-
-

Distribution truth

-

Pricing, checkout, and download docs must stay linked to open-source notices whenever shipped builds contain MPL-covered material.

-
+ {publicDocumentationPrinciples.map((principle) => ( +
+

{principle.title}

+

{principle.description}

+
+ ))} +
+
+ +
+
+ {operatorManualTracks.map((track) => ( +
+

{track.title}

+

{track.description}

+
    + {track.steps.map((step) => ( +
  • {step}
  • + ))} +
+
+ ))} +
+
+ +
+
+ {deliverySurfaceCards.map((surface) => ( +
+

{surface.title}

+

{surface.description}

+
+ ))} +
+
+ +
+
+ {simulatorManualCards.map((card) => ( +
+

{card.title}

+

{card.description}

+
    + {card.bullets.map((bullet) => ( +
  • {bullet}
  • + ))} +
+
+ ))}
@@ -497,6 +623,24 @@ export function SupportPage() { ))}
+ +
+
+ {operatorPlaybooks.map((playbook) => ( +
+

{playbook.title}

+
    + {playbook.steps.map((step) => ( +
  • {step}
  • + ))} +
+
+ ))} +
+
) @@ -536,6 +680,26 @@ export function ChangelogPage() { ))} + +
+
+ {releaseStoryCards.map((card) => ( +
+

{card.title}

+

{card.description}

+
    + {card.bullets.map((bullet) => ( +
  • {bullet}
  • + ))} +
+
+ ))} +
+
+ {releaseManifest.release_notes_url ? (
@@ -608,6 +772,20 @@ export function PricingPage() {
+
+
+ {deliverySurfaceCards.slice(0, 3).map((surface) => ( +
+

{surface.title}

+

{surface.description}

+
+ ))} +
+
+

@@ -713,6 +891,20 @@ export function DownloadPage() {

+
+
+ {deliverySurfaceCards.map((surface) => ( +
+

{surface.title}

+

{surface.description}

+
+ ))} +
+
+
{desktopReleaseSignals.map((signal) => ( @@ -859,6 +1051,25 @@ export function OpenSourceNoticesPage() {
+ +
+
+ {distributionDoctrineCards.map((card) => ( +
+

{card.title}

+

{card.description}

+
    + {card.bullets.map((bullet) => ( +
  • {bullet}
  • + ))} +
+
+ ))} +
+
) @@ -886,6 +1097,25 @@ export function PrivacyPage() {
+ +
+
+ {privacyBoundaryCards.map((card) => ( +
+

{card.title}

+

{card.description}

+
    + {card.bullets.map((bullet) => ( +
  • {bullet}
  • + ))} +
+
+ ))} +
+
) @@ -913,6 +1143,25 @@ export function TermsPage() { + +
+
+ {termsBoundaryCards.map((card) => ( +
+

{card.title}

+

{card.description}

+
    + {card.bullets.map((bullet) => ( +
  • {bullet}
  • + ))} +
+
+ ))} +
+
) @@ -941,6 +1190,25 @@ export function ShippingPaymentPage() { + +
+
+ {digitalDeliveryCards.map((card) => ( +
+

{card.title}

+

{card.description}

+
    + {card.bullets.map((bullet) => ( +
  • {bullet}
  • + ))} +
+
+ ))} +
+
) diff --git a/website/src/site-data.ts b/website/src/site-data.ts index cb70aa5..b5979f5 100644 --- a/website/src/site-data.ts +++ b/website/src/site-data.ts @@ -26,6 +26,45 @@ export const capabilityPillars = [ }, ] as const +export const deliverySurfaceCards = [ + { + title: 'Public website', + description: 'Use the web surface for product positioning, release posture, notices, pricing, and support-safe onboarding.', + bullets: [ + 'Explains what is already shipped versus retained or spec-only', + 'Carries public docs, release notes, package proof, and legal links', + 'Keeps public launch readiness honest without exposing protected downloads', + ], + }, + { + title: 'Protected browser dashboard', + description: 'Use the browser dashboard when identity, entitlement, billing, or pairing state matters.', + bullets: [ + 'Shows account, auth, billing, and release readiness posture', + 'Generates desktop-link tokens for safe browser-to-desktop handoff', + 'Keeps plan-gated download access in the protected release lane', + ], + }, + { + title: 'Native Unreal desktop runtime', + description: 'Use the desktop build for the real simulator, package-validated training maps, and higher-dimensional execution.', + bullets: [ + 'Owns recognition, replay, coaching, and packaged training behavior', + 'Owns the current higher-dimensional 120-cell and 5D runtime lane', + 'Owns the device/runtime integrations the public website does not claim', + ], + }, + { + title: 'Optional future browser-client branch', + description: 'Treat any full-browser simulator path as a separate, frozen branch until the roadmap explicitly reopens it.', + bullets: [ + 'Not part of current shipped product truth', + 'Does not displace the current desktop-first simulator posture', + 'Must prove its own runtime and backend contract before being marketed as live', + ], + }, +] as const + export const shippingNowCards = [ 'Native Unreal training runtime, coaching cockpit, and generated-mode launch', 'Classic-cube timing, drill flows, local leaderboard persistence, and replay recording', @@ -42,6 +81,285 @@ export const roadmapHonestyCards = [ 'Desktop distribution is the primary product lane. Browser access is for account, operator, release, and support surfaces unless a later browser-client packet is deliberately reopened.', ] as const +export const operatorManualTracks = [ + { + title: '1. Start in the browser shell', + description: 'Begin with public pages and the protected dashboard so account, plan, and release posture are resolved before any desktop rollout.', + steps: [ + 'Review the current launch-status banner and release posture.', + 'Create or sign in to a HyperTwist account.', + 'Use the dashboard to confirm plan, billing, and download entitlement state.', + ], + }, + { + title: '2. Move into the protected release lane', + description: 'The public site explains targets, but the actual entitled build stays in the protected download surface.', + steps: [ + 'Choose the target platform from the public download page.', + 'Preserve that platform hint through the sign-in boundary.', + 'Download the entitled package from the protected dashboard once access is resolved.', + ], + }, + { + title: '3. Pair the installed desktop runtime', + description: 'Browser identity and desktop runtime are joined through a bounded desktop-link handshake instead of password reuse.', + steps: [ + 'Generate a desktop-link token inside the dashboard.', + 'Open the installed desktop runtime and verify through the token handoff.', + 'Keep release notes and notices visible during first rollout.', + ], + }, + { + title: '4. Use the simulator for real training', + description: 'Classic-cube practice, replay, diagnostics, and higher-dimensional runtime ownership all live in the desktop lane.', + steps: [ + 'Use the desktop runtime for recognition, replay, and coaching flows.', + 'Treat higher-dimensional training as a packaged native lane, not a browser claim.', + 'Use package proof and release notes to confirm the exact build posture you are running.', + ], + }, + { + title: '5. Revisit browser surfaces for rollout governance', + description: 'Return to the website when you need operator status, support, launch readiness, or legal/distribution guidance.', + steps: [ + 'Use the dashboard for auth, billing, and launch-readiness review.', + 'Use pricing, notices, and support pages for commercial/distribution posture.', + 'Use docs and resources pages for public-safe explanations of the shipped lanes.', + ], + }, +] as const + +export const simulatorManualCards = [ + { + title: 'Classic-cube recognition and correction', + description: 'Use the desktop runtime when you need the actual recognition-to-reconstruction lane instead of public-site explanation.', + bullets: [ + 'Calibrate and observe the cube through the bounded classic-cube capture workflow.', + 'Use browser-assisted recognition, manual correction closure, and solve-guidance readout from the native training lane.', + 'Treat this as an owned desktop workflow, not as a claim that the public website performs the reconstruction job itself.', + ], + }, + { + title: 'Replay, coaching, and analytics', + description: 'Use the desktop runtime when practice needs timing, leaderboard, replay, and coaching continuity in one place.', + bullets: [ + 'Run drill and timing sessions, then review replay and coaching outputs inside the native training surface.', + 'Use the local leaderboard and diagnostics surfaces as part of the packaged runtime, not as detached web widgets.', + 'Return to the browser dashboard only when release, entitlement, or rollout status matters.', + ], + }, + { + title: 'Higher-dimensional runtime ownership', + description: 'Use the packaged Unreal maps for the serious hypercubing lane.', + bullets: [ + 'Launch the dedicated-family 120-cell and 5D training maps from the desktop runtime.', + 'Use the bounded visible-slice and projection ownership already landed for higher-dimensional exploration.', + 'Keep the public website language honest: it can describe this lane, but it does not replace the packaged runtime that executes it.', + ], + }, + { + title: 'Operator diagnostics and release proof', + description: 'Use both surfaces together when rollout confidence matters.', + bullets: [ + 'Use the browser dashboard for auth, billing, desktop-link pairing, and release-lane posture.', + 'Use the desktop runtime for browser-runtime diagnostics, simulator execution, and package-validated behavior.', + 'Use package proof, release notes, and notices together before any public-facing rollout or team distribution.', + ], + }, +] as const + +export const publicDocumentationPrinciples = [ + { + title: 'Feature-registry-backed wording only', + description: 'Public documentation should describe shipped capability, retained capability, and spec-only branches with the same discipline used in the internal feature registry.', + }, + { + title: 'Browser shell and simulator kept separate', + description: 'Account access, pricing, and download posture belong to the website. Recognition, replay, coaching, and higher-dimensional execution belong to the desktop runtime.', + }, + { + title: 'Launch and legal readiness stay visible', + description: 'Pricing, download, and notices pages remain part of one release story so package proof and corresponding-source duties do not disappear during rollout.', + }, +] as const + +export const operatorPlaybooks = [ + { + title: 'Operator onboarding', + steps: [ + 'Sign in through the browser shell before requesting a desktop package.', + 'Confirm package proof and release references before first install.', + 'Use desktop-link pairing so the installed app inherits the right account posture.', + ], + }, + { + title: 'Studio rollout', + steps: [ + 'Validate checkout, download, notices, and corresponding-source posture together.', + 'Treat package proof as part of launch readiness rather than as a separate afterthought.', + 'Keep the browser dashboard for coordination and the desktop build for actual simulator execution.', + ], + }, + { + title: 'Support and compliance', + steps: [ + 'Route account, entitlement, and rollout issues through support-safe browser surfaces.', + 'Keep pricing/download/notices links aligned for any public distribution event.', + 'Do not describe the full-browser simulator branch as live while it remains frozen.', + ], + }, +] as const + +export const releaseStoryCards = [ + { + title: 'Desktop package hardening', + description: 'Release notes should foreground the native simulator lane: packaged validation, dedicated training maps, replay proof, and higher-dimensional runtime closure.', + bullets: [ + 'Call out the Windows packaged-validation lane when it is the strongest current proof.', + 'Differentiate simulator/runtime changes from browser-shell or billing changes.', + 'Keep package proof, release notes, and distribution guidance linked together.', + ], + }, + { + title: 'Browser operator hardening', + description: 'Website change notes should explain auth, entitlement, launch-readiness, and dashboard behavior without implying browser simulator parity.', + bullets: [ + 'Summarize account, checkout, and desktop-link changes in operator language.', + 'Keep public preview-versus-launch posture visible when values are not fully live.', + 'Treat browser improvements as operator/distribution work around the desktop runtime.', + ], + }, + { + title: 'Legal and release discipline', + description: 'Distribution-facing changes are product changes, not footer trivia, because they affect whether the release lane is truthful and launchable.', + bullets: [ + 'Record corresponding-source, notices, and pricing/download posture when they change.', + 'Make release chronology readable for studios evaluating rollout risk.', + 'Keep public legal surfaces synchronized with actual downloadable posture.', + ], + }, +] as const + +export const distributionDoctrineCards = [ + { + title: 'Public pages are distribution surfaces', + description: 'Pricing, checkout, release, and download pages are part of the distributed product story whenever they sell or deliver downloadable builds.', + bullets: [ + 'Treat those pages as release-facing surfaces, not detached marketing copy.', + 'Keep open-source notices and corresponding-source links reachable from them.', + 'Do not let launch copy outrun the actual package and legal posture.', + ], + }, + { + title: 'Package proof stays visible', + description: 'The legal and release lane stays more trustworthy when package validation evidence remains visible alongside download posture.', + bullets: [ + 'Show validation summaries where public audiences assess readiness.', + 'Preserve the link between build proof, release notes, and distribution guidance.', + 'Use the protected dashboard for access control, not to hide release truth.', + ], + }, + { + title: 'Browser and desktop remain distinct', + description: 'Notices and legal guidance should reinforce that the website governs distribution while the simulator remains a native runtime.', + bullets: [ + 'Do not imply that a legal-ready website means the browser is now the simulator.', + 'Keep device/runtime obligations tied to the downloadable product lane.', + 'Describe any future full-browser client as a separate branch until reopened.', + ], + }, +] as const + +export const privacyBoundaryCards = [ + { + title: 'Browser identity and billing boundary', + description: 'The public/protected browser shell should retain only the account, session, entitlement, and billing data needed for access control and release delivery.', + bullets: [ + 'Keep sign-in, plan access, and desktop-link issuance inside the browser account lane.', + 'Avoid claiming browser ownership over native simulator telemetry by default.', + 'Use the browser shell for narrow rollout/account state, not for universal runtime state capture.', + ], + }, + { + title: 'Desktop simulator boundary', + description: 'The desktop runtime owns the simulator and training behavior, so privacy wording should not collapse those runtime facts into generic website claims.', + bullets: [ + 'Treat native training, replay, and diagnostics as desktop concerns first.', + 'Describe pair-up and access transfer truthfully without advertising password reuse.', + 'Keep browser-to-desktop token handoff as the bounded identity bridge.', + ], + }, + { + title: 'Support and compliance boundary', + description: 'Support flows should collect the smallest useful operator context while keeping legal and account duties intact.', + bullets: [ + 'Route billing, entitlement, and rollout issues through support-safe browser surfaces.', + 'Keep public documentation explicit about what data belongs to release access versus simulator use.', + 'Preserve legal-distribution disclosure without broadening into unrelated tracking claims.', + ], + }, +] as const + +export const termsBoundaryCards = [ + { + title: 'Access model', + description: 'Terms should match the actual product topology: public pages, protected dashboard access, and native desktop delivery.', + bullets: [ + 'Browser access covers public information, account, entitlement, and release coordination.', + 'Protected downloads remain tied to account and plan posture.', + 'Native runtime use remains subject to release, notice, and distribution rules.', + ], + }, + { + title: 'Simulator boundary', + description: 'Terms should not pretend the current website has replaced the native simulator lane.', + bullets: [ + 'Keep the desktop-first simulator posture explicit.', + 'Treat any later full-browser simulator work as a separate branch until reopened.', + 'Avoid vague “all features available on the web” language while that is untrue.', + ], + }, + { + title: 'Operational obligations', + description: 'Commercial and rollout terms should stay aligned with the same legal and package-proof duties visible elsewhere on the site.', + bullets: [ + 'Keep notices and corresponding-source posture reachable from access and download surfaces.', + 'Preserve release-note and support references for operator-grade deployment.', + 'Bind the commercial lane to the actual shipped release posture, not a speculative future branch.', + ], + }, +] as const + +export const digitalDeliveryCards = [ + { + title: 'Release selection before install', + description: 'Digital delivery begins in the browser shell so the operator can review platform, entitlement, and release posture before opening the desktop runtime.', + bullets: [ + 'Choose the platform from the public download surface.', + 'Carry that selection into the protected dashboard.', + 'Confirm release notes, package proof, and notices before distribution.', + ], + }, + { + title: 'Protected entitlement handoff', + description: 'The protected browser lane exists so digital delivery can stay account-aware without putting raw distribution logic on anonymous public pages.', + bullets: [ + 'Use account state to gate the real package handoff.', + 'Use desktop-link pairing to hand identity over to the installed app.', + 'Keep public pages informative while preserving protected release access.', + ], + }, + { + title: 'Post-install operator workflow', + description: 'Digital delivery is only complete when the installed runtime and the operator-facing browser shell remain aligned.', + bullets: [ + 'Use the browser dashboard for account, billing, and rollout governance.', + 'Use the desktop app for simulator execution, diagnostics, and higher-dimensional training.', + 'Return to notices, pricing, and support pages when release/compliance posture changes.', + ], + }, +] as const + export const resourceCollections = [ { title: 'Product truth', @@ -49,6 +367,7 @@ export const resourceCollections = [ 'Feature registry-backed descriptions only', 'Roadmap-honest separation between shipped and retained capability', 'Release notes that surface what actually landed', + 'Desktop-first simulator posture with browser-shell boundaries kept explicit', ], }, { @@ -57,6 +376,7 @@ export const resourceCollections = [ 'Desktop download instructions and release channels', 'Desktop-link handshake for browser-to-desktop sign-in', 'Legal/notices linkage for public distribution surfaces', + 'Package validation proof and release-readiness interpretation', ], }, { @@ -65,6 +385,7 @@ export const resourceCollections = [ 'Classic-cube recognition and correction workflows', 'Replay, coaching, analytics, and package validation posture', 'Higher-dimensional puzzle-family references and browser-host boundaries', + '120-cell and 5D dedicated-family runtime ownership overview', ], }, ] as const