Harden operator surfaces and public manual

This commit is contained in:
axiomlogicnexus 2026-06-22 23:24:30 +00:00
parent 4eb9735479
commit 00f25338d8
43 changed files with 3761 additions and 1866 deletions

2
.gitignore vendored
View file

@ -1,5 +1,7 @@
.vs/
x64/
.gitnexus/
.gitnexus-source-only-root/
mirrors/
zippedreposource/
__hyper_tokens.txt

59
.sentrux/rules.toml Normal file
View file

@ -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."

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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<K extends keyof HTMLElementTagNameMap>(
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<string, HTMLElement>();
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<string, unknown>
{
const LastCommandReceivedAtUtc = BootState.lastCommandReceivedAtUtc ?? 'none';
const LastShellStateReceivedAtUtc = BootState.lastShellStateReceivedAtUtc ?? 'none';
function buildRuntimeStatusSnapshot(
BootState: BrowserBootState,
Counters: RuntimeCounters
): Record<string, unknown>
{
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<void>
{
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
);
}

View file

@ -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();

View file

@ -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<AHyperTwistClassicCubeGameMode>(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<AHyperTwistClassicCubeGameMode>(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<AHyperTwistClassicCubeGameMode>(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<AHyperTwistClassicCubeGameMode>(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<AHyperTwistClassicCubeGameMode>(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<AHyperTwistClassicCubeGameMode>(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<AHyperTwistClassicCubeGameMode>(GetWorld()->GetAuthGameMode()))
{
GameMode->CycleVoiceProfile();
IHyperTwistClassicCubeOperatorSurface::Execute_RequestClassicCubeCycleVoiceProfile(
OperatorSurface
);
}
}

View file

@ -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<AHyperTwistClassicCubeGameMode>(GetWorld()->GetAuthGameMode()))
{
if (GameMode->ActiveCubeActor != nullptr)
{
return GameMode->ActiveCubeActor;
}
}
for (TActorIterator<AHyperTwistClassicCubeActor> ActorIt(GetWorld()); ActorIt; ++ActorIt)
{
return *ActorIt;

View file

@ -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<AHyperTwistClassicCubeActor> ActorIt(GetWorld()); ActorIt; ++ActorIt)
{
return *ActorIt;
@ -206,18 +223,13 @@ AHyperTwistClassicCubeActor* AHyperTwistClassicCubePlayerController::ResolveClas
return nullptr;
}
AHyperTwistClassicCubeGameMode* AHyperTwistClassicCubePlayerController::ResolveClassicCubeGameMode() const
{
return Cast<AHyperTwistClassicCubeGameMode>(
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
);
}
}

View file

@ -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<AHyperTwistMelindaProjectionGameMode>(GetWorld()->GetAuthGameMode()))
{
if (GameMode->ActiveProjectionActor != nullptr)
{
return GameMode->ActiveProjectionActor;
}
}
for (TActorIterator<AHyperTwistMelindaProjectionActor> ActorIt(GetWorld()); ActorIt; ++ActorIt)
{
return *ActorIt;

View file

@ -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<AHyperTwistMelindaProjectionActor> ActorIt(GetWorld()); ActorIt; ++ActorIt)
{
return *ActorIt;
@ -177,13 +157,6 @@ AHyperTwistMelindaProjectionActor* AHyperTwistMelindaProjectionPlayerController:
return nullptr;
}
AHyperTwistMelindaProjectionGameMode* AHyperTwistMelindaProjectionPlayerController::ResolveProjectionGameMode() const
{
return Cast<AHyperTwistMelindaProjectionGameMode>(
GetWorld() != nullptr ? GetWorld()->GetAuthGameMode() : nullptr
);
}
void AHyperTwistMelindaProjectionPlayerController::HandlePrimaryClick()
{
TryProcessProjectionClickFromCursor(false);

View file

@ -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<AHyperTwistVirtual3333ProjectionGameMode>(GetWorld()->GetAuthGameMode()))
{
if (GameMode->ActiveProjectionActor != nullptr)
{
return GameMode->ActiveProjectionActor;
}
}
for (TActorIterator<AHyperTwistVirtual3333ProjectionActor> ActorIt(GetWorld()); ActorIt; ++ActorIt)
{
return *ActorIt;

View file

@ -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<AHyperTwistVirtual3333ProjectionActor> ActorIt(GetWorld()); ActorIt; ++ActorIt)
{
return *ActorIt;
@ -219,14 +210,6 @@ AHyperTwistVirtual3333ProjectionPlayerController::ResolveProjectionActor() const
return nullptr;
}
AHyperTwistVirtual3333ProjectionGameMode*
AHyperTwistVirtual3333ProjectionPlayerController::ResolveProjectionGameMode() const
{
return Cast<AHyperTwistVirtual3333ProjectionGameMode>(
GetWorld() != nullptr ? GetWorld()->GetAuthGameMode() : nullptr
);
}
void AHyperTwistVirtual3333ProjectionPlayerController::HandleSliceAxisX()
{
SetSliceAxis(EHyperTwistVirtual3333Axis::X);

View file

@ -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<FHyperTwistContentPack> UHyperTwistTrainingCatalogLibrary::MakePhase3Trai
MakeAlgTrainerStarterContentPack(),
MakeCrossTrainerStarterContentPack(),
MakeFiveStyleStarterContentPack(),
UHyperTwistTrainingLibrary::MakeSampleClassicContentPack()
MakeSampleClassicContentPack()
};
FHyperTwistTrainingCatalogMaterializedBundle MaterializedBundle;

View file

@ -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(

View file

@ -232,6 +232,76 @@ struct FHyperTwistStateSnapshot
}
};
namespace HyperTwistCoreTypeValidation
{
static constexpr int32 MelindaPieceCount = 16;
static constexpr int32 MelindaOrientationCount = 24;
inline bool IsPermutationOfExpectedRange(const TArray<int32>& Values, const int32 ExpectedCount)
{
if (Values.Num() != ExpectedCount)
{
return false;
}
TArray<bool> 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<int32>& 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<bool> 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<bool> 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<FHyperTwistMelindaStickerProjection>& PeekStickers)
{
TArray<bool> SeenAxes;
SeenAxes.Init(false, 4);
for (const FHyperTwistMelindaStickerProjection& PeekSticker : PeekStickers)
{
if (!PeekSticker.IsStructurallyValid())
{
return false;
}
const int32 AxisIndex = static_cast<int32>(PeekSticker.Axis);
if (AxisIndex < 0
|| AxisIndex >= 4
|| AxisIndex == static_cast<int32>(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<FHyperTwistMelindaFlatTileProjection>& Tiles,
const EHyperTwistMelindaProjectionPanel ExpectedPanel,
TArray<bool>& 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<bool> SeenPositions;
SeenPositions.Init(false, 16);
auto ValidatePeekStickers = [](const TArray<FHyperTwistMelindaStickerProjection>& PeekStickers) -> bool
{
TArray<bool> SeenAxes;
SeenAxes.Init(false, 4);
for (const FHyperTwistMelindaStickerProjection& PeekSticker : PeekStickers)
{
if (!PeekSticker.IsStructurallyValid())
{
return false;
}
const int32 AxisIndex = static_cast<int32>(PeekSticker.Axis);
if (AxisIndex < 0
|| AxisIndex >= 4
|| AxisIndex == static_cast<int32>(EHyperTwistMelindaAxis::W)
|| SeenAxes[AxisIndex])
{
return false;
}
SeenAxes[AxisIndex] = true;
}
return PeekStickers.Num() == 3;
};
auto ValidateTiles =
[this, &SeenPositions, &ValidatePeekStickers](
const TArray<FHyperTwistMelindaFlatTileProjection>& 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
);
}
};

View file

@ -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;

View file

@ -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;
};

View file

@ -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();

View file

@ -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();

View file

@ -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();

View file

@ -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<FHyperTwistContentPack> MakePhase3TrainingCatalog();

View file

@ -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

View file

@ -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`.

View file

@ -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.

View file

@ -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.

View file

@ -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

View file

@ -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

View file

@ -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:

View file

@ -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.

View file

@ -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

View file

@ -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`

File diff suppressed because one or more lines are too long

View file

@ -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

View file

@ -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 "$@"

View file

@ -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

View file

@ -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"
)

View file

@ -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`

View file

@ -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) {

View file

@ -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(<DocsPage />, ['/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(<OpenSourceNoticesPage />, ['/open-source-notices'])
expect(screen.getByText('Distribution doctrine')).toBeTruthy()
expect(screen.getByText('Public pages are distribution surfaces')).toBeTruthy()
renderWithProviders(<PrivacyPage />, ['/privacy'])
expect(screen.getByText('Practical privacy boundary')).toBeTruthy()
expect(screen.getByText('Browser identity and billing boundary')).toBeTruthy()
renderWithProviders(<TermsPage />, ['/terms'])
expect(screen.getByText('Terms in practice')).toBeTruthy()
expect(screen.getByText('Access model')).toBeTruthy()
renderWithProviders(<ShippingPaymentPage />, ['/shipping-payment'])
expect(screen.getByText('Digital delivery workflow')).toBeTruthy()
expect(screen.getByText('Protected entitlement handoff')).toBeTruthy()
})
})

View file

@ -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<string, { title: string; description: string }> = {
@ -246,6 +256,25 @@ export function HomeLanding() {
</article>
</div>
</Section>
<Section
title="Why both browser and desktop stay"
description="The public site owns operator and distribution work the simulator should not dilute, while the simulator stays native for the runtime-heavy training job."
>
<div className="card-grid">
{deliverySurfaceCards.map((surface) => (
<article key={surface.title} className="card">
<h3>{surface.title}</h3>
<p>{surface.description}</p>
<ul className="list top-gap">
{surface.bullets.map((bullet) => (
<li key={bullet}>{bullet}</li>
))}
</ul>
</article>
))}
</div>
</Section>
</MarketingShell>
</>
)
@ -272,6 +301,20 @@ export function AboutPage() {
</div>
</Section>
<Section
title="Why the web surface remains necessary"
description="Keeping the website does not weaken the desktop-first thesis. It keeps public distribution, release, and operator-governance work outside the simulator proper."
>
<div className="card-grid">
{deliverySurfaceCards.map((surface) => (
<article key={surface.title} className="card">
<h3>{surface.title}</h3>
<p>{surface.description}</p>
</article>
))}
</div>
</Section>
<Section title="What makes the product different">
<div className="card-grid">
<article className="card">
@ -367,6 +410,43 @@ export function ResourcesPage() {
</div>
</Section>
<Section
title="Operator playbooks"
description="These are the public-safe working patterns that matter once a team moves from curiosity into real rollout."
>
<div className="card-grid">
{operatorPlaybooks.map((playbook) => (
<article key={playbook.title} className="card">
<h3>{playbook.title}</h3>
<ul className="list top-gap">
{playbook.steps.map((step) => (
<li key={step}>{step}</li>
))}
</ul>
</article>
))}
</div>
</Section>
<Section
title="Simulator use today"
description="This summary is deliberately practical: what you actually do in the desktop runtime once browser identity and release posture are already resolved."
>
<div className="card-grid">
{simulatorManualCards.map((card) => (
<article key={card.title} className="card">
<h3>{card.title}</h3>
<p>{card.description}</p>
<ul className="list top-gap">
{card.bullets.map((bullet) => (
<li key={bullet}>{bullet}</li>
))}
</ul>
</article>
))}
</div>
</Section>
{windowsValidationSummary ? (
<Section title="Current packaged desktop proof">
<article className="card">
@ -425,18 +505,64 @@ export function DocsPage() {
>
<Section title="Public documentation lanes">
<div className="card-grid">
<article className="card">
<h3>Feature truth</h3>
<p>Use documentation derived from the feature registry so shipped capability, retained capability, and spec-only branches are not mixed together.</p>
</article>
<article className="card">
<h3>Roadmap truth</h3>
<p>Roadmap-facing copy should say the embedded browser shell is live, while the optional full-browser client remains separate and frozen as spec-only.</p>
</article>
<article className="card">
<h3>Distribution truth</h3>
<p>Pricing, checkout, and download docs must stay linked to open-source notices whenever shipped builds contain MPL-covered material.</p>
</article>
{publicDocumentationPrinciples.map((principle) => (
<article key={principle.title} className="card">
<h3>{principle.title}</h3>
<p>{principle.description}</p>
</article>
))}
</div>
</Section>
<Section
title="Operator manual"
description="This is the public-facing manual for how HyperTwist is actually used today: browser first for identity and release posture, desktop first for the simulator."
>
<div className="card-grid">
{operatorManualTracks.map((track) => (
<article key={track.title} className="card">
<h3>{track.title}</h3>
<p>{track.description}</p>
<ul className="list top-gap">
{track.steps.map((step) => (
<li key={step}>{step}</li>
))}
</ul>
</article>
))}
</div>
</Section>
<Section
title="Browser versus simulator boundary"
description="The docs stay professional by being explicit about what each surface does better."
>
<div className="card-grid">
{deliverySurfaceCards.map((surface) => (
<article key={surface.title} className="card">
<h3>{surface.title}</h3>
<p>{surface.description}</p>
</article>
))}
</div>
</Section>
<Section
title="Simulator manual"
description="These are the current product-safe usage tracks for the native runtime itself."
>
<div className="card-grid">
{simulatorManualCards.map((card) => (
<article key={card.title} className="card">
<h3>{card.title}</h3>
<p>{card.description}</p>
<ul className="list top-gap">
{card.bullets.map((bullet) => (
<li key={bullet}>{bullet}</li>
))}
</ul>
</article>
))}
</div>
</Section>
@ -497,6 +623,24 @@ export function SupportPage() {
))}
</div>
</Section>
<Section
title="Support lanes"
description="Support works best when operators know whether they need public guidance, protected browser access, or native desktop follow-through."
>
<div className="card-grid">
{operatorPlaybooks.map((playbook) => (
<article key={playbook.title} className="card">
<h3>{playbook.title}</h3>
<ul className="list top-gap">
{playbook.steps.map((step) => (
<li key={step}>{step}</li>
))}
</ul>
</article>
))}
</div>
</Section>
</MarketingShell>
</>
)
@ -536,6 +680,26 @@ export function ChangelogPage() {
))}
</div>
</Section>
<Section
title="How to read the release feed"
description="HyperTwist release notes stay useful when they distinguish simulator/runtime work, browser-operator work, and distribution/legal hardening instead of collapsing them together."
>
<div className="card-grid">
{releaseStoryCards.map((card) => (
<article key={card.title} className="card">
<h3>{card.title}</h3>
<p>{card.description}</p>
<ul className="list top-gap">
{card.bullets.map((bullet) => (
<li key={bullet}>{bullet}</li>
))}
</ul>
</article>
))}
</div>
</Section>
{releaseManifest.release_notes_url ? (
<Section title="External release notes">
<a className="button button--ghost" href={releaseManifest.release_notes_url} target="_blank" rel="noreferrer">
@ -608,6 +772,20 @@ export function PricingPage() {
<PublicLaunchStatus />
</Section>
<Section
title="Why plans live in the browser while training stays native"
description="Commercial access, entitlement, and launch-readiness posture belong to the browser shell so the simulator can stay focused on training quality."
>
<div className="card-grid">
{deliverySurfaceCards.slice(0, 3).map((surface) => (
<article key={surface.title} className="card">
<h3>{surface.title}</h3>
<p>{surface.description}</p>
</article>
))}
</div>
</Section>
<Section title="Important launch note">
<article className="callout">
<p>
@ -713,6 +891,20 @@ export function DownloadPage() {
<PublicLaunchStatus title="Desktop release access stays launch-honest" />
</Section>
<Section
title="Browser and desktop responsibilities"
description="The release lane is easier to trust when the site explains why some actions stay public, some stay protected, and the simulator itself stays native."
>
<div className="card-grid">
{deliverySurfaceCards.map((surface) => (
<article key={surface.title} className="card">
<h3>{surface.title}</h3>
<p>{surface.description}</p>
</article>
))}
</div>
</Section>
<Section title="Release integrity and documentation">
<div className="card-grid">
{desktopReleaseSignals.map((signal) => (
@ -859,6 +1051,25 @@ export function OpenSourceNoticesPage() {
<Section title="Distribution readiness">
<PublicLaunchStatus title="Notices and corresponding-source readiness" />
</Section>
<Section
title="Distribution doctrine"
description="These public legal surfaces should explain why the website is part of the release story without pretending it has replaced the native simulator."
>
<div className="card-grid">
{distributionDoctrineCards.map((card) => (
<article className="card" key={card.title}>
<h3>{card.title}</h3>
<p>{card.description}</p>
<ul className="list top-gap">
{card.bullets.map((bullet) => (
<li key={bullet}>{bullet}</li>
))}
</ul>
</article>
))}
</div>
</Section>
</MarketingShell>
</>
)
@ -886,6 +1097,25 @@ export function PrivacyPage() {
</ul>
</article>
</Section>
<Section
title="Practical privacy boundary"
description="Privacy wording should follow the actual product split instead of flattening the browser shell and native simulator into one vague surface."
>
<div className="card-grid">
{privacyBoundaryCards.map((card) => (
<article key={card.title} className="card">
<h3>{card.title}</h3>
<p>{card.description}</p>
<ul className="list top-gap">
{card.bullets.map((bullet) => (
<li key={bullet}>{bullet}</li>
))}
</ul>
</article>
))}
</div>
</Section>
</MarketingShell>
</>
)
@ -913,6 +1143,25 @@ export function TermsPage() {
</ul>
</article>
</Section>
<Section
title="Terms in practice"
description="These terms-oriented boundaries keep the public site, protected dashboard, and desktop simulator aligned with the actual delivery model."
>
<div className="card-grid">
{termsBoundaryCards.map((card) => (
<article key={card.title} className="card">
<h3>{card.title}</h3>
<p>{card.description}</p>
<ul className="list top-gap">
{card.bullets.map((bullet) => (
<li key={bullet}>{bullet}</li>
))}
</ul>
</article>
))}
</div>
</Section>
</MarketingShell>
</>
)
@ -941,6 +1190,25 @@ export function ShippingPaymentPage() {
</ul>
</article>
</Section>
<Section
title="Digital delivery workflow"
description="A professional digital-delivery lane does more than expose a buy button. It keeps release posture, entitlement, and the desktop handoff coherent."
>
<div className="card-grid">
{digitalDeliveryCards.map((card) => (
<article key={card.title} className="card">
<h3>{card.title}</h3>
<p>{card.description}</p>
<ul className="list top-gap">
{card.bullets.map((bullet) => (
<li key={bullet}>{bullet}</li>
))}
</ul>
</article>
))}
</div>
</Section>
</MarketingShell>
</>
)

View file

@ -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