как остров помнит

9 сентября 2026 г. devlogsveltekit

Птицы и рыба на этом сайте предполагаются ощущаться живыми, дрейф поперёк неба и прибрежный полоса в заголовке и подножие. Что я не подумал через пока читатель (я, тестирующий мой собственный сайт как нормальный человек за раз) не указал это это что происходит когда ты нажимаешь обновить. Каждый раз, целая сцена переустанавливает. Разные птицы, разные стартовые позиции, как небо получило вытерто и переселено с нуля момент ты посмотрел прочь. Это не ощущалось живым, это ощущалось случайно переродившимся, что вещь совсем другая.

Требование, в моих собственных словах

Я написал себе примечание в то время что сказал, более или менее, «случайный если посещение в менее чем 5мин...если нет追踪 где оставил и закрепи если изменишь страницу.» Щелчок вокруг сайта не должен касаться сцены вообще, так как компоненты монтируют один раз из коренной разметки и остаются смонтированными целое время ты навигируешь клиент-сторона. Та часть была уже бесплатна, SvelteKit не ремонтирует компонент просто потому что URL изменился под ним. Действительный разрыв был другой кейс: жёсткая перезагрузка, или открытие свежей вкладки. То момент компонент ремонтирует из ничего, и вверх в тот пункт, «из ничего» означал свежий случайный розыгрыш каждый раз.

Первая версия не была неправильной, это была заморожена

Моя первая попытка в исправлении это была почти правильно и полностью сломана способом я не заметил день. Я сохранил отчисленную сцену в localStorage, какие спрайты получили выбраны и какой пункт в их цикле полёта каждый один был на, и восстановил это на следующем монтировании вместо переразыгрывания:

// первая попытка: спасти фаза, восстановить ровно та же фаза
function restoreScene(saved: ScenePhases): ScenePhases {
	return saved;
}

Та часть работала, в смысле сохранённые данные пришли назад нетронутыми. Что я забыл было время. Я сохранил ровно фазу каждая птица была на когда страница выгружена, и на следующей загрузке я просто...поместил их назад ровно там. Заморожено. Если ты оставил вкладку открытой в твой доке два минут и вернулся, птицы не движут дюйм, потому что ничего не считается два минут что действительно прошло в действительном мире.

Действительное исправление нужно было продвинуть каждую сохранённую фазу вперёд на сколько долго посетитель был действительно уходил, правильно обёрнутый вокруг каждой собственной длины цикла птицы так птица та что летит край к краю каждые 75 секунд и были «ушедшие» 200 секунд приходит назад полпути через её третий круг, не прилипая на второй двенадцать секунд круга навсегда:

export function advancePhases(
	phases: ScenePhases,
	cycles: Record<string, number>,
	elapsedMs: number
): ScenePhases {
	const elapsedS = Math.max(0, elapsedMs) / 1000;
	const advanced: ScenePhases = {};
	for (const [key, value] of Object.entries(phases)) {
		const cycle = cycles[key];
		if (!cycle || cycle <= 0 || !Number.isFinite(value)) {
			advanced[key] = value;
			continue;
		}
		advanced[key] = (((value + elapsedS) % cycle) + cycle) % cycle;
	}
	return advanced;
}

Двойное модульо на конце ((((value + elapsedS) % cycle) + cycle) % cycle) находится там потому что JavaScript % может возвращать отрицательный результат если левая сторона отрицательна, и я хотел эту функцию безопасно вызываемой с странным или отрицательным прошедшим временем тоже (смещение часов, сохранённая метка времени из будущего, что угодно) без произведения фазы за пределами её собственного действительного диапазона. Ремень и подтяжки, но дешёвые.

Морозность также имеет потолок

Ничто из этого не должно держаться навсегда. Сцена из шести часов назад не «та же посещение продолжается,» это новое посещение та что случилось разделять браузер. Так сохранённая сцена несёт её собственную проверку возраста и заканчивается через пять минут:

export const SCENE_MAX_AGE_MS = 5 * 60 * 1000;

Прошедшего того, или если ничего не сохранено, или если сохранённые данные не парсируют, это просто розыгрывает свежую сцену как это всегда делало прежде никакого этого существует. Я убедился каждое читание здесь обёрнуто в попытку/ловушку тоже, Safari в приватном просмотре выбрасывает просто касание localStorage вообще, и посетитель с хранилищем отключённым не должны получить сломанную страницу, они должны просто получить обычную свежую розыгрышную сцену без памяти прилепленной. Потеря настойчивости молча хорошо. Выбрасывание ошибки где птица предполагается быть не.

Складка: что действительно считается «перезагрузка»

Это часть та что заняла меня дольше всего чтоб получить право, после я уже думал я был сделан. Посетитель кто намеренно перезагружает страницу, нажимает фактическую кнопку обновления потому что они хотят смотреть страницу свежую, ожидает перезагрузка перетасует вещи, та же как первое посещение было бы. Но моя логика восстановления не могла рассказать «ты нажал обновить» кроме «ты щёлкнул ссылку» из внутри компонента, потому что к времени оно работает, всё что оно знает это «монтирование случилось.» Оба кейса просто выглядят как монтирование.

Браузер действительно говорит тебе различие, через API я не знал существует пока я не пошёл ищущий: performance.getEntriesByType('navigation') возвращает вход чья type буквально 'reload' для намеренной перезагрузки, насупротив 'navigate' для в основном всё остальное:

export function safeNavigationType(): NavigationType | undefined {
	try {
		if (typeof performance === 'undefined' || typeof performance.getEntriesByType !== 'function') {
			return undefined;
		}
		const [entry] = performance.getEntriesByType('navigation') as PerformanceNavigationTiming[];
		return entry?.type;
	} catch {
		return undefined;
	}
}

Вся та математика, розыгрыш, спасение, восстановление, продвижение фаз, живёт в простом TypeScript модуле полностью отдельном от Svelte компонента та что рендирирует действительных птиц, специфично так я мог писать действительные модульные тесты против это без нужды браузера или Svelte рантайма вращаемого. Дан фальш в памяти хранилища объект и фиксированная метка времени вместо действительного Date.now(), я могу утверждать ровно что пятиминутное старое сохранение восстанавливает к, что шеститиминутное старое падает назад к, и что повреждённое или частично написанное сохранение делает, всё без когда-либо касания действительной страницы. То разделение это что сделало меня достаточно уверенным чтоб отправить исправление продвижения времени в первое место, я мог видеть фазы числа приземляющиеся ровно где математика сказала они должны, нежели прищуриваясь кто-то смотрит ли птица «выглядит примерно правильно» после ожидания вокруг на действительной вкладке браузера несколько минут.

И потом действительное решение, та что читает почти скучно простую как только два куска выше существуют поддержать это:

const saved = navigationType === 'reload' ? null : loadScene<unknown>(storage, SKY_COLONY_STORAGE_KEY, now);

Действительная перезагрузка скачивает сохранённую сцену совсем и розыгрывает свежий, намеренно, каждый раз. Что-либо остальное, кнопка вернуться, свежая вкладка, просто навигирование вокруг, восстанавливает и продвигает как нормально. Это маленькое различие и оно заняло меня дольше追踪 вниз чем логика настойчивости сама делала, но это различие между сценой ведущей себя пути действительный посетитель ожидает это и технически-правильной но раздражающей.

0 комментариев

Войдите , чтобы оставить комментарий.

Войти

Забыли пароль?

Нет аккаунта?