Skip to content

Latest commit

 

History

History
171 lines (141 loc) · 8.64 KB

data_model.md

File metadata and controls

171 lines (141 loc) · 8.64 KB

Orbit Wars State Model

orbit_model.py owns the shared state dataclasses (representation only). orbit_physics.py builds and transforms that model with engine-matched helpers for fleet speed, sun collision, position prediction, lead-shot planning, in-flight fleet arrivals, and garrison projection.

Sibling docs: precomputed-table contents in precompute.md; allocation/repair boundary in repair.md; raw observation schema in agent.md; engine quirks in engine.md.

Quick Start

Model-only imports (orbit_model.py:117):

from orbit_model import GameState, PlanetState

Physics helpers and parsing (orbit_physics.py:1128):

from orbit_physics import build_game_state, lead_shot
state = build_game_state(obs)
shot = lead_shot(state, source_id, target_id, ships)

Dataclasses (orbit_model.py)

All dataclasses are frozen=True.

  • Arrival(turn, owner, ships) — fleet arrival event on a planet timeline (orbit_model.py:17).
  • GarrisonPoint(turn, owner, ships) — projected owner/garrison after events at turn resolve (orbit_model.py:26).
  • PlanetState (orbit_model.py:35): id, owner, x, y, radius, ships, production, orbiting, orbital_radius, initial_angle, is_comet, comet_path, comet_path_index, remaining_life, arrivals. pos returns (x, y). owner = -1 is neutral; orbital_radius is 0.0 and initial_angle is 0.0 for static planets; remaining_life is math.inf for non-comets.
  • FleetState(id, owner, x, y, angle, from_planet_id, ships) with pos property (orbit_model.py:58).
  • ShotPlan(angle, turns, target_x, target_y, distance, sun_blocked, path_blocked=False, comet_spawn_risk=False) (orbit_model.py:73).
  • GameState (orbit_model.py:85): player, turn, turns_remaining, angular_velocity, planets: dict[int, PlanetState], fleets, upcoming_comet_spawns, sun_x=50.0, sun_y=50.0, sun_radius=10.0, max_speed=6.0, precomputed: Precomputed | None, comets_active: bool, arrivals_materialized: bool, _position_cache. precomputed and _position_cache are compare=False, repr=False. position_at / garrison_at methods at orbit_model.py:106/111 delegate to orbit_physics.

Module Constants (orbit_physics.py:32)

BOARD_SIZE=100.0, CENTER=50.0, SUN_RADIUS=10.0, ROTATION_RADIUS_LIMIT=50.0, LAUNCH_CLEARANCE=0.1, MAX_SHIP_SPEED=6.0, TOTAL_STEPS=500, COMET_SPAWN_STEPS=(50,150,250,350,450), LEAD_SHOT_ANGULAR_OFFSETS (9 values, orbit_physics.py:40), SHOT_CACHE_TURN_RETENTION=8, COMET_RADIUS_BAND (34-row band table, orbit_physics.py:42).

Construction

build_game_state(obs, *, total_steps=TOTAL_STEPS, max_speed=MAX_SHIP_SPEED, arrival_horizon=120, arrival_cache=None) (orbit_physics.py:1053) parses the Kaggle observation into model dataclasses, builds/reuses the module-level _PRECOMPUTED_CACHE keyed by game_fingerprint, prunes the stale shot cache, then materializes observed in-flight fleet arrivals onto destination PlanetState.arrivals. When arrival_cache is provided, non-negative fleet ids read/write that table so live-agent turns can reuse predictions. Returned states have arrivals_materialized=True; hand-built states default to False. comets_active is set when any planet row is a comet. reset_precomputed_cache() at orbit_physics.py:81 clears the precompute cache (tests/tools).

Row parsing helpers accept either sequence rows or attribute-style objects via _row_value (orbit_physics.py:94):

  • parse_planet(row, initial_by_id=None, comet_paths=None, arrivals=()) (orbit_physics.py:993). Computes orbital_radius and initial_angle from initial_planets (when present), classifies orbiting via is_orbiting_position, and attaches comet path data when the planet id is in comet_paths.
  • parse_fleet(row) (orbit_physics.py:1041).

Physics Helpers (orbit_physics.py)

Function Location Notes
distance(a, b) :100 math.hypot
point_to_segment_distance(point, start, end) :104 engine-matched
segment_hits_circle(start, end, center=(50,50), radius=10) :126 strict < radius
sun_collision(start, end, sun_radius=10) :141 thin wrapper
sun_line_clear(source, target, sun_radius=10) :149 static source-to-target only
fleet_speed(ships, max_speed=6.0) :157 raises on ships < 1
launch_position(planet, angle) :213 radius + LAUNCH_CLEARANCE
is_orbiting_position(x, y, radius) :218 dist + radius < 50
position_at(planet, future_turn, state=None) :222 comet → path index; static → fixed; orbiting → see below
travel_distance_to_target(source, target_pos, target_radius=0) :291 returns (angle, travel_distance, end, straight_distance)
fleet_eta(source, target, ships, state=None) :319 uses precomputed.travel_dist_same when available
comet_spawn_risk_for_flight(state, launch_pos, angle, speed, eta) :378 sweeps state.upcoming_comet_spawns against COMET_RADIUS_BAND
lead_shot(state, source_id, target_id, ships, max_iterations=5, tolerance=0.25, angular_offsets=LEAD_SHOT_ANGULAR_OFFSETS) :608 see below
resolve_arrivals(owner, ships, arrivals) :796 engine combat rule for simultaneous arrivals
project_garrison(planet, through_turn, current_turn=0) :830 production-before-combat, returns tuple[GarrisonPoint, ...]
garrison_at(planet, future_turn, current_turn=0) :875 last entry of project_garrison
predict_fleet_arrival(state, fleet, horizon=120) :910 exact engine swept-pair CCD, returns (planet_id, Arrival) or None
target_arrival_phase_hits(state, fleet, target_id, arrival_turn) :953 target-specific swept-pair check for lead-shot repair

Position prediction (orbit_physics.py:222)

  • Cache: state._position_cache[(planet_id, future_turn)] when the planet is the canonical row in state.planets.
  • Comets: comet_path[comet_path_index + (future_turn - state.turn)], or None outside the path.
  • Precomputed: if state.precomputed covers (planet_id, future_turn), the table row is used unless nan.
  • Static: returns (planet.x, planet.y).
  • Orbiting: engine applies rotation max(0, step - 1) (orbit_physics.py:275), so future_rotation = max(0, future_turn - 1). With a state, the current position seeds the angle from current_rotation = max(0, state.turn - 1); without a state, initial_angle + angular_velocity * future_rotation.

Fleet speed (orbit_physics.py:157)

speed = 1 + (max_speed - 1) * (log(ships) / log(1000)) ** 1.5, clipped to max_speed. _speed(state, ships) at orbit_physics.py:171 prefers state.precomputed.speed_table (clamped to 1000) when available.

Lead-shot path (orbit_physics.py:608)

  1. _closed_form_outer_shot (:548) — static outer-outer corridor, returns immediately when the corridor is clear and no comet band intersects.
  2. Shot cache lookup keyed by _shot_cache_key (:413). Inner-inner pairs and safe outer-outer pairs use "free" keys (turn-invariant via RotatingShotTemplate); other pairs use ("turn", state.turn). Skip the "free" shortcut at state.turn == 0 (engine off-by-one).
  3. Lead iteration (up to max_iterations) with tolerance, then a wider horizon search over orbiting/comet targets, then validation across angular_offsets. Validation runs predict_fleet_arrival + target_arrival_phase_hits. The first hit returns a successful ShotPlan; otherwise a path_blocked=True plan at the natural angle.
  4. _cache_shot_plan (:503) stores ShotPlan for ("turn", ...) keys and a RotatingShotTemplate (precomputed.RotatingShotTemplate) for inner-inner "free" keys.

Fleet arrival (orbit_physics.py:883)

Per-tick: advance fleet by speed, bound and sun-collision tests, then a two-pass planet sweep (collision against the pre-move body position, then against the planet's sweep into the next position). Returns the first match; sun hits and out-of-bounds yield None.

Engine-Matched Rules

  • Collision predicates use strict < radius (orbit_physics.py:138); tangents do not collide.
  • Production applies before arrivals/combat each projected turn (orbit_physics.py:853).
  • Capture requires the garrison to drop strictly below zero; equality leaves owner unchanged with 0 ships (orbit_physics.py:818).
  • Orbiting planet positions recomputed from absolute step with the max(0, step - 1) off-by-one (orbit_physics.py:275).
  • lead_shot does not invent curved routes; blocked shots return path_blocked=True.

Tests

pytest -q tests/test_orbit_physics.py tests/test_transition.py