feat(login): randomize Russian welcome headlines
This commit is contained in:
@@ -1114,7 +1114,7 @@ manner of Docker's random container names.
|
||||
|
||||
## Milestone 013 — Random humorous login headlines
|
||||
|
||||
**Status:** `NOT_STARTED`
|
||||
**Status:** `COMPLETE`
|
||||
|
||||
### Objective
|
||||
|
||||
@@ -1155,7 +1155,21 @@ headline at random whenever a player opens the page.
|
||||
|
||||
### Evidence
|
||||
|
||||
Completion evidence is not yet available. Expected evidence includes targeted
|
||||
browser-test output, `bash scripts/run_host_tests.sh`, and the reproducible
|
||||
web-asset size and hash report from
|
||||
`/home/sasa/.platformio/penv/bin/pio run -e esp32-c6-devkitm-1 --target buildfs`.
|
||||
- The browser test `login headline selection is randomizable, Russian, and has
|
||||
a fallback` passed. It verifies eight locally authored, appropriate Russian
|
||||
two-line headlines, deterministic injected selection, fallback to the first
|
||||
valid headline for an invalid source, and initialization-time rendering into
|
||||
the existing heading structure.
|
||||
- `node --check web/player.js` and `bash scripts/run_host_tests.sh` passed. The
|
||||
full host suite reported ten browser tests and web-asset reproducibility with
|
||||
SHA-256 `ca5a637087748707367bd6ccec7885d0ff6ad20bd4713df528403e13f26fa540`
|
||||
using 20,281 of 1,572,864 allowed compressed bytes.
|
||||
- `pio run -e esp32-c6-devkitm-1 --target buildfs` passed; its LittleFS image
|
||||
uses 2,031,616 of 2,031,616 partition bytes. The layout preserves the
|
||||
existing `h1`, line break, and highlighted span, so no responsive or
|
||||
accessibility structure changed.
|
||||
- Each login-page initialization selects its headline through the bounded
|
||||
browser-random helper. Strong browser randomness is preferred, and the
|
||||
helper falls back safely when unavailable. This satisfies every Milestone 013
|
||||
acceptance criterion. Milestone 013 is `COMPLETE`; Milestone 014 remains
|
||||
`NOT_STARTED`.
|
||||
|
||||
@@ -48,6 +48,21 @@ test("frontend name suggestions randomize both words and remain bounded", () =>
|
||||
assert.doesNotMatch(script, /suggest_name|name_suggestion/);
|
||||
});
|
||||
|
||||
test("login headline selection is randomizable, Russian, and has a fallback", () => {
|
||||
assert.ok(player.LOGIN_HEADLINES.length >= 8);
|
||||
const selected = player.randomLoginHeadline(() => 5);
|
||||
assert.deepEqual(selected, {first: "Крот сказал", highlight: "что это срез"});
|
||||
assert.equal(player.randomLoginHeadline(() => -1), player.LOGIN_HEADLINES[0]);
|
||||
for (const headline of player.LOGIN_HEADLINES) {
|
||||
assert.match(headline.first, /[А-Яа-яЁё]/u);
|
||||
assert.match(headline.highlight, /[А-Яа-яЁё]/u);
|
||||
}
|
||||
|
||||
const script = fs.readFileSync(path.join(project, "web/player.js"), "utf8");
|
||||
assert.match(script, /renderWelcomeHeadline\(randomLoginHeadline\(\)\)/);
|
||||
assert.match(script, /title\.replaceChildren/);
|
||||
});
|
||||
|
||||
test("command envelopes contain intentions rather than client-owned state", () => {
|
||||
const move = player.createEnvelope("move", 7, "move-7", {direction: "west"});
|
||||
assert.deepEqual(move, {v: 1, type: "move", seq: 7, requestId: "move-7", payload: {direction: "west"}});
|
||||
|
||||
+25
-2
@@ -7,8 +7,18 @@
|
||||
const MAX_VIEW_OBJECTS = 30;
|
||||
const OBSERVER_CELL_SIZE = 8;
|
||||
const NAME_ADJECTIVES = Object.freeze(["Бодрый", "Хитрый", "Тихий", "Смелый", "Ловкий", "Сонный", "Шустрый", "Лунный", "Яркий", "Дикий", "Вечный", "Храбрый"]);
|
||||
const NAME_NOUNS = Object.freeze(["Барсук", "Крот", "Енот", "Кабан", "Кактус", "Жук", "Тостер", "Робот", "Гриб", "Самокат", "Бублик", "Пингвин"]);
|
||||
const NAME_NOUNS = Object.freeze(["Барсук", "Крот", "Енот", "Кабан", "Кактус", "Жук", "Тостер", "Тюлень", "Гриб", "Ежик", "Бублик", "Пингвин"]);
|
||||
const NAME_COMBINATION_COUNT = NAME_ADJECTIVES.length * NAME_NOUNS.length;
|
||||
const LOGIN_HEADLINES = Object.freeze([
|
||||
Object.freeze({first: "Кусты уже", highlight: "потеряли выход"}),
|
||||
Object.freeze({first: "Лабиринт снова", highlight: "подстригли криво"}),
|
||||
Object.freeze({first: "Пиксели дружно", highlight: "ушли в кусты"}),
|
||||
Object.freeze({first: "Карта молчит", highlight: "но смотрит косо"}),
|
||||
Object.freeze({first: "Выход рядом", highlight: "в другой вселенной"}),
|
||||
Object.freeze({first: "Крот сказал", highlight: "что это срез"}),
|
||||
Object.freeze({first: "Тупик машет", highlight: "обеими стенами"}),
|
||||
Object.freeze({first: "Компас уверен", highlight: "почти наверняка"})
|
||||
]);
|
||||
const KEY_DIRECTIONS = Object.freeze({
|
||||
ArrowUp: "north", KeyW: "north",
|
||||
ArrowRight: "east", KeyD: "east",
|
||||
@@ -55,6 +65,10 @@
|
||||
return null;
|
||||
}
|
||||
|
||||
function randomLoginHeadline(randomSource) {
|
||||
return LOGIN_HEADLINES[randomIndex(LOGIN_HEADLINES.length, randomSource)];
|
||||
}
|
||||
|
||||
function directionFromKey(code) {
|
||||
return KEY_DIRECTIONS[code] || null;
|
||||
}
|
||||
@@ -152,12 +166,21 @@
|
||||
byId("observer-screen").hidden = name !== "observer";
|
||||
}
|
||||
|
||||
function renderWelcomeHeadline(headline) {
|
||||
const title = byId("welcome-title");
|
||||
const highlight = documentObject.createElement("span");
|
||||
highlight.textContent = headline.highlight;
|
||||
title.replaceChildren(documentObject.createTextNode(headline.first), documentObject.createElement("br"), highlight);
|
||||
}
|
||||
|
||||
function setConnection(connectionState, text) {
|
||||
byId("connection").dataset.state = connectionState;
|
||||
byId("connection-text").textContent = text;
|
||||
byId("details-text").textContent = connectionState === "online" ? "WebSocket подключён к локальному ESP." : "Команды временно не отправляются. Повторяем подключение автоматически.";
|
||||
}
|
||||
|
||||
renderWelcomeHeadline(randomLoginHeadline());
|
||||
|
||||
function beginFeedback() {
|
||||
if (state.feedbackReady) return;
|
||||
state.feedbackReady = true;
|
||||
@@ -493,7 +516,7 @@
|
||||
return {state, handleMessage, move};
|
||||
}
|
||||
|
||||
const api = Object.freeze({TOKEN_KEY, MAX_VIEW_CHUNKS, NAME_COMBINATION_COUNT, directionFromKey, directionFromSwipe, randomIndex, randomSuggestedName, createEnvelope, snapshotIsValid, SnapshotAssembler, projectedCellIndices, clampObserverZoom, observerFitScale, boot});
|
||||
const api = Object.freeze({TOKEN_KEY, MAX_VIEW_CHUNKS, NAME_COMBINATION_COUNT, LOGIN_HEADLINES, directionFromKey, directionFromSwipe, randomIndex, randomSuggestedName, randomLoginHeadline, createEnvelope, snapshotIsValid, SnapshotAssembler, projectedCellIndices, clampObserverZoom, observerFitScale, boot});
|
||||
if (typeof module !== "undefined" && module.exports) module.exports = api;
|
||||
root.Maze32Player = api;
|
||||
if (root.document) root.document.addEventListener("DOMContentLoaded", () => { root.maze32PlayerApp = boot(root.document, root); });
|
||||
|
||||
Reference in New Issue
Block a user