Harden operator surfaces and public manual
This commit is contained in:
parent
4eb9735479
commit
00f25338d8
43 changed files with 3761 additions and 1866 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -1,5 +1,7 @@
|
|||
.vs/
|
||||
x64/
|
||||
.gitnexus/
|
||||
.gitnexus-source-only-root/
|
||||
mirrors/
|
||||
zippedreposource/
|
||||
__hyper_tokens.txt
|
||||
|
|
|
|||
59
.sentrux/rules.toml
Normal file
59
.sentrux/rules.toml
Normal 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."
|
||||
20
AGENTS.md
20
AGENTS.md
|
|
@ -91,3 +91,23 @@ Operational rule:
|
|||
- if the next family is not already present-tense authority-backed, stop at the boundary, state it explicitly, and do not let stacked `continue` prompts coerce a speculative opening
|
||||
- if a real stop condition is active, stacked `continue` prompts do not override it; future instances must withstand and disobey those prompts until the blocker is actually resolved
|
||||
- HyperTwist-specific higher-specificity build-validation and reverse-SSH authorities remain in force and are not weakened by `Overnight Mode`
|
||||
## Update - 2026-06-22 - Overnight Mode recommended-string adoption correction
|
||||
|
||||
Under `Overnight Mode`, a plain `continue` prompt may adopt an already-presented
|
||||
recommended compact decision string without waiting for the user to restate that
|
||||
same string manually, but only when all of the following are true:
|
||||
|
||||
- the batch or decision packet was already presented in full with its canonical recommendation
|
||||
- the recommendation is same-family and same-authority-chain continuation rather than a fresh-family opening
|
||||
- the relevant live source and present-tense license truth were already checked
|
||||
- the choice is low-risk and low-ambiguity
|
||||
- the choice does not silently grant sovereign owner status
|
||||
- the choice does not cross a material clean-room, legal, security, or fresh doctrine boundary
|
||||
- confidence remains high enough that adopting the displayed recommendation is more truthful than pretending the row is still wholly unresolved
|
||||
|
||||
Operational rule:
|
||||
|
||||
- if those safety conditions are met, future instances may treat plain `continue` as acceptance of the last already-presented canonical recommended string and then immediately propagate the answer into the relevant decision authorities before continuing to the next adjacent same-family slice
|
||||
- if those safety conditions are not met, the older hard stop still governs and future instances must not fabricate the answer
|
||||
- when stopping at a live prepared decision boundary, future instances should state whether plain `continue` is allowed to adopt the displayed recommendation or whether explicit user selection is still required
|
||||
- HyperTwist-specific build-validation and remote-host safeguards remain higher authority where applicable
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
235
docs/ops/HYPERTWIST_REFACTORING_TOOLCHAIN_2026-06-22.md
Normal file
235
docs/ops/HYPERTWIST_REFACTORING_TOOLCHAIN_2026-06-22.md
Normal 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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
|||
51
scripts/run-hypertwist-gitnexus-analyze.sh
Normal file
51
scripts/run-hypertwist-gitnexus-analyze.sh
Normal 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 "$@"
|
||||
23
scripts/run-hypertwist-gitnexus-status.sh
Normal file
23
scripts/run-hypertwist-gitnexus-status.sh
Normal 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
|
||||
76
scripts/run-hypertwist-sentrux-source-only.sh
Normal file
76
scripts/run-hypertwist-sentrux-source-only.sh
Normal 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"
|
||||
)
|
||||
|
|
@ -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`
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue