feat: add gameplay stats and restore Zenith rollout depth
Build and Push Docker Container / build-and-push (push) Successful in 3m6s
Build and Push Docker Container / build-and-push (push) Successful in 3m6s
- Add read-only gameplay statistics with filters and opponent comparisons. - Report move-trace latency percentiles and external timeout counts. - Pair engine latency with the preceding turn to separate timing stages. - Adjust Zenith rollout deepening for its larger timeout reserve. - Bump Zenith to 1.2.0 and document the fix and snake-stats command. - Cover statistics, trace aggregation, rollout depth, and verdict budgets.
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
import unittest
|
||||
|
||||
from scripts.move_trace_stats import add_game, percentile, summary_rows
|
||||
from server.database.compact.records import SnakeState, TurnRecord
|
||||
|
||||
GAME = {"your_snake_type": "ZenithBattleSnake", "your_snake_version": "1.2.0"}
|
||||
|
||||
def _turn(turn: int, thinking: object) -> TurnRecord:
|
||||
return TurnRecord(turn=turn, observed_at=None, move="up", thinking=thinking, food=None, hazards=None)
|
||||
|
||||
def _state(snake_id: str, turn: int, latency: int | None) -> SnakeState:
|
||||
return SnakeState(snake_id=snake_id, turn=turn, health=100, latency=latency, body=[])
|
||||
|
||||
def _trace(handler: float, strategy: float) -> dict:
|
||||
return {"handler_ms": handler, "strategy_ms": strategy, "budget_ms": 450.0}
|
||||
|
||||
class TestMoveTraceStats(unittest.TestCase):
|
||||
|
||||
def test_pairs_engine_latency_of_next_turn(self):
|
||||
groups = {}
|
||||
turns = [
|
||||
_turn(1, {"mode": "duel", "move_trace": _trace(200.0, 195.0)}),
|
||||
_turn(2, {"mode": "duel", "move_trace": _trace(100.0, 90.0)}),
|
||||
]
|
||||
states = [_state("you", 2, 260), _state("enemy", 2, 499), _state("you", 3, 500)]
|
||||
|
||||
add_game(groups, GAME, "you", turns, states)
|
||||
|
||||
group = groups[("ZenithBattleSnake", "1.2.0", "duel")]
|
||||
self.assertEqual(group.moves, 2)
|
||||
self.assertEqual(group.values["overhead_ms"], [5.0, 10.0])
|
||||
self.assertEqual(group.values["engine_ms"], [260.0, 500.0])
|
||||
self.assertEqual(group.values["network_ms"], [60.0, 400.0])
|
||||
self.assertEqual((group.timeouts, group.timeouts_fast_handler), (1, 1))
|
||||
|
||||
def test_unwraps_raw_thinking_and_labels_missing_mode(self):
|
||||
groups = {}
|
||||
turns = [_turn(1, {"_derived": 0, "_raw": {"move_trace": _trace(5.0, 1.0)}}), _turn(2, "text")]
|
||||
|
||||
add_game(groups, GAME, "you", turns, [_state("you", 2, -32768)])
|
||||
|
||||
group = groups[("ZenithBattleSnake", "1.2.0", "fallback")]
|
||||
self.assertEqual(group.moves, 1)
|
||||
self.assertEqual(group.values["engine_ms"], [])
|
||||
|
||||
def test_summary_formats_percentiles(self):
|
||||
groups = {}
|
||||
add_game(groups, GAME, "you", [_turn(1, {"mode": "multi", "move_trace": _trace(20.0, 18.0)})], [])
|
||||
|
||||
headers, rows = summary_rows(groups)
|
||||
|
||||
self.assertEqual(headers[:4], ["snake", "version", "mode", "moves"])
|
||||
self.assertEqual(rows[0][:6], ["ZenithBattleSnake", "1.2.0", "multi", 1, "0 (0.0%)", 0])
|
||||
self.assertEqual(rows[0][6], "18/18/18")
|
||||
self.assertEqual(rows[0][-1], "-/-/-")
|
||||
|
||||
def test_percentile_of_empty_list(self):
|
||||
self.assertIsNone(percentile([], 0.5))
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user