{shouldShowFallback ?
: null}
- {resolvedUrl && !hasError ? (
+ {shouldShowImage ? (

{
setIsLoaded(true);
}}
@@ -238,6 +255,28 @@ function JumpHopTileImage({
);
}
+function JumpHopTilePreloadImage({ asset }: { asset: JumpHopTileAsset }) {
+ const assetRefreshKey = getJumpHopTileAssetRefreshKey(asset);
+ const { resolvedUrl } = useResolvedAssetReadUrl(asset.imageSrc, {
+ refreshKey: assetRefreshKey,
+ });
+
+ if (!resolvedUrl) {
+ return null;
+ }
+
+ return (
+

+ );
+}
+
function hasJumpHopWebGLSupport() {
if (import.meta.env.MODE === 'test') {
return false;
@@ -573,6 +612,16 @@ export function JumpHopRuntimeShell({
const displayRunRef = useRef(displayRun);
const visiblePlatformsRef = useRef
([]);
const tileAssetsRef = useRef(profile?.tileAssets);
+ const stageBackgroundSource = [
+ profile?.draft.coverComposite,
+ profile?.summary.coverImageSrc,
+ ].find(isJumpHopGeneratedBackgroundSource);
+ const { resolvedUrl: stageBackgroundUrl } = useResolvedAssetReadUrl(
+ stageBackgroundSource,
+ {
+ refreshKey: stageBackgroundSource,
+ },
+ );
useEffect(() => {
activeRunRef.current = activeRun;
@@ -612,7 +661,7 @@ export function JumpHopRuntimeShell({
const platformRenderItems = useMemo(() => {
const exitingItems = platformAdvanceExitingPlatforms.map((item) => ({
...item,
- renderKey: `${item.platform.platformId}-exiting`,
+ renderKey: item.platform.platformId,
advanceState: 'exiting' as const,
}));
const visibleItems = visiblePlatforms.map((item) => ({
@@ -627,6 +676,47 @@ export function JumpHopRuntimeShell({
platformAdvanceExitingPlatforms,
visiblePlatforms,
]);
+ const preloadTileAssets = useMemo(() => {
+ const path = stageRun?.path;
+ const tileAssets = profile?.tileAssets;
+ const platforms = path?.platforms ?? [];
+ const startIndex =
+ (stageRun?.currentPlatformIndex ?? 0) + visiblePlatforms.length;
+ const assets = new Map();
+
+ for (
+ let index = startIndex;
+ index <
+ Math.min(
+ platforms.length,
+ startIndex + JUMP_HOP_TILE_PRELOAD_LOOKAHEAD_COUNT,
+ );
+ index += 1
+ ) {
+ const platform = platforms[index];
+ if (!platform) {
+ continue;
+ }
+ const asset = selectJumpHopTileAsset(
+ tileAssets,
+ path?.seed ?? null,
+ index,
+ platform.platformId,
+ );
+ if (!asset) {
+ continue;
+ }
+ const key = getJumpHopTileAssetRefreshKey(asset) ?? asset.imageSrc;
+ assets.set(key, asset);
+ }
+
+ return [...assets.values()];
+ }, [
+ profile?.tileAssets,
+ stageRun?.currentPlatformIndex,
+ stageRun?.path,
+ visiblePlatforms.length,
+ ]);
const showLandingAssist =
import.meta.env.MODE !== 'production' && isCharging && !isJumpAnimating;
const characterPosition = getJumpHopCharacterVisualPosition(
@@ -753,6 +843,7 @@ export function JumpHopRuntimeShell({
const jumpFeedbackForDisplay = getJumpHopJumpFeedbackLabel(stageRun);
const isSettled =
stageRun?.status === 'failed' || stageRun?.status === 'cleared';
+ const shouldShowFailureLeaderboard = stageRun?.status === 'failed';
const successfulJumpCount = stageRun?.successfulJumpCount ?? 0;
const durationLabel = formatJumpHopDurationLabel(
getJumpHopRunDurationMs(stageRun, nowMs),
@@ -1219,29 +1310,19 @@ export function JumpHopRuntimeShell({
-
0 ? (
+
+ {preloadTileAssets.map((asset) => (
+
+ ))}
+
+ ) : null}
+
{visualCharacterPosition && !isThreeCharacterLayerReady ? (
-
+
- {getJumpHopStatusLabel(stageRun?.status)}
+
+ {getJumpHopStatusLabel(stageRun?.status)}
+
{successfulJumpCount} è·³
{durationLabel}
+ {shouldShowFailureLeaderboard ? (
+
+ ) : null}