gammonview 3.1.0__tar.gz → 3.2.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 (294) hide show
  1. {gammonview-3.1.0 → gammonview-3.2.0}/.github/workflows/publish.yml +1 -1
  2. {gammonview-3.1.0 → gammonview-3.2.0}/CHANGELOG.md +25 -0
  3. {gammonview-3.1.0 → gammonview-3.2.0}/CONTRIBUTING.md +1 -1
  4. {gammonview-3.1.0 → gammonview-3.2.0}/PKG-INFO +8 -8
  5. {gammonview-3.1.0 → gammonview-3.2.0}/README.md +4 -4
  6. {gammonview-3.1.0 → gammonview-3.2.0}/docs/CLI.md +2 -0
  7. {gammonview-3.1.0 → gammonview-3.2.0}/gvanalysis/position.py +87 -4
  8. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/README.md +1 -1
  9. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/package.json +3 -3
  10. {gammonview-3.1.0 → gammonview-3.2.0}/pyproject.toml +3 -3
  11. {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_analyze_position.py +64 -1
  12. {gammonview-3.1.0 → gammonview-3.2.0}/.gitattributes +0 -0
  13. {gammonview-3.1.0 → gammonview-3.2.0}/.github/workflows/ci.yml +0 -0
  14. {gammonview-3.1.0 → gammonview-3.2.0}/.github/workflows/engine.yml +0 -0
  15. {gammonview-3.1.0 → gammonview-3.2.0}/.gitignore +0 -0
  16. {gammonview-3.1.0 → gammonview-3.2.0}/.python-version +0 -0
  17. {gammonview-3.1.0 → gammonview-3.2.0}/CLAUDE.md +0 -0
  18. {gammonview-3.1.0 → gammonview-3.2.0}/LICENSE +0 -0
  19. {gammonview-3.1.0 → gammonview-3.2.0}/THIRD-PARTY-NOTICES.md +0 -0
  20. {gammonview-3.1.0 → gammonview-3.2.0}/assets/logo.png +0 -0
  21. {gammonview-3.1.0 → gammonview-3.2.0}/docs/METHODOLOGY.md +0 -0
  22. {gammonview-3.1.0 → gammonview-3.2.0}/docs/OGXM_COMPUTED_FIELDS.md +0 -0
  23. {gammonview-3.1.0 → gammonview-3.2.0}/docs/OGXM_JSON_SPEC_GAMMONVIEW.md +0 -0
  24. {gammonview-3.1.0 → gammonview-3.2.0}/docs/OGXM_V2_PROFILE.md +0 -0
  25. {gammonview-3.1.0 → gammonview-3.2.0}/docs/PRESET_ACCURACY.md +0 -0
  26. {gammonview-3.1.0 → gammonview-3.2.0}/docs/upstream-bgsage.md +0 -0
  27. {gammonview-3.1.0 → gammonview-3.2.0}/docs/v1/OGXM_FORMAT_SPEC_GAMMONVIEW.md +0 -0
  28. {gammonview-3.1.0 → gammonview-3.2.0}/experiments/2026-09-16-checker-mid-tier.md +0 -0
  29. {gammonview-3.1.0 → gammonview-3.2.0}/experiments/2026-09-17-checker-mid-tier-followup.md +0 -0
  30. {gammonview-3.1.0 → gammonview-3.2.0}/experiments/2026-09-19-cross-machine-costs.md +0 -0
  31. {gammonview-3.1.0 → gammonview-3.2.0}/experiments/2026-09-20-corpus-rerun.md +0 -0
  32. {gammonview-3.1.0 → gammonview-3.2.0}/experiments/2026-10-21-wc2-corpus.md +0 -0
  33. {gammonview-3.1.0 → gammonview-3.2.0}/experiments/bench.py +0 -0
  34. {gammonview-3.1.0 → gammonview-3.2.0}/gvanalysis/__init__.py +0 -0
  35. {gammonview-3.1.0 → gammonview-3.2.0}/gvanalysis/batch.py +0 -0
  36. {gammonview-3.1.0 → gammonview-3.2.0}/gvanalysis/checker_eval.py +0 -0
  37. {gammonview-3.1.0 → gammonview-3.2.0}/gvanalysis/compare_gva.py +0 -0
  38. {gammonview-3.1.0 → gammonview-3.2.0}/gvanalysis/game_eval.py +0 -0
  39. {gammonview-3.1.0 → gammonview-3.2.0}/gvanalysis/loader.py +0 -0
  40. {gammonview-3.1.0 → gammonview-3.2.0}/gvanalysis/luck.py +0 -0
  41. {gammonview-3.1.0 → gammonview-3.2.0}/gvanalysis/match.py +0 -0
  42. {gammonview-3.1.0 → gammonview-3.2.0}/gvanalysis/ogxm_reconstructor.py +0 -0
  43. {gammonview-3.1.0 → gammonview-3.2.0}/gvanalysis/parentwatch.py +0 -0
  44. {gammonview-3.1.0 → gammonview-3.2.0}/gvanalysis/presets.py +0 -0
  45. {gammonview-3.1.0 → gammonview-3.2.0}/gvanalysis/progress.py +0 -0
  46. {gammonview-3.1.0 → gammonview-3.2.0}/gvanalysis/reconstruct_mat.py +0 -0
  47. {gammonview-3.1.0 → gammonview-3.2.0}/gvanalysis/share.py +0 -0
  48. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat/__init__.py +0 -0
  49. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat/basefill.py +0 -0
  50. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat/bgf.py +0 -0
  51. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat/binary.py +0 -0
  52. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat/export.py +0 -0
  53. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat/game_reconstructor.py +0 -0
  54. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat/legality.py +0 -0
  55. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat/mat.py +0 -0
  56. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat/mat_parser.py +0 -0
  57. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat/merge.py +0 -0
  58. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat/met.py +0 -0
  59. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat/notation.py +0 -0
  60. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat/ogid.py +0 -0
  61. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat/ogxm2.py +0 -0
  62. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat/ogxm2_passthrough.py +0 -0
  63. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat/ogxm2_writer.py +0 -0
  64. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat/place.py +0 -0
  65. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat/reader.py +0 -0
  66. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat/share.py +0 -0
  67. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat/stats.py +0 -0
  68. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat/xg.py +0 -0
  69. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/.gitignore +0 -0
  70. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/LICENSE +0 -0
  71. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/THIRD-PARTY-NOTICES.md +0 -0
  72. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/cli/README.md +0 -0
  73. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/cli/bgf2gva.js +0 -0
  74. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/cli/mat2gva.js +0 -0
  75. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/cli/xg2gva.js +0 -0
  76. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/package-lock.json +0 -0
  77. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/src/basefill.js +0 -0
  78. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/src/bgf2gva.js +0 -0
  79. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/src/binary.js +0 -0
  80. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/src/constants.js +0 -0
  81. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/src/export.js +0 -0
  82. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/src/index.js +0 -0
  83. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/src/legality.js +0 -0
  84. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/src/mat2gva.js +0 -0
  85. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/src/merge.js +0 -0
  86. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/src/met.js +0 -0
  87. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/src/notation.js +0 -0
  88. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/src/og2gva.js +0 -0
  89. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/src/ogid.js +0 -0
  90. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/src/ogxm2.js +0 -0
  91. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/src/ogxm2_passthrough.js +0 -0
  92. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/src/ogxm2_writer.js +0 -0
  93. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/src/place.js +0 -0
  94. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/src/reader.js +0 -0
  95. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/src/sha256.js +0 -0
  96. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/src/share.js +0 -0
  97. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/src/stats.js +0 -0
  98. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/src/xg2gva.js +0 -0
  99. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/mat/3WNK_g1Z-PLsh_HyvQ5j4a.json +0 -0
  100. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/mat/5nqfGw9bWG3deTaU.json +0 -0
  101. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/mat/B4_SrGcsKAQmoTyHlgJCbM.json +0 -0
  102. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/mat/C07OQCKyw4Pe-CnjFiAMYO.json +0 -0
  103. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/mat/MYdvw1qGuyaRUH__.json +0 -0
  104. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/mat/Sz-2PgKnvb6aFls69-qFXg.json +0 -0
  105. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/mat/UL-CV6F9jMM-E1Pu.json +0 -0
  106. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/mat/XCu3RJ0UvDg_Tldl.json +0 -0
  107. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/mat/baR-U643iUDpvzTC.json +0 -0
  108. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/mat/eXBNG5wZHS5Alu3P.json +0 -0
  109. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/mat/hQ8sVn2LbTdF4wRm.json +0 -0
  110. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/mat/rK7pXm4TqLb9NzWd.json +0 -0
  111. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/mat/uwvEbd63UvdX7SDin5o_Iw.json +0 -0
  112. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/annos-docs.json +0 -0
  113. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/annos-edits-sha256.json +0 -0
  114. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/annos-sha256.json +0 -0
  115. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/annos.expected.json +0 -0
  116. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/annos.ogxm +0 -0
  117. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/bgf-writer-sha256.json +0 -0
  118. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/blocks-docs.json +0 -0
  119. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/blocks-sha256.json +0 -0
  120. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/blocks.expected.json +0 -0
  121. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/blocks.ogxm +0 -0
  122. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/cube-on-two.expected.json +0 -0
  123. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/cube-on-two.ogxm +0 -0
  124. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/fields-docs.json +0 -0
  125. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/fields-sha256.json +0 -0
  126. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/fields.expected.json +0 -0
  127. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/fields.ogxm +0 -0
  128. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/foreign.ogxm +0 -0
  129. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/gen.py +0 -0
  130. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/match.expected.json +0 -0
  131. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/match.ogxm +0 -0
  132. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/passthrough-sha256.json +0 -0
  133. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/resign.expected.json +0 -0
  134. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/resign.ogxm +0 -0
  135. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/set-up.expected.json +0 -0
  136. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/set-up.ogxm +0 -0
  137. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/two-blocks.expected.json +0 -0
  138. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/two-blocks.ogxm +0 -0
  139. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/v1-chunks.gvab +0 -0
  140. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/fixtures/ogxm2/writer-sha256.json +0 -0
  141. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-alternative-move-steps.js +0 -0
  142. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-basefill.js +0 -0
  143. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-bgf-alternatives.js +0 -0
  144. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-bgf-embedded-cube.js +0 -0
  145. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-bgf-move-steps.js +0 -0
  146. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-chunk-passthrough.js +0 -0
  147. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-equity-loss.js +0 -0
  148. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-illegal-move.js +0 -0
  149. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-illegal-play-steps.js +0 -0
  150. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-legality.js +0 -0
  151. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-mat.js +0 -0
  152. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-merge.js +0 -0
  153. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-met.js +0 -0
  154. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-og.js +0 -0
  155. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-ogid.js +0 -0
  156. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-ogxm2-annos.js +0 -0
  157. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-ogxm2-blocks.js +0 -0
  158. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-ogxm2-fields.js +0 -0
  159. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-ogxm2-limits.js +0 -0
  160. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-ogxm2-passthrough.js +0 -0
  161. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-ogxm2-writer.js +0 -0
  162. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-ogxm2.js +0 -0
  163. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-place.js +0 -0
  164. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-robustness.js +0 -0
  165. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-roundtrip.js +0 -0
  166. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-share.js +0 -0
  167. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-stats.js +0 -0
  168. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-xg-cube.js +0 -0
  169. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-xg-eval-levels.js +0 -0
  170. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-xg-move-steps.js +0 -0
  171. {gammonview-3.1.0 → gammonview-3.2.0}/gvformat-js/test/test-xg-zero-win-eval.js +0 -0
  172. {gammonview-3.1.0 → gammonview-3.2.0}/samples/README.md +0 -0
  173. {gammonview-3.1.0 → gammonview-3.2.0}/samples/bgf/3WNK_g1Z-PLsh_HyvQ5j4a.bgf +0 -0
  174. {gammonview-3.1.0 → gammonview-3.2.0}/samples/bgf/5nqfGw9bWG3deTaU.bgf +0 -0
  175. {gammonview-3.1.0 → gammonview-3.2.0}/samples/bgf/B4_SrGcsKAQmoTyHlgJCbM.bgf +0 -0
  176. {gammonview-3.1.0 → gammonview-3.2.0}/samples/bgf/C07OQCKyw4Pe-CnjFiAMYO.bgf +0 -0
  177. {gammonview-3.1.0 → gammonview-3.2.0}/samples/bgf/MYdvw1qGuyaRUH__.bgf +0 -0
  178. {gammonview-3.1.0 → gammonview-3.2.0}/samples/bgf/Sz-2PgKnvb6aFls69-qFXg.bgf +0 -0
  179. {gammonview-3.1.0 → gammonview-3.2.0}/samples/bgf/UL-CV6F9jMM-E1Pu.bgf +0 -0
  180. {gammonview-3.1.0 → gammonview-3.2.0}/samples/bgf/XCu3RJ0UvDg_Tldl.bgf +0 -0
  181. {gammonview-3.1.0 → gammonview-3.2.0}/samples/bgf/baR-U643iUDpvzTC.bgf +0 -0
  182. {gammonview-3.1.0 → gammonview-3.2.0}/samples/bgf/eXBNG5wZHS5Alu3P.bgf +0 -0
  183. {gammonview-3.1.0 → gammonview-3.2.0}/samples/bgf/uwvEbd63UvdX7SDin5o_Iw.bgf +0 -0
  184. {gammonview-3.1.0 → gammonview-3.2.0}/samples/gv/3WNK_g1Z-PLsh_HyvQ5j4a.gvab +0 -0
  185. {gammonview-3.1.0 → gammonview-3.2.0}/samples/gv/5nqfGw9bWG3deTaU.gvab +0 -0
  186. {gammonview-3.1.0 → gammonview-3.2.0}/samples/gv/B4_SrGcsKAQmoTyHlgJCbM.gva +0 -0
  187. {gammonview-3.1.0 → gammonview-3.2.0}/samples/gv/B4_SrGcsKAQmoTyHlgJCbM.gvab +0 -0
  188. {gammonview-3.1.0 → gammonview-3.2.0}/samples/gv/C07OQCKyw4Pe-CnjFiAMYO.gvab +0 -0
  189. {gammonview-3.1.0 → gammonview-3.2.0}/samples/gv/MYdvw1qGuyaRUH__.gvab +0 -0
  190. {gammonview-3.1.0 → gammonview-3.2.0}/samples/gv/Sz-2PgKnvb6aFls69-qFXg.gvab +0 -0
  191. {gammonview-3.1.0 → gammonview-3.2.0}/samples/gv/UL-CV6F9jMM-E1Pu.gvab +0 -0
  192. {gammonview-3.1.0 → gammonview-3.2.0}/samples/gv/XCu3RJ0UvDg_Tldl.gvab +0 -0
  193. {gammonview-3.1.0 → gammonview-3.2.0}/samples/gv/baR-U643iUDpvzTC.gvab +0 -0
  194. {gammonview-3.1.0 → gammonview-3.2.0}/samples/gv/eXBNG5wZHS5Alu3P.gvab +0 -0
  195. {gammonview-3.1.0 → gammonview-3.2.0}/samples/gv/uwvEbd63UvdX7SDin5o_Iw.gvab +0 -0
  196. {gammonview-3.1.0 → gammonview-3.2.0}/samples/mat/3WNK_g1Z-PLsh_HyvQ5j4a.mat +0 -0
  197. {gammonview-3.1.0 → gammonview-3.2.0}/samples/mat/5nqfGw9bWG3deTaU.mat +0 -0
  198. {gammonview-3.1.0 → gammonview-3.2.0}/samples/mat/B4_SrGcsKAQmoTyHlgJCbM.mat +0 -0
  199. {gammonview-3.1.0 → gammonview-3.2.0}/samples/mat/C07OQCKyw4Pe-CnjFiAMYO.mat +0 -0
  200. {gammonview-3.1.0 → gammonview-3.2.0}/samples/mat/MYdvw1qGuyaRUH__.mat +0 -0
  201. {gammonview-3.1.0 → gammonview-3.2.0}/samples/mat/Sz-2PgKnvb6aFls69-qFXg.mat +0 -0
  202. {gammonview-3.1.0 → gammonview-3.2.0}/samples/mat/UL-CV6F9jMM-E1Pu.mat +0 -0
  203. {gammonview-3.1.0 → gammonview-3.2.0}/samples/mat/XCu3RJ0UvDg_Tldl.mat +0 -0
  204. {gammonview-3.1.0 → gammonview-3.2.0}/samples/mat/baR-U643iUDpvzTC.mat +0 -0
  205. {gammonview-3.1.0 → gammonview-3.2.0}/samples/mat/eXBNG5wZHS5Alu3P.mat +0 -0
  206. {gammonview-3.1.0 → gammonview-3.2.0}/samples/mat/hQ8sVn2LbTdF4wRm.mat +0 -0
  207. {gammonview-3.1.0 → gammonview-3.2.0}/samples/mat/rK7pXm4TqLb9NzWd.mat +0 -0
  208. {gammonview-3.1.0 → gammonview-3.2.0}/samples/mat/uwvEbd63UvdX7SDin5o_Iw.mat +0 -0
  209. {gammonview-3.1.0 → gammonview-3.2.0}/samples/xg/3WNK_g1Z-PLsh_HyvQ5j4a.xg +0 -0
  210. {gammonview-3.1.0 → gammonview-3.2.0}/samples/xg/5nqfGw9bWG3deTaU.xg +0 -0
  211. {gammonview-3.1.0 → gammonview-3.2.0}/samples/xg/B4_SrGcsKAQmoTyHlgJCbM.xg +0 -0
  212. {gammonview-3.1.0 → gammonview-3.2.0}/samples/xg/C07OQCKyw4Pe-CnjFiAMYO.xg +0 -0
  213. {gammonview-3.1.0 → gammonview-3.2.0}/samples/xg/MYdvw1qGuyaRUH__.xg +0 -0
  214. {gammonview-3.1.0 → gammonview-3.2.0}/samples/xg/Sz-2PgKnvb6aFls69-qFXg.xg +0 -0
  215. {gammonview-3.1.0 → gammonview-3.2.0}/samples/xg/UL-CV6F9jMM-E1Pu.xg +0 -0
  216. {gammonview-3.1.0 → gammonview-3.2.0}/samples/xg/XCu3RJ0UvDg_Tldl.xg +0 -0
  217. {gammonview-3.1.0 → gammonview-3.2.0}/samples/xg/baR-U643iUDpvzTC.xg +0 -0
  218. {gammonview-3.1.0 → gammonview-3.2.0}/samples/xg/eXBNG5wZHS5Alu3P.xg +0 -0
  219. {gammonview-3.1.0 → gammonview-3.2.0}/samples/xg/hQ8sVn2LbTdF4wRm.xg +0 -0
  220. {gammonview-3.1.0 → gammonview-3.2.0}/samples/xg/rK7pXm4TqLb9NzWd.xg +0 -0
  221. {gammonview-3.1.0 → gammonview-3.2.0}/samples/xg/uwvEbd63UvdX7SDin5o_Iw.xg +0 -0
  222. {gammonview-3.1.0 → gammonview-3.2.0}/tests/audit_corpus.py +0 -0
  223. {gammonview-3.1.0 → gammonview-3.2.0}/tests/fixtures.py +0 -0
  224. {gammonview-3.1.0 → gammonview-3.2.0}/tests/gen_js_v2_fixtures.py +0 -0
  225. {gammonview-3.1.0 → gammonview-3.2.0}/tests/gen_ogxm2_annos_fixtures.py +0 -0
  226. {gammonview-3.1.0 → gammonview-3.2.0}/tests/gen_ogxm2_blocks_fixtures.py +0 -0
  227. {gammonview-3.1.0 → gammonview-3.2.0}/tests/gen_ogxm2_fields_fixtures.py +0 -0
  228. {gammonview-3.1.0 → gammonview-3.2.0}/tests/gen_ogxm2_passthrough_fixtures.py +0 -0
  229. {gammonview-3.1.0 → gammonview-3.2.0}/tests/golden/3WNK_g1Z-PLsh_HyvQ5j4a.fast.gva +0 -0
  230. {gammonview-3.1.0 → gammonview-3.2.0}/tests/golden/3WNK_g1Z-PLsh_HyvQ5j4a.fast.gvab +0 -0
  231. {gammonview-3.1.0 → gammonview-3.2.0}/tests/golden/5nqfGw9bWG3deTaU.fast.gva +0 -0
  232. {gammonview-3.1.0 → gammonview-3.2.0}/tests/golden/5nqfGw9bWG3deTaU.fast.gvab +0 -0
  233. {gammonview-3.1.0 → gammonview-3.2.0}/tests/golden/B4_SrGcsKAQmoTyHlgJCbM.fast.gva +0 -0
  234. {gammonview-3.1.0 → gammonview-3.2.0}/tests/golden/B4_SrGcsKAQmoTyHlgJCbM.fast.gvab +0 -0
  235. {gammonview-3.1.0 → gammonview-3.2.0}/tests/golden/MYdvw1qGuyaRUH__.fast.gva +0 -0
  236. {gammonview-3.1.0 → gammonview-3.2.0}/tests/golden/MYdvw1qGuyaRUH__.fast.gvab +0 -0
  237. {gammonview-3.1.0 → gammonview-3.2.0}/tests/golden/PROVENANCE.json +0 -0
  238. {gammonview-3.1.0 → gammonview-3.2.0}/tests/golden/baR-U643iUDpvzTC.fast.gva +0 -0
  239. {gammonview-3.1.0 → gammonview-3.2.0}/tests/golden/baR-U643iUDpvzTC.fast.gvab +0 -0
  240. {gammonview-3.1.0 → gammonview-3.2.0}/tests/ogxm2_annos_cases.py +0 -0
  241. {gammonview-3.1.0 → gammonview-3.2.0}/tests/ogxm2_blocks_cases.py +0 -0
  242. {gammonview-3.1.0 → gammonview-3.2.0}/tests/ogxm2_fields_cases.py +0 -0
  243. {gammonview-3.1.0 → gammonview-3.2.0}/tests/ogxm2_oracle.py +0 -0
  244. {gammonview-3.1.0 → gammonview-3.2.0}/tests/passthrough_cases.py +0 -0
  245. {gammonview-3.1.0 → gammonview-3.2.0}/tests/regen_golden.py +0 -0
  246. {gammonview-3.1.0 → gammonview-3.2.0}/tests/run_all.py +0 -0
  247. {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_alternative_move_steps.py +0 -0
  248. {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_analysis_orientation.py +0 -0
  249. {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_basefill.py +0 -0
  250. {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_bgf_alternatives.py +0 -0
  251. {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_bgf_cube_response.py +0 -0
  252. {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_bgf_move_steps.py +0 -0
  253. {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_chunk_passthrough.py +0 -0
  254. {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_count_illegal.py +0 -0
  255. {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_cube_tiers.py +0 -0
  256. {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_docs_presets.py +0 -0
  257. {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_equity_loss.py +0 -0
  258. {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_gvab_writer.py +0 -0
  259. {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_illegal_move.py +0 -0
  260. {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_illegal_play_steps.py +0 -0
  261. {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_legality.py +0 -0
  262. {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_loader_formats.py +0 -0
  263. {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_luck_fill.py +0 -0
  264. {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_merge_resign.py +0 -0
  265. {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_met.py +0 -0
  266. {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_ogxm2.py +0 -0
  267. {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_ogxm2_annos.py +0 -0
  268. {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_ogxm2_blocks.py +0 -0
  269. {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_ogxm2_blocks_analysis.py +0 -0
  270. {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_ogxm2_fields.py +0 -0
  271. {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_ogxm2_fields_analysis.py +0 -0
  272. {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_ogxm2_limits.py +0 -0
  273. {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_ogxm2_passthrough.py +0 -0
  274. {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_ogxm2_reference.py +0 -0
  275. {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_ogxm2_writer.py +0 -0
  276. {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_ogxm_conformance.py +0 -0
  277. {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_ogxm_export.py +0 -0
  278. {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_ogxm_pipeline.py +0 -0
  279. {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_ogxm_stats.py +0 -0
  280. {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_parent_watch.py +0 -0
  281. {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_position_id.py +0 -0
  282. {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_presets.py +0 -0
  283. {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_read_gvab.py +0 -0
  284. {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_reconstruct_mat.py +0 -0
  285. {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_resign_actor.py +0 -0
  286. {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_resign_errors.py +0 -0
  287. {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_restated_play_analysis.py +0 -0
  288. {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_screened_checker.py +0 -0
  289. {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_share_link.py +0 -0
  290. {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_xg_cube.py +0 -0
  291. {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_xg_eval_levels.py +0 -0
  292. {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_xg_move_steps.py +0 -0
  293. {gammonview-3.1.0 → gammonview-3.2.0}/tests/test_xg_zero_win_eval.py +0 -0
  294. {gammonview-3.1.0 → gammonview-3.2.0}/uv.lock +0 -0
@@ -7,7 +7,7 @@
7
7
  # must be done before the first run, because the project does not exist yet:
8
8
  #
9
9
  # PyPI project name : gammonview
10
- # Owner : ngvlamis
10
+ # Owner : gammonview
11
11
  # Repository name : gammonview-analysis
12
12
  # Workflow name : publish.yml
13
13
  # Environment name : pypi
@@ -13,6 +13,31 @@ 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.2.0 — 2026-11-04
17
+
18
+ **`analyze_position` takes the money rules: Jacoby and beavers** *(added)*
19
+
20
+ `analyze_position(..., jacoby=None, beaver=None)` and `gvan-position
21
+ --jacoby/--no-jacoby --beaver/--no-beaver`. Until now every money position was
22
+ analysed under bgsage's defaults, Jacoby on and beavers allowed, with no way to
23
+ ask otherwise. A bool chooses the rule; left unset it is the id's own. The reply
24
+ gains `jacoby` and `beaver`, the rules actually applied, both `False` at any
25
+ match score, where bgsage ignores them. With beavers allowed, a wrong double is
26
+ answered with a beaver and the cube's `double_take_equity` is the beaver's
27
+ (twice the take's), with `is_beaver` set, as it always was.
28
+
29
+ **A money XGID's rules field is read as the rules** *(fixed)*
30
+
31
+ At money, the XGID field that holds the Crawford flag in a match holds the
32
+ rules instead -- "Jacoby+2xBeaver", in XG's own spec (eXtreme Gammon 2 Help,
33
+ "XGID"), now quoted in `parse_xgid`'s docstring. It was read as Crawford in both
34
+ cases, so a money "1" claimed a Crawford game and the Jacoby rule it stated was
35
+ lost, and so was the beaver bit. `PositionState` gains `jacoby` and `beaver`
36
+ (None for an OGID, which cannot say, and at a match score). Behaviour change:
37
+ unset, both follow the XGID, so a money XGID with "0" there -- which states
38
+ "No Jacoby, no Beaver" -- is now analysed with neither, where it was analysed
39
+ with both. An OGID is still analysed with both.
40
+
16
41
  ## 3.1.0 — 2026-10-22
17
42
 
18
43
  **`engine_build` names the gammonview version that ran the analysis** *(changed)*
@@ -6,7 +6,7 @@ you need to know before sending one.
6
6
  ## Getting set up
7
7
 
8
8
  ```bash
9
- git clone https://github.com/ngvlamis/gammonview-analysis
9
+ git clone https://github.com/gammonview/gammonview-analysis
10
10
  cd gammonview-analysis
11
11
  uv sync # gvformat + gvanalysis + bgsage + everything the tests need
12
12
  ```
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: gammonview
3
- Version: 3.1.0
3
+ Version: 3.2.0
4
4
  Summary: Backgammon match analysis — the pure-Python OGXM/GVA codec (`gvformat`) plus bgsage-powered match and position analysis (`gvanalysis`).
5
- Project-URL: Homepage, https://github.com/ngvlamis/gammonview-analysis
6
- Project-URL: Repository, https://github.com/ngvlamis/gammonview-analysis
7
- Project-URL: Issues, https://github.com/ngvlamis/gammonview-analysis/issues
5
+ Project-URL: Homepage, https://github.com/gammonview/gammonview-analysis
6
+ Project-URL: Repository, https://github.com/gammonview/gammonview-analysis
7
+ Project-URL: Issues, https://github.com/gammonview/gammonview-analysis/issues
8
8
  Author: Nicholas Vlamis
9
9
  License-Expression: MIT
10
10
  License-File: LICENSE
@@ -28,15 +28,15 @@ Requires-Dist: pyyaml>=6.0.3; extra == 'engine'
28
28
  Description-Content-Type: text/markdown
29
29
 
30
30
  <p align="center">
31
- <img src="https://raw.githubusercontent.com/ngvlamis/gammonview-analysis/main/assets/logo.png"
31
+ <img src="https://raw.githubusercontent.com/gammonview/gammonview-analysis/main/assets/logo.png"
32
32
  alt="" width="104" height="104">
33
33
  </p>
34
34
 
35
35
  <h1 align="center">GammonView</h1>
36
36
 
37
37
  <p align="center">
38
- <a href="https://github.com/ngvlamis/gammonview-analysis/actions/workflows/ci.yml"><img src="https://github.com/ngvlamis/gammonview-analysis/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
39
- <a href="https://github.com/ngvlamis/gammonview-analysis/actions/workflows/engine.yml"><img src="https://github.com/ngvlamis/gammonview-analysis/actions/workflows/engine.yml/badge.svg" alt="engine"></a>
38
+ <a href="https://github.com/gammonview/gammonview-analysis/actions/workflows/ci.yml"><img src="https://github.com/gammonview/gammonview-analysis/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
39
+ <a href="https://github.com/gammonview/gammonview-analysis/actions/workflows/engine.yml"><img src="https://github.com/gammonview/gammonview-analysis/actions/workflows/engine.yml/badge.svg" alt="engine"></a>
40
40
  <a href="https://www.python.org/downloads/"><img src="https://img.shields.io/badge/python-3.10%2B-blue.svg" alt="Python 3.10+"></a>
41
41
  <a href="LICENSE"><img src="https://img.shields.io/badge/licence-MIT-green.svg" alt="Licence: MIT"></a>
42
42
  </p>
@@ -82,7 +82,7 @@ the environment — it installs both packages in editable mode, plus bgsage and
82
82
  the test dependencies:
83
83
 
84
84
  ```bash
85
- git clone https://github.com/ngvlamis/gammonview-analysis.git
85
+ git clone https://github.com/gammonview/gammonview-analysis.git
86
86
  cd gammonview-analysis
87
87
  uv sync
88
88
  uv run python tests/run_all.py --fast # ~3s, no engine needed
@@ -1,13 +1,13 @@
1
1
  <p align="center">
2
- <img src="https://raw.githubusercontent.com/ngvlamis/gammonview-analysis/main/assets/logo.png"
2
+ <img src="https://raw.githubusercontent.com/gammonview/gammonview-analysis/main/assets/logo.png"
3
3
  alt="" width="104" height="104">
4
4
  </p>
5
5
 
6
6
  <h1 align="center">GammonView</h1>
7
7
 
8
8
  <p align="center">
9
- <a href="https://github.com/ngvlamis/gammonview-analysis/actions/workflows/ci.yml"><img src="https://github.com/ngvlamis/gammonview-analysis/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
10
- <a href="https://github.com/ngvlamis/gammonview-analysis/actions/workflows/engine.yml"><img src="https://github.com/ngvlamis/gammonview-analysis/actions/workflows/engine.yml/badge.svg" alt="engine"></a>
9
+ <a href="https://github.com/gammonview/gammonview-analysis/actions/workflows/ci.yml"><img src="https://github.com/gammonview/gammonview-analysis/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
10
+ <a href="https://github.com/gammonview/gammonview-analysis/actions/workflows/engine.yml"><img src="https://github.com/gammonview/gammonview-analysis/actions/workflows/engine.yml/badge.svg" alt="engine"></a>
11
11
  <a href="https://www.python.org/downloads/"><img src="https://img.shields.io/badge/python-3.10%2B-blue.svg" alt="Python 3.10+"></a>
12
12
  <a href="LICENSE"><img src="https://img.shields.io/badge/licence-MIT-green.svg" alt="Licence: MIT"></a>
13
13
  </p>
@@ -53,7 +53,7 @@ the environment — it installs both packages in editable mode, plus bgsage and
53
53
  the test dependencies:
54
54
 
55
55
  ```bash
56
- git clone https://github.com/ngvlamis/gammonview-analysis.git
56
+ git clone https://github.com/gammonview/gammonview-analysis.git
57
57
  cd gammonview-analysis
58
58
  uv sync
59
59
  uv run python tests/run_all.py --fast # ~3s, no engine needed
@@ -174,6 +174,8 @@ position shape is matched (an XGID never matches it).
174
174
  | Flag | Effect |
175
175
  |---|---|
176
176
  | `--level LEVEL` | `1ply`, `2ply`, `3ply`, `4ply`, `truncated1/2/3`, `rollout`. Default `3ply`. |
177
+ | `--jacoby` / `--no-jacoby` | Money games: analyse with or without the Jacoby rule (gammons count single while the cube is centred). Default: what the id says — an XGID's field 8 — and on for an OGID, which has no field for it. Ignored at a match score. |
178
+ | `--beaver` / `--no-beaver` | Money games: allow or forbid beavers. Default as for `--jacoby`: the XGID's field 8, allowed for an OGID. Ignored at a match score. |
177
179
  | `--no-progress` | Suppress the rollout progress bar. |
178
180
 
179
181
  At a rollout level a progress bar is drawn on **stderr**, so piping the report
@@ -58,6 +58,12 @@ class PositionState:
58
58
  #: perspective either way; this is what maps a move back to the absolute
59
59
  #: point numbering the OGXM step form (and every consumer of it) uses.
60
60
  mover_is_white: bool = True
61
+ #: The money-game Jacoby rule, when the id says: an XGID's rules field
62
+ #: does at money, an OGID never does (None). Always None at a match score.
63
+ jacoby: bool | None = None
64
+ #: Whether beavers are allowed, the other half of that field; None in the
65
+ #: same cases.
66
+ beaver: bool | None = None
61
67
 
62
68
 
63
69
  #: Back-compat alias -- this was XGID-only before OGID input was added.
@@ -65,6 +71,32 @@ XGIDState = PositionState
65
71
 
66
72
 
67
73
  def parse_xgid(xgid: str) -> PositionState:
74
+ """Parse an XGID into the mover-perspective state bgsage analyses.
75
+
76
+ The XGID is eXtreme Gammon's, specified in its own help ("XGID", eXtreme
77
+ Gammon 2 Help rev. 1.01, 2011, pp. 145-146) as ten colon-separated fields:
78
+
79
+ 1. Position 26 chars: the top player's bar, points 1-24 from the
80
+ bottom player's side, the bottom player's bar. '-' empty,
81
+ 'A'-'P' 1-16 bottom-player checkers, 'a'-'p' top-player.
82
+ 2. Cube the cube is 2**value.
83
+ 3. Cube pos. 1 bottom player owns it, 0 centred, -1 top player.
84
+ 4. Turn 1 the bottom player's turn, -1 the top player's.
85
+ 5. Dice "00" to roll or double, "D" doubled (take or drop), "B"
86
+ beavered, "R" raccooned, or the two dice ("35").
87
+ 6. Score 1 the bottom player's.
88
+ 7. Score 2 the top player's.
89
+ 8. Crawford, Jacoby
90
+ "Match play: 1 means the current game is Crawford, 0 means
91
+ the current game is not played with the Crawford rule.
92
+ Unlimited games: the value is a Jacoby+2xBeaver" --
93
+ 0 none, 1 Jacoby, 2 beaver, 3 both.
94
+ 9. Match length 0 for unlimited games (money).
95
+ 10. Max cube 2**value.
96
+
97
+ (GNU Backgammon's ``SetXGID`` reads field 8 the same way.) Field 10 is
98
+ optional here, as it is in ids found in the wild.
99
+ """
68
100
  s = xgid.strip()
69
101
  if s.upper().startswith("XGID="):
70
102
  s = s[5:]
@@ -120,7 +152,13 @@ def parse_xgid(xgid: str) -> PositionState:
120
152
  die2 = int(dice_str[1]) if len(dice_str) > 1 and dice_str[1].isdigit() else 0
121
153
 
122
154
  match_len = int(parts[8]) if parts[8].lstrip("-").isdigit() else 0
123
- is_crawford = parts[7] == "1"
155
+ # Field 8 is two fields in one (see the docstring): the Crawford flag in a
156
+ # match, Jacoby + 2 x Beaver at money -- so a money "1" is the Jacoby rule,
157
+ # not a Crawford game money cannot have.
158
+ rules = int(parts[7]) if parts[7].isdigit() else 0
159
+ is_crawford = match_len > 0 and rules == 1
160
+ jacoby = bool(rules & 1) if match_len <= 0 else None
161
+ beaver = bool(rules & 2) if match_len <= 0 else None
124
162
 
125
163
  if match_len > 0:
126
164
  # Scores are written player 1 (White) first; `away1` is the MOVER's.
@@ -141,7 +179,7 @@ def parse_xgid(xgid: str) -> PositionState:
141
179
 
142
180
  return PositionState(
143
181
  board, die1, die2, cube_value, cube_owner, away1, away2, is_crawford,
144
- mover_is_white=mover_is_white,
182
+ mover_is_white=mover_is_white, jacoby=jacoby, beaver=beaver,
145
183
  )
146
184
 
147
185
 
@@ -232,6 +270,8 @@ def analyze_position(
232
270
  max_alternatives: int = MAX_ALTERNATIVES,
233
271
  analyzer=None,
234
272
  on_progress: "Callable[[int, int, str], None] | None" = None,
273
+ jacoby: bool | None = None,
274
+ beaver: bool | None = None,
235
275
  ) -> dict:
236
276
  """Analyze one position and return the result as an OGXM-shaped dict.
237
277
 
@@ -253,6 +293,22 @@ def analyze_position(
253
293
  one costs ~40ms, worth reusing across requests); it must have been built at
254
294
  ``level``, which is reported back as ``eval_level``.
255
295
 
296
+ ``jacoby`` is the money-game rule the position is played under: gammons
297
+ and backgammons count single while the cube is centred. Left as ``None``
298
+ it is the id's own: a money XGID states it in its rules field, and an OGID,
299
+ which cannot, is read under the rule (bgsage's own default). A bool
300
+ overrides the id. It means nothing at a match score, where bgsage ignores
301
+ it, and the reply's ``jacoby`` reports the rule actually applied -- so it
302
+ is ``False`` for every match position.
303
+
304
+ ``beaver`` is the same for beavers: whether a player doubled may redouble
305
+ at once and keep the cube. Left as ``None`` it is the XGID's own beaver bit,
306
+ and allowed for an OGID (bgsage's default). Allowed, a wrong double --
307
+ double/take below zero -- is answered with a beaver, and the cube's
308
+ ``double_take_equity`` is that beaver's (twice the take's), with
309
+ ``is_beaver`` set. The reply's ``beaver`` is the rule applied, ``False`` at
310
+ a match score.
311
+
256
312
  ``on_progress(completed, total, phase)`` reports a rollout's progress; it is
257
313
  never called at the full-width levels, which return too fast to report.
258
314
  ``phase`` is ``"trials"`` for the planned work and ``"finalizing"`` for the
@@ -280,6 +336,13 @@ def analyze_position(
280
336
  away2=state.away2,
281
337
  is_crawford=state.is_crawford,
282
338
  )
339
+ money = not state.away1 and not state.away2
340
+ if jacoby is None:
341
+ jacoby = state.jacoby if state.jacoby is not None else True
342
+ if beaver is None:
343
+ beaver = state.beaver if state.beaver is not None else True
344
+ match_kwargs["jacoby"] = jacoby
345
+ match_kwargs["beaver"] = beaver
283
346
 
284
347
  started = time.perf_counter()
285
348
  result: dict = {
@@ -292,6 +355,8 @@ def analyze_position(
292
355
  "away1": state.away1,
293
356
  "away2": state.away2,
294
357
  "is_crawford": state.is_crawford,
358
+ "jacoby": bool(jacoby) and money,
359
+ "beaver": bool(beaver) and money,
295
360
  "dice": [state.die1, state.die2] if is_checker else None,
296
361
  }
297
362
 
@@ -431,6 +496,17 @@ def main() -> None:
431
496
  parser.add_argument(
432
497
  "--level", default="3ply", choices=EVAL_LEVELS,
433
498
  help="Eval level (default: 3ply)")
499
+ parser.add_argument(
500
+ "--jacoby", action=argparse.BooleanOptionalAction, default=None,
501
+ help="Money games: analyse with or without the Jacoby rule, gammons "
502
+ "counting single while the cube is centred. Default: what the id "
503
+ "says -- an XGID's rules field -- and on for an OGID, which cannot "
504
+ "say. Ignored at a match score")
505
+ parser.add_argument(
506
+ "--beaver", action=argparse.BooleanOptionalAction, default=None,
507
+ help="Money games: allow or forbid beavers. Default: what the id says "
508
+ "-- an XGID's rules field -- and allowed for an OGID. Ignored at a "
509
+ "match score")
434
510
  parser.add_argument(
435
511
  "--no-progress", action="store_true",
436
512
  help="Suppress the rollout progress bar (it is stderr-only and already "
@@ -439,7 +515,13 @@ def main() -> None:
439
515
 
440
516
  fmt = "OGID" if looks_like_ogid(args.position_id) else "XGID"
441
517
  state = parse_position_id(args.position_id)
442
- context = "money game" if state.away1 == 0 else f"match {state.away1}-away vs {state.away2}-away"
518
+ jacoby = args.jacoby if args.jacoby is not None else (
519
+ state.jacoby if state.jacoby is not None else True)
520
+ beaver = args.beaver if args.beaver is not None else (
521
+ state.beaver if state.beaver is not None else True)
522
+ context = ("money game, " + ("Jacoby" if jacoby else "no Jacoby")
523
+ + (", beavers" if beaver else ", no beavers")
524
+ if state.away1 == 0 else f"match {state.away1}-away vs {state.away2}-away")
443
525
  print(f"Position ({fmt}): cube={state.cube_value} ({state.cube_owner}) {context}"
444
526
  + (" [Crawford]" if state.is_crawford else ""))
445
527
 
@@ -468,7 +550,8 @@ def main() -> None:
468
550
 
469
551
  try:
470
552
  result = analyze_position(args.position_id, level=args.level,
471
- on_progress=_progress)
553
+ on_progress=_progress, jacoby=jacoby,
554
+ beaver=beaver)
472
555
  finally:
473
556
  if bar is not None:
474
557
  bar.clear()
@@ -7,7 +7,7 @@ OGIDs. **No engine, no WebAssembly, no build step.** One runtime
7
7
  dependency (`pako`, for zlib).
8
8
 
9
9
  This is the browser half of
10
- [gammonview-analysis](https://github.com/ngvlamis/gammonview-analysis): Python
10
+ [gammonview-analysis](https://github.com/gammonview/gammonview-analysis): Python
11
11
  writes `.gvab` server-side, this reads it client-side. It is an ESM mirror of the
12
12
  Python `gvformat` package and **the definitive copy of the JavaScript** — the
13
13
  port is maintained here, not downstream.
@@ -10,12 +10,12 @@
10
10
  "description": "OGXM binary codec — read/write .gvab match files",
11
11
  "repository": {
12
12
  "type": "git",
13
- "url": "git+https://github.com/ngvlamis/gammonview-analysis.git",
13
+ "url": "git+https://github.com/gammonview/gammonview-analysis.git",
14
14
  "directory": "gvformat-js"
15
15
  },
16
- "homepage": "https://github.com/ngvlamis/gammonview-analysis/tree/main/gvformat-js",
16
+ "homepage": "https://github.com/gammonview/gammonview-analysis/tree/main/gvformat-js",
17
17
  "bugs": {
18
- "url": "https://github.com/ngvlamis/gammonview-analysis/issues"
18
+ "url": "https://github.com/gammonview/gammonview-analysis/issues"
19
19
  },
20
20
  "publishConfig": {
21
21
  "access": "public"
@@ -31,9 +31,9 @@ classifiers = [
31
31
  dependencies = []
32
32
 
33
33
  [project.urls]
34
- Homepage = "https://github.com/ngvlamis/gammonview-analysis"
35
- Repository = "https://github.com/ngvlamis/gammonview-analysis"
36
- Issues = "https://github.com/ngvlamis/gammonview-analysis/issues"
34
+ Homepage = "https://github.com/gammonview/gammonview-analysis"
35
+ Repository = "https://github.com/gammonview/gammonview-analysis"
36
+ Issues = "https://github.com/gammonview/gammonview-analysis/issues"
37
37
 
38
38
  [project.optional-dependencies]
39
39
  # The bgsage-powered analysis layer (`gvanalysis`: game_eval, match, position
@@ -29,7 +29,7 @@ from pathlib import Path
29
29
  _REPO_ROOT = Path(__file__).resolve().parent.parent
30
30
  sys.path.insert(0, str(_REPO_ROOT))
31
31
 
32
- from gvanalysis.position import EVAL_LEVELS, analyze_position
32
+ from gvanalysis.position import EVAL_LEVELS, analyze_position, parse_position_id
33
33
 
34
34
  _checks = 0
35
35
  _failures: list[str] = []
@@ -227,6 +227,69 @@ def main() -> int:
227
227
  == [a["notation"] for a in many["alternatives"][:5]],
228
228
  "a smaller cap truncates the same ranking, it does not reorder it")
229
229
 
230
+ print("11. the Jacoby rule is the caller's, and money-only")
231
+ # A blitz: three checkers on the bar against a closed board. Gammons are
232
+ # most of the wins, so with the cube centred the rule decides the cube:
233
+ # under Jacoby a gammon counts single and holding is worth at most a point,
234
+ # without it holding is worth far more than the point a pass gives.
235
+ blitz = "XGID=cBBBBBB-C----------cccc---:0:0:1:00:0:0:0:0:10"
236
+ jac = analyze_position(blitz, level="1ply", jacoby=True)
237
+ nojac = analyze_position(blitz, level="1ply", jacoby=False)
238
+ check(jac["jacoby"] is True and nojac["jacoby"] is False,
239
+ "the reply says which rule was applied")
240
+ check(jac["cube"]["no_double_equity"] <= 1.0 + 1e-9,
241
+ f"under Jacoby, holding is worth at most a point (got {jac['cube']['no_double_equity']})")
242
+ check(nojac["cube"]["no_double_equity"] > 1.2,
243
+ f"without it, gammons make holding worth more (got {nojac['cube']['no_double_equity']})")
244
+ match = analyze_position(
245
+ "XGID=cBBBBBB-C----------cccc---:0:0:1:00:0:0:0:7:10", level="1ply")
246
+ check(match["jacoby"] is False, "a match position reports no Jacoby, however asked")
247
+
248
+ print("12. a money XGID's rules field is the Jacoby rule, not Crawford")
249
+ # Field 8 at money is "Jacoby+2xBeaver" (XG's own spec; see parse_xgid).
250
+ # An OGID has no such field.
251
+ said_on = parse_position_id(blitz.replace(":0:0:0:0:10", ":0:0:1:0:10"))
252
+ said_off = parse_position_id(blitz)
253
+ check(said_on.jacoby is True and said_on.is_crawford is False,
254
+ "a money '1' is Jacoby, and no Crawford game")
255
+ check(parse_position_id(blitz.replace(":0:0:0:0:10", ":0:0:3:0:10")).jacoby is True,
256
+ "Jacoby and beavers together ('3') is still Jacoby")
257
+ check(said_off.jacoby is False, "a money '0' states the rule is off")
258
+ check(parse_position_id(OPENING_31_OGID).jacoby is None, "an OGID says nothing")
259
+ m = parse_position_id("XGID=-b----E-C---eE---c-e----B-:0:0:1:31:0:0:1:7:10")
260
+ check(m.is_crawford is True and m.jacoby is None, "at a match score '1' is still Crawford")
261
+ check(analyze_position(blitz.replace(":0:0:0:0:10", ":0:0:1:0:10"), level="1ply")["jacoby"] is True
262
+ and analyze_position(blitz, level="1ply")["jacoby"] is False,
263
+ "left unset, the analysis follows what the XGID says")
264
+ check(analyze_position(OPENING_CUBE_XGID.replace("XGID=", ""), level="1ply")["jacoby"] is False,
265
+ "(an XGID written with '0' there, as most are, says off)")
266
+
267
+ print("13. beavers: the caller's, or the XGID's, and money-only")
268
+ # The blitz from the other side: three checkers on the bar against a
269
+ # closed board, and it is their turn. A double here is a gift, so if the
270
+ # rules allow it the opponent beavers -- which doubles the take's equity.
271
+ victim = "XGID=cBBBBBB-C----------cccc---:0:0:-1:00:0:0:0:0:10"
272
+ allowed = analyze_position(victim, level="1ply", beaver=True)
273
+ forbidden = analyze_position(victim, level="1ply", beaver=False)
274
+ check(allowed["beaver"] is True and forbidden["beaver"] is False,
275
+ "the reply says which rule was applied")
276
+ check(allowed["cube"]["is_beaver"] and not forbidden["cube"]["is_beaver"],
277
+ "a wrong double is beavered only when beavers are allowed")
278
+ check(abs(allowed["cube"]["double_take_equity"]
279
+ - 2 * forbidden["cube"]["double_take_equity"]) < 1e-3,
280
+ f"and the beaver doubles the take's equity (got {allowed['cube']['double_take_equity']} "
281
+ f"vs {forbidden['cube']['double_take_equity']})")
282
+ check(parse_position_id(victim.replace(":0:0:0:0:10", ":0:0:2:0:10")).beaver is True
283
+ and parse_position_id(victim).beaver is False
284
+ and parse_position_id(OPENING_31_OGID).beaver is None,
285
+ "an XGID's beaver bit is read; an OGID says nothing")
286
+ check(analyze_position(victim.replace(":0:0:0:0:10", ":0:0:2:0:10"), level="1ply")["cube"]["is_beaver"]
287
+ and not analyze_position(victim, level="1ply")["cube"]["is_beaver"],
288
+ "left unset, the analysis follows the XGID")
289
+ check(analyze_position(
290
+ "XGID=cBBBBBB-C----------cccc---:0:0:-1:00:0:0:0:7:10", level="1ply")["beaver"] is False,
291
+ "a match position reports no beavers")
292
+
230
293
  print(f"\n{_checks - len(_failures)}/{_checks} checks passed.")
231
294
  if _failures:
232
295
  print("FAILURES:")
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes