wingspan-rl 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (400) hide show
  1. wingspan_rl-1.0.0/LICENSE +21 -0
  2. wingspan_rl-1.0.0/PKG-INFO +420 -0
  3. wingspan_rl-1.0.0/README.md +389 -0
  4. wingspan_rl-1.0.0/pyproject.toml +118 -0
  5. wingspan_rl-1.0.0/setup.cfg +4 -0
  6. wingspan_rl-1.0.0/src/wingspan/__init__.py +3 -0
  7. wingspan_rl-1.0.0/src/wingspan/__main__.py +134 -0
  8. wingspan_rl-1.0.0/src/wingspan/agents/__init__.py +20 -0
  9. wingspan_rl-1.0.0/src/wingspan/agents/base.py +34 -0
  10. wingspan_rl-1.0.0/src/wingspan/agents/cli.py +278 -0
  11. wingspan_rl-1.0.0/src/wingspan/agents/display.py +596 -0
  12. wingspan_rl-1.0.0/src/wingspan/agents/interactive.py +435 -0
  13. wingspan_rl-1.0.0/src/wingspan/aid/__init__.py +8 -0
  14. wingspan_rl-1.0.0/src/wingspan/aid/advisor.py +297 -0
  15. wingspan_rl-1.0.0/src/wingspan/aid/app.py +334 -0
  16. wingspan_rl-1.0.0/src/wingspan/aid/console.py +147 -0
  17. wingspan_rl-1.0.0/src/wingspan/aid/entry.py +385 -0
  18. wingspan_rl-1.0.0/src/wingspan/aid/hooks.py +202 -0
  19. wingspan_rl-1.0.0/src/wingspan/aid/models.py +193 -0
  20. wingspan_rl-1.0.0/src/wingspan/aid/oracle.py +287 -0
  21. wingspan_rl-1.0.0/src/wingspan/aid/oracle_state.py +192 -0
  22. wingspan_rl-1.0.0/src/wingspan/aid/placeholders.py +88 -0
  23. wingspan_rl-1.0.0/src/wingspan/aid/preview.py +84 -0
  24. wingspan_rl-1.0.0/src/wingspan/aid/relay.py +284 -0
  25. wingspan_rl-1.0.0/src/wingspan/aid/widgets.py +376 -0
  26. wingspan_rl-1.0.0/src/wingspan/analysis/__init__.py +21 -0
  27. wingspan_rl-1.0.0/src/wingspan/analysis/attention_probe.py +356 -0
  28. wingspan_rl-1.0.0/src/wingspan/analysis/cli.py +424 -0
  29. wingspan_rl-1.0.0/src/wingspan/analysis/head_to_head.py +56 -0
  30. wingspan_rl-1.0.0/src/wingspan/analysis/layer_probe.py +233 -0
  31. wingspan_rl-1.0.0/src/wingspan/analysis/models.py +268 -0
  32. wingspan_rl-1.0.0/src/wingspan/analysis/probe_set.py +144 -0
  33. wingspan_rl-1.0.0/src/wingspan/analysis/representation.py +596 -0
  34. wingspan_rl-1.0.0/src/wingspan/architecture.py +884 -0
  35. wingspan_rl-1.0.0/src/wingspan/cards/__init__.py +82 -0
  36. wingspan_rl-1.0.0/src/wingspan/cards/lookup.py +187 -0
  37. wingspan_rl-1.0.0/src/wingspan/cards/parse/__init__.py +69 -0
  38. wingspan_rl-1.0.0/src/wingspan/cards/parse/catalog.py +67 -0
  39. wingspan_rl-1.0.0/src/wingspan/cards/parse/fields.py +172 -0
  40. wingspan_rl-1.0.0/src/wingspan/cards/parse/loader.py +93 -0
  41. wingspan_rl-1.0.0/src/wingspan/cards/parse/matchers.py +779 -0
  42. wingspan_rl-1.0.0/src/wingspan/cards/parse/pink_matchers.py +117 -0
  43. wingspan_rl-1.0.0/src/wingspan/cards/parse/power.py +59 -0
  44. wingspan_rl-1.0.0/src/wingspan/cards/parse/registry.py +40 -0
  45. wingspan_rl-1.0.0/src/wingspan/cards/parse/tags.py +38 -0
  46. wingspan_rl-1.0.0/src/wingspan/cards/schema.py +498 -0
  47. wingspan_rl-1.0.0/src/wingspan/cli.py +494 -0
  48. wingspan_rl-1.0.0/src/wingspan/cloud/__init__.py +33 -0
  49. wingspan_rl-1.0.0/src/wingspan/cloud/__main__.py +104 -0
  50. wingspan_rl-1.0.0/src/wingspan/cloud/monitor.py +159 -0
  51. wingspan_rl-1.0.0/src/wingspan/cloud/runfile.py +106 -0
  52. wingspan_rl-1.0.0/src/wingspan/cloud/runner.py +295 -0
  53. wingspan_rl-1.0.0/src/wingspan/cloud/s3sync.py +242 -0
  54. wingspan_rl-1.0.0/src/wingspan/cloud/status.py +147 -0
  55. wingspan_rl-1.0.0/src/wingspan/compat/__init__.py +139 -0
  56. wingspan_rl-1.0.0/src/wingspan/compat/v1_0.py +173 -0
  57. wingspan_rl-1.0.0/src/wingspan/compat/v1_3.py +229 -0
  58. wingspan_rl-1.0.0/src/wingspan/compat/v1_4.py +405 -0
  59. wingspan_rl-1.0.0/src/wingspan/data/bonus.json +1030 -0
  60. wingspan_rl-1.0.0/src/wingspan/data/general.json +172 -0
  61. wingspan_rl-1.0.0/src/wingspan/data/goals.json +618 -0
  62. wingspan_rl-1.0.0/src/wingspan/data/master.json +57535 -0
  63. wingspan_rl-1.0.0/src/wingspan/data/parameters.json +5 -0
  64. wingspan_rl-1.0.0/src/wingspan/data/release/last.pt +0 -0
  65. wingspan_rl-1.0.0/src/wingspan/data/release/manifest.json +8 -0
  66. wingspan_rl-1.0.0/src/wingspan/data/release/run_config_20261021-113648.json +222 -0
  67. wingspan_rl-1.0.0/src/wingspan/data/release/setup.pt +0 -0
  68. wingspan_rl-1.0.0/src/wingspan/decisions.py +850 -0
  69. wingspan_rl-1.0.0/src/wingspan/encode/__init__.py +172 -0
  70. wingspan_rl-1.0.0/src/wingspan/encode/choice_encode.py +1715 -0
  71. wingspan_rl-1.0.0/src/wingspan/encode/layout.py +1257 -0
  72. wingspan_rl-1.0.0/src/wingspan/encode/state_encode.py +726 -0
  73. wingspan_rl-1.0.0/src/wingspan/encode/stripes/__init__.py +60 -0
  74. wingspan_rl-1.0.0/src/wingspan/encode/stripes/card_feature.py +271 -0
  75. wingspan_rl-1.0.0/src/wingspan/encode/stripes/choice.py +944 -0
  76. wingspan_rl-1.0.0/src/wingspan/encode/stripes/descriptors.py +236 -0
  77. wingspan_rl-1.0.0/src/wingspan/encode/stripes/embed_rules.py +411 -0
  78. wingspan_rl-1.0.0/src/wingspan/encode/stripes/state.py +1124 -0
  79. wingspan_rl-1.0.0/src/wingspan/engine/__init__.py +20 -0
  80. wingspan_rl-1.0.0/src/wingspan/engine/actions.py +1038 -0
  81. wingspan_rl-1.0.0/src/wingspan/engine/core.py +840 -0
  82. wingspan_rl-1.0.0/src/wingspan/engine/forecast.py +448 -0
  83. wingspan_rl-1.0.0/src/wingspan/engine/helpers.py +219 -0
  84. wingspan_rl-1.0.0/src/wingspan/engine/ledger.py +597 -0
  85. wingspan_rl-1.0.0/src/wingspan/engine/log_format.py +275 -0
  86. wingspan_rl-1.0.0/src/wingspan/engine/playability.py +553 -0
  87. wingspan_rl-1.0.0/src/wingspan/engine/powers/__init__.py +52 -0
  88. wingspan_rl-1.0.0/src/wingspan/engine/powers/dispatch.py +263 -0
  89. wingspan_rl-1.0.0/src/wingspan/engine/powers/drafting.py +268 -0
  90. wingspan_rl-1.0.0/src/wingspan/engine/powers/egg_trade.py +211 -0
  91. wingspan_rl-1.0.0/src/wingspan/engine/powers/grants.py +855 -0
  92. wingspan_rl-1.0.0/src/wingspan/engine/powers/multi_actor.py +316 -0
  93. wingspan_rl-1.0.0/src/wingspan/engine/powers/nest_aggregate.py +145 -0
  94. wingspan_rl-1.0.0/src/wingspan/engine/powers/predator_repeat.py +222 -0
  95. wingspan_rl-1.0.0/src/wingspan/engine/powers/registry.py +51 -0
  96. wingspan_rl-1.0.0/src/wingspan/engine/powers/tray_trade.py +266 -0
  97. wingspan_rl-1.0.0/src/wingspan/engine/reactors.py +346 -0
  98. wingspan_rl-1.0.0/src/wingspan/engine/scoring.py +1171 -0
  99. wingspan_rl-1.0.0/src/wingspan/engine/setup_flow.py +244 -0
  100. wingspan_rl-1.0.0/src/wingspan/gamelog/__init__.py +6 -0
  101. wingspan_rl-1.0.0/src/wingspan/gamelog/models.py +747 -0
  102. wingspan_rl-1.0.0/src/wingspan/gamelog/recorder.py +589 -0
  103. wingspan_rl-1.0.0/src/wingspan/gamelog/render_jsonl.py +247 -0
  104. wingspan_rl-1.0.0/src/wingspan/gamelog/render_text.py +86 -0
  105. wingspan_rl-1.0.0/src/wingspan/gamelog/summarize.py +380 -0
  106. wingspan_rl-1.0.0/src/wingspan/instrumentation/__init__.py +26 -0
  107. wingspan_rl-1.0.0/src/wingspan/instrumentation/config.py +148 -0
  108. wingspan_rl-1.0.0/src/wingspan/instrumentation/dispatcher.py +243 -0
  109. wingspan_rl-1.0.0/src/wingspan/instrumentation/events.py +301 -0
  110. wingspan_rl-1.0.0/src/wingspan/instrumentation/handlers/__init__.py +27 -0
  111. wingspan_rl-1.0.0/src/wingspan/instrumentation/handlers/card_visits.py +58 -0
  112. wingspan_rl-1.0.0/src/wingspan/instrumentation/handlers/decision_logger.py +56 -0
  113. wingspan_rl-1.0.0/src/wingspan/instrumentation/handlers/game_log_html.py +199 -0
  114. wingspan_rl-1.0.0/src/wingspan/instrumentation/handlers/game_stats.py +193 -0
  115. wingspan_rl-1.0.0/src/wingspan/instrumentation/registry.py +57 -0
  116. wingspan_rl-1.0.0/src/wingspan/model/__init__.py +12 -0
  117. wingspan_rl-1.0.0/src/wingspan/model/core.py +1106 -0
  118. wingspan_rl-1.0.0/src/wingspan/model/hand_model.py +130 -0
  119. wingspan_rl-1.0.0/src/wingspan/model/mlp.py +102 -0
  120. wingspan_rl-1.0.0/src/wingspan/players/__init__.py +55 -0
  121. wingspan_rl-1.0.0/src/wingspan/players/decision_probe.py +83 -0
  122. wingspan_rl-1.0.0/src/wingspan/players/factory.py +347 -0
  123. wingspan_rl-1.0.0/src/wingspan/players/loaders.py +239 -0
  124. wingspan_rl-1.0.0/src/wingspan/players/spec.py +114 -0
  125. wingspan_rl-1.0.0/src/wingspan/players/value_sink.py +38 -0
  126. wingspan_rl-1.0.0/src/wingspan/release.py +63 -0
  127. wingspan_rl-1.0.0/src/wingspan/release_prep.py +138 -0
  128. wingspan_rl-1.0.0/src/wingspan/reporting/__init__.py +13 -0
  129. wingspan_rl-1.0.0/src/wingspan/reporting/card_view.py +281 -0
  130. wingspan_rl-1.0.0/src/wingspan/reporting/encode_viewer.py +707 -0
  131. wingspan_rl-1.0.0/src/wingspan/reporting/game_log_capture.py +655 -0
  132. wingspan_rl-1.0.0/src/wingspan/reporting/game_log_csv.py +115 -0
  133. wingspan_rl-1.0.0/src/wingspan/reporting/game_log_html.py +1551 -0
  134. wingspan_rl-1.0.0/src/wingspan/reporting/html.py +946 -0
  135. wingspan_rl-1.0.0/src/wingspan/reporting/humanize.py +319 -0
  136. wingspan_rl-1.0.0/src/wingspan/reporting/inspect_cli.py +448 -0
  137. wingspan_rl-1.0.0/src/wingspan/reporting/summary_cli.py +61 -0
  138. wingspan_rl-1.0.0/src/wingspan/reporting/svg.py +1891 -0
  139. wingspan_rl-1.0.0/src/wingspan/reporting/training_summary.py +1789 -0
  140. wingspan_rl-1.0.0/src/wingspan/research/__init__.py +8 -0
  141. wingspan_rl-1.0.0/src/wingspan/research/app.py +250 -0
  142. wingspan_rl-1.0.0/src/wingspan/research/constants.py +46 -0
  143. wingspan_rl-1.0.0/src/wingspan/research/game_stats.py +235 -0
  144. wingspan_rl-1.0.0/src/wingspan/research/game_stats_agg.py +760 -0
  145. wingspan_rl-1.0.0/src/wingspan/research/models.py +525 -0
  146. wingspan_rl-1.0.0/src/wingspan/research/progress.py +104 -0
  147. wingspan_rl-1.0.0/src/wingspan/research/setup_keep.py +355 -0
  148. wingspan_rl-1.0.0/src/wingspan/sampling.py +24 -0
  149. wingspan_rl-1.0.0/src/wingspan/setup_model/__init__.py +74 -0
  150. wingspan_rl-1.0.0/src/wingspan/setup_model/architecture.py +447 -0
  151. wingspan_rl-1.0.0/src/wingspan/setup_model/candidates.py +135 -0
  152. wingspan_rl-1.0.0/src/wingspan/setup_model/encode.py +416 -0
  153. wingspan_rl-1.0.0/src/wingspan/setup_model/generate.py +204 -0
  154. wingspan_rl-1.0.0/src/wingspan/setup_model/record.py +60 -0
  155. wingspan_rl-1.0.0/src/wingspan/setup_model/stripes.py +596 -0
  156. wingspan_rl-1.0.0/src/wingspan/state.py +896 -0
  157. wingspan_rl-1.0.0/src/wingspan/tournament/__init__.py +34 -0
  158. wingspan_rl-1.0.0/src/wingspan/tournament/app.py +359 -0
  159. wingspan_rl-1.0.0/src/wingspan/tournament/dashboard.py +166 -0
  160. wingspan_rl-1.0.0/src/wingspan/tournament/elo.py +36 -0
  161. wingspan_rl-1.0.0/src/wingspan/tournament/models.py +360 -0
  162. wingspan_rl-1.0.0/src/wingspan/tournament/participants.py +227 -0
  163. wingspan_rl-1.0.0/src/wingspan/tournament/picker.py +172 -0
  164. wingspan_rl-1.0.0/src/wingspan/tournament/results.py +155 -0
  165. wingspan_rl-1.0.0/src/wingspan/tournament/runner.py +394 -0
  166. wingspan_rl-1.0.0/src/wingspan/tournament/schedule.py +76 -0
  167. wingspan_rl-1.0.0/src/wingspan/tournament/state.py +146 -0
  168. wingspan_rl-1.0.0/src/wingspan/training/__init__.py +37 -0
  169. wingspan_rl-1.0.0/src/wingspan/training/__main__.py +8 -0
  170. wingspan_rl-1.0.0/src/wingspan/training/app.py +576 -0
  171. wingspan_rl-1.0.0/src/wingspan/training/artifacts.py +93 -0
  172. wingspan_rl-1.0.0/src/wingspan/training/batched_collect.py +418 -0
  173. wingspan_rl-1.0.0/src/wingspan/training/charts/__init__.py +34 -0
  174. wingspan_rl-1.0.0/src/wingspan/training/charts/braille.py +79 -0
  175. wingspan_rl-1.0.0/src/wingspan/training/charts/convergence_chart.py +680 -0
  176. wingspan_rl-1.0.0/src/wingspan/training/charts/geometry.py +57 -0
  177. wingspan_rl-1.0.0/src/wingspan/training/charts/histogram.py +110 -0
  178. wingspan_rl-1.0.0/src/wingspan/training/charts/insets.py +270 -0
  179. wingspan_rl-1.0.0/src/wingspan/training/charts/text_helpers.py +84 -0
  180. wingspan_rl-1.0.0/src/wingspan/training/collect.py +589 -0
  181. wingspan_rl-1.0.0/src/wingspan/training/config.py +1366 -0
  182. wingspan_rl-1.0.0/src/wingspan/training/config_file.py +142 -0
  183. wingspan_rl-1.0.0/src/wingspan/training/configure/__init__.py +19 -0
  184. wingspan_rl-1.0.0/src/wingspan/training/configure/arch_diagram.py +1449 -0
  185. wingspan_rl-1.0.0/src/wingspan/training/configure/controller.py +838 -0
  186. wingspan_rl-1.0.0/src/wingspan/training/configure/fields.py +1772 -0
  187. wingspan_rl-1.0.0/src/wingspan/training/configure/keys.py +193 -0
  188. wingspan_rl-1.0.0/src/wingspan/training/configure/runs.py +398 -0
  189. wingspan_rl-1.0.0/src/wingspan/training/configure/screen.py +727 -0
  190. wingspan_rl-1.0.0/src/wingspan/training/configure/state.py +129 -0
  191. wingspan_rl-1.0.0/src/wingspan/training/configure/user_defaults.py +187 -0
  192. wingspan_rl-1.0.0/src/wingspan/training/convergence.py +215 -0
  193. wingspan_rl-1.0.0/src/wingspan/training/cpu_threads.py +44 -0
  194. wingspan_rl-1.0.0/src/wingspan/training/dashboard.py +959 -0
  195. wingspan_rl-1.0.0/src/wingspan/training/evaluate.py +306 -0
  196. wingspan_rl-1.0.0/src/wingspan/training/learner.py +1062 -0
  197. wingspan_rl-1.0.0/src/wingspan/training/loop.py +378 -0
  198. wingspan_rl-1.0.0/src/wingspan/training/loop_anneal.py +98 -0
  199. wingspan_rl-1.0.0/src/wingspan/training/loop_checkpoint.py +259 -0
  200. wingspan_rl-1.0.0/src/wingspan/training/loop_collect.py +187 -0
  201. wingspan_rl-1.0.0/src/wingspan/training/loop_eval.py +314 -0
  202. wingspan_rl-1.0.0/src/wingspan/training/loop_metrics.py +187 -0
  203. wingspan_rl-1.0.0/src/wingspan/training/loop_probe.py +91 -0
  204. wingspan_rl-1.0.0/src/wingspan/training/loop_resume.py +367 -0
  205. wingspan_rl-1.0.0/src/wingspan/training/loop_setup.py +246 -0
  206. wingspan_rl-1.0.0/src/wingspan/training/loop_target.py +163 -0
  207. wingspan_rl-1.0.0/src/wingspan/training/metrics.py +361 -0
  208. wingspan_rl-1.0.0/src/wingspan/training/metrics_log.py +79 -0
  209. wingspan_rl-1.0.0/src/wingspan/training/mp_collect.py +841 -0
  210. wingspan_rl-1.0.0/src/wingspan/training/policy.py +246 -0
  211. wingspan_rl-1.0.0/src/wingspan/training/returns.py +133 -0
  212. wingspan_rl-1.0.0/src/wingspan/training/runmeta.py +429 -0
  213. wingspan_rl-1.0.0/src/wingspan/training/runstate.py +687 -0
  214. wingspan_rl-1.0.0/src/wingspan/training/setup_learner.py +226 -0
  215. wingspan_rl-1.0.0/src/wingspan/training/setup_net.py +491 -0
  216. wingspan_rl-1.0.0/src/wingspan/training/setup_runmeta.py +117 -0
  217. wingspan_rl-1.0.0/src/wingspan/training/steps.py +58 -0
  218. wingspan_rl-1.0.0/src/wingspan/training/sysmon.py +60 -0
  219. wingspan_rl-1.0.0/src/wingspan/training/theme.py +202 -0
  220. wingspan_rl-1.0.0/src/wingspan/training/timestamps.py +205 -0
  221. wingspan_rl-1.0.0/src/wingspan/version.py +308 -0
  222. wingspan_rl-1.0.0/src/wingspan_rl.egg-info/PKG-INFO +420 -0
  223. wingspan_rl-1.0.0/src/wingspan_rl.egg-info/SOURCES.txt +398 -0
  224. wingspan_rl-1.0.0/src/wingspan_rl.egg-info/dependency_links.txt +1 -0
  225. wingspan_rl-1.0.0/src/wingspan_rl.egg-info/entry_points.txt +2 -0
  226. wingspan_rl-1.0.0/src/wingspan_rl.egg-info/requires.txt +17 -0
  227. wingspan_rl-1.0.0/src/wingspan_rl.egg-info/top_level.txt +1 -0
  228. wingspan_rl-1.0.0/tests/test_activation_log.py +178 -0
  229. wingspan_rl-1.0.0/tests/test_aid_advisor.py +456 -0
  230. wingspan_rl-1.0.0/tests/test_aid_console.py +94 -0
  231. wingspan_rl-1.0.0/tests/test_aid_counts_widgets.py +253 -0
  232. wingspan_rl-1.0.0/tests/test_aid_entry_widgets.py +353 -0
  233. wingspan_rl-1.0.0/tests/test_aid_oracle.py +582 -0
  234. wingspan_rl-1.0.0/tests/test_aid_relay.py +601 -0
  235. wingspan_rl-1.0.0/tests/test_aid_session.py +364 -0
  236. wingspan_rl-1.0.0/tests/test_aid_widgets.py +492 -0
  237. wingspan_rl-1.0.0/tests/test_analysis_probe.py +505 -0
  238. wingspan_rl-1.0.0/tests/test_anneal_schedule.py +472 -0
  239. wingspan_rl-1.0.0/tests/test_becomes_unplayable.py +865 -0
  240. wingspan_rl-1.0.0/tests/test_birdfeeder.py +542 -0
  241. wingspan_rl-1.0.0/tests/test_birds_no_eggs_integration.py +166 -0
  242. wingspan_rl-1.0.0/tests/test_bonus_categories.py +221 -0
  243. wingspan_rl-1.0.0/tests/test_bonus_linear_value.py +106 -0
  244. wingspan_rl-1.0.0/tests/test_bootstrap_opponent.py +370 -0
  245. wingspan_rl-1.0.0/tests/test_card_parse.py +153 -0
  246. wingspan_rl-1.0.0/tests/test_card_table_cache.py +132 -0
  247. wingspan_rl-1.0.0/tests/test_cards_lookup.py +166 -0
  248. wingspan_rl-1.0.0/tests/test_choice_landing_slot.py +246 -0
  249. wingspan_rl-1.0.0/tests/test_cli_multiplayer.py +202 -0
  250. wingspan_rl-1.0.0/tests/test_cli_setup_dialog.py +300 -0
  251. wingspan_rl-1.0.0/tests/test_cloud_runfile.py +75 -0
  252. wingspan_rl-1.0.0/tests/test_cloud_s3sync.py +73 -0
  253. wingspan_rl-1.0.0/tests/test_cloud_status.py +105 -0
  254. wingspan_rl-1.0.0/tests/test_collect_nplayers.py +90 -0
  255. wingspan_rl-1.0.0/tests/test_combine_gain_food.py +397 -0
  256. wingspan_rl-1.0.0/tests/test_commit_row_pricing.py +434 -0
  257. wingspan_rl-1.0.0/tests/test_compat_v1_0.py +282 -0
  258. wingspan_rl-1.0.0/tests/test_compat_v1_3.py +550 -0
  259. wingspan_rl-1.0.0/tests/test_compat_v1_4.py +1114 -0
  260. wingspan_rl-1.0.0/tests/test_config_file.py +170 -0
  261. wingspan_rl-1.0.0/tests/test_convergence_charts.py +297 -0
  262. wingspan_rl-1.0.0/tests/test_dagger.py +579 -0
  263. wingspan_rl-1.0.0/tests/test_decision_families.py +167 -0
  264. wingspan_rl-1.0.0/tests/test_decision_shapes.py +823 -0
  265. wingspan_rl-1.0.0/tests/test_device_split.py +374 -0
  266. wingspan_rl-1.0.0/tests/test_discard_egg_for_wild.py +222 -0
  267. wingspan_rl-1.0.0/tests/test_dynamic_bonus_scoring.py +101 -0
  268. wingspan_rl-1.0.0/tests/test_egg_goal_delta.py +264 -0
  269. wingspan_rl-1.0.0/tests/test_encode.py +1438 -0
  270. wingspan_rl-1.0.0/tests/test_encode_spec.py +403 -0
  271. wingspan_rl-1.0.0/tests/test_encode_viewer.py +410 -0
  272. wingspan_rl-1.0.0/tests/test_encoding_golden_n2.py +76 -0
  273. wingspan_rl-1.0.0/tests/test_encoding_layout_nplayers.py +201 -0
  274. wingspan_rl-1.0.0/tests/test_encoding_values_nplayers.py +273 -0
  275. wingspan_rl-1.0.0/tests/test_engine_agent_dispatch.py +111 -0
  276. wingspan_rl-1.0.0/tests/test_era_pinned_resume.py +398 -0
  277. wingspan_rl-1.0.0/tests/test_evaluate_core.py +90 -0
  278. wingspan_rl-1.0.0/tests/test_evaluate_nplayers.py +175 -0
  279. wingspan_rl-1.0.0/tests/test_extra_play.py +244 -0
  280. wingspan_rl-1.0.0/tests/test_food_payment.py +393 -0
  281. wingspan_rl-1.0.0/tests/test_forecast.py +312 -0
  282. wingspan_rl-1.0.0/tests/test_gain_food_choice.py +280 -0
  283. wingspan_rl-1.0.0/tests/test_game_log_csv.py +204 -0
  284. wingspan_rl-1.0.0/tests/test_game_log_html.py +906 -0
  285. wingspan_rl-1.0.0/tests/test_gamelog_jsonl.py +389 -0
  286. wingspan_rl-1.0.0/tests/test_gamelog_ledger.py +362 -0
  287. wingspan_rl-1.0.0/tests/test_gamelog_nplayers.py +181 -0
  288. wingspan_rl-1.0.0/tests/test_gamelog_render_text.py +186 -0
  289. wingspan_rl-1.0.0/tests/test_gamelog_serialize.py +301 -0
  290. wingspan_rl-1.0.0/tests/test_gamelog_summarize.py +395 -0
  291. wingspan_rl-1.0.0/tests/test_gamelog_tree.py +322 -0
  292. wingspan_rl-1.0.0/tests/test_goal_delta_ignoring_eggs.py +224 -0
  293. wingspan_rl-1.0.0/tests/test_habitat_action_track.py +535 -0
  294. wingspan_rl-1.0.0/tests/test_hand_pooling.py +290 -0
  295. wingspan_rl-1.0.0/tests/test_index_accuracy.py +209 -0
  296. wingspan_rl-1.0.0/tests/test_inspect_era.py +197 -0
  297. wingspan_rl-1.0.0/tests/test_instrumentation_config.py +82 -0
  298. wingspan_rl-1.0.0/tests/test_instrumentation_dispatch.py +220 -0
  299. wingspan_rl-1.0.0/tests/test_instrumentation_validation.py +44 -0
  300. wingspan_rl-1.0.0/tests/test_known_hand.py +359 -0
  301. wingspan_rl-1.0.0/tests/test_learner_minibatch.py +319 -0
  302. wingspan_rl-1.0.0/tests/test_learner_rewards.py +304 -0
  303. wingspan_rl-1.0.0/tests/test_loop_iteration_order.py +162 -0
  304. wingspan_rl-1.0.0/tests/test_loop_probe.py +384 -0
  305. wingspan_rl-1.0.0/tests/test_loop_setup_update.py +81 -0
  306. wingspan_rl-1.0.0/tests/test_main_action.py +160 -0
  307. wingspan_rl-1.0.0/tests/test_metrics_log.py +104 -0
  308. wingspan_rl-1.0.0/tests/test_model_and_self_play.py +372 -0
  309. wingspan_rl-1.0.0/tests/test_model_board_attention.py +1174 -0
  310. wingspan_rl-1.0.0/tests/test_model_nplayers.py +317 -0
  311. wingspan_rl-1.0.0/tests/test_move_goal_delta.py +198 -0
  312. wingspan_rl-1.0.0/tests/test_mp_collect.py +278 -0
  313. wingspan_rl-1.0.0/tests/test_multiplayer_engine.py +150 -0
  314. wingspan_rl-1.0.0/tests/test_play_bird_payment.py +172 -0
  315. wingspan_rl-1.0.0/tests/test_play_cli.py +219 -0
  316. wingspan_rl-1.0.0/tests/test_playability.py +625 -0
  317. wingspan_rl-1.0.0/tests/test_players_factory.py +146 -0
  318. wingspan_rl-1.0.0/tests/test_players_loaders.py +170 -0
  319. wingspan_rl-1.0.0/tests/test_players_spec.py +63 -0
  320. wingspan_rl-1.0.0/tests/test_power_coverage.py +50 -0
  321. wingspan_rl-1.0.0/tests/test_power_dispatch_edge_cases.py +126 -0
  322. wingspan_rl-1.0.0/tests/test_powers_all_lay_egg.py +570 -0
  323. wingspan_rl-1.0.0/tests/test_powers_all_lay_nest.py +31 -0
  324. wingspan_rl-1.0.0/tests/test_powers_autoaccept.py +325 -0
  325. wingspan_rl-1.0.0/tests/test_powers_birdfeeder_choice.py +29 -0
  326. wingspan_rl-1.0.0/tests/test_powers_cache_food.py +74 -0
  327. wingspan_rl-1.0.0/tests/test_powers_coverage_batch.py +659 -0
  328. wingspan_rl-1.0.0/tests/test_powers_discard_egg_gain_wild.py +24 -0
  329. wingspan_rl-1.0.0/tests/test_powers_discard_food_tuck.py +25 -0
  330. wingspan_rl-1.0.0/tests/test_powers_draft_cards.py +15 -0
  331. wingspan_rl-1.0.0/tests/test_powers_draw_bonus_keep.py +19 -0
  332. wingspan_rl-1.0.0/tests/test_powers_draw_tray_all.py +15 -0
  333. wingspan_rl-1.0.0/tests/test_powers_each_player_birdfeeder.py +21 -0
  334. wingspan_rl-1.0.0/tests/test_powers_each_player_die.py +521 -0
  335. wingspan_rl-1.0.0/tests/test_powers_gain_all_birdfeeder.py +21 -0
  336. wingspan_rl-1.0.0/tests/test_powers_gain_birdfeeder_die.py +15 -0
  337. wingspan_rl-1.0.0/tests/test_powers_house_wren.py +15 -0
  338. wingspan_rl-1.0.0/tests/test_powers_lay_egg_all_nest.py +27 -0
  339. wingspan_rl-1.0.0/tests/test_powers_misc_unique.py +1084 -0
  340. wingspan_rl-1.0.0/tests/test_powers_pink_predator_die.py +20 -0
  341. wingspan_rl-1.0.0/tests/test_powers_pink_reactive.py +322 -0
  342. wingspan_rl-1.0.0/tests/test_powers_predator_tuck.py +34 -0
  343. wingspan_rl-1.0.0/tests/test_powers_repeat_predator.py +11 -0
  344. wingspan_rl-1.0.0/tests/test_powers_trade_food.py +11 -0
  345. wingspan_rl-1.0.0/tests/test_powers_tuck_conditional.py +564 -0
  346. wingspan_rl-1.0.0/tests/test_ppo_gae.py +342 -0
  347. wingspan_rl-1.0.0/tests/test_quality_gate_args.py +156 -0
  348. wingspan_rl-1.0.0/tests/test_release.py +210 -0
  349. wingspan_rl-1.0.0/tests/test_report_panels.py +515 -0
  350. wingspan_rl-1.0.0/tests/test_reporting_humanize.py +675 -0
  351. wingspan_rl-1.0.0/tests/test_reporting_nplayers.py +144 -0
  352. wingspan_rl-1.0.0/tests/test_research_game_stats.py +608 -0
  353. wingspan_rl-1.0.0/tests/test_research_progress.py +110 -0
  354. wingspan_rl-1.0.0/tests/test_research_setup_keep.py +416 -0
  355. wingspan_rl-1.0.0/tests/test_returns.py +263 -0
  356. wingspan_rl-1.0.0/tests/test_round_goal_coverage.py +103 -0
  357. wingspan_rl-1.0.0/tests/test_round_goal_freeze.py +130 -0
  358. wingspan_rl-1.0.0/tests/test_round_goal_scoring.py +100 -0
  359. wingspan_rl-1.0.0/tests/test_run_config.py +433 -0
  360. wingspan_rl-1.0.0/tests/test_run_metadata.py +189 -0
  361. wingspan_rl-1.0.0/tests/test_scoring_nplayer.py +215 -0
  362. wingspan_rl-1.0.0/tests/test_scoring_played_with_eggs.py +327 -0
  363. wingspan_rl-1.0.0/tests/test_setup_actor_critic.py +433 -0
  364. wingspan_rl-1.0.0/tests/test_setup_arch_key_restart.py +164 -0
  365. wingspan_rl-1.0.0/tests/test_setup_candidates.py +98 -0
  366. wingspan_rl-1.0.0/tests/test_setup_collect.py +89 -0
  367. wingspan_rl-1.0.0/tests/test_setup_data_store_guard.py +5 -0
  368. wingspan_rl-1.0.0/tests/test_setup_embedders.py +172 -0
  369. wingspan_rl-1.0.0/tests/test_setup_encode.py +179 -0
  370. wingspan_rl-1.0.0/tests/test_setup_encode_pricing.py +323 -0
  371. wingspan_rl-1.0.0/tests/test_setup_feature_off.py +37 -0
  372. wingspan_rl-1.0.0/tests/test_setup_flow.py +239 -0
  373. wingspan_rl-1.0.0/tests/test_setup_generate.py +81 -0
  374. wingspan_rl-1.0.0/tests/test_setup_net.py +213 -0
  375. wingspan_rl-1.0.0/tests/test_setup_state_construction.py +74 -0
  376. wingspan_rl-1.0.0/tests/test_setup_stripes.py +178 -0
  377. wingspan_rl-1.0.0/tests/test_setup_train_cpu.py +67 -0
  378. wingspan_rl-1.0.0/tests/test_smoke.py +25 -0
  379. wingspan_rl-1.0.0/tests/test_split_setup_bonus.py +132 -0
  380. wingspan_rl-1.0.0/tests/test_split_setup_food.py +223 -0
  381. wingspan_rl-1.0.0/tests/test_state_food_unlock.py +177 -0
  382. wingspan_rl-1.0.0/tests/test_state_known_hand.py +159 -0
  383. wingspan_rl-1.0.0/tests/test_state_seams.py +144 -0
  384. wingspan_rl-1.0.0/tests/test_step_timestamps.py +189 -0
  385. wingspan_rl-1.0.0/tests/test_timeline_chart.py +550 -0
  386. wingspan_rl-1.0.0/tests/test_tournament_dashboard.py +116 -0
  387. wingspan_rl-1.0.0/tests/test_tournament_discovery.py +115 -0
  388. wingspan_rl-1.0.0/tests/test_tournament_elo.py +67 -0
  389. wingspan_rl-1.0.0/tests/test_tournament_regime.py +149 -0
  390. wingspan_rl-1.0.0/tests/test_tournament_results.py +90 -0
  391. wingspan_rl-1.0.0/tests/test_tournament_schedule.py +63 -0
  392. wingspan_rl-1.0.0/tests/test_tournament_smoke.py +142 -0
  393. wingspan_rl-1.0.0/tests/test_training_app_config.py +340 -0
  394. wingspan_rl-1.0.0/tests/test_training_configurator.py +2322 -0
  395. wingspan_rl-1.0.0/tests/test_training_dashboard.py +671 -0
  396. wingspan_rl-1.0.0/tests/test_training_iteration_n3.py +108 -0
  397. wingspan_rl-1.0.0/tests/test_training_summary.py +445 -0
  398. wingspan_rl-1.0.0/tests/test_tray_refill.py +373 -0
  399. wingspan_rl-1.0.0/tests/test_tray_set_embedding.py +219 -0
  400. wingspan_rl-1.0.0/tests/test_version.py +100 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Brian Boucher
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,420 @@
1
+ Metadata-Version: 2.4
2
+ Name: wingspan-rl
3
+ Version: 1.0.0
4
+ Summary: Wingspan board-game simulator and RL training pipeline, with a bundled trained model
5
+ License: MIT
6
+ Project-URL: Homepage, https://github.com/bmboucher/wingspan
7
+ Project-URL: Repository, https://github.com/bmboucher/wingspan
8
+ Classifier: Programming Language :: Python :: 3.12
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Topic :: Games/Entertainment :: Board Games
11
+ Requires-Python: >=3.12
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: numpy>=1.24
15
+ Requires-Dist: torch>=2.0
16
+ Requires-Dist: pydantic>=2.0
17
+ Requires-Dist: pyyaml
18
+ Requires-Dist: rich>=13.7
19
+ Requires-Dist: psutil>=5.9
20
+ Requires-Dist: boto3>=1.34
21
+ Provides-Extra: dev
22
+ Requires-Dist: pytest>=7.0; extra == "dev"
23
+ Requires-Dist: pytest-cov>=5.0; extra == "dev"
24
+ Requires-Dist: pytest-xdist>=3.5; extra == "dev"
25
+ Requires-Dist: pyright>=1.1.400; extra == "dev"
26
+ Requires-Dist: black>=24.0; extra == "dev"
27
+ Requires-Dist: isort>=9.0; extra == "dev"
28
+ Requires-Dist: boto3-stubs[s3]; extra == "dev"
29
+ Requires-Dist: types-PyYAML; extra == "dev"
30
+ Dynamic: license-file
31
+
32
+ # Wingspan
33
+
34
+ A simulator and reinforcement-learning training pipeline for the **core-set**
35
+ board game [Wingspan](https://stonemaiergames.com/games/wingspan/), supporting
36
+ **2-4 players from the CLI** (the engine/config support up to 5; see
37
+ [docs/PROJECT.md](docs/PROJECT.md)). A given trained network plays at exactly
38
+ the seat count it was trained at (`num_players`, a per-run config field);
39
+ `wingspan play` seats any mix of human, random, and AI players up to that
40
+ count. You can play a full
41
+ game from the terminal, run quick automated games for logs or debugging, watch
42
+ a trained network play (against itself, a frozen past self, or a random
43
+ agent), and train a neural-network agent by self-play while watching it
44
+ improve on a live dashboard.
45
+
46
+ ## Setup
47
+
48
+ Requires **Python 3.12+**. Install the released package from PyPI:
49
+
50
+ ```
51
+ pip install wingspan-rl
52
+ ```
53
+
54
+ and play a game with the bundled trained model right away:
55
+
56
+ ```
57
+ wingspan play --html game.html
58
+ ```
59
+
60
+ PyTorch is the heavy transitive dependency. For a much smaller install, put the
61
+ CPU-only wheel in first (see the
62
+ [PyTorch install selector](https://pytorch.org/get-started/locally/); e.g.
63
+ `pip install torch --index-url https://download.pytorch.org/whl/cpu`) and then
64
+ `pip install wingspan-rl`.
65
+
66
+ ### Bundled model
67
+
68
+ The package ships a trained model (artifact era 1.5, trained by self-play,
69
+ **2-player only**). When `--p0` / `--p1` are `last` / `best` / `opponent` and
70
+ no such checkpoint exists in the checkpoint directory, `wingspan play` falls
71
+ back to the bundled model and says so; `--p0 release` selects it explicitly.
72
+ `--p2` / `--p3` (3-4 player tables) require a model you trained yourself.
73
+ `wingspan --version` shows the package version and the bundled model's run.
74
+
75
+ ### Development install
76
+
77
+ To work on the code, install this checkout (editable) instead:
78
+
79
+ ```
80
+ pip install -e .
81
+ ```
82
+
83
+ This pulls in everything needed to play and to train (PyTorch, NumPy, Pydantic,
84
+ rich). For the test suite and developer tooling (pyright, black, isort, pytest):
85
+
86
+ ```
87
+ pip install -e ".[dev]"
88
+ ```
89
+
90
+ Training runs on **CPU**: self-play collection is CPU-bound and fans out across
91
+ worker processes, and the gradient update is small, so the whole pipeline is
92
+ designed for CPU. (A CUDA PyTorch build still works for one-off experiments, but
93
+ CPU is the supported path.)
94
+
95
+ ## Play games
96
+
97
+ One command runs every matchup — interactive play, quick automated games, and
98
+ trained-AI matches. Set each seat with `--p0` / `--p1` / `--p2` / `--p3`; the
99
+ value is `human` (interactive play in the terminal), `random` (the
100
+ uniform-random agent), a named checkpoint (`last`, `best`, or `opponent`,
101
+ resolved against `--checkpoint-dir`), a path to a `.pt` file, or a run
102
+ directory (its `last.pt` is seated). `--p0` / `--p1` are always active
103
+ (2-seat default); `--p2` / `--p3` add a 3rd / 4th seat and must be filled
104
+ contiguously (`--p3` without `--p2` is rejected). A trained checkpoint can
105
+ only be seated at the table size it was trained at (`num_players`) — mixing,
106
+ say, a 2-player checkpoint into a 3-seat table is refused up front. The
107
+ default matchup is `last` vs `last` — the most recent trained model playing
108
+ itself — so the bare command needs a trained model (see **Train an agent**);
109
+ use `--p0 human --p1 random` to play without one:
110
+
111
+ ```
112
+ wingspan play # latest trained model vs itself
113
+ wingspan play --p0 human --p1 random # play interactively vs a random opponent
114
+ wingspan play --p0 human --p1 human # two humans, hotseat on one keyboard
115
+ wingspan play --p0 human --p1 best --seed 42 # take on the strongest checkpoint, reproducible deal
116
+ wingspan play --p0 random --p1 random --games 5 --log games.log # quick automated games -> games.log.0 ..
117
+ wingspan play --p0 best --p1 opponent --games 10 # best vs the frozen ladder rung
118
+ wingspan play --p0 best --p1 best --greedy --log ai.log # best vs itself, argmax play
119
+ wingspan play --p0 runs/exp/last.pt --p1 random # a checkpoint by path
120
+ wingspan play --p0 best --p1 best --html game.html # navigable HTML game-log viewer
121
+ wingspan play --p0 random --p1 random --p2 random --html game.html # 3-seat random game, HTML log
122
+ wingspan play --p0 best --p1 best --jsonl game.jsonl # flat structured log, for analysis
123
+ ```
124
+
125
+ A `human` seat gets a numbered menu for every choice the rules require — pick a
126
+ bird, choose a habitat, pay food, lay eggs, and so on. When a seat is
127
+ AI-driven, every genuine decision is annotated in the game log with the
128
+ policy's ranked probability distribution over the legal options, turning the
129
+ log into a move-by-move readout of what the network was "thinking", and
130
+ `--greedy` makes AI seats take the argmax option instead of sampling. `--games
131
+ N` plays a series (game *i* deals with `--seed + i`), `--log` writes a
132
+ structured plaintext game log per game (sourced from the event tree — the
133
+ same log the `--jsonl` and `--html` outputs are built from), and `--quiet`
134
+ suppresses the per-game summary. For the older raw `engine.log` dump, use
135
+ `--debug-log FILE` instead; by default it splits into `FILE_p0.log` /
136
+ `FILE_p1.log` (one file per seat), or pass `--collate` for a single
137
+ interleaved file. `--device` selects the torch device AI seats run
138
+ inference on (`cpu` by default). `--html FILE` additionally writes a
139
+ self-contained HTML viewer that replays the
140
+ game one phase/turn at a time — prev/next arrows, a per-seat view toggle
141
+ (`P0 / Both / P1` at 2 seats, `All / P0 / P1 / ...` at 3+ seats), 3x5 board
142
+ grids pinned at the top, and a collapsible decision log beneath (for a
143
+ `--games` series the game index is inserted before the extension, `game.html ->
144
+ game.0.html`). The **Timeline** modal (opened via the toolbar button) shows
145
+ per-seat score lines and per-seat critic/target return lines; it includes a
146
+ **⬇ CSV** download link that yields one row per in-game decision with columns
147
+ for timestamp, player, per-seat scores, and per-seat critic/target values
148
+ (each seat's own future-return margin vs its best other seat, in VP — not
149
+ projected onto any other seat's axis).
150
+
151
+ ### The flat structured log
152
+
153
+ `--jsonl FILE` writes the same game as data: one JSON object per line — a header
154
+ row per match, then one row per event and sub-event. Every state change the
155
+ engine made is there with its own typed columns (`card`, `food`, `amount`,
156
+ `bird`, `habitat`), each event row carries the same at-a-glance header the HTML
157
+ viewer shows, and the tree's nesting survives as `event_id` / `parent_id` /
158
+ `depth` columns. Unlike `--log`, a `--games` series appends to the *one* file,
159
+ keyed by `game_id`.
160
+
161
+ ```python
162
+ import pandas
163
+
164
+ rows = pandas.read_json("game.jsonl", lines=True)
165
+ rows[rows.kind == "lay_egg"].groupby("player_id")["count"].sum()
166
+ ```
167
+
168
+ `wingspan tournament --jsonl games.jsonl` does the same for every game of a
169
+ round-robin. See [`docs/GAMELOG.md`](docs/GAMELOG.md) for the full row schema.
170
+
171
+ ## Train an agent
172
+
173
+ The training app is **FLIGHT PLAN** — a full-screen TUI that opens on a
174
+ configuration screen first, then transitions to a live `top`-style dashboard
175
+ once a run is started or resumed:
176
+
177
+ ```
178
+ wingspan dashboard # open FLIGHT PLAN (always starts in config)
179
+ wingspan dashboard --collect-device cpu --train-device cuda # CPU pool feeding a GPU learner
180
+ wingspan dashboard --train-device cpu # CPU-only
181
+ wingspan dashboard --games-per-iter 256 --eval-every 5 --eval-games 128
182
+ wingspan dashboard --config runs/my_run.json # open the config screen seeded from FILE
183
+ wingspan dashboard --config runs/my_run.json --start # skip the screen and launch immediately
184
+ ```
185
+
186
+ `--config FILE` supplies the whole run config from a file instead of building
187
+ it from flags — a dated `run_config_<stamp>.json` artifact, a
188
+ `configurator_defaults.json`-style envelope, a cloud run-file's `train:`
189
+ block, or a bare `RunConfig` JSON/YAML dump (see
190
+ `wingspan.training.config_file`). Only the five run-identity flags
191
+ (`--checkpoint-dir`, `--run-name`, `--collect-device`, `--train-device`,
192
+ `--resume`/`--no-resume`) may be combined with `--config` to override the
193
+ file; any other flag is a usage error, since the file already speaks for
194
+ every other field. Without `--start` the config screen still opens, seeded
195
+ from the file, so you can review or tweak it before pressing Start; with
196
+ `--start` the run launches immediately and headlessly — it never archives or
197
+ overwrites an existing run on its own, so an incompatible or resume-disabled
198
+ checkpoint directory is refused with a message pointing you at the config
199
+ screen's archive action instead.
200
+
201
+ The config screen lets you tune every hyperparameter (learning rate,
202
+ games/iteration, evaluation cadence, target-iteration milestone, network
203
+ width, …) and manage the runs already in the checkpoint directory. Arrow keys
204
+ move between fields, ←/→ nudge a value (or cycle a choice), and Enter edits
205
+ one directly; every value is validated as you type. The screen shows whether
206
+ the directory holds a resumable run and flags which edits would force a fresh
207
+ start (changing the network width can't reuse old weights). From there:
208
+
209
+ - **Start** resumes a compatible run, or starts a fresh one in an empty
210
+ directory.
211
+ - **New run** (or starting after an incompatible edit) prompts you to
212
+ **archive** the existing run to `checkpoints/archive/<label>/` — preserving
213
+ its checkpoints, metrics, and log — before the new run begins, so a long
214
+ training run is never silently overwritten.
215
+
216
+ Once started, the dashboard runs until you press **Ctrl+C**, which asks it to
217
+ finish the current game, save a final checkpoint, and print a summary. As
218
+ strength improves the evaluation opponent advances from the random agent to
219
+ frozen past selves (a self-play ladder). Checkpoints (`last.pt`, `best.pt`, the
220
+ frozen `opponent.pt`), a per-iteration `metrics.jsonl`, and a per-game
221
+ `games.jsonl` are written to the checkpoint directory (`checkpoints/` by
222
+ default; change with `--checkpoint-dir`), and runs are resumable. Pass
223
+ `--iterations N` to stop automatically after N rounds instead.
224
+
225
+ You can also set a **target-iteration** milestone (in the config screen):
226
+ the run pauses there, runs a large fixed-model self-play evaluation, and waits
227
+ for you to **[C]ontinue** — optionally entering a new target — or **[E]nd** the
228
+ run and return to the config screen.
229
+
230
+ See [docs/TRAINING.md](docs/TRAINING.md) for the training program and
231
+ [docs/DECISIONS.md](docs/DECISIONS.md) for the per-decision modelling direction.
232
+
233
+ ## Run a tournament
234
+
235
+ `wingspan tournament` runs a round-robin between any mix of trained AIs and the
236
+ random agent, tracks Elo ratings, and writes a JSON report.
237
+
238
+ ```
239
+ wingspan tournament # interactive picker (default)
240
+ wingspan tournament --base-dir checkpoints # picker scans a different dir
241
+ wingspan tournament --no-picker --ai runs/exp1 --ai runs/exp2 # skip picker; explicit dirs
242
+ wingspan tournament --no-picker --ai runs/exp1 --include-random # add the random agent
243
+ wingspan tournament --no-picker --ai runs/exp1 --ai runs/exp2 \
244
+ --games-per-pair 64 --out results.json # 64 mirrored games per pair
245
+ wingspan tournament --quiet # no live UI; periodic progress only
246
+ ```
247
+
248
+ By default an interactive picker scans `checkpoints/` and lets you choose
249
+ competitors with arrow keys. Pass `--no-picker` to specify them via `--ai
250
+ <checkpoint-dir>` flags (repeatable) and optionally `--include-random`. Each
251
+ pair plays `--games-per-pair` games (default 32) as mirrored deals. The live
252
+ dashboard (skipped with `--quiet`) shows Elo ratings and W-L-T updating as
253
+ games finish; **q** / Ctrl+C stops after the current games complete. The final
254
+ standings and the full JSON report are written to `--out`
255
+ (`tournament_report.json` by default).
256
+
257
+ The tournament is **2-seat only for now**: N-seat scheduling and Elo are
258
+ deferred scope, so a checkpoint trained at `num_players != 2` is refused with
259
+ a clear error rather than being silently seated. 3-4 player checkpoints still
260
+ play fine through `wingspan play`; only the round-robin tournament format is
261
+ restricted.
262
+
263
+ ## Get live advice for a physical game
264
+
265
+ `wingspan aid` is a live assistant for a 2-player game played at a physical
266
+ table, not on the computer. It runs a terminal session that starts at game
267
+ setup — you enter your dealt hand, bonus cards, round goals, the initial
268
+ tray, and the feeder roll — and a trained model then ranks your options for
269
+ every decision you make for the rest of the game, showing its top picks and
270
+ expected VP margin before you confirm what you actually played:
271
+
272
+ ```
273
+ wingspan aid # advise with the latest trained model
274
+ wingspan aid best --seed 42 # advise with the strongest checkpoint
275
+ wingspan aid --log session.log --jsonl session.jsonl # write session logs
276
+ wingspan aid --trust-me # auto-play the model's picks for your seat
277
+ ```
278
+
279
+ In a real terminal, card prompts are type-to-filter pickers (type a few
280
+ letters to narrow the list, arrows + Enter to select; Enter on an empty
281
+ query marks a revealed card as face-down where that's allowed) and dice are
282
+ entered as per-face counts (arrows to move between faces and adjust, Enter
283
+ to accept once the total matches). When stdio is scripted or redirected the
284
+ same prompts fall back to plain text: comma-separated card names and
285
+ space-separated die faces.
286
+
287
+ The opponent's moves are relayed by you as they happen — the assistant asks
288
+ "what did they do?" for each of their decisions and tracks their hand and
289
+ bonus cards as hidden counts (never guessing their identities), matching
290
+ what the encoder itself is allowed to see. At the end of the final round it
291
+ offers to enter the opponent's bonus card(s) for exact scoring; skipping
292
+ that leaves their bonus VP as a placeholder to count manually. Only
293
+ checkpoints trained at `num_players=2` can drive the advisor seat, and a
294
+ session runs setup-to-end with no mid-game entry or undo.
295
+
296
+ Pass `--trust-me` to skip confirming your own moves: the model's top pick is
297
+ committed automatically for your seat, so you only sit through setup entry,
298
+ oracle reveals/dice, and relaying the opponent's moves — the recommendation
299
+ display still prints first, since under `--trust-me` it's what tells you
300
+ which move to actually execute at the table.
301
+
302
+ ## Run a research study
303
+
304
+ `wingspan research` hosts the offline, fixed-model studies from
305
+ [docs/RESEARCH.md](docs/RESEARCH.md). The first is the setup keep-rate
306
+ **experience study**: it deals random setups, asks the run's setup model which
307
+ birds it keeps, and writes one CSV row per dealt bird (an *exposure*) with a
308
+ 0/1 `kept` flag (the *observation*), so a card's keep rate is
309
+ `sum(kept) / count(rows)` and pivots by any other column.
310
+
311
+ ```
312
+ wingspan research setup-keep # 1000 setups from checkpoints/ -> setup_keep.csv
313
+ wingspan research setup-keep --setups 20000 --workers 8 # fan the deals across 8 processes
314
+ wingspan research setup-keep --checkpoint-dir runs/exp1 --out exp1_keep.csv
315
+ wingspan research setup-keep --temperature 1.0 # sample the keep instead of taking the argmax
316
+ ```
317
+
318
+ Each setup is one seat of a fresh seeded game dealt exactly as the engine deals
319
+ it (tray, feeder roll, round goals, five birds, two bonus cards); games use
320
+ consecutive seeds from `--seed`, so a study can be extended by continuing the
321
+ range, and the CSV is identical whether it ran in-process or under `--workers`.
322
+ Every row carries the exposure key (`setup_id`, `game_seed`, `seat`,
323
+ `card_slot`), the observation (`kept`, the policy's marginal `keep_prob`,
324
+ `n_kept`, the critic's `deal_value`, `bonus_kept`), the card's printed
325
+ attributes (points, egg limit, wingspan, nest, power color, habitats, per-food
326
+ cost, flocking/predator, effect kinds), and the rest of the setup (`hand`,
327
+ `tray_1..3`, the six `feeder_*` die counts, `goal_1..4` with categories,
328
+ `bonus_1`/`bonus_2`). The seat count defaults to the run's trained
329
+ `num_players`; the run must have trained a setup model (`setup.pt`).
330
+
331
+ The second is the **game-stats study**: greedy self-play of one run's model
332
+ against itself (its setup net resolves the opening keep), capturing one CSV row
333
+ per seat per game — the score after every turn (0–26, in both a projected and a
334
+ locked-in round-goal convention), the main action chosen on every turn, the
335
+ winner/tie, and the six-way final score breakdown. Alongside the raw table it
336
+ writes chart-ready aggregates (per-turn mean/CI/percentile score curves and
337
+ action frequencies for all vs. won games, the category breakdown as CSV and
338
+ markdown, paired final scores per game) plus a self-contained `preview.html`.
339
+ With `--blog-snippets` it also writes `blog_snippets/` — one ready-to-paste
340
+ Plotly embed block per figure for a markdown blog post (the scattergram
341
+ subsampled to keep the page light), plus the pinned CDN `<script>` tag.
342
+
343
+ ```
344
+ wingspan research game-stats --out stats/ # 10k games from checkpoints/last.pt
345
+ wingspan research game-stats --games 100000 --workers 10 --out stats/
346
+ wingspan research game-stats --aggregate-only stats/games_raw.csv --out stats/ --blog-snippets
347
+ ```
348
+
349
+ ## Probe a model's architecture
350
+
351
+ `wingspan analysis probe` measures how much each part of a trained network's
352
+ architecture actually matters: it self-plays a sample of on-distribution
353
+ decisions, then reports board-attention behavior and ablation sensitivity
354
+ (zero / uniform / per-head knockout, scored by policy KL and value shift),
355
+ trunk and choice-encoder layer capacity (effective rank, linear
356
+ predictability, dead units), and which trunk input groups the network reads
357
+ from.
358
+
359
+ ```
360
+ wingspan analysis probe last --games 50 --reference best # probe the latest checkpoint, compare against best.pt
361
+ ```
362
+
363
+ See `docs/TRAINING.md` "Representation diagnostics" for how to read the
364
+ numbers and `src/wingspan/analysis/INDEX.md` for the package's caveats.
365
+
366
+ ## Review a finished run
367
+
368
+ `training_summary.html` is a standalone, interactive HTML page charting a
369
+ run's `metrics.jsonl` progression end to end: strength vs. the reference
370
+ opponent, score composition, loss curves, throughput, and — when the run used
371
+ them — the setup-model and architecture-probe readouts. Every chart is a
372
+ `Plotly.js` chart (pan/zoom/hover), unlike the live dashboard's fixed-size
373
+ terminal charts.
374
+
375
+ The page is written automatically at the target milestone, alongside
376
+ `final_eval_<n>.json`, and can be regenerated on demand for any run directory
377
+ with `wingspan summary [RUN_DIR] [--out FILE]` (`RUN_DIR` defaults to
378
+ `checkpoints`). Because the charts load Plotly from its CDN
379
+ (`cdn.plot.ly`), viewing the page needs internet access.
380
+
381
+ ## Installed commands
382
+
383
+ After `pip install -e .` all tools are available through a single `wingspan`
384
+ command with subcommands:
385
+
386
+ | Command | What it does |
387
+ | ----------------------- | ------------------------------------------------------- |
388
+ | `wingspan play` | Games between any mix of human, random, and AI seats |
389
+ | `wingspan aid` | Live assistant for a physical 2-player game |
390
+ | `wingspan dashboard` | FLIGHT PLAN: config screen → live training dashboard |
391
+ | `wingspan tournament` | Round-robin tournament between trained AIs |
392
+ | `wingspan inspect` | Model introspection report (vectors, architecture, params) |
393
+ | `wingspan summary` | Training-progression HTML report for a run directory |
394
+ | `wingspan cloud` | Headless S3-persisted training (container use) |
395
+ | `wingspan monitor` | FLOCK WATCH: live roster of cloud runs |
396
+ | `wingspan research` | Offline research studies (setup keep-rate, game-stats) |
397
+ | `wingspan analysis` | Architecture-importance probe over a trained checkpoint |
398
+
399
+ Run `wingspan --help` for the full list, or `wingspan <command> --help` for
400
+ per-command usage.
401
+
402
+ ## Tests
403
+
404
+ Run the full quality gate (pyright + isort + black + pytest):
405
+
406
+ ```
407
+ bash scripts/quality_gate.sh
408
+ ```
409
+
410
+ See `CLAUDE.md` for the gate's section flags and pass-through argument reference.
411
+
412
+ ## Further reading
413
+
414
+ | File | What it covers |
415
+ | ---- | -------------- |
416
+ | [docs/PROJECT.md](docs/PROJECT.md) | Full package layout and what's modelled |
417
+ | [docs/DECISIONS.md](docs/DECISIONS.md) | Decision/choice taxonomy, `ALL_DECISION_CLASSES` ordering, featurization |
418
+ | [docs/BIRDS.md](docs/BIRDS.md) | Every bird power: `EffectKind` patterns, handler mappings, implementation gaps |
419
+ | [docs/TRAINING.md](docs/TRAINING.md) | Training program, hyperparameter guidance, eval ladder |
420
+ | [docs/RESEARCH.md](docs/RESEARCH.md) | Research agenda, per-project feasibility analysis |