"""Rebuild the dashboard replay payload from compact records.""" from .codec import decode_latency from .records import SnakeIdentity, SnakeState, TurnRecord, you_view from .thinking import derived_values, restore_thinking _DIRECTIONS = {(1, 0): "right", (-1, 0): "left", (0, 1): "up", (0, -1): "down"} def _head(state:SnakeState) -> dict|None: return state.body[0] if state.body else None def _inferred_move(previous:SnakeState|None, current:SnakeState) -> str|None: if previous is None: return None old, new = _head(previous), _head(current) if old is None or new is None: return None return _DIRECTIONS.get((new["x"] - old["x"], new["y"] - old["y"])) def _sort_key(identity:SnakeIdentity|None): name = identity.name if identity else None is_you = bool(identity and identity.is_you) return (not is_you, name is None, name or "") def hydrate_replay_turns( game:dict, identities:dict[str, SnakeIdentity], turns:list[TurnRecord], states:list[SnakeState], ) -> list[dict]: states_by_turn:dict[int, dict[str, SnakeState]] = {} for state in states: states_by_turn.setdefault(state.turn, {})[state.snake_id] = state width, height = game.get("width"), game.get("height") you_id = game.get("your_snake_id") food:list = [] hazards:list = [] output = [] for record in sorted(turns, key=lambda item: item.turn): food = food if record.food is None else record.food hazards = hazards if record.hazards is None else record.hazards current = states_by_turn.get(record.turn, {}) previous = states_by_turn.get(record.turn - 1, {}) ordered = sorted(current.values(), key=lambda s: _sort_key(identities.get(s.snake_id))) snakes = [] api_snakes = [] for state in ordered: identity = identities.get(state.snake_id) name = identity.name if identity else None customizations = identity.customizations if identity else {} latency = decode_latency(state.latency) api_snake = { "id": state.snake_id, "name": name, "health": state.health, "length": len(state.body), "head": _head(state) or {"x": None, "y": None}, "body": state.body, "customizations": customizations, } if latency is not None: api_snake["latency"] = latency api_snakes.append(api_snake) snakes.append({ "snake_id": state.snake_id, "snake_name": name, "health": state.health, "length": len(state.body), "head": api_snake["head"], "body": state.body, "customizations": customizations, "is_you": bool(identity and identity.is_you), "inferred_move": _inferred_move(previous.get(state.snake_id), state), "latency": latency, }) derived = derived_values( record.turn, record.move, you_view(list(current.values()), you_id), food, width, height, len(current), ) output.append({ "turn": record.turn, "observed_at": record.observed_at, "my_move": record.move, "my_thinking": restore_thinking(record.thinking, derived), "board": { "width": width, "height": height, "food": food, "hazards": hazards, "snakes": api_snakes, }, "food": food, "hazards": hazards, "you": next((snake for snake in api_snakes if snake["id"] == you_id), {}), "snakes": snakes, }) return output