hatetris-policy: an autoresearch@home project
Find a file
2026-11-01 22:09:54 +02:00
grader hatetris-policy: the base (PER-361), projects/hatetris-policy at autoresearch-at-home d63ee92 2026-11-01 22:09:54 +02:00
harness hatetris-policy: the base (PER-361), projects/hatetris-policy at autoresearch-at-home d63ee92 2026-11-01 22:09:54 +02:00
sample hatetris-policy: the base (PER-361), projects/hatetris-policy at autoresearch-at-home d63ee92 2026-11-01 22:09:54 +02:00
src hatetris-policy: the base (PER-361), projects/hatetris-policy at autoresearch-at-home d63ee92 2026-11-01 22:09:54 +02:00
autoresearch.toml hatetris-policy: the base (PER-361), projects/hatetris-policy at autoresearch-at-home d63ee92 2026-11-01 22:09:54 +02:00
compose.yaml hatetris-policy: the base (PER-361), projects/hatetris-policy at autoresearch-at-home d63ee92 2026-11-01 22:09:54 +02:00
CONTRACT-javascript.md hatetris-policy: the base (PER-361), projects/hatetris-policy at autoresearch-at-home d63ee92 2026-11-01 22:09:54 +02:00
CONTRACT-jvm.md hatetris-policy: the base (PER-361), projects/hatetris-policy at autoresearch-at-home d63ee92 2026-11-01 22:09:54 +02:00
CONTRACT-perl.md hatetris-policy: the base (PER-361), projects/hatetris-policy at autoresearch-at-home d63ee92 2026-11-01 22:09:54 +02:00
CONTRACT-ruby.md hatetris-policy: the base (PER-361), projects/hatetris-policy at autoresearch-at-home d63ee92 2026-11-01 22:09:54 +02:00
CONTRACT-rust.md hatetris-policy: the base (PER-361), projects/hatetris-policy at autoresearch-at-home d63ee92 2026-11-01 22:09:54 +02:00
CONTRACT-shell.md hatetris-policy: the base (PER-361), projects/hatetris-policy at autoresearch-at-home d63ee92 2026-11-01 22:09:54 +02:00
CONTRACT.md hatetris-policy: the base (PER-361), projects/hatetris-policy at autoresearch-at-home d63ee92 2026-11-01 22:09:54 +02:00
dockerfile.grader hatetris-policy: the base (PER-361), projects/hatetris-policy at autoresearch-at-home d63ee92 2026-11-01 22:09:54 +02:00
dockerfile.worker hatetris-policy: the base (PER-361), projects/hatetris-policy at autoresearch-at-home d63ee92 2026-11-01 22:09:54 +02:00
LICENSE hatetris-policy: the base (PER-361), projects/hatetris-policy at autoresearch-at-home d63ee92 2026-11-01 22:09:54 +02:00
PROGRAM.md hatetris-policy: the base (PER-361), projects/hatetris-policy at autoresearch-at-home d63ee92 2026-11-01 22:09:54 +02:00
README.md hatetris-policy: the base (PER-361), projects/hatetris-policy at autoresearch-at-home d63ee92 2026-11-01 22:09:54 +02:00

hatetris-policy: lines in wells you haven't seen

A count project (PER-361), the hatetris project's companion. Agents improve a HATETRIS player (src/), and the grader plays it live against 48 held-out variants of the game: wells of other sizes, each starting from hidden debris, qntm's adversary with a hidden tie-break order, and Heidi Burgiel's adversary. The score is the mean lines a game (lines, maximized). The worker sees one piece at a time and never the adversary, so a memorised game can't win. The hatetris project scores the opposite way: one known game, replayed from a committed move string.

File What it is
autoresearch.toml the metric (lines: a count, maximized, MMI 0.4 lines a game absolute, deterministic, epsilon 0), runner class cpu-s, editable src/**, K = 50; no upstream; public
PROGRAM.md instructions for the agents: the player, its budget, the rules, what counts as cheating
CONTRACT.md the worker's HTTP contract: the template's, plus this project's rounds and states
CONTRACT-*.md the template's language companions of the contract (auto-loaded files and loader variables per ecosystem), verbatim
src/player.py the player being improved: a greedy one-piece placer to start with
harness/worker.py the frozen HTTP server: it imports src/player.py and answers each state with play(state)
grader/grade.py, grader/hatetris_variants.py the grader, and the rules it plays the games under
grader/arh_grader the grader SDK, release 4 (sdk/grader/src verbatim)
sample/items.jsonl the public sample: 8 variants, none in the held-out set
dockerfile.worker, dockerfile.grader, compose.yaml Python 3.13, standard library only; the worker on core 0 and the grader on core 1, as runner class cpu-s places them
LICENSE MIT, with qntm's notice for the ported rules

The rules and the variants

grader/hatetris_variants.py is the hatetris project's port (its grader/hatetris.py) of qntm's HATETRIS (https://github.com/qntm/hatetris, MIT, Copyright (c) 2021 qntm, commit 9e5962e60f89eef26d0d3f2eda726708149323a4). It adds:

  • the variants: any size, a starting well (garbage: rows of debris at the bottom, none full, none empty), the HATETRIS adversary's tie-break order, and Burgiel's adversary (src/enemy-ais/burgiel.ts), which starts with S or Z;
  • per-piece play: Game.play_piece.

Calibration. tests/projects/test_hatetris_policy.py (in the platform's repository) replays records with this copy of the engine, each at exactly its published lines:

  • the hatetris project's standard-game records up to 366 lines, with the default variant;
  • both records of Burgiel's adversary on qntm.org/records, under qntm's lenient replay of the published string:
Date Holder Lines Source
2024-12-25 Piotr Grochowski 643 https://qntm.org/records
2025-10-29 David & Felipe 676 https://qntm.org/records

The sets. sdk/hatetris-policy/variants.py (in the platform's repository) draws them with fixed seeds. Each variant is 16 to 24 deep, with the bar at 4. It starts from 2 to 5 rows of debris at the bottom, each with 1 to 3 holes, drawn for each game. Its adversary is one of:

  • qntm's, with its order a random permutation of the seven pieces, on a well 8 to 12 wide;
  • Burgiel's, starting with S or Z, on a well 9 to 12 wide. At width 8 a one-piece greedy placer survives Burgiel's alternation to the piece cap from about one starting well in six, and one such game would be the whole baseline.

Each variant draws Burgiel's adversary with probability one in four. The held-out set holds 48 variants: 36 against qntm's adversary and 12 against Burgiel's. The sample holds 8: 3 against qntm's and 5 against Burgiel's. No variant is in both sets.

The debris is what keeps public games out. Without it, every game starts from an empty well, where the published replays have been. A book of their states, with greedy where it has none, transfers. In review (PER-361), Burgiel's 676-line replay alone scored 17 MMIs over the baseline, and every public replay together scored 85, qntm's games included. From hidden debris, no game reaches a published state. A test plays that book against the held-out set and requires it to stay within one MMI of the baseline. Apart from its debris, no variant is a published record's game either: 10 x 20 against the original order, or against Burgiel starting with S.

How a run is scored

  1. The variants are checked: each one's fields, its size, and an order of the seven pieces. Otherwise the run is invalid.
  2. The games are played in lockstep. The grader starts every game. Each round, one request carries the state of every game still going, and each answer is that game's piece's moves, played strictly (Game.play_piece). A game fails at the first move that isn't L, R, D or U, that changes nothing, or that comes after the lock, or when its moves end before the lock. A game ends when it is over, or at 1,000 pieces.
  3. A failed game makes the run invalid, and the grader stops there: the games still going count as failed (abandoned). The result counts the failures by kind (the SDK's release 4 failure_kinds): the SDK's own for an answer that never came or didn't fit the schema, and not_a_move, blocked, after_the_lock or no_lock for an illegal one. Every game is accounted for: completed + failed + abandoned = n_items.
  4. Otherwise lines is the mean of the games' lines. Each game's lines are its item score, in the file's order. The secondary values are the pieces locked, and the games that reached 1,000 pieces (capped).

The MMI

The metric is deterministic: the grader's play depends only on the variants and the player's answers, and players must be deterministic too (PROGRAM.md). Its floor is the held-out set's sampling error: twice the baseline's bootstrap standard error over its 48 games' lines. At the greedy baseline's 1.1667 (56 lines in 48 games) that is 2 x 0.1952 = 0.390, so the MMI is 0.4 lines a game (19.2 lines over the 48 games), with epsilon 0.

As players improve, their games' lines spread out, and so does the error: a later reward version measures its own floor. A game ends at 1,000 pieces, which caps what a game can score; a player that reaches the cap on most games needs a new version with a higher one.

A known limit: Burgiel's games will saturate. Burgiel's alternation can be survived for a long time: its published records run to 643 and 676 lines, and at width 8 even greedy loops. A strong player will learn to reach the cap against it at widths 9 to 12 too, about 400 lines a game. Once it does, those 12 games carry nearly all of the mean and its error. A later reward version will rebalance them, with a smaller share of Burgiel's games, a separate cap, or a weight.

Running it

docker compose up --build --abort-on-container-exit --exit-code-from grader
docker compose run --rm --no-deps --entrypoint cat grader /result/result.json
docker compose down --volumes

The baseline scores lines 1.5 on the public sample (12 lines in 8 games, 147 pieces). arh run (the platform's CLI, cli/README.md) runs the same three commands and prints the result.

Licences

What Source Licence
grader/hatetris_variants.py (a port), the piece drawings in src/player.py qntm's HATETRIS, https://github.com/qntm/hatetris, Copyright (c) 2021 qntm MIT
everything else this project MIT
Python 3.13 (python:3.13-slim-bookworm) Docker Hub, the images' base PSF-2.0

The project has no upstream ([project].upstream left out, ADR-0004). Its repository on the platform is its own, based on this directory in one commit, with no pull mirror.