feat(player-ui): add pre-reader controls and onboarding

This commit is contained in:
2026-09-09 20:56:38 +03:00
parent f043b63732
commit 3827ba7ec7
9 changed files with 424 additions and 52 deletions
+56
View File
@@ -22,6 +22,56 @@ test("keyboard and swipe controls produce equivalent directions", () => {
assert.equal(player.directionFromSwipe(0, 0, 12, 4), null);
});
test("joystick produces only cardinal, bounded movement intentions", () => {
assert.equal(player.joystickDirection(10, 10, null), null);
assert.equal(player.joystickDirection(44, 8, null), "east");
assert.equal(player.joystickDirection(-8, -44, null), "north");
assert.equal(player.joystickDirection(42, 38, "east"), "east");
assert.equal(player.joystickDirection(38, 42, "north"), "south");
const vector = player.joystickVector(300, 0, 57, null);
assert.equal(vector.direction, "east");
assert.equal(vector.x, 57);
assert.equal(vector.y, 0);
});
test("joystick repeat interval is monotonic and remains slower than cooldown", () => {
assert.equal(player.joystickRepeatMs(player.JOYSTICK_DEAD_ZONE, 60), null);
const near = player.joystickRepeatMs(25, 60);
const far = player.joystickRepeatMs(60, 60);
assert.ok(near > far);
assert.ok(far >= player.JOYSTICK_FAST_REPEAT_MS);
assert.ok(player.JOYSTICK_FAST_REPEAT_MS > 150);
});
test("player map cells enlarge small projections without exceeding a readable bound", () => {
const sevenBySeven = player.playerCellSize(396, 730, 7, 7);
const fourByFour = player.playerCellSize(396, 730, 4, 4);
assert.ok(sevenBySeven >= 46);
assert.ok(fourByFour > sevenBySeven);
assert.ok(fourByFour <= 72);
});
test("onboarding has bounded, replayable steps", () => {
assert.equal(player.ONBOARDING_STEPS.length, 8);
assert.equal(player.nextOnboardingStep(0), 1);
assert.equal(player.nextOnboardingStep(6), 7);
assert.equal(player.nextOnboardingStep(7), null);
});
test("child controls reset safely and retain accessible alternatives", () => {
const html = fs.readFileSync(path.join(project, "web/index.html"), "utf8");
const script = fs.readFileSync(path.join(project, "web/player.js"), "utf8");
assert.match(script, /addEventListener\("pointerup", stopJoystick\)/);
assert.match(script, /addEventListener\("pointercancel", stopJoystick\)/);
assert.match(script, /addEventListener\("blur", resetJoystick\)/);
assert.match(script, /connectionState !== "online"\) resetJoystick\(\)/);
assert.match(script, /snapshot\.phase === "paused"\) \{ resetJoystick\(\)/);
assert.match(script, /setMuted\(!state\.muted\)/);
assert.match(script, /setReducedMotion\(!state\.reducedMotion\)/);
assert.match(script, /onboarding-replay/);
assert.match(html, /id="onboarding-dots"[\s\S]*?<span><\/span><span><\/span><span><\/span><span><\/span><span><\/span><span><\/span><span><\/span><span><\/span>/);
});
test("frontend name suggestions randomize both words and remain bounded", () => {
const picks = [3, 4];
const suggestion = player.randomSuggestedName([], () => picks.shift());
@@ -112,6 +162,12 @@ test("player assets are local, Russian, accessible, and responsive", () => {
assert.match(css, /@media \(min-width: 721px\) and \(max-width: 980px\)/);
assert.match(css, /height: calc\(100dvh - 78px\)/);
assert.match(css, /\.dpad \{ display: none; \}/);
assert.match(html, /id="joystick"/);
assert.match(html, /id="onboarding"/);
assert.match(html, /id="mute-toggle"/);
assert.match(css, /\.joystick \{[^}]*width: 180px; height: 180px/);
assert.match(script, /resetJoystick/);
assert.match(script, /playerCellSize/);
assert.match(css, /\(hover: none\) and \(pointer: coarse\) and \(max-height: 720px\)/);
assert.match(icons, /id="key"/);
assert.match(icons, /id="maze"/);