shove-replay: реплей сесії агента, коли рушій уже не той
SHOVE — тактична головоломка в дусі Into the Breach: загін мехів тримає три будівлі проти багів, п'ять ходів на все. Кожен ворог наперед світить офсет свого удару — цей офсет і зветься телеграфом. Штовхнути атакера — це перенаправити його в воду до сусіда, а не бити в лоб.
Спроєктувала, закодила і зіграла гру Claude Fable 5 — без людини за штурвалом, шість партій поспіль. Репо: robss2020/claude-fable-5-having-fun.
Але цікаве тут не гра. Цікаве — те, що від неї лишилось на диску.
Що насправді зберігається в журналі
Play ledger — журнал зіграних партій — тримає тільки команди, стану дошки в ньому нема. Щоб побачити партію знову, береш той самий сід і ганяєш той самий рушій з нуля, крок за кроком. Це детерміноване відтворення, а не запис відео: рушій — джерело істини, лог — просто вхід для нього (відео показало б картинку саме по собі; лог без рушія не показує нічого).
Наслідок прямий, як обрив кабелю: без точної версії рушія лог — просто список чисел.
Проблема: рушій перерів сам себе
За шість партій рушій переписав сам себе шість разів (v1–v6). Вихідний код v1–v5 уже втрачено — ніхто, включно з агентом, який його й писав, не здогадався заархівувати рушій окремо від логів, що він плодив. Реально відтворюваною лишилась одна партія з шести: S6, сід 31337, на v6. П'ять інших живуть як список команд, який більше нема чим програти.
Рішення: чесна межа замість імітації
Viewer, який я зробив під це, реконструює єдину уцілілу партію: грид-борд, юніти, вороги і — головне — locked-in телеграф кожного ворога в кожен момент. Тобто рівно та позиція, яку рушій бачив, коли приймав рішення. Апстрім (shove.py) ліцензії не має, тому я його не вендорю — він тягнеться at build time; MIT у репо покриває лише сам viewer, чужий код лишається чужим.
Для п'яти втрачених сесій я не став нічого домальовувати. Синтетичний стан, підставлений замість втраченого, — та сама дистиляція, тільки в пікселях: виглядає повно, а насправді брехня. Чесніше показати межу відтворюваного, ніж видати реконструкцію за факт.
Демо: tools.mandrock.me/shove-replay · Репо viewer'а: github.com/0mandrock1/shove-replay
Таблиця рішень
| Ситуація | Що робити |
|---|---|
| Є журнал команд, стану немає | реконструювати детерміновано реплею; потрібен точний рушій і точний сід |
| Версія рушія, яка грала сесію, невідома або загублена | сесія непридатна до реплею; визнати це, не імітувати стан |
| Апстрім без ліцензії, потрібен для роботи | не вендорити чужий код; тягнути at build time, ліцензувати окремо лише свій шар |
| Інструмент агента змінюється між сесіями | версіонувати й архівувати рушій окремо від логів, які він генерує |
| Треба показати, що саме бачив агент у момент рішення | зберігати сирі проміжні сигнали (офсет/намір), не лише кінцевий стан |
Антипатерни
- Версіонувати інструмент разом з його логами. Лог переживає код, який його породив, — і без цього коду лог німий, програвати нема чим.
- Плутати запис дій із записом стану. Command log без детермінованого рушія тієї самої версії не відтвориться, скільки не бийся.
- Вендорити чужий код без ліцензії заради зручного білда. Тягни at build time і чесно розділяй, яка ліцензія на що лягає.
- Показувати лише фінальний стан сесії. Так втрачається рівно те, заради чого артефакт AI-агента взагалі цікавий — проміжні рішення, а не підсумок.
- Домальовувати відсутній стан, щоб реплей виглядав повним. Чесна межа відтворюваного варта більше за суцільну, але фальшиву картинку.
Чекліст
- Явно з'ясовано, що в лозі: команди чи повний стан
- Версія рушія, яка згенерувала лог, лежить поряд із самим логом
- Апстрім без ліцензії не вендориться; свій шар ліцензовано окремо
- Реплей прогнано на реальному сіді з живого логу, не на синтетичному прикладі
- Видно проміжні рішення агента, а не лише фінальний стан
SHOVE: РУШІЙ ШТОВХАЄ САМ СЕБЕ
┌─────────────────────────────────┐
│ хід: SHOVE(власний вихідний код)│
└─────────────────────────────────┘
│
┌─────────┴────────────────┐
▼ ▼
┌──────────────────────┐ ┌──────────────────────┐
│ куди полетів удар │ │ що встигли прочитати │
│ (v1–v5) │ │ (v6, seed 31337) │
└──────────────────────┘ └──────────────────────┘
▼ ▼
┌──────────────────────┐ ┌──────────────────────┐
│ 0 байт коду, │ │ 1 партія з 6, │
│ назавжди │ │ єдина яку видно │
└──────────────────────┘ └──────────────────────┘
Телеграф показує, куди прилетить удар. На свій власний рушій так жодного разу й не глянув.