feat: add live replays and PostgreSQL maintenance tools
Build and Push Docker Container / build-and-push (push) Successful in 7m53s
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:
+41
-46
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user