{"id":51525551,"url":"https://github.com/jacklvd/orbit_war_2026","last_synced_at":"2026-07-08T21:01:06.058Z","repository":{"id":366738937,"uuid":"1277657745","full_name":"jacklvd/orbit_war_2026","owner":"jacklvd","description":"This repo serves as archive for the agent built for the Orbit War competition on Kaggle 2026","archived":false,"fork":false,"pushed_at":"2026-06-23T04:50:31.000Z","size":179,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-06-23T06:22:20.676Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jacklvd.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-23T04:47:24.000Z","updated_at":"2026-06-23T04:50:36.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/jacklvd/orbit_war_2026","commit_stats":null,"previous_names":["jacklvd/orbit_war_2026"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/jacklvd/orbit_war_2026","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jacklvd%2Forbit_war_2026","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jacklvd%2Forbit_war_2026/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jacklvd%2Forbit_war_2026/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jacklvd%2Forbit_war_2026/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jacklvd","download_url":"https://codeload.github.com/jacklvd/orbit_war_2026/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jacklvd%2Forbit_war_2026/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35278166,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-08T02:00:06.796Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-07-08T21:01:05.474Z","updated_at":"2026-07-08T21:01:06.043Z","avatar_url":"https://github.com/jacklvd.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Orbit Wars\n\nA competition agent for **Orbit Wars** — a real-time strategy game where 2 or 4\nplayers conquer planets orbiting a sun in continuous 2D space. Whoever holds the\nmost ships (on planets + in fleets) after 500 turns wins.\n\nThis repo holds the agent (`orbit_v2/`), the analysis tooling used to develop it,\nand the design records for the experiments. The [game rules](#game-rules-reference)\nare documented at the bottom.\n\nhttps://www.kaggle.com/competitions/orbit-wars\n\n---\n\n## Result\n\n| | |\n|---|---|\n| **Best stable score** | ~**1239** Elo (peak **1352**) — the `orbit_v2` agent, 2026-06-12 |\n| **Final submitted pair** | `holdwindow8` + `B_holdwindow` (the two most *consistent* configs) |\n| **Goal** | Top 10% of the field |\n\nThe leaderboard uses a continuously-updated Elo: agents keep playing and the rating\ndrifts. Fresh submissions start at ~600 and take ~a day of games to converge to their\ntrue level. The final pair was chosen for **consistency** (validated across both the\nleaderboard and the local arena), not single-run peak.\n\n---\n\n## The agent (`orbit_v2/`)\n\nThe agent is a **flow-based greedy planner**, not a search or ML policy. Each turn it\nprojects the board forward and scores candidate fleet launches by their effect on a\ncompetitive objective, then greedily commits the best non-conflicting waves.\n\n**Pipeline (`orbit_v2/main.py` → `orbit_v2/orbit_lite/`):**\n\n1. **Forward projection** (`orbit_lite/movement.py`, `garrison_launch.py`) — a\n   bit-exact simulation of planet rotation, fleet flight, production, and combat over a\n   short horizon (18 turns in 2P, 13 in 4P).\n2. **Candidate generation** (`plan_lite_waves` in `main.py`) — for each (source, target)\n   pair it computes a **capture floor** (`capture_floor`: min ships to take the planet,\n   accounting for the defender's projected garrison) and a **safe drain** (`safe_drain`:\n   max ships a source can shed while still holding itself).\n3. **Scoring** (`score_candidates` → `sparse_launch_flow_delta` → `competitive_score`) —\n   each launch is scored by the *delta* it causes in `Δnet_ships_me − Σ Δnet_ships_opp`,\n   computed exactly via a sparse per-planet flow projector.\n4. **Greedy selection** (`_greedy_select`) — picks the best wave each iteration (one per\n   target, source-budget-aware) while its competitive score clears an ROI gate; leftover\n   ships are reinforced/regrouped.\n\n**The one winning lever:** a **reactive-reinforcement margin** (`reinforce_margin_frac=0.6`)\nfed into `capture_floor`. Stock scoring sizes attacks against a *do-nothing* projection;\nthe margin makes the agent send enough to survive the defender's reactive counter — fixing\nthe scorer's main blind spot. This took the agent from the ~870-890 tier (`proto_agent`\nv1.x) to ~1240.\n\nBehavior is tuned by `ProducerLiteConfig`; `CONFIG_2P` / `CONFIG_4P` hold the shipped\nvalues, overridable at runtime via the `ORBIT_V2_2P` / `ORBIT_V2_4P` env vars (JSON).\n\n---\n\n## What worked, and what didn't\n\nThe hard-won lesson of this project: **candidate-level tweaks that the flow scorer can\nalready price are outcome-neutral, and \"expand more\" levers are actively harmful.** Real\nedges must fix what the scorer *cannot see* (opponent reactions).\n\n**Worked**\n- Reactive-reinforcement margin (`reinforce_margin_frac=0.6`) — the core win.\n- Hold-window margin (`margin_hold_turns`) — only count enemy mass that reaches a target\n  *after* our arrival, so we capture what survives the immediate counter (`B`/`holdwindow8`).\n\n**Refuted (documented so they aren't retried)**\n- **Expansion-volume levers** — `expand_value_frac` (thin-garrison trap), early quality\n  multipliers, blanket fleet-fraction cuts, the **surplus-expansion valve** (shipped, LB\n  1073 vs 1176), and the **terminal-value tempo knob** (implemented default-off, no robust\n  local edge). Conclusion: expanding *more* just takes planets we can't hold under enemy\n  reaction. The unbuilt direction is expansion *quality* / reaction-aware holdability.\n- **Defensive reworks** — precise-defense / post-capture holdability (LB 1062).\n- Candidate-level no-ops the scorer already prices: cheap-target shortlist slots,\n  multi-size sends, cap-drain margin, 4P capacity scaling.\n\nFull write-ups in `analysis/FINDINGS_2026-06-21_latest2.md` and\n`docs/superpowers/specs/`.\n\n---\n\n## Repo layout\n\n```\norbit_v2/                  Active agent\n  main.py                  Planner + ProducerLiteConfig (CONFIG_2P / CONFIG_4P)\n  orbit_lite/              Engine: movement, garrison/flow projection, scoring, aiming\n  build_stock_submissions.py   Builds a submission tarball for a given config\n  orbit_v2_submission*.tar.gz   The 3 tarballs active on the leaderboard\n  PLAN.md                  Blind-spot / future-edge list\nanalysis/                  Development \u0026 analysis tooling (see below)\ndocs/superpowers/          Design specs \u0026 implementation plans for experiments\nagent_log/                 Downloaded leaderboard replays (ours + top opponents)\nopponents/                 Local sparring agents (buddy_v5, etc.)\narchive/                   Older proto_agent work + the forward simulator\nproto_agent.py             v1.x agent (pre-pivot, ~870-890 tier)\n.env                       Kaggle API token (gitignored)\n```\n\n## Running it\n\nEverything runs with the project venv (`venv/bin/python`) — the agent imports `torch`\nbut **not** the kaggle CLI, so it's unaffected by the slow-import caveat below.\n\n```bash\n# Run the agent vs a local opponent on a seed (kaggle_environments must be installed)\nvenv/bin/python -c \"from kaggle_environments import make; ...\"   # see analysis/ scripts\n\n# Build a submission tarball for a config\nvenv/bin/python orbit_v2/build_stock_submissions.py\n\n# Submit / check standings (needs the throwaway kaggle CLI — see Caveats)\nset -a; source .env; set +a\n/tmp/kgl/bin/kaggle competitions submit orbit-wars -f orbit_v2/\u003ctarball\u003e -m \"...\"\n/tmp/kgl/bin/kaggle competitions submissions orbit-wars\n```\n\n## Tooling (`analysis/`)\n\n| Script | Purpose |\n|---|---|\n| `pull_replays.py` | Download recent episode replays for a submission (stdlib-only) |\n| `phdef_autopsy.py` | Per-replay autopsy: planet/ship trajectories, win/loss diagnosis |\n| `arena_oracle.py` | Local regression gate vs a fixed opponent pool |\n| `verify_terminal_value.py` | Subprocess-isolated config A/B + λ sweep harness |\n| `test_terminal_value.py` | Unit tests for the terminal-value scoring term |\n\n**Win/loss discriminator** (from replay analysis): track when our total-ship stockpile\npeaks. Wins peak at the *end* of the game (snowball); losses peak *mid-game* (we hoard\nidle ships and get out-expanded). `argmax(ships)/T` near 1.0 = healthy.\n\n## Caveats (read before extending)\n\n- **Local oracle ceiling** — local opponents cap at the ~1000-1100 tier; only `buddy_v5`\n  and 4P self-play discriminate. Local win-rate is a regression gate, **not** proof of a\n  leaderboard gain. Real validation is an A/B submission.\n- **Win-rate varies hugely by seed batch** — never conclude from one batch; a single good\n  batch repeatedly turned out to be noise.\n- **Harness nondeterminism** — `kaggle_environments` + agent is deterministic *across*\n  processes but **not within one** (global state leaks between sequential `env.run` calls).\n  Run each (seed, config) cell in its own subprocess (`verify_terminal_value.py` does this).\n- **Kaggle CLI** — the Google-Drive filesystem times out importing the kaggle package's\n  protobuf, so the CLI lives in a throwaway venv at `/tmp/kgl`. Recreate with\n  `python3 -m venv /tmp/kgl \u0026\u0026 /tmp/kgl/bin/pip install kaggle` (token is in `.env`).\n  The agent itself does not need this.\n\n---\n\n## Game Rules Reference\n\nConquer planets rotating around a sun in continuous 2D space. A real-time strategy game\nfor 2 or 4 players.\n\n### Overview\n\nPlayers start with a single home planet and compete to control the map by sending fleets\nto capture neutral and enemy planets. The board is a 100x100 continuous space with a sun\nat the center. Planets orbit the sun, comets fly through on elliptical trajectories, and\nfleets travel in straight lines. The game lasts 500 turns. The player with the most total\nships (on planets + in fleets) at the end wins.\n\n### Board Layout\n\n- **Board**: 100x100 continuous space, origin at top-left.\n- **Sun**: Centered at (50, 50) with radius 10. Fleets that cross the sun are destroyed.\n- **Symmetry**: All planets and comets are placed with 4-fold mirror symmetry around the\n  center: (x, y), (100-x, y), (x, 100-y), (100-x, 100-y).\n\n### Planets\n\nEach planet is `[id, owner, x, y, radius, ships, production]`.\n\n- **owner**: Player ID (0-3), or -1 for neutral.\n- **radius**: `1 + ln(production)`.\n- **production**: Integer 1-5; each turn an owned planet generates this many ships.\n- **ships**: Current garrison. Starts between 5 and 99 (skewed low).\n- **Orbiting** planets (`orbital_radius + planet_radius \u003c 50`) rotate at 0.025-0.05\n  rad/turn; **static** planets do not. Use `initial_planets` + `angular_velocity` to\n  predict positions. Map has 20-40 planets (5-10 symmetric groups of 4).\n- **Home planets**: one symmetric group; 2P start diagonally opposite (Q1/Q4), 4P one\n  each. Home planets start with 10 ships.\n\n### Fleets\n\nEach fleet is `[id, owner, x, y, angle, from_planet_id, ships]`.\n\n- **Speed** scales with size: `speed = 1.0 + (maxSpeed-1.0) * (log(ships)/log(1000))^1.5`\n  (1 ship → 1.0, ~1000 ships → max 6.0).\n- Fleets travel straight and are removed on out-of-bounds, sun crossing, or planet\n  collision (continuous path-segment detection; collision triggers combat).\n- **Launch**: each turn return `[from_planet_id, direction_angle, num_ships]` moves from\n  planets you own; can't send more than the planet holds; multiple launches allowed.\n\n### Comets\n\nTemporary objects on elliptical orbits, spawning in groups of 4 (one per quadrant) at\nsteps 50/150/250/350/450. Radius 1.0, production 1/turn, low starting ships. They obey\nall planet rules. Identified via `comet_planet_ids`; trajectories in `comets`. Removed\n(with their garrison) when they leave the board, before fleet launches.\n\n### Turn Order\n\n1. Comet expiration → 2. Comet spawning → 3. Fleet launch → 4. Production →\n5. Fleet movement (out-of-bounds / sun / planet collision) → 6. Planet rotation \u0026 comet\nmovement (sweeps caught fleets into combat) → 7. Combat resolution.\n\n### Combat\n\nWhen fleets collide with a planet:\n\n1. Arriving fleets grouped by owner; same-owner ships summed.\n2. Largest attacking force fights the second largest; the difference survives.\n3. A surviving attacker either reinforces (same owner) or fights the garrison (different\n   owner); if attackers exceed the garrison, the planet flips and the garrison becomes\n   the surplus.\n4. A tie destroys all attacking ships.\n\n### Scoring and Termination\n\nGame ends at 500 steps or when one/zero players remain. Final score = ships on owned\nplanets + ships in owned fleets. Highest wins.\n\n### Observation Reference\n\n| Field | Description |\n|-------|-------------|\n| `planets` | `[[id, owner, x, y, radius, ships, production], ...]` (incl. comets) |\n| `fleets` | `[[id, owner, x, y, angle, from_planet_id, ships], ...]` |\n| `player` | Your player ID (0-3) |\n| `angular_velocity` | Planet rotation speed (rad/turn) |\n| `initial_planets` | Planet positions at game start |\n| `comets` | `[{planet_ids, paths, path_index}, ...]` |\n| `comet_planet_ids` | Planet IDs that are comets |\n| `remainingOverageTime` | Remaining overage time budget (seconds) |\n\n### Action Format\n\nReturn `[[from_planet_id, direction_angle, num_ships], ...]` (angle in radians, 0 = right,\nπ/2 = down), or `[]` for no action.\n\n### Configuration\n\n| Parameter | Default | Description |\n|-----------|---------|-------------|\n| `episodeSteps` | 500 | Max turns |\n| `actTimeout` | 1 | Seconds per turn |\n| `shipSpeed` | 6.0 | Max fleet speed |\n| `sunRadius` | 10.0 | Sun radius |\n| `boardSize` | 100.0 | Board dimensions |\n| `cometSpeed` | 4.0 | Comet speed (units/turn) |\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjacklvd%2Forbit_war_2026","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjacklvd%2Forbit_war_2026","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjacklvd%2Forbit_war_2026/lists"}