gammonview 2.0.2__tar.gz → 3.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 (303) hide show
  1. {gammonview-2.0.2 → gammonview-3.0.0}/CHANGELOG.md +139 -0
  2. {gammonview-2.0.2 → gammonview-3.0.0}/CLAUDE.md +148 -55
  3. {gammonview-2.0.2 → gammonview-3.0.0}/PKG-INFO +34 -47
  4. {gammonview-2.0.2 → gammonview-3.0.0}/README.md +33 -46
  5. {gammonview-2.0.2 → gammonview-3.0.0}/docs/CLI.md +151 -94
  6. {gammonview-2.0.2 → gammonview-3.0.0}/docs/OGXM_V2_PROFILE.md +3 -1
  7. {gammonview-2.0.2 → gammonview-3.0.0}/docs/PRESET_ACCURACY.md +8 -0
  8. gammonview-3.0.0/experiments/2026-10-21-wc2-corpus.md +118 -0
  9. {gammonview-2.0.2 → gammonview-3.0.0}/gvanalysis/__init__.py +11 -1
  10. {gammonview-2.0.2 → gammonview-3.0.0}/gvanalysis/batch.py +6 -1
  11. gammonview-3.0.0/gvanalysis/checker_eval.py +656 -0
  12. {gammonview-2.0.2 → gammonview-3.0.0}/gvanalysis/game_eval.py +209 -19
  13. {gammonview-2.0.2 → gammonview-3.0.0}/gvanalysis/match.py +78 -22
  14. {gammonview-2.0.2 → gammonview-3.0.0}/gvanalysis/ogxm_reconstructor.py +50 -2
  15. {gammonview-2.0.2 → gammonview-3.0.0}/gvanalysis/presets.py +385 -94
  16. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat/export.py +27 -0
  17. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat/merge.py +7 -4
  18. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat/ogxm2.py +14 -2
  19. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat/ogxm2_writer.py +6 -1
  20. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat/stats.py +22 -1
  21. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat/xg.py +50 -1
  22. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/package.json +1 -1
  23. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/src/export.js +27 -3
  24. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/src/merge.js +7 -4
  25. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/src/ogxm2.js +14 -2
  26. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/src/ogxm2_writer.js +5 -1
  27. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/src/stats.js +25 -0
  28. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/src/xg2gva.js +44 -1
  29. gammonview-3.0.0/gvformat-js/test/fixtures/ogxm2/annos-docs.json +1 -0
  30. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/fixtures/ogxm2/annos-sha256.json +4 -4
  31. gammonview-3.0.0/gvformat-js/test/fixtures/ogxm2/blocks-docs.json +1 -0
  32. gammonview-3.0.0/gvformat-js/test/fixtures/ogxm2/blocks-sha256.json +42 -0
  33. gammonview-3.0.0/gvformat-js/test/fixtures/ogxm2/blocks.expected.json +1 -0
  34. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/fixtures/ogxm2/blocks.ogxm +0 -0
  35. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/test-ogxm2-writer.js +16 -0
  36. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/test-xg-eval-levels.js +32 -0
  37. gammonview-3.0.0/tests/golden/3WNK_g1Z-PLsh_HyvQ5j4a.fast.gva +1 -0
  38. {gammonview-2.0.2 → gammonview-3.0.0}/tests/golden/3WNK_g1Z-PLsh_HyvQ5j4a.fast.gvab +0 -0
  39. gammonview-3.0.0/tests/golden/5nqfGw9bWG3deTaU.fast.gva +1 -0
  40. {gammonview-2.0.2 → gammonview-3.0.0}/tests/golden/5nqfGw9bWG3deTaU.fast.gvab +0 -0
  41. gammonview-3.0.0/tests/golden/B4_SrGcsKAQmoTyHlgJCbM.fast.gva +1 -0
  42. {gammonview-2.0.2 → gammonview-3.0.0}/tests/golden/B4_SrGcsKAQmoTyHlgJCbM.fast.gvab +0 -0
  43. gammonview-3.0.0/tests/golden/MYdvw1qGuyaRUH__.fast.gva +1 -0
  44. {gammonview-2.0.2 → gammonview-3.0.0}/tests/golden/MYdvw1qGuyaRUH__.fast.gvab +0 -0
  45. {gammonview-2.0.2 → gammonview-3.0.0}/tests/golden/PROVENANCE.json +1 -1
  46. gammonview-3.0.0/tests/golden/baR-U643iUDpvzTC.fast.gva +1 -0
  47. {gammonview-2.0.2 → gammonview-3.0.0}/tests/golden/baR-U643iUDpvzTC.fast.gvab +0 -0
  48. {gammonview-2.0.2 → gammonview-3.0.0}/tests/run_all.py +1 -0
  49. {gammonview-2.0.2 → gammonview-3.0.0}/tests/test_cube_tiers.py +23 -5
  50. {gammonview-2.0.2 → gammonview-3.0.0}/tests/test_docs_presets.py +7 -2
  51. {gammonview-2.0.2 → gammonview-3.0.0}/tests/test_ogxm2_reference.py +16 -5
  52. {gammonview-2.0.2 → gammonview-3.0.0}/tests/test_ogxm2_writer.py +20 -1
  53. {gammonview-2.0.2 → gammonview-3.0.0}/tests/test_ogxm_pipeline.py +3 -1
  54. gammonview-3.0.0/tests/test_presets.py +313 -0
  55. gammonview-3.0.0/tests/test_resign_errors.py +329 -0
  56. {gammonview-2.0.2 → gammonview-3.0.0}/tests/test_screened_checker.py +186 -1
  57. {gammonview-2.0.2 → gammonview-3.0.0}/tests/test_xg_eval_levels.py +34 -0
  58. gammonview-2.0.2/gvanalysis/checker_eval.py +0 -378
  59. gammonview-2.0.2/gvformat-js/test/fixtures/ogxm2/annos-docs.json +0 -1
  60. gammonview-2.0.2/gvformat-js/test/fixtures/ogxm2/blocks-docs.json +0 -1
  61. gammonview-2.0.2/gvformat-js/test/fixtures/ogxm2/blocks-sha256.json +0 -42
  62. gammonview-2.0.2/gvformat-js/test/fixtures/ogxm2/blocks.expected.json +0 -1
  63. gammonview-2.0.2/tests/golden/3WNK_g1Z-PLsh_HyvQ5j4a.fast.gva +0 -1
  64. gammonview-2.0.2/tests/golden/5nqfGw9bWG3deTaU.fast.gva +0 -1
  65. gammonview-2.0.2/tests/golden/B4_SrGcsKAQmoTyHlgJCbM.fast.gva +0 -1
  66. gammonview-2.0.2/tests/golden/MYdvw1qGuyaRUH__.fast.gva +0 -1
  67. gammonview-2.0.2/tests/golden/baR-U643iUDpvzTC.fast.gva +0 -1
  68. {gammonview-2.0.2 → gammonview-3.0.0}/.gitattributes +0 -0
  69. {gammonview-2.0.2 → gammonview-3.0.0}/.github/workflows/ci.yml +0 -0
  70. {gammonview-2.0.2 → gammonview-3.0.0}/.github/workflows/engine.yml +0 -0
  71. {gammonview-2.0.2 → gammonview-3.0.0}/.github/workflows/publish.yml +0 -0
  72. {gammonview-2.0.2 → gammonview-3.0.0}/.gitignore +0 -0
  73. {gammonview-2.0.2 → gammonview-3.0.0}/.python-version +0 -0
  74. {gammonview-2.0.2 → gammonview-3.0.0}/CONTRIBUTING.md +0 -0
  75. {gammonview-2.0.2 → gammonview-3.0.0}/LICENSE +0 -0
  76. {gammonview-2.0.2 → gammonview-3.0.0}/THIRD-PARTY-NOTICES.md +0 -0
  77. {gammonview-2.0.2 → gammonview-3.0.0}/assets/logo.png +0 -0
  78. {gammonview-2.0.2 → gammonview-3.0.0}/docs/METHODOLOGY.md +0 -0
  79. {gammonview-2.0.2 → gammonview-3.0.0}/docs/OGXM_COMPUTED_FIELDS.md +0 -0
  80. {gammonview-2.0.2 → gammonview-3.0.0}/docs/OGXM_JSON_SPEC_GAMMONVIEW.md +0 -0
  81. {gammonview-2.0.2 → gammonview-3.0.0}/docs/upstream-bgsage.md +0 -0
  82. {gammonview-2.0.2 → gammonview-3.0.0}/docs/v1/OGXM_FORMAT_SPEC_GAMMONVIEW.md +0 -0
  83. {gammonview-2.0.2 → gammonview-3.0.0}/experiments/2026-09-16-checker-mid-tier.md +0 -0
  84. {gammonview-2.0.2 → gammonview-3.0.0}/experiments/2026-09-17-checker-mid-tier-followup.md +0 -0
  85. {gammonview-2.0.2 → gammonview-3.0.0}/experiments/2026-09-19-cross-machine-costs.md +0 -0
  86. {gammonview-2.0.2 → gammonview-3.0.0}/experiments/2026-09-20-corpus-rerun.md +0 -0
  87. {gammonview-2.0.2 → gammonview-3.0.0}/experiments/bench.py +0 -0
  88. {gammonview-2.0.2 → gammonview-3.0.0}/gvanalysis/compare_gva.py +0 -0
  89. {gammonview-2.0.2 → gammonview-3.0.0}/gvanalysis/loader.py +0 -0
  90. {gammonview-2.0.2 → gammonview-3.0.0}/gvanalysis/luck.py +0 -0
  91. {gammonview-2.0.2 → gammonview-3.0.0}/gvanalysis/parentwatch.py +0 -0
  92. {gammonview-2.0.2 → gammonview-3.0.0}/gvanalysis/position.py +0 -0
  93. {gammonview-2.0.2 → gammonview-3.0.0}/gvanalysis/progress.py +0 -0
  94. {gammonview-2.0.2 → gammonview-3.0.0}/gvanalysis/reconstruct_mat.py +0 -0
  95. {gammonview-2.0.2 → gammonview-3.0.0}/gvanalysis/share.py +0 -0
  96. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat/__init__.py +0 -0
  97. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat/basefill.py +0 -0
  98. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat/bgf.py +0 -0
  99. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat/binary.py +0 -0
  100. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat/game_reconstructor.py +0 -0
  101. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat/legality.py +0 -0
  102. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat/mat.py +0 -0
  103. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat/mat_parser.py +0 -0
  104. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat/met.py +0 -0
  105. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat/notation.py +0 -0
  106. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat/ogid.py +0 -0
  107. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat/ogxm2_passthrough.py +0 -0
  108. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat/place.py +0 -0
  109. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat/reader.py +0 -0
  110. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat/share.py +0 -0
  111. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/.gitignore +0 -0
  112. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/LICENSE +0 -0
  113. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/README.md +0 -0
  114. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/THIRD-PARTY-NOTICES.md +0 -0
  115. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/cli/README.md +0 -0
  116. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/cli/bgf2gva.js +0 -0
  117. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/cli/mat2gva.js +0 -0
  118. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/cli/xg2gva.js +0 -0
  119. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/package-lock.json +0 -0
  120. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/src/basefill.js +0 -0
  121. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/src/bgf2gva.js +0 -0
  122. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/src/binary.js +0 -0
  123. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/src/constants.js +0 -0
  124. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/src/index.js +0 -0
  125. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/src/legality.js +0 -0
  126. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/src/mat2gva.js +0 -0
  127. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/src/met.js +0 -0
  128. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/src/notation.js +0 -0
  129. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/src/og2gva.js +0 -0
  130. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/src/ogid.js +0 -0
  131. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/src/ogxm2_passthrough.js +0 -0
  132. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/src/place.js +0 -0
  133. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/src/reader.js +0 -0
  134. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/src/sha256.js +0 -0
  135. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/src/share.js +0 -0
  136. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/fixtures/mat/3WNK_g1Z-PLsh_HyvQ5j4a.json +0 -0
  137. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/fixtures/mat/5nqfGw9bWG3deTaU.json +0 -0
  138. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/fixtures/mat/B4_SrGcsKAQmoTyHlgJCbM.json +0 -0
  139. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/fixtures/mat/C07OQCKyw4Pe-CnjFiAMYO.json +0 -0
  140. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/fixtures/mat/MYdvw1qGuyaRUH__.json +0 -0
  141. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/fixtures/mat/Sz-2PgKnvb6aFls69-qFXg.json +0 -0
  142. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/fixtures/mat/UL-CV6F9jMM-E1Pu.json +0 -0
  143. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/fixtures/mat/XCu3RJ0UvDg_Tldl.json +0 -0
  144. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/fixtures/mat/baR-U643iUDpvzTC.json +0 -0
  145. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/fixtures/mat/eXBNG5wZHS5Alu3P.json +0 -0
  146. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/fixtures/mat/hQ8sVn2LbTdF4wRm.json +0 -0
  147. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/fixtures/mat/rK7pXm4TqLb9NzWd.json +0 -0
  148. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/fixtures/mat/uwvEbd63UvdX7SDin5o_Iw.json +0 -0
  149. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/fixtures/ogxm2/annos-edits-sha256.json +0 -0
  150. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/fixtures/ogxm2/annos.expected.json +0 -0
  151. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/fixtures/ogxm2/annos.ogxm +0 -0
  152. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/fixtures/ogxm2/bgf-writer-sha256.json +0 -0
  153. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/fixtures/ogxm2/cube-on-two.expected.json +0 -0
  154. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/fixtures/ogxm2/cube-on-two.ogxm +0 -0
  155. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/fixtures/ogxm2/fields-docs.json +0 -0
  156. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/fixtures/ogxm2/fields-sha256.json +0 -0
  157. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/fixtures/ogxm2/fields.expected.json +0 -0
  158. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/fixtures/ogxm2/fields.ogxm +0 -0
  159. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/fixtures/ogxm2/foreign.ogxm +0 -0
  160. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/fixtures/ogxm2/gen.py +0 -0
  161. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/fixtures/ogxm2/match.expected.json +0 -0
  162. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/fixtures/ogxm2/match.ogxm +0 -0
  163. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/fixtures/ogxm2/passthrough-sha256.json +0 -0
  164. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/fixtures/ogxm2/resign.expected.json +0 -0
  165. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/fixtures/ogxm2/resign.ogxm +0 -0
  166. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/fixtures/ogxm2/set-up.expected.json +0 -0
  167. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/fixtures/ogxm2/set-up.ogxm +0 -0
  168. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/fixtures/ogxm2/two-blocks.expected.json +0 -0
  169. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/fixtures/ogxm2/two-blocks.ogxm +0 -0
  170. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/fixtures/ogxm2/v1-chunks.gvab +0 -0
  171. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/fixtures/ogxm2/writer-sha256.json +0 -0
  172. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/test-alternative-move-steps.js +0 -0
  173. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/test-basefill.js +0 -0
  174. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/test-bgf-alternatives.js +0 -0
  175. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/test-bgf-embedded-cube.js +0 -0
  176. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/test-bgf-move-steps.js +0 -0
  177. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/test-chunk-passthrough.js +0 -0
  178. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/test-equity-loss.js +0 -0
  179. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/test-illegal-move.js +0 -0
  180. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/test-illegal-play-steps.js +0 -0
  181. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/test-legality.js +0 -0
  182. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/test-mat.js +0 -0
  183. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/test-merge.js +0 -0
  184. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/test-met.js +0 -0
  185. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/test-og.js +0 -0
  186. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/test-ogid.js +0 -0
  187. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/test-ogxm2-annos.js +0 -0
  188. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/test-ogxm2-blocks.js +0 -0
  189. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/test-ogxm2-fields.js +0 -0
  190. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/test-ogxm2-limits.js +0 -0
  191. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/test-ogxm2-passthrough.js +0 -0
  192. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/test-ogxm2.js +0 -0
  193. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/test-place.js +0 -0
  194. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/test-robustness.js +0 -0
  195. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/test-roundtrip.js +0 -0
  196. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/test-share.js +0 -0
  197. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/test-stats.js +0 -0
  198. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/test-xg-cube.js +0 -0
  199. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/test-xg-move-steps.js +0 -0
  200. {gammonview-2.0.2 → gammonview-3.0.0}/gvformat-js/test/test-xg-zero-win-eval.js +0 -0
  201. {gammonview-2.0.2 → gammonview-3.0.0}/pyproject.toml +0 -0
  202. {gammonview-2.0.2 → gammonview-3.0.0}/samples/README.md +0 -0
  203. {gammonview-2.0.2 → gammonview-3.0.0}/samples/bgf/3WNK_g1Z-PLsh_HyvQ5j4a.bgf +0 -0
  204. {gammonview-2.0.2 → gammonview-3.0.0}/samples/bgf/5nqfGw9bWG3deTaU.bgf +0 -0
  205. {gammonview-2.0.2 → gammonview-3.0.0}/samples/bgf/B4_SrGcsKAQmoTyHlgJCbM.bgf +0 -0
  206. {gammonview-2.0.2 → gammonview-3.0.0}/samples/bgf/C07OQCKyw4Pe-CnjFiAMYO.bgf +0 -0
  207. {gammonview-2.0.2 → gammonview-3.0.0}/samples/bgf/MYdvw1qGuyaRUH__.bgf +0 -0
  208. {gammonview-2.0.2 → gammonview-3.0.0}/samples/bgf/Sz-2PgKnvb6aFls69-qFXg.bgf +0 -0
  209. {gammonview-2.0.2 → gammonview-3.0.0}/samples/bgf/UL-CV6F9jMM-E1Pu.bgf +0 -0
  210. {gammonview-2.0.2 → gammonview-3.0.0}/samples/bgf/XCu3RJ0UvDg_Tldl.bgf +0 -0
  211. {gammonview-2.0.2 → gammonview-3.0.0}/samples/bgf/baR-U643iUDpvzTC.bgf +0 -0
  212. {gammonview-2.0.2 → gammonview-3.0.0}/samples/bgf/eXBNG5wZHS5Alu3P.bgf +0 -0
  213. {gammonview-2.0.2 → gammonview-3.0.0}/samples/bgf/uwvEbd63UvdX7SDin5o_Iw.bgf +0 -0
  214. {gammonview-2.0.2 → gammonview-3.0.0}/samples/gv/3WNK_g1Z-PLsh_HyvQ5j4a.gvab +0 -0
  215. {gammonview-2.0.2 → gammonview-3.0.0}/samples/gv/5nqfGw9bWG3deTaU.gvab +0 -0
  216. {gammonview-2.0.2 → gammonview-3.0.0}/samples/gv/B4_SrGcsKAQmoTyHlgJCbM.gva +0 -0
  217. {gammonview-2.0.2 → gammonview-3.0.0}/samples/gv/B4_SrGcsKAQmoTyHlgJCbM.gvab +0 -0
  218. {gammonview-2.0.2 → gammonview-3.0.0}/samples/gv/C07OQCKyw4Pe-CnjFiAMYO.gvab +0 -0
  219. {gammonview-2.0.2 → gammonview-3.0.0}/samples/gv/MYdvw1qGuyaRUH__.gvab +0 -0
  220. {gammonview-2.0.2 → gammonview-3.0.0}/samples/gv/Sz-2PgKnvb6aFls69-qFXg.gvab +0 -0
  221. {gammonview-2.0.2 → gammonview-3.0.0}/samples/gv/UL-CV6F9jMM-E1Pu.gvab +0 -0
  222. {gammonview-2.0.2 → gammonview-3.0.0}/samples/gv/XCu3RJ0UvDg_Tldl.gvab +0 -0
  223. {gammonview-2.0.2 → gammonview-3.0.0}/samples/gv/baR-U643iUDpvzTC.gvab +0 -0
  224. {gammonview-2.0.2 → gammonview-3.0.0}/samples/gv/eXBNG5wZHS5Alu3P.gvab +0 -0
  225. {gammonview-2.0.2 → gammonview-3.0.0}/samples/gv/uwvEbd63UvdX7SDin5o_Iw.gvab +0 -0
  226. {gammonview-2.0.2 → gammonview-3.0.0}/samples/mat/3WNK_g1Z-PLsh_HyvQ5j4a.mat +0 -0
  227. {gammonview-2.0.2 → gammonview-3.0.0}/samples/mat/5nqfGw9bWG3deTaU.mat +0 -0
  228. {gammonview-2.0.2 → gammonview-3.0.0}/samples/mat/B4_SrGcsKAQmoTyHlgJCbM.mat +0 -0
  229. {gammonview-2.0.2 → gammonview-3.0.0}/samples/mat/C07OQCKyw4Pe-CnjFiAMYO.mat +0 -0
  230. {gammonview-2.0.2 → gammonview-3.0.0}/samples/mat/MYdvw1qGuyaRUH__.mat +0 -0
  231. {gammonview-2.0.2 → gammonview-3.0.0}/samples/mat/Sz-2PgKnvb6aFls69-qFXg.mat +0 -0
  232. {gammonview-2.0.2 → gammonview-3.0.0}/samples/mat/UL-CV6F9jMM-E1Pu.mat +0 -0
  233. {gammonview-2.0.2 → gammonview-3.0.0}/samples/mat/XCu3RJ0UvDg_Tldl.mat +0 -0
  234. {gammonview-2.0.2 → gammonview-3.0.0}/samples/mat/baR-U643iUDpvzTC.mat +0 -0
  235. {gammonview-2.0.2 → gammonview-3.0.0}/samples/mat/eXBNG5wZHS5Alu3P.mat +0 -0
  236. {gammonview-2.0.2 → gammonview-3.0.0}/samples/mat/hQ8sVn2LbTdF4wRm.mat +0 -0
  237. {gammonview-2.0.2 → gammonview-3.0.0}/samples/mat/rK7pXm4TqLb9NzWd.mat +0 -0
  238. {gammonview-2.0.2 → gammonview-3.0.0}/samples/mat/uwvEbd63UvdX7SDin5o_Iw.mat +0 -0
  239. {gammonview-2.0.2 → gammonview-3.0.0}/samples/xg/3WNK_g1Z-PLsh_HyvQ5j4a.xg +0 -0
  240. {gammonview-2.0.2 → gammonview-3.0.0}/samples/xg/5nqfGw9bWG3deTaU.xg +0 -0
  241. {gammonview-2.0.2 → gammonview-3.0.0}/samples/xg/B4_SrGcsKAQmoTyHlgJCbM.xg +0 -0
  242. {gammonview-2.0.2 → gammonview-3.0.0}/samples/xg/C07OQCKyw4Pe-CnjFiAMYO.xg +0 -0
  243. {gammonview-2.0.2 → gammonview-3.0.0}/samples/xg/MYdvw1qGuyaRUH__.xg +0 -0
  244. {gammonview-2.0.2 → gammonview-3.0.0}/samples/xg/Sz-2PgKnvb6aFls69-qFXg.xg +0 -0
  245. {gammonview-2.0.2 → gammonview-3.0.0}/samples/xg/UL-CV6F9jMM-E1Pu.xg +0 -0
  246. {gammonview-2.0.2 → gammonview-3.0.0}/samples/xg/XCu3RJ0UvDg_Tldl.xg +0 -0
  247. {gammonview-2.0.2 → gammonview-3.0.0}/samples/xg/baR-U643iUDpvzTC.xg +0 -0
  248. {gammonview-2.0.2 → gammonview-3.0.0}/samples/xg/eXBNG5wZHS5Alu3P.xg +0 -0
  249. {gammonview-2.0.2 → gammonview-3.0.0}/samples/xg/hQ8sVn2LbTdF4wRm.xg +0 -0
  250. {gammonview-2.0.2 → gammonview-3.0.0}/samples/xg/rK7pXm4TqLb9NzWd.xg +0 -0
  251. {gammonview-2.0.2 → gammonview-3.0.0}/samples/xg/uwvEbd63UvdX7SDin5o_Iw.xg +0 -0
  252. {gammonview-2.0.2 → gammonview-3.0.0}/tests/audit_corpus.py +0 -0
  253. {gammonview-2.0.2 → gammonview-3.0.0}/tests/fixtures.py +0 -0
  254. {gammonview-2.0.2 → gammonview-3.0.0}/tests/gen_js_v2_fixtures.py +0 -0
  255. {gammonview-2.0.2 → gammonview-3.0.0}/tests/gen_ogxm2_annos_fixtures.py +0 -0
  256. {gammonview-2.0.2 → gammonview-3.0.0}/tests/gen_ogxm2_blocks_fixtures.py +0 -0
  257. {gammonview-2.0.2 → gammonview-3.0.0}/tests/gen_ogxm2_fields_fixtures.py +0 -0
  258. {gammonview-2.0.2 → gammonview-3.0.0}/tests/gen_ogxm2_passthrough_fixtures.py +0 -0
  259. {gammonview-2.0.2 → gammonview-3.0.0}/tests/ogxm2_annos_cases.py +0 -0
  260. {gammonview-2.0.2 → gammonview-3.0.0}/tests/ogxm2_blocks_cases.py +0 -0
  261. {gammonview-2.0.2 → gammonview-3.0.0}/tests/ogxm2_fields_cases.py +0 -0
  262. {gammonview-2.0.2 → gammonview-3.0.0}/tests/ogxm2_oracle.py +0 -0
  263. {gammonview-2.0.2 → gammonview-3.0.0}/tests/passthrough_cases.py +0 -0
  264. {gammonview-2.0.2 → gammonview-3.0.0}/tests/regen_golden.py +0 -0
  265. {gammonview-2.0.2 → gammonview-3.0.0}/tests/test_alternative_move_steps.py +0 -0
  266. {gammonview-2.0.2 → gammonview-3.0.0}/tests/test_analysis_orientation.py +0 -0
  267. {gammonview-2.0.2 → gammonview-3.0.0}/tests/test_analyze_position.py +0 -0
  268. {gammonview-2.0.2 → gammonview-3.0.0}/tests/test_basefill.py +0 -0
  269. {gammonview-2.0.2 → gammonview-3.0.0}/tests/test_bgf_alternatives.py +0 -0
  270. {gammonview-2.0.2 → gammonview-3.0.0}/tests/test_bgf_cube_response.py +0 -0
  271. {gammonview-2.0.2 → gammonview-3.0.0}/tests/test_bgf_move_steps.py +0 -0
  272. {gammonview-2.0.2 → gammonview-3.0.0}/tests/test_chunk_passthrough.py +0 -0
  273. {gammonview-2.0.2 → gammonview-3.0.0}/tests/test_count_illegal.py +0 -0
  274. {gammonview-2.0.2 → gammonview-3.0.0}/tests/test_equity_loss.py +0 -0
  275. {gammonview-2.0.2 → gammonview-3.0.0}/tests/test_gvab_writer.py +0 -0
  276. {gammonview-2.0.2 → gammonview-3.0.0}/tests/test_illegal_move.py +0 -0
  277. {gammonview-2.0.2 → gammonview-3.0.0}/tests/test_illegal_play_steps.py +0 -0
  278. {gammonview-2.0.2 → gammonview-3.0.0}/tests/test_legality.py +0 -0
  279. {gammonview-2.0.2 → gammonview-3.0.0}/tests/test_loader_formats.py +0 -0
  280. {gammonview-2.0.2 → gammonview-3.0.0}/tests/test_luck_fill.py +0 -0
  281. {gammonview-2.0.2 → gammonview-3.0.0}/tests/test_met.py +0 -0
  282. {gammonview-2.0.2 → gammonview-3.0.0}/tests/test_ogxm2.py +0 -0
  283. {gammonview-2.0.2 → gammonview-3.0.0}/tests/test_ogxm2_annos.py +0 -0
  284. {gammonview-2.0.2 → gammonview-3.0.0}/tests/test_ogxm2_blocks.py +0 -0
  285. {gammonview-2.0.2 → gammonview-3.0.0}/tests/test_ogxm2_blocks_analysis.py +0 -0
  286. {gammonview-2.0.2 → gammonview-3.0.0}/tests/test_ogxm2_fields.py +0 -0
  287. {gammonview-2.0.2 → gammonview-3.0.0}/tests/test_ogxm2_fields_analysis.py +0 -0
  288. {gammonview-2.0.2 → gammonview-3.0.0}/tests/test_ogxm2_limits.py +0 -0
  289. {gammonview-2.0.2 → gammonview-3.0.0}/tests/test_ogxm2_passthrough.py +0 -0
  290. {gammonview-2.0.2 → gammonview-3.0.0}/tests/test_ogxm_conformance.py +0 -0
  291. {gammonview-2.0.2 → gammonview-3.0.0}/tests/test_ogxm_export.py +0 -0
  292. {gammonview-2.0.2 → gammonview-3.0.0}/tests/test_ogxm_stats.py +0 -0
  293. {gammonview-2.0.2 → gammonview-3.0.0}/tests/test_parent_watch.py +0 -0
  294. {gammonview-2.0.2 → gammonview-3.0.0}/tests/test_position_id.py +0 -0
  295. {gammonview-2.0.2 → gammonview-3.0.0}/tests/test_read_gvab.py +0 -0
  296. {gammonview-2.0.2 → gammonview-3.0.0}/tests/test_reconstruct_mat.py +0 -0
  297. {gammonview-2.0.2 → gammonview-3.0.0}/tests/test_resign_actor.py +0 -0
  298. {gammonview-2.0.2 → gammonview-3.0.0}/tests/test_restated_play_analysis.py +0 -0
  299. {gammonview-2.0.2 → gammonview-3.0.0}/tests/test_share_link.py +0 -0
  300. {gammonview-2.0.2 → gammonview-3.0.0}/tests/test_xg_cube.py +0 -0
  301. {gammonview-2.0.2 → gammonview-3.0.0}/tests/test_xg_move_steps.py +0 -0
  302. {gammonview-2.0.2 → gammonview-3.0.0}/tests/test_xg_zero_win_eval.py +0 -0
  303. {gammonview-2.0.2 → gammonview-3.0.0}/uv.lock +0 -0
@@ -13,6 +13,145 @@ when the repository was opened; they are kept because they record why things
13
13
  are the way they are — particularly the breaking changes and the measurements
14
14
  behind several design decisions. Dates are the tag dates.
15
15
 
16
+ ## 3.0.0 — 2026-10-22
17
+
18
+ **Presets are versioned, and a built-in cannot be changed** *(breaking)*
19
+
20
+ An analysis now records which version of a preset produced it: `world_class@2`
21
+ for a built-in, `custom/<name>@<digest>` for a preset from `presets.yaml`. Two
22
+ analyses with the same label used the same method, so a player comparing
23
+ matches, or a title-granting body requiring one method, can check that.
24
+ Locking the built-ins is what makes that hold. A `presets.yaml` entry that
25
+ reuses a built-in's name or alias is now skipped with a warning, where it used
26
+ to override the built-in. A custom preset's label comes from a digest of its
27
+ whole definition, screening constants included, so renaming it changes nothing
28
+ and a changed threshold changes the label. An environment override
29
+ (`GVAN_STAGED=0`, `GVAN_NO_SCREEN`, `GVAN_ROLLOUT_SELECT=0`) runs as `world_class@2+<digest>` rather
30
+ than under the real label. `--preset world_class@2` runs exactly that revision,
31
+ and refuses if anything else would run. The label travels in the block's
32
+ `x-gammonview-analysis` annotation (`preset=`), in Python and JS; older files
33
+ carry none.
34
+
35
+ **Checker plays are screened in stages, as XG screens them** *(change)*
36
+
37
+ `fast`, `deep` and `world_class` move to revision 2:
38
+
39
+ 1. Every move is screened at 1-ply.
40
+ 2. The top 14 are rescored at 2-ply, or every move within 0.2 of the 1-ply
41
+ leader when that is more (at most 40). A fixed count ignored a crowded
42
+ field: on 10,715 decisions the 3-ply best ranked 14th or worse at 1-ply on
43
+ 0.19% of them, and the window recovers what that cost.
44
+ 3. Moves within 0.16 of the 2-ply leader, at most 12, are evaluated at full
45
+ depth, along with the played move.
46
+
47
+ A middle tier now starts from the screen's numbers, and deepens only the moves
48
+ within `close_threshold` of the best, at most 8. That is XG's search interval,
49
+ 4 moves within 0.080. The top-two gap is measured between moves at the same
50
+ depth, so a lone deep move is never "close", and it stays at the screen's
51
+ depth as XG's does. 4-ply checker work falls to about 36% of its cost.
52
+ `world_class` ran 1.31x faster end to end on 12 matches on a 24-core machine,
53
+ and 1.45x on an 8-core one. PR is identical to two decimals on 24 players, and
54
+ 8 of 2,323 best moves differ, every one of them a tie. `very_quick` never
55
+ reaches the staged screen and stays at revision 1.
56
+
57
+ **A checker rollout rolls out the moves the tier below chose** *(change)*
58
+
59
+ When a checker play goes to the rollout tier, the moves rolled out are now
60
+ the best move from the tier that sent it (the 3-ply screen, or 4-ply after a
61
+ middle tier), its runner-up when within 0.02 of that best at the same depth,
62
+ and the played move. Every other move keeps that tier's numbers. bgsage
63
+ used to choose the set again from a fresh 2-ply filter, plus a loop that rolled
64
+ out any unrolled move ranked above the rolled ones: 3.85 moves a decision across
65
+ 43,691 decisions in our corpus outputs, where XG World Class rolls out 2.06
66
+ (93.9% of its Roller+ decisions roll out exactly the best and the played move).
67
+ Ours now average 2.26. On 11 matches `world_class` ran 28% faster (448s to
68
+ 320s); 4 of 1,937 best moves changed, together 0.029 equity of error, and PR
69
+ moved by 0.006 on average (at most 0.11). Each rolled-out row is bgsage's own
70
+ rollout of that board, to the last digit. `GVAN_ROLLOUT_SELECT=0` restores
71
+ bgsage's choice for A/B work. Part of `world_class@2`.
72
+
73
+ **`world_class_fast` is retired** *(breaking)*
74
+
75
+ `--preset world_class_fast` (`wcf`) now fails with a message pointing to
76
+ `world_class` or `deep`. Faster rollouts and the staged screen made
77
+ `world_class` cheap enough that `world_class_fast` stopped earning its place.
78
+ On 11 matches it took 74% of `world_class`'s time (234s against 318s) for PR
79
+ 0.13 away from it on average (at most 0.38), while `deep` takes 9% (29s). Two
80
+ world-class methods would also split the comparison that preset labels exist
81
+ for. Files analyzed with it are unaffected, and it is not aliased to
82
+ `world_class`, since that would mislabel the analysis.
83
+
84
+ **`world_class` rolls out every wrong cube** *(change)*
85
+
86
+ A cube is still screened at 3-ply, but deepened to 4-ply within 0.12 of its
87
+ double or take point (was 0.08), and any wrong cube action is now rolled out
88
+ (was: only one wrong by more than 0.02), as XG World Class's "if different"
89
+ does. On 11 matches (1,109 cubes) it costs 2% of wall time and 6 more cube
90
+ rollouts, and it reaches the same optimal action on every cube as screening
91
+ them all at 4-ply would, which costs 11%. Against the old shape it flips 3
92
+ optimal actions; PR moves by 0.02 on average (at most 0.23). A preset's
93
+ `first_pass` also takes a `{checker, cube}` mapping now, for a cube screen at
94
+ its own depth. Part of `world_class@2`.
95
+
96
+ **A custom preset can be passed as data** *(new)*
97
+
98
+ `custom_preset(name, spec)` builds a custom preset from a definition held in
99
+ memory, in the shape of one `presets.yaml` entry, and `analyze_match` (with
100
+ `analyze_file`, `analyze_ogxm` and `resolve_preset`) accepts the `Preset` it
101
+ returns wherever it took a name. It obeys the file's rules and gets the file's
102
+ label, `custom/<name>@<digest>`, so a program that keeps presets somewhere of its
103
+ own no longer has to write a YAML file and re-import the module to run one.
104
+ Both are exported from `gvanalysis`, with `Preset` and `PresetConfigError`.
105
+
106
+ **A preset can size cube errors at a level of their own** *(new)*
107
+
108
+ `second_pass` takes a `{checker, cube}` mapping, as `first_pass` does, so a
109
+ preset can roll out a wrong cube and settle a wrong checker play at 4-ply, or
110
+ the reverse. A rollout of a cube is one position and a rollout of a checker
111
+ play is every candidate that survives, so the level worth paying for is not the
112
+ same one. The checker level is required: every kind still has an on-error
113
+ tier. A mapping naming one level for both is the bare level, with the same
114
+ digest, and no built-in changes. The analysis summary gains
115
+ `second_pass_cube_level`, set only when the two differ, and a cube decision
116
+ records the level it was sized at.
117
+
118
+ **Resign errors** *(new, opt-in)*
119
+
120
+ `--resign-errors` (`resign_errors=True` in the API) judges every accepted
121
+ resignation, as BMAB's configuration of XG does. It is off by default and no
122
+ preset turns it on; an analysis run with it is labelled `<preset>+resign`
123
+ (`world_class@2+resign`), and a pinned `--preset world_class@2` still accepts
124
+ it. A resignation counts as a decision for each player: the resigner is charged
125
+ if they conceded more than the position was worth, and the acceptor if they
126
+ took less. A resignation conceding the position's exact value counts for
127
+ neither. The position is the one the record shows was resigned: before the roll
128
+ after a play or a dance, and after it when the record shows a roll that had a
129
+ legal play and was left unplayed, since the resigner had seen it. In the XG
130
+ corpus 18 of 149 judged resignations come after a seen roll; judged before the
131
+ roll instead, two of them would be charged 0.33 and 0.60 that the roll actually
132
+ seen makes 0. A resignation on a pending double is not judged. The analysis goes
133
+ in v2's resign record (`resign_error`, `take_resign_error`, with its level), and
134
+ `compute_aggregates` / `computeAggregates` now count it in PR.
135
+
136
+ **Auto `--jobs` is `cpu // 3` workers, at least 2** *(behaviour change on
137
+ `--jobs 0`)*
138
+
139
+ It was `ceil(cpu / 7)`. A staged decision keeps fewer cores busy, so 4 workers
140
+ left a 24-core machine short: 8 workers ran 162.6s against 171.4s. On 8 and 4
141
+ cores the count is unchanged, at 2. Output is identical at any setting.
142
+
143
+ **An XG match's header names the deepest level it was read at** *(fix)*
144
+
145
+ `convert_xg` / `convertXg` set the block's `eval_level` to the plain n-ply
146
+ depth, which ignored XG Roller levels (and full rollouts) altogether. Since XG
147
+ stamps no analysis time -- `timestamp` is the match's date -- two runs of one
148
+ match that differed only in sending the doubtful plays to Roller+ or to
149
+ Roller++ converted to identical headers, and GammonView's library took the
150
+ second for the first. The header now names the deepest level any decision in
151
+ the document reports (`truncated2`, `truncated3`, `rollout`), with `ply`
152
+ unchanged. Every decision's own level is untouched; across the 13-file XG
153
+ corpus only headers moved.
154
+
16
155
  ## 2.0.2 — 2026-10-12
17
156
 
18
157
  **A set-up start is written once** *(fix)*
@@ -218,58 +218,103 @@ in step when a flag changes; the rationale belongs only here.
218
218
  two-pass scheme: a cheap first pass screens every decision, a stronger second
219
219
  pass runs only on an error (the played checker move or cube action disagrees
220
220
  with the first pass). Single-pass presets judge everything at the first pass.
221
- The five built-in presets live in `gvanalysis/presets.py` (always available). Optional
222
- `presets.yaml` overrides are layered on top from two locations, project-local
223
- winning over global: `~/.config/bgsage/presets.yaml` then `./presets.yaml`.
224
- Create either with `--init-presets` (add `--global` for the config-dir one).
225
- Overrides merge into a built-in by key (field-by-field) or add new presets;
226
- `default:` sets the default. Restore defaults by deleting the file(s); a
227
- malformed file is skipped with a warning (the other layer still loads).
221
+ The four built-in presets live in `gvanalysis/presets.py` (always available)
222
+ and **cannot be overridden**. Optional `presets.yaml` files add custom presets
223
+ from two locations, project-local winning over global:
224
+ `~/.config/bgsage/presets.yaml` then `./presets.yaml`. Create either with
225
+ `--init-presets` (add `--global` for the config-dir one). An entry reusing a
226
+ built-in's name or alias is skipped with a warning; `default:` sets the
227
+ default. Restore defaults by deleting the file(s); a malformed file is skipped
228
+ with a warning (the other layer still loads).
229
+ `custom_preset(name, spec)` is the in-memory twin of one file entry (same
230
+ rules via `_check_custom`, same label), and every entry point that takes a
231
+ preset name also takes the `Preset` it returns.
232
+
233
+ **A preset label names one method.** Output records `Preset.label`:
234
+ `world_class@2` for a built-in, `custom/<name>@<digest>` for a user preset
235
+ (digest of its whole definition). The point is comparability -- a user, or a
236
+ title-granting body, must be able to tell and require that analyses used the
237
+ same version; it is not an anti-tampering measure. `Preset.definition()`
238
+ includes the screening constants from `checker_eval.screening()`, so any
239
+ change that can move results changes the digest, and `_REVISIONS` pins the
240
+ digest of every shipped revision: **`test_presets` fails until a changed
241
+ definition appends a new revision -- append a row, never edit one.** An
242
+ environment override (`GVAN_STAGED`, `GVAN_NO_SCREEN`) shows as
243
+ `world_class@2+<digest>` rather than running under the real label, and
244
+ `--preset world_class@2` is refused unless that exact revision is what runs.
245
+ Old revisions are listed, not runnable. The label travels in the block's
246
+ `x-gammonview-analysis` annotation (`preset=`); before that it was not
247
+ written to `.gvab` at all, so older files carry no preset.
228
248
 
229
249
  | Preset (aliases) | 1st pass | 2nd pass | XG equivalent |
230
250
  |---|---|---|---|
231
251
  | `very_quick` (`vq`) | `2ply` | — | Very quick |
232
252
  | `fast` (`f`) | `2ply` | `3ply` | Fast |
233
253
  | `deep` (`d`) | `3ply` | — | Deep |
234
- | `world_class` (`wc`) | `3ply` → `4ply` on close | `truncated2` | World Class (XG Roller+) |
235
- | `world_class_fast` (`wcf`) | `3ply` (cube: → rollout on close too) | `truncated2` | World Class (3-tier) |
236
-
237
- `world_class_fast` is a 3-tier scheme (`mid_pass` + `close_threshold` on the
238
- preset): `3ply` baseline, `truncated2` rollout on error, and a middle tier on
239
- borderline decisions — but only for cubes. A middle tier is added on top of
240
- the screen — it fires on decisions the screen would otherwise have settled —
241
- so what decides whether it pays is its cost against the *screen below*, not
242
- against the tier above. Measured per decision off a `3ply` screen, 4-ply is
243
- 19x the screen on a move list (25ms → 467ms) and only 4x on one pre-roll cube
244
- (24ms → 100ms). Dropping the checker middle tier made the preset ~36%
245
- faster while it ran twice as many rollouts, so borderline checker plays now go
246
- straight from the 3-ply screen to `truncated2` when they are wrong, and are
247
- left at the screen when they are right. The argument now rests on the
248
- borderline *rate* alone: at a 0.04 window a cube is borderline ~5% of the time
249
- and a checker play 53.8%, so the same rule costs ~0.4s per match on one and
250
- tens of seconds on the other. The old second argument — that restoring the
251
- tier "diverts 65 of the 83 checker rollouts into 4-ply, the weaker estimator"
252
- — described the **escalation defect**, not the tier: a borderline decision
253
- used to be capped at the middle tier however large its error proved. It
254
- escalates now, so a middle tier no longer steals rollouts (it would still
255
- cost, and more, since those decisions pay both tiers). Note also that the cost
256
- order has flipped since that measurement: on 760 paired checker decisions
257
- 4-ply beats `truncated2` on 710, median 2.23x, because `checker_eval.py`
258
- screens candidates before the full-width pass. `mid_pass` takes a
259
- `{checker, cube}` mapping as well as a bare level (which still sets both); an
260
- omitted kind is plain 2-tier.
261
-
262
- The cube middle tier names `truncated2` — the same level as `second_pass`, so
263
- the cube rule reads "borderline **or** wrong → roll it out". It was `4ply`
264
- until 632 borderline cubes from XG-analyzed matches were scored against an
265
- independent `truncated3` arbiter: 4-ply disagreed with the arbiter's verdict
266
- on 14% of them (mean gap error 0.0127) against 12%/0.0091 for a single
267
- `truncated2` run, and averaging ten `truncated2` seeds improved that to only
268
- 0.0090 — so a 360-trial rollout is limited by its horizon, not its variance,
269
- and 4-ply's zero variance buys nothing back. Naming an existing level in
270
- `mid_pass` costs no extra analyzer (levels are built once and shared), and
271
- routing the ~6% of cubes that are borderline to the rollout adds roughly
272
- 0.4s per match.
254
+ | `world_class` (`wc`) | `3ply` → `4ply` on close (cube: within 0.12) | `truncated2` | World Class (XG Roller+) |
255
+
256
+ `world_class`'s cubes: a 3-ply screen, 4-ply within 0.12 of the double or
257
+ take point (`close_threshold: {checker: 0.08, cube: 0.12}`), and cube
258
+ `error_threshold` 0 -- any wrong action rolled out ("if different"). XG's own
259
+ routing, read off 39,284 cube records in 524 XG files, is shallower: no
260
+ cube is 3-ply; it screens at 2-ply and goes to 4-ply only within ~0.25 of the
261
+ double point (no 2-ply record above a gap of -0.24), every actual double,
262
+ take and pass at 4-ply or Roller+. We keep the deeper screen. Measured on
263
+ 11 matches, 1,109 cubes, against the old shape (0.08 window, rollout above
264
+ 0.02) and against 4-ply on every cube:
265
+
266
+ | cube shape | wall | 3-ply / 4-ply / rollout | action flips vs all-4-ply |
267
+ |---|---|---|---|
268
+ | old (@1) | 442s | 1013 / 49 / 47 | 3 |
269
+ | all 4-ply | 491s (+11%) | 0 / 1057 / 52 | -- |
270
+ | **@2** | 451s (+2%) | 981 / 75 / 53 | **0** |
271
+
272
+ So @2 reaches every verdict the all-4-ply shape does at a fifth of its cost.
273
+ PR still differs from it by 0.097 mean (max 0.56), and not through any cube
274
+ error -- lost equity is identical on all 1,109 -- but through the decision
275
+ count: 3-ply and 4-ply disagree on whether 1-2 cubes per player are trivial.
276
+ The checker interval stays at XG's Normal: Gigantic (16 within 0.32)
277
+ costs 3x the non-rollout checker CPU for 0.089 total best-move regret over
278
+ 11,037 decisions, one decision holding 0.071 of it, rollouts unchanged.
279
+
280
+ **Resign errors are an analysis option, never a preset's** (`--resign-errors`,
281
+ `resign_errors=True`; `Preset.with_resign_errors`). They move PR, so the label
282
+ says so (`world_class@2+resign`), but they stay out of the definition and
283
+ digest: a pin names the preset, and `presets.yaml` refuses the key. The
284
+ position judged is the one the record shows was resigned. After a play or a
285
+ dance it is pre-roll for the next player. After a roll left unplayed that had
286
+ a legal play, it is that roll's best play, because the resigner saw the dice. In the XG
287
+ corpus 18 of 149 judged resignations follow a seen roll, and pre-roll
288
+ judging would have charged two of them 0.33 and 0.60 where the seen roll
289
+ makes both 0. A roll seen but not recorded cannot be detected; XG does record
290
+ one.
291
+
292
+ **`world_class_fast` was retired 2026-10-21**, before any release carried
293
+ preset labels. It was `world_class` without the checker middle tier: a 3-ply
294
+ screen, `truncated2` on error, and cubes that were merely borderline also
295
+ rolled out. Once seeded rollouts and the staged screen made `world_class`
296
+ cheap, it stopped earning a place. Paired on 11 matches it took 234s against
297
+ `world_class`'s 318s, for PR 0.13 away from it (mean; max 0.38). Cube verdicts
298
+ matched on all but 5 of 1,164, and 98 of 1,937 best moves differed, nearly
299
+ all near-ties that `world_class` deepens. `deep` took 29s. A second
300
+ world-class method also splits the pool that preset labels exist to keep
301
+ comparable, and analysis runs in the helper, not on a server, so there is no
302
+ hosting bill to save. Like `balanced`, it stays in `_RETIRED` and is not
303
+ aliased.
304
+
305
+ What its design established still holds for any 3-tier preset. A middle tier
306
+ pays against the *screen below* it, not the tier above, because it fires on
307
+ decisions the screen would otherwise have settled. Measured off a `3ply`
308
+ screen, 4-ply is 19x the screen on a move list (25ms → 467ms) and only 4x on
309
+ one pre-roll cube (24ms → 100ms). At a 0.04 window a cube is borderline ~5%
310
+ of the time and a checker play 53.8%. `mid_pass` takes a `{checker, cube}`
311
+ mapping as well as a bare level for that reason; no built-in uses it now. On
312
+ 632 borderline cubes from XG-analyzed matches scored against an independent
313
+ `truncated3` arbiter, 4-ply disagreed with the arbiter's verdict on 14% of
314
+ them (mean gap error 0.0127) against 12%/0.0091 for a single `truncated2`
315
+ run. Averaging ten `truncated2` seeds improved that only to 0.0090, so a
316
+ 360-trial rollout is limited by its horizon, not its variance, and 4-ply's
317
+ zero variance buys nothing back.
273
318
 
274
319
  **Two thresholds, not one — they answer different questions.**
275
320
  `close_threshold` gates the *middle* tier: how near a tie before the screen's
@@ -297,8 +342,8 @@ in step when a flag changes; the rationale belongs only here.
297
342
  threshold, worst 0.56) and a borderline cube was re-checked never (0.4% of
298
343
  cubes, median 0.07, max 0.22). The escalation is skipped when `mid_pass` names
299
344
  `second_pass`'s own level — analyzers are memoized by level, so identity is
300
- exact and re-running would buy the same answer twice. That makes it a no-op
301
- for `world_class_fast`'s cube tier by construction.
345
+ exact and re-running would buy the same answer twice. That made it a no-op
346
+ for the retired `world_class_fast`'s cube tier by construction.
302
347
 
303
348
  **Every sizing tier is `truncated2`, never `4ply`, and that holds for both
304
349
  decision kinds.** The cube half is the arbiter test above (12%/0.0091 against
@@ -324,8 +369,8 @@ in step when a flag changes; the rationale belongs only here.
324
369
  name in `_RETIRED` and fails with an explanation rather than a bare "unknown
325
370
  preset"; it is deliberately **not** aliased to `world_class_fast`, since the
326
371
  preset name is written into the output document and a silent redirect would
327
- mislabel the analysis. The gap below `world_class_fast` is now `deep`, an
328
- order of magnitude cheaper. Don't reinvent it without a sizing tier cheaper
372
+ mislabel the analysis. With `world_class_fast` retired too, the gap below
373
+ `world_class` is `deep`, an order of magnitude cheaper. Don't reinvent it without a sizing tier cheaper
329
374
  than `truncated2`, because that is the whole of what went wrong.
330
375
 
331
376
  - **`gvan-batch`** (`gvanalysis/batch.py`) — Batch-analyze many match files
@@ -455,7 +500,10 @@ legal — our `.mat` path put bar entries last) and the duplicate played move.
455
500
 
456
501
  `tests/test_ogxm2_reference.py` is the older ratchet: the reference `v1_to_v2`
457
502
  over each golden's content written as v1, against a **pinned** table. Never edit
458
- a row to make a run pass. The goldens themselves are v2 now.
503
+ a row to make a run pass. The goldens themselves are v2 now. Since the staged screen every row
504
+ reads `alternatives`: a top-ten list mixes 3-ply and 2-ply plays, and the
505
+ reference's frozen v1 reader gives every alternative the decision's level.
506
+ That is v1's limit; the v2 files we write pass (`test_ogxm2_writer`).
459
507
 
460
508
  The two v1 parity suites, `test_gvab_writer` and `test_ogxm_conformance`,
461
509
  compare against libogxm's *v1* writer and projection, and skip — saying so —
@@ -523,14 +571,59 @@ label. The best move agrees with bgsage's full-width answer on every decision
523
571
  measured — 4 differences in 1567, all exact ties — while evaluating 1.8x fewer
524
572
  moves.
525
573
 
526
- Three constants tune it, all documented with their measurements in the module:
527
- `ELEVATE_THRESHOLD` / `ELEVATE_MAX_MOVES` (the accuracy filter) and
528
- `ELEVATE_MIN_MOVES` (a display-quality floor, so the top ten a viewer reads is
529
- always N-ply). Rollout tiers, cubeless and 1-ply analyzers, and any bgsage whose
574
+ **The screen is staged, as XG's is** (since the presets' `@2` revisions). A
575
+ decision goes 1-ply on every move -> 2-ply on the top `STAGE_SCREEN_MOVES` (14:
576
+ the display floor, paid at the cheapest real depth), widened to every move
577
+ within `STAGE_SCREEN_WINDOW` (0.2) of the 1-ply leader on a crowded field, at
578
+ most `STAGE_SCREEN_CAP` (40) -> N-ply on the 2-ply moves
579
+ within `STAGE_THRESHOLD` (0.16) of the leader, at most `STAGE_MAX_MOVES` (12),
580
+ plus the played move. A middle tier is handed the screen's result (`seed=`) and
581
+ deepens only its moves within `close_threshold` of the best, at most
582
+ `DEEPEN_MAX_MOVES` (8) -- XG's search interval, "4 moves within 0.080 at
583
+ 3-ply" -- keeping the screen's numbers for the rest. `game_eval` measures the
584
+ top-two gap only between moves at the same depth, so a lone deep move is never
585
+ "close" (XG's rule: nothing in the window, it stays at 3-ply). Why: 4-ply was
586
+ 90% of world_class's non-rollout checker CPU, re-screened from 1-ply on 14+
587
+ moves; staged it is 36% of that cost, PR identical to two decimals on 24
588
+ players, 8 of 2,323 best moves different and every one a tie, and
589
+ world_class 1.31x faster end to end (727s -> 554s, 12 matches), 1.45x on the
590
+ 8-core M3. The XG facts behind it (window AND cap, no display floor -- a
591
+ quarter of XG World Class's listed moves are 1-ply) were read off the corpus.
592
+ A fixed 14 ignores how crowded the field is: on 10,715 decisions the 3-ply best
593
+ ranked 14th or worse at 1-ply on 0.19% of them, and the window recovers all that
594
+ loss (0.175 -> 0.130 total equity, the floor of sending every move to 2-ply) for
595
+ 1.4 more 2-ply evaluations a decision. It went into `@2` before `@2` shipped.
596
+ `GVAN_STAGED=0` restores the flat screen for A/B work, and the preset label
597
+ then says `@2+<digest>`.
598
+
599
+ The flat screen's constants, still used at 2-ply and with `GVAN_STAGED=0`, are
600
+ documented with their measurements in the module: `ELEVATE_THRESHOLD` /
601
+ `ELEVATE_MAX_MOVES` (the accuracy filter) and `ELEVATE_MIN_MOVES` (a
602
+ display-quality floor). **Every one of these constants is part of a preset's
603
+ definition** (`checker_eval.screening()` feeds `Preset.digest()`), so changing
604
+ one fails `test_presets` until the affected presets get a new revision. An
605
+ unseeded rollout tier, cubeless and 1-ply analyzers, and any bgsage whose
530
606
  internals have moved all fall back to `analyzer.checker_play` unchanged;
531
607
  `GVAN_NO_SCREEN=1` forces that fallback everywhere, which is how the A/B
532
608
  measurements above were taken.
533
609
 
610
+ **A rollout tier rolls out what the tier below chose, as XG's does.**
611
+ `game_eval` hands the second pass the screen's (or middle tier's) result as
612
+ `seed=`, and a rollout analyzer then rolls out only that result's best, its
613
+ runner-up within `ROLLOUT_RUNNER_UP_WINDOW` (0.02) at the same depth, and the
614
+ played move, keeping the seed's numbers for the rest. Left to itself bgsage
615
+ throws the seed away and picks again with a 2-ply filter (within 0.08, at most
616
+ 5) plus a promotion loop: 3.85 rollouts a decision over 43,691 in our corpus
617
+ outputs, against XG World Class's 2.06 (93.9% exactly best + played; 6% add one,
618
+ nearly always the runner-up). Ours average 2.26 (26% take the runner-up, so the
619
+ window is wider than XG's; it is cheap insurance). On 11 matches `world_class`
620
+ went 448s -> 320s (-28%); 4 of 1,937 best moves changed, 0.029 equity of error
621
+ in total, PR within 0.006 mean (0.11 max). Each rolled row is bgsage's own
622
+ `cubeful_evaluate_board` call, so it matches bgsage exactly
623
+ (`test_screened_checker.check_seeded_rollout`). The second pass gets a seed
624
+ but no `seed_window`, so a multi-ply second pass (`fast`'s 3-ply) does not
625
+ deepen and is unchanged. `GVAN_ROLLOUT_SELECT=0` restores bgsage's choice.
626
+
534
627
  **Because it duplicates a bgsage call site, it inherits that call's signature.**
535
628
  bgsage 2.0 added `root_board=` to `cubeful_probs_and_equity_nply` — Stage 11's
536
629
  snake NN is chosen once from the root position and held for the whole tree,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: gammonview
3
- Version: 2.0.2
3
+ Version: 3.0.0
4
4
  Summary: Backgammon match analysis — the pure-Python OGXM/GVA codec (`gvformat`) plus bgsage-powered match and position analysis (`gvanalysis`).
5
5
  Project-URL: Homepage, https://github.com/ngvlamis/gammonview-analysis
6
6
  Project-URL: Repository, https://github.com/ngvlamis/gammonview-analysis
@@ -202,7 +202,7 @@ own; analysis does not need them.) `--link` encodes the whole match into a
202
202
  ### Analysis presets
203
203
 
204
204
  `--preset` selects an eXtreme Gammon–style scheme: a cheap first pass screens
205
- every decision, and a stronger pass runs only where it is needed. The five
205
+ every decision, and a stronger pass runs only where it is needed. The four
206
206
  built-ins:
207
207
 
208
208
  | Preset (aliases) | 1st pass | Middle tier | 2nd pass | XG equivalent |
@@ -210,60 +210,46 @@ built-ins:
210
210
  | `very_quick` (`vq`) | `2ply` | — | — | Very quick |
211
211
  | `fast` (`f`) | `2ply` | — | `3ply` | Fast |
212
212
  | `deep` (`d`) | `3ply` | — | — | Deep |
213
- | `world_class` (`wc`) | `3ply` | `4ply` (both kinds) | `truncated2` | World Class (XG Roller+) |
214
- | `world_class_fast` (`wcf`) | `3ply` | `truncated2` (**cube only**) | `truncated2` | World Class (3-tier) |
213
+ | `world_class` (`wc`) | `3ply` | `4ply` | `truncated2` | World Class (XG Roller+) |
215
214
 
216
215
  The second pass runs on an **error**; the middle tier runs on a **borderline**
217
216
  decision the screen would otherwise have settled. `fast` is the default.
218
217
 
219
218
  #### Which one to use
220
219
 
221
- 493 matches were analyzed by XG at World Class and by each of the strong
222
- presets, then compared decision by decision:
220
+ 493 matches were analyzed by XG at World Class and by `world_class`, then
221
+ compared decision by decision:
223
222
 
224
- | | mean gap to XG's match PR | same checker play as XG | PR within 1 of XG | cost, ms/ply (24c → 4c) |
225
- |---|---|---|---|---|
226
- | `world_class` | 0.423 | 90.3% | 91.7% | 291 → 2048 |
227
- | `world_class_fast` | 0.431 | 90.0% | 91.3% | 166 → 1016 |
223
+ | | mean gap to XG's match PR | PR within 1 of XG | same cube action as XG |
224
+ |---|---|---|---|
225
+ | `world_class@2` | 0.420 | 92.0% | 96.2% |
228
226
 
229
- Re-measured 2026-09-20 over the same 493 matches after the retune. The retune
230
- moved both presets about 0.01 PR closer to XG, which is **not** a significant
231
- improvement — the paired confidence intervals span zero — but does establish
232
- that it cost nothing. The checker-play column is carried over from the original
233
- study; a like-for-like check says the retune moves it by at most 0.6 points, but
234
- its exact figure could not be reproduced (see
235
- [the write-up](experiments/2026-09-20-corpus-rerun.md)).
227
+ Measured 2026-10-21. Against the September `world_class` on the same matches
228
+ the gap moved by −0.003, inside the noise (95% CI [−0.010, +0.004]), while the
229
+ run took 1.83× less time: 3 h 31 m against 6 h 25 m on a 24-core M2 Ultra, or
230
+ about 150 ms per ply. See [the write-up](experiments/2026-10-21-wc2-corpus.md).
236
231
 
237
232
  **Mean gap** is the average distance between the two *ratings* of the same
238
233
  player-match, in either direction: rate a match with `world_class` and XG rates
239
- it 0.434 PR away, typically. It is not what following the preset costs you —
240
- priced by XG's own equities, a player who made `world_class`'s recommended
241
- checker play every time would post a PR of 0.333 and `world_class_fast`
242
- 0.353. Both are inside what XG itself calls world-class play. (Those two
243
- figures predate the retune and were not re-measured; everything else in this
244
- section was.)
245
-
246
- Cost is milliseconds per ply over six matches, on a 24-core M2 Ultra and a
247
- 4-core i5-7600 — absolutes rather than a multiple of `fast`, because that
248
- multiple is not constant across machines. Individual matches spread about 2×
249
- around these means. See [the timings](docs/CLI.md#what-they-cost).
250
-
251
- - **`world_class_fast` is the recommended analysis.** It lands 0.008 PR behind
252
- the deepest preset for a little under half the time — a difference the
253
- 493-match corpus cannot resolve from zero (95% CI [−0.020, +0.004]). The two sit three times
254
- closer to each other (0.136) than either does to XG, because what separates
255
- them from XG is the *engine*, not the search depth — 95% of
256
- `world_class_fast`'s squared PR error against XG is that engine floor, which
257
- no preset can spend its way past.
258
- - **`world_class` is for reproducing XG specifically**, not for being more
259
- accurate. Its only real edge is on borderline cubes, and it gets there by
260
- rolling them out *less* often than `world_class_fast` does — which is
261
- XG-like, not demonstrably better. It costs roughly twice as much for that.
262
- - **If you want cheap, drop to `deep`**, which costs an order of magnitude
263
- less than either. There is nothing between them: a `balanced` preset used to
264
- sit in that gap, but after the September 2026 retune it cost about what
265
- `world_class_fast` costs while agreeing with XG less often, so it was
266
- retired.
234
+ it about 0.42 PR away, typically. It is not what following the preset costs you.
235
+ Priced by XG's own equities, a player who made `world_class`'s recommended
236
+ checker play every time would post a PR of 0.333, inside what XG itself calls
237
+ world-class play. (That figure predates the September retune and was not
238
+ re-measured.)
239
+
240
+ - **`world_class` is the analysis to trust.** It routes as XG World Class
241
+ does: checker plays from a 3-ply screen to 4-ply on near-ties, cubes from a
242
+ 3-ply screen to 4-ply within 0.12 of the double or take point, any wrong cube
243
+ action rolled out, and checker errors over 0.02 rolled out. Most of what
244
+ separates it from XG is the *engine*, not the search depth, and no preset can
245
+ spend its way past that. Resign errors, which BMAB's configuration counts,
246
+ are a separate switch (`--resign-errors`) that any preset can run with.
247
+ - **If you want cheap, drop to `deep`**, which costs about a tenth as much.
248
+ Nothing sits between them any more. `balanced` was retired in September 2026
249
+ for costing what the preset above it cost. `world_class_fast` was retired in
250
+ October 2026 once faster rollouts made `world_class` nearly as cheap: on 11
251
+ matches it took 74% of `world_class`'s time for a PR 0.13 away from it. Both
252
+ names now fail with a message saying so.
267
253
 
268
254
  Every number above measures *similarity to XG*, which is not the same as being
269
255
  right — where the two engines differ there is no arbiter here to say which is
@@ -272,9 +258,10 @@ experiment: how the presets split on checker play versus the cube, why rolling
272
258
  out *costs* agreement with XG, and the evidence that the residual disagreement
273
259
  is systematic rather than noise.
274
260
 
275
- Presets live in `gvanalysis/presets.py` and are overridable via `presets.yaml`.
261
+ Presets live in `gvanalysis/presets.py`; every analysis records the preset's
262
+ version (`world_class@2`), and custom presets can be added via `presets.yaml`.
276
263
  See **[`docs/CLI.md`](docs/CLI.md)** for the full reference — every flag, how the
277
- tiers fire, parallelism, and preset overrides.
264
+ tiers fire, parallelism, preset versions and custom presets.
278
265
 
279
266
  ## Documentation
280
267