gammonview 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (210) hide show
  1. gammonview-1.0.0/.gitattributes +21 -0
  2. gammonview-1.0.0/.github/workflows/ci.yml +127 -0
  3. gammonview-1.0.0/.github/workflows/engine.yml +70 -0
  4. gammonview-1.0.0/.github/workflows/publish.yml +93 -0
  5. gammonview-1.0.0/.gitignore +40 -0
  6. gammonview-1.0.0/.python-version +1 -0
  7. gammonview-1.0.0/CHANGELOG.md +451 -0
  8. gammonview-1.0.0/CLAUDE.md +367 -0
  9. gammonview-1.0.0/CONTRIBUTING.md +160 -0
  10. gammonview-1.0.0/LICENSE +21 -0
  11. gammonview-1.0.0/PKG-INFO +319 -0
  12. gammonview-1.0.0/README.md +290 -0
  13. gammonview-1.0.0/THIRD-PARTY-NOTICES.md +80 -0
  14. gammonview-1.0.0/assets/logo.png +0 -0
  15. gammonview-1.0.0/docs/CLI.md +311 -0
  16. gammonview-1.0.0/docs/METHODOLOGY.md +111 -0
  17. gammonview-1.0.0/docs/OGXM_COMPUTED_FIELDS.md +275 -0
  18. gammonview-1.0.0/docs/OGXM_FORMAT_SPEC_GAMMONVIEW.md +703 -0
  19. gammonview-1.0.0/docs/OGXM_JSON_SPEC_GAMMONVIEW.md +851 -0
  20. gammonview-1.0.0/docs/PRESET_ACCURACY.md +229 -0
  21. gammonview-1.0.0/docs/upstream-bgsage.md +258 -0
  22. gammonview-1.0.0/gvanalysis/__init__.py +118 -0
  23. gammonview-1.0.0/gvanalysis/batch.py +232 -0
  24. gammonview-1.0.0/gvanalysis/checker_eval.py +378 -0
  25. gammonview-1.0.0/gvanalysis/compare_gva.py +323 -0
  26. gammonview-1.0.0/gvanalysis/game_eval.py +1010 -0
  27. gammonview-1.0.0/gvanalysis/loader.py +67 -0
  28. gammonview-1.0.0/gvanalysis/luck.py +143 -0
  29. gammonview-1.0.0/gvanalysis/match.py +832 -0
  30. gammonview-1.0.0/gvanalysis/ogxm_reconstructor.py +225 -0
  31. gammonview-1.0.0/gvanalysis/position.py +474 -0
  32. gammonview-1.0.0/gvanalysis/presets.py +344 -0
  33. gammonview-1.0.0/gvanalysis/progress.py +98 -0
  34. gammonview-1.0.0/gvanalysis/reconstruct_mat.py +306 -0
  35. gammonview-1.0.0/gvanalysis/share.py +30 -0
  36. gammonview-1.0.0/gvformat/__init__.py +65 -0
  37. gammonview-1.0.0/gvformat/basefill.py +300 -0
  38. gammonview-1.0.0/gvformat/bgf.py +1569 -0
  39. gammonview-1.0.0/gvformat/binary.py +956 -0
  40. gammonview-1.0.0/gvformat/export.py +1351 -0
  41. gammonview-1.0.0/gvformat/game_reconstructor.py +311 -0
  42. gammonview-1.0.0/gvformat/legality.py +243 -0
  43. gammonview-1.0.0/gvformat/mat.py +144 -0
  44. gammonview-1.0.0/gvformat/mat_parser.py +323 -0
  45. gammonview-1.0.0/gvformat/merge.py +133 -0
  46. gammonview-1.0.0/gvformat/met.py +196 -0
  47. gammonview-1.0.0/gvformat/notation.py +212 -0
  48. gammonview-1.0.0/gvformat/ogid.py +849 -0
  49. gammonview-1.0.0/gvformat/place.py +54 -0
  50. gammonview-1.0.0/gvformat/reader.py +1061 -0
  51. gammonview-1.0.0/gvformat/share.py +50 -0
  52. gammonview-1.0.0/gvformat/stats.py +450 -0
  53. gammonview-1.0.0/gvformat/xg.py +1413 -0
  54. gammonview-1.0.0/gvformat-js/.gitignore +1 -0
  55. gammonview-1.0.0/gvformat-js/LICENSE +21 -0
  56. gammonview-1.0.0/gvformat-js/README.md +108 -0
  57. gammonview-1.0.0/gvformat-js/THIRD-PARTY-NOTICES.md +80 -0
  58. gammonview-1.0.0/gvformat-js/cli/README.md +27 -0
  59. gammonview-1.0.0/gvformat-js/cli/bgf2gva.js +27 -0
  60. gammonview-1.0.0/gvformat-js/cli/mat2gva.js +27 -0
  61. gammonview-1.0.0/gvformat-js/cli/xg2gva.js +27 -0
  62. gammonview-1.0.0/gvformat-js/package-lock.json +35 -0
  63. gammonview-1.0.0/gvformat-js/package.json +39 -0
  64. gammonview-1.0.0/gvformat-js/src/basefill.js +263 -0
  65. gammonview-1.0.0/gvformat-js/src/bgf2gva.js +1337 -0
  66. gammonview-1.0.0/gvformat-js/src/binary.js +999 -0
  67. gammonview-1.0.0/gvformat-js/src/constants.js +92 -0
  68. gammonview-1.0.0/gvformat-js/src/export.js +1028 -0
  69. gammonview-1.0.0/gvformat-js/src/index.js +27 -0
  70. gammonview-1.0.0/gvformat-js/src/legality.js +220 -0
  71. gammonview-1.0.0/gvformat-js/src/mat2gva.js +737 -0
  72. gammonview-1.0.0/gvformat-js/src/merge.js +203 -0
  73. gammonview-1.0.0/gvformat-js/src/met.js +149 -0
  74. gammonview-1.0.0/gvformat-js/src/notation.js +212 -0
  75. gammonview-1.0.0/gvformat-js/src/og2gva.js +410 -0
  76. gammonview-1.0.0/gvformat-js/src/ogid.js +615 -0
  77. gammonview-1.0.0/gvformat-js/src/place.js +54 -0
  78. gammonview-1.0.0/gvformat-js/src/reader.js +1082 -0
  79. gammonview-1.0.0/gvformat-js/src/share.js +58 -0
  80. gammonview-1.0.0/gvformat-js/src/stats.js +359 -0
  81. gammonview-1.0.0/gvformat-js/src/xg2gva.js +1175 -0
  82. gammonview-1.0.0/gvformat-js/test/fixtures/mat/3WNK_g1Z-PLsh_HyvQ5j4a.json +1 -0
  83. gammonview-1.0.0/gvformat-js/test/fixtures/mat/5nqfGw9bWG3deTaU.json +1 -0
  84. gammonview-1.0.0/gvformat-js/test/fixtures/mat/B4_SrGcsKAQmoTyHlgJCbM.json +1 -0
  85. gammonview-1.0.0/gvformat-js/test/fixtures/mat/C07OQCKyw4Pe-CnjFiAMYO.json +1 -0
  86. gammonview-1.0.0/gvformat-js/test/fixtures/mat/MYdvw1qGuyaRUH__.json +1 -0
  87. gammonview-1.0.0/gvformat-js/test/fixtures/mat/Sz-2PgKnvb6aFls69-qFXg.json +1 -0
  88. gammonview-1.0.0/gvformat-js/test/fixtures/mat/UL-CV6F9jMM-E1Pu.json +1 -0
  89. gammonview-1.0.0/gvformat-js/test/fixtures/mat/XCu3RJ0UvDg_Tldl.json +1 -0
  90. gammonview-1.0.0/gvformat-js/test/fixtures/mat/baR-U643iUDpvzTC.json +1 -0
  91. gammonview-1.0.0/gvformat-js/test/fixtures/mat/eXBNG5wZHS5Alu3P.json +1 -0
  92. gammonview-1.0.0/gvformat-js/test/fixtures/mat/uwvEbd63UvdX7SDin5o_Iw.json +1 -0
  93. gammonview-1.0.0/gvformat-js/test/test-alternative-move-steps.js +145 -0
  94. gammonview-1.0.0/gvformat-js/test/test-basefill.js +268 -0
  95. gammonview-1.0.0/gvformat-js/test/test-bgf-alternatives.js +73 -0
  96. gammonview-1.0.0/gvformat-js/test/test-bgf-embedded-cube.js +93 -0
  97. gammonview-1.0.0/gvformat-js/test/test-bgf-move-steps.js +124 -0
  98. gammonview-1.0.0/gvformat-js/test/test-chunk-passthrough.js +161 -0
  99. gammonview-1.0.0/gvformat-js/test/test-equity-loss.js +207 -0
  100. gammonview-1.0.0/gvformat-js/test/test-illegal-move.js +123 -0
  101. gammonview-1.0.0/gvformat-js/test/test-illegal-play-steps.js +122 -0
  102. gammonview-1.0.0/gvformat-js/test/test-legality.js +145 -0
  103. gammonview-1.0.0/gvformat-js/test/test-mat.js +270 -0
  104. gammonview-1.0.0/gvformat-js/test/test-merge.js +330 -0
  105. gammonview-1.0.0/gvformat-js/test/test-met.js +76 -0
  106. gammonview-1.0.0/gvformat-js/test/test-og.js +495 -0
  107. gammonview-1.0.0/gvformat-js/test/test-ogid.js +258 -0
  108. gammonview-1.0.0/gvformat-js/test/test-place.js +121 -0
  109. gammonview-1.0.0/gvformat-js/test/test-robustness.js +221 -0
  110. gammonview-1.0.0/gvformat-js/test/test-roundtrip.js +409 -0
  111. gammonview-1.0.0/gvformat-js/test/test-share.js +110 -0
  112. gammonview-1.0.0/gvformat-js/test/test-stats.js +227 -0
  113. gammonview-1.0.0/gvformat-js/test/test-xg-cube.js +67 -0
  114. gammonview-1.0.0/gvformat-js/test/test-xg-eval-levels.js +106 -0
  115. gammonview-1.0.0/gvformat-js/test/test-xg-move-steps.js +117 -0
  116. gammonview-1.0.0/gvformat-js/test/test-xg-zero-win-eval.js +91 -0
  117. gammonview-1.0.0/pyproject.toml +95 -0
  118. gammonview-1.0.0/samples/README.md +245 -0
  119. gammonview-1.0.0/samples/bgf/3WNK_g1Z-PLsh_HyvQ5j4a.bgf +0 -0
  120. gammonview-1.0.0/samples/bgf/5nqfGw9bWG3deTaU.bgf +0 -0
  121. gammonview-1.0.0/samples/bgf/B4_SrGcsKAQmoTyHlgJCbM.bgf +0 -0
  122. gammonview-1.0.0/samples/bgf/C07OQCKyw4Pe-CnjFiAMYO.bgf +0 -0
  123. gammonview-1.0.0/samples/bgf/MYdvw1qGuyaRUH__.bgf +0 -0
  124. gammonview-1.0.0/samples/bgf/Sz-2PgKnvb6aFls69-qFXg.bgf +0 -0
  125. gammonview-1.0.0/samples/bgf/UL-CV6F9jMM-E1Pu.bgf +0 -0
  126. gammonview-1.0.0/samples/bgf/XCu3RJ0UvDg_Tldl.bgf +0 -0
  127. gammonview-1.0.0/samples/bgf/baR-U643iUDpvzTC.bgf +0 -0
  128. gammonview-1.0.0/samples/bgf/eXBNG5wZHS5Alu3P.bgf +0 -0
  129. gammonview-1.0.0/samples/bgf/uwvEbd63UvdX7SDin5o_Iw.bgf +0 -0
  130. gammonview-1.0.0/samples/gv/3WNK_g1Z-PLsh_HyvQ5j4a.gvab +0 -0
  131. gammonview-1.0.0/samples/gv/5nqfGw9bWG3deTaU.gvab +0 -0
  132. gammonview-1.0.0/samples/gv/B4_SrGcsKAQmoTyHlgJCbM.gva +1 -0
  133. gammonview-1.0.0/samples/gv/B4_SrGcsKAQmoTyHlgJCbM.gvab +0 -0
  134. gammonview-1.0.0/samples/gv/C07OQCKyw4Pe-CnjFiAMYO.gvab +0 -0
  135. gammonview-1.0.0/samples/gv/MYdvw1qGuyaRUH__.gvab +0 -0
  136. gammonview-1.0.0/samples/gv/Sz-2PgKnvb6aFls69-qFXg.gvab +0 -0
  137. gammonview-1.0.0/samples/gv/UL-CV6F9jMM-E1Pu.gvab +0 -0
  138. gammonview-1.0.0/samples/gv/XCu3RJ0UvDg_Tldl.gvab +0 -0
  139. gammonview-1.0.0/samples/gv/baR-U643iUDpvzTC.gvab +0 -0
  140. gammonview-1.0.0/samples/gv/eXBNG5wZHS5Alu3P.gvab +0 -0
  141. gammonview-1.0.0/samples/gv/uwvEbd63UvdX7SDin5o_Iw.gvab +0 -0
  142. gammonview-1.0.0/samples/mat/3WNK_g1Z-PLsh_HyvQ5j4a.mat +179 -0
  143. gammonview-1.0.0/samples/mat/5nqfGw9bWG3deTaU.mat +157 -0
  144. gammonview-1.0.0/samples/mat/B4_SrGcsKAQmoTyHlgJCbM.mat +58 -0
  145. gammonview-1.0.0/samples/mat/C07OQCKyw4Pe-CnjFiAMYO.mat +188 -0
  146. gammonview-1.0.0/samples/mat/MYdvw1qGuyaRUH__.mat +91 -0
  147. gammonview-1.0.0/samples/mat/Sz-2PgKnvb6aFls69-qFXg.mat +161 -0
  148. gammonview-1.0.0/samples/mat/UL-CV6F9jMM-E1Pu.mat +138 -0
  149. gammonview-1.0.0/samples/mat/XCu3RJ0UvDg_Tldl.mat +76 -0
  150. gammonview-1.0.0/samples/mat/baR-U643iUDpvzTC.mat +75 -0
  151. gammonview-1.0.0/samples/mat/eXBNG5wZHS5Alu3P.mat +130 -0
  152. gammonview-1.0.0/samples/mat/uwvEbd63UvdX7SDin5o_Iw.mat +165 -0
  153. gammonview-1.0.0/samples/xg/3WNK_g1Z-PLsh_HyvQ5j4a.xg +0 -0
  154. gammonview-1.0.0/samples/xg/5nqfGw9bWG3deTaU.xg +0 -0
  155. gammonview-1.0.0/samples/xg/B4_SrGcsKAQmoTyHlgJCbM.xg +0 -0
  156. gammonview-1.0.0/samples/xg/C07OQCKyw4Pe-CnjFiAMYO.xg +0 -0
  157. gammonview-1.0.0/samples/xg/MYdvw1qGuyaRUH__.xg +0 -0
  158. gammonview-1.0.0/samples/xg/Sz-2PgKnvb6aFls69-qFXg.xg +0 -0
  159. gammonview-1.0.0/samples/xg/UL-CV6F9jMM-E1Pu.xg +0 -0
  160. gammonview-1.0.0/samples/xg/XCu3RJ0UvDg_Tldl.xg +0 -0
  161. gammonview-1.0.0/samples/xg/baR-U643iUDpvzTC.xg +0 -0
  162. gammonview-1.0.0/samples/xg/eXBNG5wZHS5Alu3P.xg +0 -0
  163. gammonview-1.0.0/samples/xg/uwvEbd63UvdX7SDin5o_Iw.xg +0 -0
  164. gammonview-1.0.0/tests/audit_corpus.py +256 -0
  165. gammonview-1.0.0/tests/fixtures.py +137 -0
  166. gammonview-1.0.0/tests/golden/3WNK_g1Z-PLsh_HyvQ5j4a.fast.gva +1 -0
  167. gammonview-1.0.0/tests/golden/3WNK_g1Z-PLsh_HyvQ5j4a.fast.gvab +0 -0
  168. gammonview-1.0.0/tests/golden/5nqfGw9bWG3deTaU.fast.gva +1 -0
  169. gammonview-1.0.0/tests/golden/5nqfGw9bWG3deTaU.fast.gvab +0 -0
  170. gammonview-1.0.0/tests/golden/B4_SrGcsKAQmoTyHlgJCbM.fast.gva +1 -0
  171. gammonview-1.0.0/tests/golden/B4_SrGcsKAQmoTyHlgJCbM.fast.gvab +0 -0
  172. gammonview-1.0.0/tests/golden/MYdvw1qGuyaRUH__.fast.gva +1 -0
  173. gammonview-1.0.0/tests/golden/MYdvw1qGuyaRUH__.fast.gvab +0 -0
  174. gammonview-1.0.0/tests/golden/PROVENANCE.json +7 -0
  175. gammonview-1.0.0/tests/golden/baR-U643iUDpvzTC.fast.gva +1 -0
  176. gammonview-1.0.0/tests/golden/baR-U643iUDpvzTC.fast.gvab +0 -0
  177. gammonview-1.0.0/tests/regen_golden.py +129 -0
  178. gammonview-1.0.0/tests/run_all.py +147 -0
  179. gammonview-1.0.0/tests/test_alternative_move_steps.py +153 -0
  180. gammonview-1.0.0/tests/test_analyze_position.py +237 -0
  181. gammonview-1.0.0/tests/test_basefill.py +310 -0
  182. gammonview-1.0.0/tests/test_bgf_alternatives.py +106 -0
  183. gammonview-1.0.0/tests/test_bgf_cube_response.py +148 -0
  184. gammonview-1.0.0/tests/test_bgf_move_steps.py +133 -0
  185. gammonview-1.0.0/tests/test_chunk_passthrough.py +231 -0
  186. gammonview-1.0.0/tests/test_count_illegal.py +130 -0
  187. gammonview-1.0.0/tests/test_cube_tiers.py +168 -0
  188. gammonview-1.0.0/tests/test_docs_presets.py +135 -0
  189. gammonview-1.0.0/tests/test_equity_loss.py +177 -0
  190. gammonview-1.0.0/tests/test_gvab_writer.py +666 -0
  191. gammonview-1.0.0/tests/test_illegal_move.py +149 -0
  192. gammonview-1.0.0/tests/test_illegal_play_steps.py +180 -0
  193. gammonview-1.0.0/tests/test_legality.py +183 -0
  194. gammonview-1.0.0/tests/test_luck_fill.py +138 -0
  195. gammonview-1.0.0/tests/test_met.py +104 -0
  196. gammonview-1.0.0/tests/test_ogxm_conformance.py +377 -0
  197. gammonview-1.0.0/tests/test_ogxm_export.py +650 -0
  198. gammonview-1.0.0/tests/test_ogxm_pipeline.py +224 -0
  199. gammonview-1.0.0/tests/test_ogxm_stats.py +209 -0
  200. gammonview-1.0.0/tests/test_position_id.py +194 -0
  201. gammonview-1.0.0/tests/test_read_gvab.py +564 -0
  202. gammonview-1.0.0/tests/test_reconstruct_mat.py +187 -0
  203. gammonview-1.0.0/tests/test_resign_actor.py +107 -0
  204. gammonview-1.0.0/tests/test_screened_checker.py +371 -0
  205. gammonview-1.0.0/tests/test_share_link.py +141 -0
  206. gammonview-1.0.0/tests/test_xg_cube.py +87 -0
  207. gammonview-1.0.0/tests/test_xg_eval_levels.py +108 -0
  208. gammonview-1.0.0/tests/test_xg_move_steps.py +124 -0
  209. gammonview-1.0.0/tests/test_xg_zero_win_eval.py +106 -0
  210. gammonview-1.0.0/uv.lock +357 -0
@@ -0,0 +1,21 @@
1
+ # Normalise line endings to LF. Nothing tracked here contains a CRLF today, so
2
+ # this is a no-op on the current tree; it exists so a clone with
3
+ # core.autocrlf=true cannot start rewriting one, in a repo whose tests compare
4
+ # fixtures exactly.
5
+ * text=auto eol=lf
6
+
7
+ # Binary fixtures, declared rather than detected. Git's heuristic would catch
8
+ # every one of these (all carry NULs in the first bytes -- checked), but "would
9
+ # detect" is the wrong guarantee for files a test compares as raw bytes: the
10
+ # .gvab goldens that pin the format, and the .xg/.bgf sources the converters
11
+ # are asserted against ply by ply. Declaring it also stops these showing up as
12
+ # diff noise.
13
+ *.gvab binary
14
+ *.xg binary
15
+ *.bgf binary
16
+ *.png binary
17
+
18
+ # Generated lockfiles: collapsed in diffs on GitHub, and never worth reviewing
19
+ # line by line. They are still tracked, and still reviewed as a version bump.
20
+ uv.lock linguist-generated=true -diff
21
+ gvformat-js/package-lock.json linguist-generated=true -diff
@@ -0,0 +1,127 @@
1
+ # The cheap half of the suite, on every push and pull request.
2
+ #
3
+ # `tests/run_all.py --fast` skips the thirteen names in that file's ENGINE_BACKED
4
+ # set -- the tests that need the `[engine]` extra and minutes of CPU. Those run
5
+ # in engine.yml on a slower cadence. Splitting them is what keeps this workflow
6
+ # a few seconds per row, so the matrix below can afford to be wide.
7
+ name: CI
8
+
9
+ on:
10
+ push:
11
+ branches: [main]
12
+ pull_request:
13
+ workflow_dispatch:
14
+
15
+ permissions:
16
+ contents: read
17
+
18
+ # A newer push to the same ref makes the older run's verdict worthless.
19
+ concurrency:
20
+ group: ci-${{ github.ref }}
21
+ cancel-in-progress: true
22
+
23
+ jobs:
24
+ codec:
25
+ name: codec (${{ matrix.os }}, py${{ matrix.python }})
26
+ runs-on: ${{ matrix.os }}
27
+ strategy:
28
+ # One red row should not hide the state of the others -- the usual reason
29
+ # to look at this matrix is to find out *which* versions broke.
30
+ fail-fast: false
31
+ matrix:
32
+ os: [ubuntu-latest]
33
+ python: ["3.10", "3.11", "3.12", "3.13", "3.14"]
34
+ include:
35
+ # Windows earns a row on its own merits: the suite compares .gvab
36
+ # goldens as raw bytes and reads .gva back with read_text(), which is
37
+ # precisely the sort of thing a line-ending or default-encoding
38
+ # difference breaks. .gitattributes is the other half of that defence.
39
+ - os: windows-latest
40
+ python: "3.13"
41
+ # macOS is the author's platform, so it is the configuration every
42
+ # change is written against; a row here says so out loud.
43
+ - os: macos-latest
44
+ python: "3.13"
45
+ steps:
46
+ - uses: actions/checkout@v4
47
+
48
+ - uses: astral-sh/setup-uv@v6
49
+ with:
50
+ enable-cache: true
51
+
52
+ # Two flags, each load-bearing:
53
+ #
54
+ # `--python` is not optional. A tracked .python-version pins 3.13 and uv
55
+ # honours it, so without an explicit override every row of this matrix
56
+ # would quietly run 3.13 and the 3.10 row would prove nothing at all.
57
+ #
58
+ # `--no-dev` is the assertion, not a saving. The dev group carries bgsage
59
+ # and pyyaml; leaving it out means this job installs the project with zero
60
+ # third-party dependencies, which is the README's central claim about
61
+ # `gvformat`. If an import creeps in from the engine side, this goes red.
62
+ - name: fast suite, no engine
63
+ run: uv run --python ${{ matrix.python }} --no-dev python tests/run_all.py --fast
64
+
65
+ js:
66
+ name: gvformat-js (node ${{ matrix.node }})
67
+ runs-on: ubuntu-latest
68
+ strategy:
69
+ fail-fast: false
70
+ # package.json declares `engines: node >=18`. Test the floor it promises
71
+ # and the current release; the versions between them are not where an ESM
72
+ # or TypedArray difference is going to appear.
73
+ matrix:
74
+ node: ["18", "24"]
75
+ steps:
76
+ - uses: actions/checkout@v4
77
+
78
+ - uses: actions/setup-node@v4
79
+ with:
80
+ node-version: ${{ matrix.node }}
81
+ cache: npm
82
+ cache-dependency-path: gvformat-js/package-lock.json
83
+
84
+ # `npm ci`, not `npm install`: the one runtime dependency (pako) is
85
+ # pinned in the lockfile, and the lockfile is what the published tarball
86
+ # is built against. The only publishConfig here is `access: public`,
87
+ # which scoped packages need at publish time and which affects nothing
88
+ # about this install.
89
+ - run: npm ci
90
+ working-directory: gvformat-js
91
+
92
+ - run: npm test
93
+ working-directory: gvformat-js
94
+
95
+ build:
96
+ name: build (sdist + wheel)
97
+ runs-on: ubuntu-latest
98
+ steps:
99
+ - uses: actions/checkout@v4
100
+ with:
101
+ # hatch-vcs derives the version from git tags. A shallow clone has no
102
+ # tags and the build does not fail -- it quietly stamps 0.1.dev1+g...
103
+ # on the artifacts. Fetch everything, then print what it decided.
104
+ fetch-depth: 0
105
+
106
+ - uses: astral-sh/setup-uv@v6
107
+ with:
108
+ enable-cache: true
109
+
110
+ - run: uv build
111
+
112
+ - name: report the derived version
113
+ run: ls -l dist/
114
+
115
+ # The wheel's real test is whether the base install works on its own. No
116
+ # project, no extras, nothing from the lockfile: just the wheel and a
117
+ # bare interpreter. This is what `pip install gammonview` gets.
118
+ #
119
+ # It runs from outside the checkout on purpose. With the repo as cwd,
120
+ # `import gvformat` finds the source tree sitting right there and the
121
+ # check passes without the wheel being involved at all -- so the assert
122
+ # insists the module actually came out of a site-packages.
123
+ - name: the wheel imports with no extras
124
+ working-directory: ${{ runner.temp }}
125
+ run: |
126
+ uv run --isolated --no-project --with "$GITHUB_WORKSPACE"/dist/*.whl \
127
+ python -c 'import gvformat, pathlib; p = pathlib.Path(gvformat.__file__); print(p); assert "site-packages" in p.parts, p'
@@ -0,0 +1,70 @@
1
+ # The engine-backed half of the suite: the thirteen names in run_all.py's
2
+ # ENGINE_BACKED set. They need the `[engine]` extra -- bgsage, a large compiled
3
+ # wheel, plus pyyaml -- and they analyze a whole match, so they cost minutes
4
+ # where ci.yml costs seconds. That is the only reason they are split out; it is
5
+ # a cadence decision, not a statement about how much they matter.
6
+ #
7
+ # One thing to know before reading this job's output: three of these tests
8
+ # consume tests/golden/, and two compare engine output to it *exactly*.
9
+ # test_ogxm_pipeline re-analyzes the corpus and requires the .gvab to match
10
+ # byte-for-byte; test_luck_fill re-measures luck and requires identical floats,
11
+ # on purpose ("not close, identical"). Those goldens were generated on macOS
12
+ # arm64, and **bgsage does not reproduce them here**: measured 2026-09-13 on
13
+ # this runner with the same bgsage build, one best move of 521 changed, one cube
14
+ # verdict of 296 changed, and one match's PR moved 6.448 -> 6.246. It is not
15
+ # rounding, and it is not a bug -- it is a different CPU.
16
+ #
17
+ # So those two comparisons are platform-gated: they skip by name wherever the
18
+ # machine does not match tests/golden/PROVENANCE.json, and every structural
19
+ # check in the same two files still runs. Expect a handful of SKIP lines here
20
+ # and nothing red. The gate is in tests/fixtures.py:goldens_are_native; the
21
+ # provenance file is stamped by regen_golden.py, so regenerating on a different
22
+ # machine moves the gate rather than needing this comment edited.
23
+ #
24
+ # tests/regen_golden.py is genuinely not run: regenerating is a maintainer action
25
+ # on the machine that owns the goldens, and CLAUDE.md explains why the diff wants
26
+ # reading rather than accepting.
27
+ name: engine
28
+
29
+ on:
30
+ push:
31
+ branches: [main]
32
+ schedule:
33
+ # Mondays, 06:00 UTC. This is the run that catches a bgsage release moving
34
+ # the numbers underneath us without anyone here touching a file.
35
+ - cron: "0 6 * * 1"
36
+ workflow_dispatch:
37
+
38
+ permissions:
39
+ contents: read
40
+
41
+ concurrency:
42
+ group: engine-${{ github.ref }}
43
+ cancel-in-progress: true
44
+
45
+ jobs:
46
+ full-suite:
47
+ name: full suite (py${{ matrix.python }})
48
+ # ubuntu-latest is x86_64 on purpose. bgsage publishes manylinux wheels for
49
+ # x86_64 only, so an arm64 runner would fall back to the sdist and spend the
50
+ # job compiling an engine instead of testing against one.
51
+ runs-on: ubuntu-latest
52
+ strategy:
53
+ fail-fast: false
54
+ # The floor and the version this repo develops on. The middle of the
55
+ # range is covered by ci.yml, which exercises the same source without
56
+ # the engine.
57
+ matrix:
58
+ python: ["3.10", "3.13"]
59
+ steps:
60
+ - uses: actions/checkout@v4
61
+
62
+ - uses: astral-sh/setup-uv@v6
63
+ with:
64
+ enable-cache: true
65
+
66
+ # No --no-dev here: the dev group is exactly what these tests need. The
67
+ # runner reports skips separately from passes, so a suite that quietly
68
+ # stopped finding its fixtures does not read as green.
69
+ - name: full suite
70
+ run: uv run --python ${{ matrix.python }} python tests/run_all.py
@@ -0,0 +1,93 @@
1
+ # Publish to PyPI via trusted publishing (OIDC). No API token exists anywhere:
2
+ # PyPI is configured to trust *this workflow in this repository*, and mints a
3
+ # short-lived credential for the job at run time. Nothing to store, leak, or
4
+ # rotate, and a stolen repo secret cannot publish because there is no secret.
5
+ #
6
+ # One-time setup on PyPI (https://pypi.org/manage/account/publishing/), which
7
+ # must be done before the first run, because the project does not exist yet:
8
+ #
9
+ # PyPI project name : gammonview
10
+ # Owner : ngvlamis
11
+ # Repository name : gammonview-analysis
12
+ # Workflow name : publish.yml
13
+ # Environment name : pypi
14
+ #
15
+ # That registers a "pending publisher"; the project is created by the first
16
+ # successful upload.
17
+ name: publish
18
+
19
+ on:
20
+ # A tag is the release signal, matching how the version is derived: hatch-vcs
21
+ # reads it from git, so the tag *is* the version. Pushing v1.2.3 publishes
22
+ # 1.2.3 -- there is no number to bump anywhere else, and no way for the tag
23
+ # and the artifact to disagree.
24
+ push:
25
+ tags: ["v*"]
26
+ # Manual trigger, for a tag that already exists (as v1.0.0 did when this
27
+ # workflow was written) or to retry a failed upload.
28
+ workflow_dispatch:
29
+
30
+ permissions:
31
+ contents: read
32
+
33
+ jobs:
34
+ build:
35
+ name: build sdist + wheel
36
+ runs-on: ubuntu-latest
37
+ steps:
38
+ - uses: actions/checkout@v4
39
+ with:
40
+ # hatch-vcs derives the version from git tags, and a shallow clone has
41
+ # none. It does not fail -- it quietly stamps 0.1.dev1+g<sha> on the
42
+ # artifacts, which is the sort of thing you discover on PyPI.
43
+ fetch-depth: 0
44
+
45
+ - uses: astral-sh/setup-uv@v6
46
+ with:
47
+ enable-cache: true
48
+
49
+ - run: uv build
50
+
51
+ # Refuse to publish a version that git did not name. Without this, a run
52
+ # from an untagged commit uploads a dev version to PyPI, where it cannot
53
+ # be taken back.
54
+ - name: the artifacts carry a real release version
55
+ run: |
56
+ ls -l dist/
57
+ version=$(ls dist/*.whl | sed -E 's|.*/gammonview-([^-]+)-py3.*|\1|')
58
+ echo "derived version: $version"
59
+ case "$version" in
60
+ *dev*|*+*)
61
+ echo "::error::refusing to publish $version -- hatch-vcs found no tag on this commit"
62
+ exit 1 ;;
63
+ esac
64
+
65
+ - name: metadata renders on PyPI
66
+ run: uv run --isolated --no-project --with twine python -m twine check dist/*
67
+
68
+ - uses: actions/upload-artifact@v4
69
+ with:
70
+ name: dist
71
+ path: dist/
72
+
73
+ pypi:
74
+ name: upload to PyPI
75
+ needs: build
76
+ runs-on: ubuntu-latest
77
+ # The environment is the approval gate: GitHub can require a manual review
78
+ # before the job runs, and PyPI's trusted publisher is bound to this name,
79
+ # so a workflow that does not declare it cannot publish.
80
+ environment:
81
+ name: pypi
82
+ url: https://pypi.org/p/gammonview
83
+ permissions:
84
+ # The OIDC token this mints is what PyPI trades for an upload credential.
85
+ # Without it the action fails with a confusing 403.
86
+ id-token: write
87
+ steps:
88
+ - uses: actions/download-artifact@v4
89
+ with:
90
+ name: dist
91
+ path: dist/
92
+
93
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,40 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Test-runner scratch. The suite here is standalone scripts run by
10
+ # tests/run_all.py, not pytest -- but contributors reach for pytest anyway, and
11
+ # it leaves this behind.
12
+ .pytest_cache/
13
+
14
+ # Virtual environments
15
+ .venv
16
+
17
+ # JS mirror dependencies (installed by `npm install` in gvformat-js/)
18
+ node_modules/
19
+
20
+ # Agent instruction data -- covered by a global gitignore on the author's
21
+ # machine, which a public clone obviously cannot rely on.
22
+ .claude/
23
+
24
+ # User-created preset overrides (built-in defaults live in presets.py)
25
+ presets.yaml
26
+
27
+ # Live OpenGammon API data for test-og.js's parity section: pulled from a real
28
+ # account, so it carries real handles. Everything else under samples/ is
29
+ # committed and anonymized -- this one directory is not.
30
+ samples/og/
31
+
32
+ # Instructions for non-Claude agents. Not part of the project.
33
+ AGENTS.md
34
+
35
+ # Local maintainer notes (NOTES.local.md and friends): superseded workflows,
36
+ # host paths -- true and useful, but not public-repo material.
37
+ *.local.md
38
+
39
+ # Mac System files
40
+ .DS_Store
@@ -0,0 +1 @@
1
+ 3.13