feat(snake): optimize duels and persist customizations
Build and Push Docker Container / build-and-push (push) Successful in 7m29s

- Add deadline-aware iterative duel search with bitboards and tuple bodies.

- Reuse transposition bounds and move-order hints across search depths.

- Persist snake colors, heads, and tails in SQLite and PostgreSQL.

- Restore customization metadata when hydrating dashboard replays.

- Cover duel deadlines, cache reuse, schema storage, and replay output.
This commit is contained in:
2026-08-01 17:19:35 +02:00
parent 65c97b219b
commit 4f022d3d01
7 changed files with 449 additions and 11 deletions
@@ -112,6 +112,7 @@ class SqliteGameplayBackend(GameplayBackendTemplate):
snake_id TEXT NOT NULL,
snake_name TEXT,
is_you INTEGER NOT NULL DEFAULT 0,
customizations_json TEXT NOT NULL DEFAULT '{}',
PRIMARY KEY (game_id, snake_id),
FOREIGN KEY (game_id) REFERENCES games(game_id) ON DELETE CASCADE
);
@@ -140,6 +141,7 @@ class SqliteGameplayBackend(GameplayBackendTemplate):
self._ensure_column_exists(connection, "games", "your_snake_version", "TEXT")
self._ensure_column_exists(connection, "games", "game_type", "TEXT")
self._ensure_column_exists(connection, "snake_turns", "latency", "TEXT")
self._ensure_column_exists(connection, "game_snakes", "customizations_json", "TEXT NOT NULL DEFAULT '{}'")
self._ensure_column_exists(connection, "games", "winner_name", "TEXT")
self._ensure_column_exists(connection, "games", "has_replay", "INTEGER NOT NULL DEFAULT 1")
self._ensure_column_exists(connection, "games", "quality_status", "TEXT NOT NULL DEFAULT 'retained'")
@@ -266,14 +268,22 @@ class SqliteGameplayBackend(GameplayBackendTemplate):
with self._connect() as connection:
connection.executemany("""
INSERT INTO game_snakes (game_id, snake_id, snake_name, is_you)
VALUES (?, ?, ?, ?)
INSERT INTO game_snakes (
game_id, snake_id, snake_name, is_you, customizations_json
) VALUES (?, ?, ?, ?, ?)
ON CONFLICT(game_id, snake_id) DO UPDATE SET
snake_name = excluded.snake_name,
is_you = excluded.is_you
is_you = excluded.is_you,
customizations_json = excluded.customizations_json
""",
[
(game_id, snake.get("id"), snake.get("name"), 1 if snake.get("id") == you.get("id") else 0)
(
game_id,
snake.get("id"),
snake.get("name"),
1 if snake.get("id") == you.get("id") else 0,
self._to_json(snake.get("customizations") or {}),
)
for snake in snakes if snake.get("id") is not None
],
)
@@ -545,6 +555,7 @@ class SqliteGameplayBackend(GameplayBackendTemplate):
COALESCE(gs.snake_name, st.snake_name) AS snake_name,
st.health, st.length, st.head_x, st.head_y, st.body_json,
COALESCE(gs.is_you, st.is_you) AS is_you,
COALESCE(gs.customizations_json, '{}') AS customizations_json,
st.inferred_move, st.latency
FROM snake_turns AS st
LEFT JOIN game_snakes AS gs