fix: preserve gameplay data and correct duel evaluation

- Preserve snake customizations across database migrations and merges.
- Lazily load optional storage backends for SQLite maintenance scripts.
- Match Apex territory and nearest-food tie-breaking semantics.
- Resolve duel occupancy after simultaneous movement and food growth.
- Recompute simulated head-to-head danger after body growth.
- Add regression coverage and declare the aiofiles dependency.
This commit is contained in:
2026-08-01 18:16:04 +02:00
parent 4f022d3d01
commit c646392b84
14 changed files with 323 additions and 107 deletions
+84
View File
@@ -0,0 +1,84 @@
import sqlite3
import tempfile
import unittest
from pathlib import Path
from scripts.migrate_gameplay_database import copy_game_snakes
from server.database.backend.SqliteGameplayBackend import SqliteGameplayBackend
class TestMigrateGameplayDatabase(unittest.TestCase):
def test_copy_game_snakes_preserves_customizations(self):
with tempfile.TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
source_path = root / "source.sqlite3"
destination_path = root / "destination.sqlite3"
SqliteGameplayBackend(str(source_path))
SqliteGameplayBackend(str(destination_path))
with sqlite3.connect(source_path) as source:
source.execute("PRAGMA foreign_keys = OFF")
source.execute("""
INSERT INTO game_snakes (
game_id, snake_id, snake_name, is_you, customizations_json
) VALUES (?, ?, ?, ?, ?)
""", (
"game-1", "snake-1", "PrismBattleSnake", 1,
'{"color":"#663399","head":"ferret","tail":"swirl"}',
))
source = sqlite3.connect(source_path)
destination = sqlite3.connect(destination_path)
try:
copied = copy_game_snakes(
source, destination, batch_size=10, retained_ids={"game-1"},
)
destination.commit()
row = destination.execute("""
SELECT snake_name, is_you, customizations_json
FROM game_snakes WHERE game_id = ? AND snake_id = ?
""", ("game-1", "snake-1")).fetchone()
finally:
source.close()
destination.close()
self.assertEqual(copied, 1)
self.assertEqual(row, (
"PrismBattleSnake", 1,
'{"color":"#663399","head":"ferret","tail":"swirl"}',
))
def test_copy_game_snakes_defaults_legacy_schema_to_empty_customizations(self):
source = sqlite3.connect(":memory:")
destination = sqlite3.connect(":memory:")
try:
source.execute("""
CREATE TABLE game_snakes (
game_id TEXT, snake_id TEXT, snake_name TEXT, is_you INTEGER
)
""")
source.execute(
"INSERT INTO game_snakes VALUES (?, ?, ?, ?)",
("game-1", "snake-1", "LegacySnake", 0),
)
destination.execute("""
CREATE TABLE game_snakes (
game_id TEXT, snake_id TEXT, snake_name TEXT, is_you INTEGER,
customizations_json TEXT NOT NULL DEFAULT '{}'
)
""")
copied = copy_game_snakes(
source, destination, batch_size=10, retained_ids={"game-1"},
)
row = destination.execute(
"SELECT customizations_json FROM game_snakes"
).fetchone()
finally:
source.close()
destination.close()
self.assertEqual(copied, 1)
self.assertEqual(row, ("{}",))
if __name__ == "__main__":
unittest.main()