feat: add live replays and PostgreSQL maintenance tools
Build and Push Docker Container / build-and-push (push) Successful in 7m53s

- Stream compact live replay updates across local and clustered dashboards.
- Render responsive snake bodies as SVG paths with aligned custom icons.
- Add cache-busted assets, replay fallback routes, and live-follow playback.
- Support PostgreSQL benchmark sampling and idempotent SQLite migration.
- Add dry-run cleanup for old low-quality PostgreSQL replay payloads.
- Reward safe perimeter lanes and bump Prism to version 1.5.0.
- Add backend, migration, dashboard, and perimeter regression coverage.
This commit is contained in:
2026-08-02 00:50:46 +02:00
parent 9b99b526e4
commit f14d780f29
29 changed files with 1574 additions and 310 deletions
+41 -46
View File
@@ -132,66 +132,61 @@ class SnakeUtils {
return `rgba(${parsed.r}, ${parsed.g}, ${parsed.b}, ${alpha})`;
}
static inferHeadDirection(snake) {
const body = Array.isArray(snake && snake.body) ? snake.body : [];
if (body.length >= 2) {
const head = body[0];
const neck = body[1];
if (head && neck) {
const dx = Number(head.x) - Number(neck.x);
const dy = Number(head.y) - Number(neck.y);
if (dx > 0) return "right";
if (dx < 0) return "left";
if (dy > 0) return "up";
if (dy < 0) return "down";
}
}
const inferred = String(snake && snake.inferred_move ? snake.inferred_move : "").toLowerCase();
if (["up", "down", "left", "right"].includes(inferred)) return inferred;
if (body.length < 2) return "right";
const head = body[0];
const neck = body[1];
if (!head || !neck) return "right";
const dx = Number(head.x) - Number(neck.x);
const dy = Number(head.y) - Number(neck.y);
// Board coordinates are y-up, so a positive dy means "up".
static _deltaToDirection(dx, dy) {
if (dx > 0) return "right";
if (dx < 0) return "left";
if (dy > 0) return "up";
if (dy < 0) return "down";
return null;
}
static _fallbackDirection(snake) {
const inferred = String(snake && snake.inferred_move ? snake.inferred_move : "").toLowerCase();
if (["up", "down", "left", "right"].includes(inferred)) return inferred;
return "right";
}
// Segments stack on spawn and right after eating, so both ends scan past
// duplicates to find the first cell that actually differs.
static inferHeadDirection(snake) {
const body = Array.isArray(snake && snake.body) ? snake.body : [];
const head = body[0];
if (!head) return SnakeUtils._fallbackDirection(snake);
for (let idx = 1; idx < body.length; idx += 1) {
const neck = body[idx];
if (!neck) continue;
if (Number(neck.x) === Number(head.x) && Number(neck.y) === Number(head.y)) continue;
const direction = SnakeUtils._deltaToDirection(
Number(head.x) - Number(neck.x),
Number(head.y) - Number(neck.y),
);
if (direction) return direction;
break;
}
return SnakeUtils._fallbackDirection(snake);
}
static inferTailDirection(snake) {
const body = Array.isArray(snake && snake.body) ? snake.body : [];
if (body.length < 2) return "right";
const tail = body[body.length - 1];
if (!tail) return "right";
if (!tail) return SnakeUtils._fallbackDirection(snake);
let beforeTail = null;
for (let idx = body.length - 2; idx >= 0; idx -= 1) {
const candidate = body[idx];
if (!candidate) continue;
if (Number(candidate.x) !== Number(tail.x) || Number(candidate.y) !== Number(tail.y)) {
beforeTail = candidate;
break;
}
const beforeTail = body[idx];
if (!beforeTail) continue;
if (Number(beforeTail.x) === Number(tail.x) && Number(beforeTail.y) === Number(tail.y)) continue;
const direction = SnakeUtils._deltaToDirection(
Number(beforeTail.x) - Number(tail.x),
Number(beforeTail.y) - Number(tail.y),
);
if (direction) return direction;
break;
}
if (!beforeTail) {
const inferred = String(snake && snake.inferred_move ? snake.inferred_move : "").toLowerCase();
if (["up", "down", "left", "right"].includes(inferred)) return inferred;
return "right";
}
const dx = Number(beforeTail.x) - Number(tail.x);
const dy = Number(beforeTail.y) - Number(tail.y);
if (dx > 0) return "right";
if (dx < 0) return "left";
if (dy > 0) return "up";
if (dy < 0) return "down";
return "right";
return SnakeUtils._fallbackDirection(snake);
}
static directionToHeadTransform(direction) {