gammonview 2.0.0__tar.gz → 2.0.2__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.
- {gammonview-2.0.0 → gammonview-2.0.2}/CHANGELOG.md +65 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/PKG-INFO +1 -1
- {gammonview-2.0.0 → gammonview-2.0.2}/docs/OGXM_JSON_SPEC_GAMMONVIEW.md +11 -6
- {gammonview-2.0.0 → gammonview-2.0.2}/docs/OGXM_V2_PROFILE.md +17 -4
- {gammonview-2.0.0 → gammonview-2.0.2}/gvanalysis/match.py +7 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat/binary.py +2 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat/export.py +1 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat/ogxm2.py +54 -6
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat/ogxm2_passthrough.py +7 -4
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat/ogxm2_writer.py +59 -17
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/package.json +1 -1
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/src/constants.js +2 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/src/legality.js +5 -3
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/src/ogxm2.js +69 -22
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/src/ogxm2_passthrough.js +7 -4
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/src/ogxm2_writer.js +67 -21
- gammonview-2.0.2/gvformat-js/test/fixtures/ogxm2/annos-docs.json +1 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/fixtures/ogxm2/annos-edits-sha256.json +6 -6
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/fixtures/ogxm2/annos-sha256.json +4 -4
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/fixtures/ogxm2/annos.expected.json +1 -1
- gammonview-2.0.2/gvformat-js/test/fixtures/ogxm2/bgf-writer-sha256.json +46 -0
- gammonview-2.0.2/gvformat-js/test/fixtures/ogxm2/blocks-docs.json +1 -0
- gammonview-2.0.2/gvformat-js/test/fixtures/ogxm2/blocks-sha256.json +42 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/fixtures/ogxm2/blocks.expected.json +1 -1
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/fixtures/ogxm2/blocks.ogxm +0 -0
- gammonview-2.0.2/gvformat-js/test/fixtures/ogxm2/fields-docs.json +1 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/fixtures/ogxm2/fields.expected.json +1 -1
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/fixtures/ogxm2/passthrough-sha256.json +12 -12
- gammonview-2.0.2/gvformat-js/test/fixtures/ogxm2/writer-sha256.json +46 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/test-legality.js +8 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/test-ogxm2-blocks.js +3 -1
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/test-ogxm2-fields.js +8 -8
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/test-ogxm2-limits.js +16 -1
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/test-ogxm2-writer.js +24 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/test-ogxm2.js +5 -4
- gammonview-2.0.2/tests/golden/3WNK_g1Z-PLsh_HyvQ5j4a.fast.gva +1 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/golden/3WNK_g1Z-PLsh_HyvQ5j4a.fast.gvab +0 -0
- gammonview-2.0.2/tests/golden/5nqfGw9bWG3deTaU.fast.gva +1 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/golden/5nqfGw9bWG3deTaU.fast.gvab +0 -0
- gammonview-2.0.2/tests/golden/B4_SrGcsKAQmoTyHlgJCbM.fast.gva +1 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/golden/B4_SrGcsKAQmoTyHlgJCbM.fast.gvab +0 -0
- gammonview-2.0.2/tests/golden/MYdvw1qGuyaRUH__.fast.gva +1 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/golden/MYdvw1qGuyaRUH__.fast.gvab +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/golden/PROVENANCE.json +1 -1
- gammonview-2.0.2/tests/golden/baR-U643iUDpvzTC.fast.gva +1 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/golden/baR-U643iUDpvzTC.fast.gvab +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/ogxm2_blocks_cases.py +7 -5
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/ogxm2_fields_cases.py +2 -2
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/test_ogxm2.py +5 -4
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/test_ogxm2_blocks.py +7 -4
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/test_ogxm2_fields.py +8 -8
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/test_ogxm2_limits.py +15 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/test_ogxm2_writer.py +12 -3
- gammonview-2.0.0/docs/GAMMONVIEW_V2_HANDOFF.md +0 -197
- gammonview-2.0.0/gvformat-js/test/fixtures/ogxm2/annos-docs.json +0 -1
- gammonview-2.0.0/gvformat-js/test/fixtures/ogxm2/bgf-writer-sha256.json +0 -46
- gammonview-2.0.0/gvformat-js/test/fixtures/ogxm2/blocks-docs.json +0 -1
- gammonview-2.0.0/gvformat-js/test/fixtures/ogxm2/blocks-sha256.json +0 -42
- gammonview-2.0.0/gvformat-js/test/fixtures/ogxm2/fields-docs.json +0 -1
- gammonview-2.0.0/gvformat-js/test/fixtures/ogxm2/writer-sha256.json +0 -46
- gammonview-2.0.0/tests/golden/3WNK_g1Z-PLsh_HyvQ5j4a.fast.gva +0 -1
- gammonview-2.0.0/tests/golden/5nqfGw9bWG3deTaU.fast.gva +0 -1
- gammonview-2.0.0/tests/golden/B4_SrGcsKAQmoTyHlgJCbM.fast.gva +0 -1
- gammonview-2.0.0/tests/golden/MYdvw1qGuyaRUH__.fast.gva +0 -1
- gammonview-2.0.0/tests/golden/baR-U643iUDpvzTC.fast.gva +0 -1
- {gammonview-2.0.0 → gammonview-2.0.2}/.gitattributes +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/.github/workflows/ci.yml +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/.github/workflows/engine.yml +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/.github/workflows/publish.yml +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/.gitignore +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/.python-version +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/CLAUDE.md +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/CONTRIBUTING.md +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/LICENSE +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/README.md +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/THIRD-PARTY-NOTICES.md +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/assets/logo.png +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/docs/CLI.md +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/docs/METHODOLOGY.md +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/docs/OGXM_COMPUTED_FIELDS.md +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/docs/PRESET_ACCURACY.md +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/docs/upstream-bgsage.md +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/docs/v1/OGXM_FORMAT_SPEC_GAMMONVIEW.md +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/experiments/2026-09-16-checker-mid-tier.md +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/experiments/2026-09-17-checker-mid-tier-followup.md +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/experiments/2026-09-19-cross-machine-costs.md +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/experiments/2026-09-20-corpus-rerun.md +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/experiments/bench.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvanalysis/__init__.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvanalysis/batch.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvanalysis/checker_eval.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvanalysis/compare_gva.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvanalysis/game_eval.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvanalysis/loader.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvanalysis/luck.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvanalysis/ogxm_reconstructor.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvanalysis/parentwatch.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvanalysis/position.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvanalysis/presets.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvanalysis/progress.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvanalysis/reconstruct_mat.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvanalysis/share.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat/__init__.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat/basefill.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat/bgf.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat/game_reconstructor.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat/legality.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat/mat.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat/mat_parser.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat/merge.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat/met.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat/notation.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat/ogid.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat/place.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat/reader.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat/share.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat/stats.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat/xg.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/.gitignore +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/LICENSE +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/README.md +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/THIRD-PARTY-NOTICES.md +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/cli/README.md +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/cli/bgf2gva.js +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/cli/mat2gva.js +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/cli/xg2gva.js +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/package-lock.json +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/src/basefill.js +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/src/bgf2gva.js +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/src/binary.js +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/src/export.js +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/src/index.js +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/src/mat2gva.js +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/src/merge.js +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/src/met.js +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/src/notation.js +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/src/og2gva.js +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/src/ogid.js +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/src/place.js +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/src/reader.js +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/src/sha256.js +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/src/share.js +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/src/stats.js +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/src/xg2gva.js +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/fixtures/mat/3WNK_g1Z-PLsh_HyvQ5j4a.json +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/fixtures/mat/5nqfGw9bWG3deTaU.json +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/fixtures/mat/B4_SrGcsKAQmoTyHlgJCbM.json +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/fixtures/mat/C07OQCKyw4Pe-CnjFiAMYO.json +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/fixtures/mat/MYdvw1qGuyaRUH__.json +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/fixtures/mat/Sz-2PgKnvb6aFls69-qFXg.json +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/fixtures/mat/UL-CV6F9jMM-E1Pu.json +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/fixtures/mat/XCu3RJ0UvDg_Tldl.json +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/fixtures/mat/baR-U643iUDpvzTC.json +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/fixtures/mat/eXBNG5wZHS5Alu3P.json +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/fixtures/mat/hQ8sVn2LbTdF4wRm.json +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/fixtures/mat/rK7pXm4TqLb9NzWd.json +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/fixtures/mat/uwvEbd63UvdX7SDin5o_Iw.json +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/fixtures/ogxm2/annos.ogxm +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/fixtures/ogxm2/cube-on-two.expected.json +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/fixtures/ogxm2/cube-on-two.ogxm +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/fixtures/ogxm2/fields-sha256.json +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/fixtures/ogxm2/fields.ogxm +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/fixtures/ogxm2/foreign.ogxm +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/fixtures/ogxm2/gen.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/fixtures/ogxm2/match.expected.json +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/fixtures/ogxm2/match.ogxm +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/fixtures/ogxm2/resign.expected.json +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/fixtures/ogxm2/resign.ogxm +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/fixtures/ogxm2/set-up.expected.json +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/fixtures/ogxm2/set-up.ogxm +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/fixtures/ogxm2/two-blocks.expected.json +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/fixtures/ogxm2/two-blocks.ogxm +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/fixtures/ogxm2/v1-chunks.gvab +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/test-alternative-move-steps.js +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/test-basefill.js +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/test-bgf-alternatives.js +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/test-bgf-embedded-cube.js +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/test-bgf-move-steps.js +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/test-chunk-passthrough.js +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/test-equity-loss.js +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/test-illegal-move.js +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/test-illegal-play-steps.js +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/test-mat.js +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/test-merge.js +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/test-met.js +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/test-og.js +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/test-ogid.js +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/test-ogxm2-annos.js +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/test-ogxm2-passthrough.js +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/test-place.js +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/test-robustness.js +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/test-roundtrip.js +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/test-share.js +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/test-stats.js +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/test-xg-cube.js +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/test-xg-eval-levels.js +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/test-xg-move-steps.js +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/gvformat-js/test/test-xg-zero-win-eval.js +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/pyproject.toml +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/samples/README.md +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/samples/bgf/3WNK_g1Z-PLsh_HyvQ5j4a.bgf +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/samples/bgf/5nqfGw9bWG3deTaU.bgf +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/samples/bgf/B4_SrGcsKAQmoTyHlgJCbM.bgf +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/samples/bgf/C07OQCKyw4Pe-CnjFiAMYO.bgf +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/samples/bgf/MYdvw1qGuyaRUH__.bgf +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/samples/bgf/Sz-2PgKnvb6aFls69-qFXg.bgf +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/samples/bgf/UL-CV6F9jMM-E1Pu.bgf +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/samples/bgf/XCu3RJ0UvDg_Tldl.bgf +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/samples/bgf/baR-U643iUDpvzTC.bgf +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/samples/bgf/eXBNG5wZHS5Alu3P.bgf +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/samples/bgf/uwvEbd63UvdX7SDin5o_Iw.bgf +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/samples/gv/3WNK_g1Z-PLsh_HyvQ5j4a.gvab +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/samples/gv/5nqfGw9bWG3deTaU.gvab +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/samples/gv/B4_SrGcsKAQmoTyHlgJCbM.gva +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/samples/gv/B4_SrGcsKAQmoTyHlgJCbM.gvab +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/samples/gv/C07OQCKyw4Pe-CnjFiAMYO.gvab +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/samples/gv/MYdvw1qGuyaRUH__.gvab +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/samples/gv/Sz-2PgKnvb6aFls69-qFXg.gvab +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/samples/gv/UL-CV6F9jMM-E1Pu.gvab +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/samples/gv/XCu3RJ0UvDg_Tldl.gvab +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/samples/gv/baR-U643iUDpvzTC.gvab +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/samples/gv/eXBNG5wZHS5Alu3P.gvab +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/samples/gv/uwvEbd63UvdX7SDin5o_Iw.gvab +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/samples/mat/3WNK_g1Z-PLsh_HyvQ5j4a.mat +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/samples/mat/5nqfGw9bWG3deTaU.mat +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/samples/mat/B4_SrGcsKAQmoTyHlgJCbM.mat +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/samples/mat/C07OQCKyw4Pe-CnjFiAMYO.mat +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/samples/mat/MYdvw1qGuyaRUH__.mat +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/samples/mat/Sz-2PgKnvb6aFls69-qFXg.mat +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/samples/mat/UL-CV6F9jMM-E1Pu.mat +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/samples/mat/XCu3RJ0UvDg_Tldl.mat +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/samples/mat/baR-U643iUDpvzTC.mat +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/samples/mat/eXBNG5wZHS5Alu3P.mat +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/samples/mat/hQ8sVn2LbTdF4wRm.mat +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/samples/mat/rK7pXm4TqLb9NzWd.mat +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/samples/mat/uwvEbd63UvdX7SDin5o_Iw.mat +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/samples/xg/3WNK_g1Z-PLsh_HyvQ5j4a.xg +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/samples/xg/5nqfGw9bWG3deTaU.xg +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/samples/xg/B4_SrGcsKAQmoTyHlgJCbM.xg +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/samples/xg/C07OQCKyw4Pe-CnjFiAMYO.xg +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/samples/xg/MYdvw1qGuyaRUH__.xg +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/samples/xg/Sz-2PgKnvb6aFls69-qFXg.xg +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/samples/xg/UL-CV6F9jMM-E1Pu.xg +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/samples/xg/XCu3RJ0UvDg_Tldl.xg +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/samples/xg/baR-U643iUDpvzTC.xg +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/samples/xg/eXBNG5wZHS5Alu3P.xg +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/samples/xg/hQ8sVn2LbTdF4wRm.xg +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/samples/xg/rK7pXm4TqLb9NzWd.xg +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/samples/xg/uwvEbd63UvdX7SDin5o_Iw.xg +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/audit_corpus.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/fixtures.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/gen_js_v2_fixtures.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/gen_ogxm2_annos_fixtures.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/gen_ogxm2_blocks_fixtures.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/gen_ogxm2_fields_fixtures.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/gen_ogxm2_passthrough_fixtures.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/ogxm2_annos_cases.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/ogxm2_oracle.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/passthrough_cases.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/regen_golden.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/run_all.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/test_alternative_move_steps.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/test_analysis_orientation.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/test_analyze_position.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/test_basefill.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/test_bgf_alternatives.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/test_bgf_cube_response.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/test_bgf_move_steps.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/test_chunk_passthrough.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/test_count_illegal.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/test_cube_tiers.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/test_docs_presets.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/test_equity_loss.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/test_gvab_writer.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/test_illegal_move.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/test_illegal_play_steps.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/test_legality.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/test_loader_formats.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/test_luck_fill.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/test_met.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/test_ogxm2_annos.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/test_ogxm2_blocks_analysis.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/test_ogxm2_fields_analysis.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/test_ogxm2_passthrough.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/test_ogxm2_reference.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/test_ogxm_conformance.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/test_ogxm_export.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/test_ogxm_pipeline.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/test_ogxm_stats.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/test_parent_watch.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/test_position_id.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/test_read_gvab.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/test_reconstruct_mat.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/test_resign_actor.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/test_restated_play_analysis.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/test_screened_checker.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/test_share_link.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/test_xg_cube.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/test_xg_eval_levels.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/test_xg_move_steps.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/tests/test_xg_zero_win_eval.py +0 -0
- {gammonview-2.0.0 → gammonview-2.0.2}/uv.lock +0 -0
|
@@ -13,6 +13,71 @@ 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
|
+
## 2.0.2 — 2026-10-12
|
|
17
|
+
|
|
18
|
+
**A set-up start is written once** *(fix)*
|
|
19
|
+
|
|
20
|
+
2.0.1's fix for v1 set-up starts went too far. A game whose first ply sets up
|
|
21
|
+
the board *with* dice keeps that ply as a ply, and the writers then also took
|
|
22
|
+
the game's `initial_board` as a second start, so a rewritten file read back
|
|
23
|
+
with an extra set-up ply in front and every later ply one place out. Both
|
|
24
|
+
writers now take `initial_board` as the start only when the game has no
|
|
25
|
+
leading set-up ply of either kind, which is the v1 case 2.0.1 was for.
|
|
26
|
+
|
|
27
|
+
## 2.0.1 — 2026-10-10
|
|
28
|
+
|
|
29
|
+
**Plies are counted the way v2 counts them** *(fix)*
|
|
30
|
+
|
|
31
|
+
OGXM counts plies from the alternative, as gnubg does, so a 3-ply search is `checker_ply` 2 in
|
|
32
|
+
the file; HedgeHog's author confirmed this as the convention. We wrote our own
|
|
33
|
+
count, so other readers took our 3ply for 4-ply, and we read HedgeHog's 3-ply
|
|
34
|
+
as 2ply. The writer now subtracts one from `checker_ply`, `cube_ply` and a
|
|
35
|
+
rollout's `move_ply`, and the reader adds it back. Documents keep the count
|
|
36
|
+
players use. A file of ours written by 2.0.0 still reads as it did: blocks
|
|
37
|
+
written since carry `plies=gnubg` in their `x-gammonview-analysis` annotation,
|
|
38
|
+
and a block of ours without it is read unshifted.
|
|
39
|
+
|
|
40
|
+
**`site` is never derived** *(change)*
|
|
41
|
+
|
|
42
|
+
The reader filled a missing `site` in from `city`, else the platform, and the
|
|
43
|
+
writer left out a `site` that matched them. Choosing a place to show is the
|
|
44
|
+
viewer's call, not the codec's, so `site` now reads back exactly as written,
|
|
45
|
+
and none when none was: a document with a city and no site keeps no site. The
|
|
46
|
+
writer stores any `site` in `x-gammonview-site`. A 2.0.0 file that left out a
|
|
47
|
+
site equal to its city now reads with no site.
|
|
48
|
+
|
|
49
|
+
**Our analysis is named in other readers** *(change)*
|
|
50
|
+
|
|
51
|
+
An analysis block now carries `model_name` "GammonView bgsage" beside its
|
|
52
|
+
`model_id`. HedgeHog's viewer names an analysis by `model_name` alone, so a
|
|
53
|
+
match analysed here and opened there showed no engine. The identifier is
|
|
54
|
+
unchanged, so nothing that keys on it moves.
|
|
55
|
+
|
|
56
|
+
**A v1 set-up start survives a rewrite** *(fix)*
|
|
57
|
+
|
|
58
|
+
A v1 file states a game that starts from a set-up board in its `GAME` header,
|
|
59
|
+
so a v1 read gives that game an `initial_board` and no leading set-position
|
|
60
|
+
ply. The v2 writers took the start only from such a ply, so rewriting the file
|
|
61
|
+
dropped the board, replayed the game from the opening, and flagged its plays
|
|
62
|
+
illegal. Both writers now take a bare `initial_board` as the game's start; a
|
|
63
|
+
leading set-up ply, where there is one, still wins.
|
|
64
|
+
|
|
65
|
+
**Long text values are split, not refused** *(fix)*
|
|
66
|
+
|
|
67
|
+
An event or a `site` too long for one v2 string is carried in our annotation,
|
|
68
|
+
which the profile says is split over `key~1`, `key~2`, … — but only the
|
|
69
|
+
analysis and annotation records were split, so the writer raised instead. Every
|
|
70
|
+
`x-gammonview-` record is split now, at character boundaries. A player name
|
|
71
|
+
past v2's 4 096 bytes, the one text field with no annotation to fall back on,
|
|
72
|
+
is still refused, and the profile now says so.
|
|
73
|
+
|
|
74
|
+
**`isPlayLegal` on a roll with no legal play** *(fix, JavaScript only)*
|
|
75
|
+
|
|
76
|
+
`legalPlays` returned no `best` for a dance, so `isPlayLegal` — and with it
|
|
77
|
+
`write_gvab` — threw a `TypeError` on a dice ply that recorded checker steps on
|
|
78
|
+
a roll that could not be played, where the Python writer flagged the play
|
|
79
|
+
illegal. The two writers now agree on such a file.
|
|
80
|
+
|
|
16
81
|
## 2.0.0 — 2026-10-08
|
|
17
82
|
|
|
18
83
|
**`.gvab` is OGXM v2** *(format change)*
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: gammonview
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.2
|
|
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
|
|
@@ -66,8 +66,10 @@ unknown sections, presence-bit tails a later spec adds), it rides along in
|
|
|
66
66
|
- **Output only** keys are produced by a reader (or a converter) for display and
|
|
67
67
|
ignored by the writer, which derives them again: `ogid_before`, `ogid_after`,
|
|
68
68
|
`notation`, `diff`, `played_equity`, `eval.equity`, `game_index`,
|
|
69
|
-
`is_crawford`, `first_to_move`, `
|
|
70
|
-
`
|
|
69
|
+
`is_crawford`, `first_to_move`, `action` (on a `cube_decision`) and
|
|
70
|
+
`analysis_info.preset`. `initial_board` is output only *unless* the game has
|
|
71
|
+
no leading set-up ply, which is how a v1 read states a set-up start; the
|
|
72
|
+
writer then takes the board from it.
|
|
71
73
|
|
|
72
74
|
---
|
|
73
75
|
|
|
@@ -342,7 +344,7 @@ analysis to a document that already has one promotes it to this form
|
|
|
342
344
|
| `initial_cube_value` | uint | The cube's value when the game opens (a power of two, within `cube_limit`). Default-omitted at 1 | `GAME.initial_cube_value` |
|
|
343
345
|
| `initial_cube_owner` | uint | 0 White, 1 Black, 2 centred. Default-omitted at 2 | `GAME.initial_cube_owner` |
|
|
344
346
|
| `auto_doubles` | uint | Automatic doubles this game opened with. The cube's real starting value is `initial_cube_value · 2^auto_doubles`. Default-omitted at 0 | `GAME.auto_doubles` |
|
|
345
|
-
| `initial_board` | int[26] |
|
|
347
|
+
| `initial_board` | int[26] | The board of a game that starts from a set-up position, as a v2 read also states it as the game's first ply. **Output only** when that ply is there; a v1 read gives this key alone, and the writer then takes the start from it | `GAME.initial_board` |
|
|
346
348
|
| `annotations` | Annotation[] | Notes on the game | `ANNO`, game scope |
|
|
347
349
|
| `plies` | [Ply](#ply)[] | The game's actions in order | |
|
|
348
350
|
|
|
@@ -623,7 +625,7 @@ A hold with the cube dead has none.
|
|
|
623
625
|
| `action` | string | **Output only.** `"no_double"` when holding is correct; else `"double_take"` (when `double_pass_equity ≥ double_take_equity`) or `"double_pass"` |
|
|
624
626
|
| `equity_loss` | float | Fresh converter output only (0.0 for a correct hold); not stored, absent in a read. The error belongs to `missed_double` |
|
|
625
627
|
| `eval` | Eval | The pre-roll probabilities |
|
|
626
|
-
| `decision` | bool | Whether this cube decision counted as a PR decision (not trivial). **Absent when a `missed_double` sits beside it** in a block of ours or in a v1 read — that record carries the count, so one cube is never counted twice.
|
|
628
|
+
| `decision` | bool | Whether this cube decision counted as a PR decision (not trivial). **Absent when a `missed_double` sits beside it** in a block of ours or in a v1 read — that record carries the count, so one cube is never counted twice. A block of another producer completed by `basefill` leaves it absent too; see [`OGXM_COMPUTED_FIELDS.md`](OGXM_COMPUTED_FIELDS.md) |
|
|
627
629
|
| `eval_level` | string | The label of the cube result used. Our blocks only |
|
|
628
630
|
| `level`, `take_point`, `window_searched`, `is_optional`, `is_free_cube`, `cubeful_take_value`, `currency`, `producer_ref`, `annotations` | | See [Per-decision v2 fields](#per-decision-v2-fields) |
|
|
629
631
|
|
|
@@ -921,8 +923,11 @@ The v1 byte layout is frozen in
|
|
|
921
923
|
Those of v2 (spec §10): 1 000 games, 1 500 plies per game, 100 000 plies in a
|
|
922
924
|
file, 1 024 alternatives per decision, 64 analysis blocks, 4 096 bytes per
|
|
923
925
|
string, 4 096 annotations (256 KiB) and 256 drawings per annotation, a video URL
|
|
924
|
-
of 512 bytes. The writer
|
|
925
|
-
|
|
926
|
+
of 512 bytes. The writer cuts an alternatives list at 1 024, and carries a
|
|
927
|
+
string past a field's own cap in an `x-gammonview-` annotation, split over as
|
|
928
|
+
many records as it needs. It raises `ValueError` past the game, ply, block and
|
|
929
|
+
annotation limits, and for a player name past 4 096 bytes, which has no
|
|
930
|
+
annotation to fall back on.
|
|
926
931
|
|
|
927
932
|
---
|
|
928
933
|
|
|
@@ -37,7 +37,7 @@ below over the whole sample corpus and against the reference codec.
|
|
|
37
37
|
| `stage`, `round`, `table`, `city`, `country`, `event_url`, `match_ref` | the same fields |
|
|
38
38
|
| `platform` | v2's `site`: the platform's host name. Renamed because our `site` predates it |
|
|
39
39
|
| `white_profile`, `black_profile` | the `player` records (4.1): `user_id`, `rating` (a float, to 0.01), `rating_system`, `country`, `kind` |
|
|
40
|
-
| `site` | `x-gammonview-site`,
|
|
40
|
+
| `site` | `x-gammonview-site`, always. Ours is whatever the source or the user called the place, sometimes a city and sometimes a platform, and nothing tells which; it is never derived from `city` or `platform`, and a reader showing a place may fall back to them itself |
|
|
41
41
|
| `games[].winner`, `points_won`, `is_lastgame` | `GAME` bits 0-2; `points_won` is written exactly when `winner` is |
|
|
42
42
|
| `games[].termination`, `initial_cube_value`, `initial_cube_owner`, `auto_doubles` | the same `GAME` fields. The game's cube starts at `initial_cube_value · 2^auto_doubles`, owned by `initial_cube_owner` (5.3), in the OGIDs and in `gvanalysis` |
|
|
43
43
|
| `games[].first_to_move`, `is_crawford`, `game_index` | derived, not stored |
|
|
@@ -71,6 +71,8 @@ has not assigned is kept whole, as `extras_raw`.
|
|
|
71
71
|
A game that starts from a set-up board (a leading set-position ply with no
|
|
72
72
|
dice) stores it as `GAME.initial_board` — unless the board is the opening
|
|
73
73
|
position, which v2 never writes as an initial board, and then it stays a ply.
|
|
74
|
+
A game read from a v1 file states its set-up board only as `initial_board`,
|
|
75
|
+
with no such ply; the writer stores that as `GAME.initial_board` too.
|
|
74
76
|
|
|
75
77
|
**Illegal plays.** A play that is not legal (M4) for its roll — or that any
|
|
76
78
|
analysis block flags `illegal_move` — is written the way v2 records one: a
|
|
@@ -130,6 +132,16 @@ alternative overrides it only where its own differs, and a decision's or cube's
|
|
|
130
132
|
`ply` is `checker_ply` / `cube_ply`. A luck record states its depth as
|
|
131
133
|
`checker_ply`; the luck label travels once, in the block's annotation.
|
|
132
134
|
|
|
135
|
+
**Counting plies.** v2 counts plies gnubg's way, from the alternative rather
|
|
136
|
+
than from the position, and our documents count them the way players do: a
|
|
137
|
+
search our document calls `3ply` is `checker_ply` 2 in the file, and a rollout
|
|
138
|
+
whose moves are played at 2-ply has `move_ply` 1. The spec does not say this;
|
|
139
|
+
HedgeHog's files count so and its author confirmed it as the convention. So the
|
|
140
|
+
writer subtracts one from `checker_ply`, `cube_ply` and `move_ply`, and the
|
|
141
|
+
reader adds it back. A block of ours written before gvformat 2.0.1 counted as
|
|
142
|
+
its document does; it is told apart by its annotation, which since then carries
|
|
143
|
+
`plies=gnubg`, and is read without the shift.
|
|
144
|
+
|
|
133
145
|
**Checker decisions** carry the alternatives in their order, each with its
|
|
134
146
|
steps, equity, probabilities when recorded, and `is_played`. v2 then derives
|
|
135
147
|
the best equity (the first alternative's) and, when the played move is listed,
|
|
@@ -190,7 +202,7 @@ holds is split over `key`, `key~1`, `key~2`, ... and joined in that order.
|
|
|
190
202
|
|
|
191
203
|
| Key | Scope | Value after `1:` |
|
|
192
204
|
|---|---|---|
|
|
193
|
-
| `x-gammonview-analysis/<analysis_id>` | match | `;`-separated items: `level=<label>` (the block's own level, when it is not the block preset; empty for none), `luck=<label>` (when the block has luck), `pr=<tokens>` (always present; it marks the block as ours), `frame=<entries>` (when plies carry a source frame) |
|
|
205
|
+
| `x-gammonview-analysis/<analysis_id>` | match | `;`-separated items: `level=<label>` (the block's own level, when it is not the block preset; empty for none), `luck=<label>` (when the block has luck), `pr=<tokens>` (always present; it marks the block as ours), `plies=gnubg` (since gvformat 2.0.1: the block's levels count plies v2's way; see *Counting plies*), `frame=<entries>` (when plies carry a source frame) |
|
|
194
206
|
| `x-gammonview-decisions/<analysis_id>` | ply | base64 of `DECS` records for this ply, in kind order. They stand in for `DECS`'s records of the same kind at that ply |
|
|
195
207
|
| `x-gammonview-illegal-ply` | ply | the steps of the illegal play, `from/pips` joined by `,` (empty for a dance) |
|
|
196
208
|
| `x-gammonview-site` | match | `site` |
|
|
@@ -234,7 +246,7 @@ decisions where the document says otherwise, each as its `ply_ref` and a letter:
|
|
|
234
246
|
| met-id | a match block names its table: `kazaross-xg2` when the document named none |
|
|
235
247
|
| mwc-frame | a source's frame (`mwc_frame`, §2) is carried by v2 only; v1 loses it |
|
|
236
248
|
| default-omitted | a value equal to v2's default is not written, so it reads back absent: `variant` 0, a 0-0 `score_start`, `initial_cube_value` 1, `initial_cube_owner` 2 (centred), `auto_doubles` 0 or false, `rules_other` 0, a false flag, an empty string |
|
|
237
|
-
| site-
|
|
249
|
+
| site-is-stated | `site` reads back exactly as written, and a document with none reads back with none, whatever its `city` or `platform` |
|
|
238
250
|
| resign-derived | a `resign_value` equal to what the game's points and the cube give is not kept; only a different one is |
|
|
239
251
|
| settle-millionths | a `settle_value` is held to a millionth of a point |
|
|
240
252
|
| block-provenance | what v2's block record states (producer, completeness, coverage, model name, identifier and digest, engine build, currency, cube efficiency, tables, dials, completion time, sources, a level beyond the labels) is in the v2 read-back only; v1 has no field for it |
|
|
@@ -256,7 +268,8 @@ decisions where the document says otherwise, each as its `ply_ref` and a letter:
|
|
|
256
268
|
The writer refuses, with a `ValueError`, what v2's limits cannot hold rather than
|
|
257
269
|
writing an invalid file: more than 1000 games, more than 1500 plies in a game,
|
|
258
270
|
more than 100000 plies, more than 64 analysis blocks (`write_gvab_v1`: more than
|
|
259
|
-
16)
|
|
271
|
+
16), and a player name longer than v2's 4096-byte string (the one text field
|
|
272
|
+
with no annotation to fall back on). `tests/test_ogxm2_limits.py` pins these and the cut above.
|
|
260
273
|
|
|
261
274
|
Each rule above is in `tests/test_ogxm2_writer.py`, which fails on any difference
|
|
262
275
|
none of them explains.
|
|
@@ -95,6 +95,12 @@ from .progress import ProgressBar
|
|
|
95
95
|
# engine upgrade invalidates the goldens anyway.
|
|
96
96
|
MODEL_NAME = "gv-bgsage"
|
|
97
97
|
|
|
98
|
+
#: What a reader shows for this analysis: v2's ``model_name``, beside the
|
|
99
|
+
#: opaque ``model_id``. HedgeHog's viewer names an analysis by this field alone,
|
|
100
|
+
#: so it is worded for a reader there, saying whose analysis it is; GammonView
|
|
101
|
+
#: labels a ``gv-bgsage`` block from the identifier and never shows it.
|
|
102
|
+
DISPLAY_NAME = "GammonView bgsage"
|
|
103
|
+
|
|
98
104
|
|
|
99
105
|
def model_id() -> str:
|
|
100
106
|
"""``gv-bgsage/<bgsage version>`` -- the producer of this analysis.
|
|
@@ -631,6 +637,7 @@ def analyze_ogxm(
|
|
|
631
637
|
return {
|
|
632
638
|
"summary": {
|
|
633
639
|
"engine": model_id(),
|
|
640
|
+
"model_name": DISPLAY_NAME,
|
|
634
641
|
"engine_build": engine_build(),
|
|
635
642
|
# Every decision of the match was looked at: v2's `complete`, as
|
|
636
643
|
# against an incremental run that covered some of them.
|
|
@@ -135,6 +135,8 @@ GVAN_CUBE_REC = 2
|
|
|
135
135
|
GVAN_CHECKER_REC_V2 = 7
|
|
136
136
|
GVAN_CUBE_REC_V2 = 6
|
|
137
137
|
|
|
138
|
+
# v1's cap on a decision's alternatives, used by ``write_gvab_v1`` only. v2
|
|
139
|
+
# holds 1024 (``MAX_ALTS`` in ogxm2_writer).
|
|
138
140
|
MAX_ALTS_PER_DECISION = 50
|
|
139
141
|
|
|
140
142
|
# The largest hop a ply record can hold. A move step is one byte -- the start
|
|
@@ -1552,6 +1552,7 @@ def to_ogxm_json(result: dict, *, keep_orientation: bool = False) -> dict:
|
|
|
1552
1552
|
# bgsage its version, so the label arrives in the summary;
|
|
1553
1553
|
# the bare fallback keeps an older caller's output valid.
|
|
1554
1554
|
"model_id": summary.get("engine") or "bgsage",
|
|
1555
|
+
**({"model_name": summary["model_name"]} if summary.get("model_name") else {}),
|
|
1555
1556
|
"timestamp": ts,
|
|
1556
1557
|
# What ran it, and that it ran the whole match (v2's `engine_build`
|
|
1557
1558
|
# and `complete`); supplied for the same reason as `model_id`.
|
|
@@ -119,6 +119,11 @@ CURRENCY_CUBEFUL_MATCH = 2
|
|
|
119
119
|
# holds; every value starts with GV_FORMAT.
|
|
120
120
|
GV_FORMAT = "1:"
|
|
121
121
|
GV_KEY_ANALYSIS = "x-gammonview-analysis/"
|
|
122
|
+
#: The ``x-gammonview-analysis`` item saying the block counts plies v2's way
|
|
123
|
+
#: (``PLY_SHIFT``). Written since gvformat 2.0.1; a block of ours without it
|
|
124
|
+
#: counts them as its document does.
|
|
125
|
+
GV_ITEM_PLIES = "plies"
|
|
126
|
+
GV_PLIES_GNUBG = "gnubg"
|
|
122
127
|
GV_KEY_DECISIONS = "x-gammonview-decisions/"
|
|
123
128
|
GV_KEY_ILLEGAL_PLY = "x-gammonview-illegal-ply"
|
|
124
129
|
GV_KEY_SITE = "x-gammonview-site"
|
|
@@ -354,6 +359,37 @@ def _level(cur: _Cursor) -> dict:
|
|
|
354
359
|
return lv
|
|
355
360
|
|
|
356
361
|
|
|
362
|
+
#: How v2 counts plies: gnubg's way, from the alternative rather than from the
|
|
363
|
+
#: position, so a search a document calls ``3ply`` is ``checker_ply`` 2 on the
|
|
364
|
+
#: wire. HedgeHog's own files count so, and its author confirmed it as the
|
|
365
|
+
#: convention (the spec does not say). A document keeps the count players use;
|
|
366
|
+
#: the codec moves by this much at the boundary, both ways.
|
|
367
|
+
PLY_SHIFT = 1
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def shift_level(lv: dict | None, by: int) -> None:
|
|
371
|
+
"""Move a level's depths -- ``checker_ply``, ``cube_ply`` and a rollout's
|
|
372
|
+
``move_ply`` -- by ``by``, in place, never below 0."""
|
|
373
|
+
if not lv:
|
|
374
|
+
return
|
|
375
|
+
for k in ("checker_ply", "cube_ply"):
|
|
376
|
+
if k in lv:
|
|
377
|
+
lv[k] = max(lv[k] + by, 0)
|
|
378
|
+
r = lv.get("rollout")
|
|
379
|
+
if r and "move_ply" in r:
|
|
380
|
+
r["move_ply"] = max(r["move_ply"] + by, 0)
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def shift_levels(anal: dict, decisions, by: int) -> None:
|
|
384
|
+
"""``shift_level`` over a block's level and every level its decision
|
|
385
|
+
records (``_decode_decs``'s) and their alternatives state."""
|
|
386
|
+
shift_level(anal.get("level"), by)
|
|
387
|
+
for d in decisions:
|
|
388
|
+
shift_level(d.get("level"), by)
|
|
389
|
+
for alt in d.get("alternatives") or []:
|
|
390
|
+
shift_level(alt.get("level"), by)
|
|
391
|
+
|
|
392
|
+
|
|
357
393
|
def _resolve(parent: dict, override: dict | None) -> dict:
|
|
358
394
|
"""The effective level: each field from the innermost tier carrying it."""
|
|
359
395
|
if not override:
|
|
@@ -1842,10 +1878,10 @@ def read_ogxm2(data: bytes, *, verify_crc: bool = True, derive_ogids: bool = Tru
|
|
|
1842
1878
|
ogxm[name] = {**ogxm.get(name, {}), **restored}
|
|
1843
1879
|
for gi, game in enumerate(games):
|
|
1844
1880
|
_restore_fields(game, gv_game, gi, GV_GAME_FIELDS)
|
|
1845
|
-
# `site` is
|
|
1846
|
-
#
|
|
1847
|
-
|
|
1848
|
-
|
|
1881
|
+
# `site` is ours, free text, and v2 has no field for it: it is what our
|
|
1882
|
+
# annotation says, or nothing. It is never derived from `city` or the
|
|
1883
|
+
# platform -- that is a display choice, and the codec makes none.
|
|
1884
|
+
ogxm["site"] = gv_match.get((0, GV_KEY_SITE))
|
|
1849
1885
|
if (0, GV_KEY_SCORE) in gv_match:
|
|
1850
1886
|
try:
|
|
1851
1887
|
ogxm["white_score"], ogxm["black_score"] = (
|
|
@@ -1897,14 +1933,24 @@ def read_ogxm2(data: bytes, *, verify_crc: bool = True, derive_ogids: bool = Tru
|
|
|
1897
1933
|
decoded = []
|
|
1898
1934
|
base_blocks = []
|
|
1899
1935
|
ours_ids: set = set()
|
|
1936
|
+
recounted: set = set()
|
|
1900
1937
|
alt_maps: dict = {}
|
|
1901
1938
|
for blk in blocks:
|
|
1902
1939
|
aid = blk["anal"]["analysis_id"]
|
|
1903
1940
|
ours = None
|
|
1941
|
+
items = None
|
|
1904
1942
|
if (0, GV_KEY_ANALYSIS + aid) in gv_match:
|
|
1905
|
-
ours_ids.add(aid)
|
|
1906
1943
|
items = dict(item.partition("=")[::2]
|
|
1907
1944
|
for item in gv_match[(0, GV_KEY_ANALYSIS + aid)].split(";") if item)
|
|
1945
|
+
# Every producer counts plies v2's way except our writer before
|
|
1946
|
+
# gvformat 2.0.1, whose blocks say so by not naming the count.
|
|
1947
|
+
shift = items is None or items.get(GV_ITEM_PLIES) == GV_PLIES_GNUBG
|
|
1948
|
+
if shift:
|
|
1949
|
+
shift_levels(blk["anal"], blk["decisions"], PLY_SHIFT)
|
|
1950
|
+
elif items is not None:
|
|
1951
|
+
recounted.add(aid)
|
|
1952
|
+
if items is not None:
|
|
1953
|
+
ours_ids.add(aid)
|
|
1908
1954
|
extra = {}
|
|
1909
1955
|
for (ref, key), value in gv_ply.items():
|
|
1910
1956
|
if key == GV_KEY_DECISIONS + aid:
|
|
@@ -1913,6 +1959,8 @@ def read_ogxm2(data: bytes, *, verify_crc: bool = True, derive_ogids: bool = Tru
|
|
|
1913
1959
|
except ValueError as exc:
|
|
1914
1960
|
raise GvabError(f"annotation on ply {ref} is not base64") from exc
|
|
1915
1961
|
recs = _decode_decs(raw)
|
|
1962
|
+
if shift:
|
|
1963
|
+
shift_levels({}, recs, PLY_SHIFT)
|
|
1916
1964
|
if any(d["ply_ref"] != ref for d in recs):
|
|
1917
1965
|
raise GvabError(f"annotation on ply {ref} holds another ply's decision")
|
|
1918
1966
|
extra[ref] = recs
|
|
@@ -1958,5 +2006,5 @@ def read_ogxm2(data: bytes, *, verify_crc: bool = True, derive_ogids: bool = Tru
|
|
|
1958
2006
|
# What the file holds that this document cannot: kept for the writer.
|
|
1959
2007
|
from .ogxm2_passthrough import attach
|
|
1960
2008
|
header = (struct.unpack_from("<H", data, 6)[0], struct.unpack_from("<H", data, 10)[0])
|
|
1961
|
-
attach(ogxm, data, header, sections, annos, ours_ids)
|
|
2009
|
+
attach(ogxm, data, header, sections, annos, ours_ids, recounted)
|
|
1962
2010
|
return ogxm
|
|
@@ -461,11 +461,14 @@ def v1_sign_to_v2(body: bytes) -> bytes | None:
|
|
|
461
461
|
# ---------------------------------------------------------------------------
|
|
462
462
|
|
|
463
463
|
def attach(ogxm: dict, data: bytes, header: tuple, sections: list, annos: list,
|
|
464
|
-
ours_ids: set) -> None:
|
|
464
|
+
ours_ids: set, recounted: set = frozenset()) -> None:
|
|
465
465
|
"""Attach ``_ogxm2_passthrough`` to ``ogxm`` when the file holds anything
|
|
466
466
|
the document cannot. ``sections`` is ``[(type, offset, payload)]`` in file
|
|
467
|
-
order, ``annos`` the decoded ``ANNO`` records (each with its ``raw`` bytes)
|
|
468
|
-
|
|
467
|
+
order, ``annos`` the decoded ``ANNO`` records (each with its ``raw`` bytes),
|
|
468
|
+
``ours_ids`` the ``analysis_id`` of every block our writer made, and
|
|
469
|
+
``recounted`` those of them that count plies as their document does (before
|
|
470
|
+
gvformat 2.0.1): their bytes are never kept, since the writer now counts
|
|
471
|
+
otherwise and would pair them with an annotation saying so.
|
|
469
472
|
|
|
470
473
|
A file the writer reproduces byte for byte gets nothing, since there is
|
|
471
474
|
nothing to keep: that is our own files, the common case, and it is decided
|
|
@@ -546,7 +549,7 @@ def attach(ogxm: dict, data: bytes, header: tuple, sections: list, annos: list,
|
|
|
546
549
|
if mine is None or "decs" not in blk:
|
|
547
550
|
return
|
|
548
551
|
blk["fp"] = fingerprint(mine.anal, mine.decs, mine.anno_bytes())
|
|
549
|
-
pt["blocks"] = pt_blocks
|
|
552
|
+
pt["blocks"] = {aid: blk for aid, blk in pt_blocks.items() if aid not in recounted}
|
|
550
553
|
# The clock and the video are in the document; the source's bytes stand for
|
|
551
554
|
# them while the document still encodes to what they decoded to.
|
|
552
555
|
for name, canon in (("clck", parts.clck), ("vido", parts.vido)):
|
|
@@ -28,8 +28,8 @@ reader keeps and never interprets:
|
|
|
28
28
|
``x-gammonview-illegal-ply`` (ply scope)
|
|
29
29
|
The steps of an illegal play that our document holds as a dice ply.
|
|
30
30
|
``x-gammonview-site``, ``x-gammonview-event`` (match scope)
|
|
31
|
-
|
|
32
|
-
host name), and an over-long ``event``.
|
|
31
|
+
Our free-text ``site``, which v2 has no field for (v2's own ``site`` is a
|
|
32
|
+
platform's host name), and an over-long ``event``.
|
|
33
33
|
``x-gammonview-<field>`` (match scope), ``x-gammonview-<side>_profile.<field>``, and
|
|
34
34
|
``x-gammonview-<field>`` at game scope
|
|
35
35
|
A value v2 cannot hold (a string past its cap or outside its byte set, a
|
|
@@ -192,7 +192,10 @@ _ROLLOUT_UINTS = ("trials", "truncation_depth", "move_ply", "variance_reduction"
|
|
|
192
192
|
|
|
193
193
|
|
|
194
194
|
def _level_bytes(lv: dict) -> bytes:
|
|
195
|
-
"""A ``level`` record from an override (``_level_diff``'s shape)
|
|
195
|
+
"""A ``level`` record from an override (``_level_diff``'s shape), its depths
|
|
196
|
+
counted v2's way (``R.PLY_SHIFT``)."""
|
|
197
|
+
lv = copy.deepcopy(lv)
|
|
198
|
+
R.shift_level(lv, -R.PLY_SHIFT)
|
|
196
199
|
fields: dict[int, bytes] = {}
|
|
197
200
|
if "preset" in lv:
|
|
198
201
|
fields[0] = _str(lv["preset"])
|
|
@@ -456,6 +459,26 @@ def _game_start(g: dict, opening_abs: list) -> int:
|
|
|
456
459
|
and [int(v) for v in first.get("set_position") or [0] * 26] != opening_abs)
|
|
457
460
|
|
|
458
461
|
|
|
462
|
+
def _bare_initial_board(g: dict, opening_abs: list, start: int):
|
|
463
|
+
"""The set-up board a game states only as ``initial_board``, or None.
|
|
464
|
+
|
|
465
|
+
A v1 read states a set-up start that way, with no leading ply (v1 kept the
|
|
466
|
+
board in the GAME header), so a v1 file rewritten as v2 would otherwise
|
|
467
|
+
lose its start -- and its plies, replayed from the opening, would be
|
|
468
|
+
flagged illegal. A leading set-up ply, where there is one, wins: a v2 read
|
|
469
|
+
states the board both ways. So does one *with* dice, which `_game_start`
|
|
470
|
+
keeps as a ply: the document already states its start, and taking
|
|
471
|
+
``initial_board`` too would write that start twice."""
|
|
472
|
+
ib = g.get("initial_board")
|
|
473
|
+
if start or not isinstance(ib, list) or len(ib) != 26:
|
|
474
|
+
return None
|
|
475
|
+
plies = g.get("plies") or []
|
|
476
|
+
if plies and plies[0].get("action_id") == R.ACTION_SET_POSITION:
|
|
477
|
+
return None
|
|
478
|
+
board_abs = [int(v) for v in ib]
|
|
479
|
+
return None if board_abs == opening_abs else board_abs
|
|
480
|
+
|
|
481
|
+
|
|
459
482
|
def ply_layout(doc: dict):
|
|
460
483
|
"""``(keys, starts)``: the ``(game, ply)`` of every ply v2 holds, in
|
|
461
484
|
``ply_ref`` order, and each game's leading set-up position (0 or 1) that it
|
|
@@ -543,8 +566,10 @@ class _Match:
|
|
|
543
566
|
fields: dict[int, bytes] = {}
|
|
544
567
|
start = _game_start(g, opening_abs)
|
|
545
568
|
if start:
|
|
546
|
-
|
|
547
|
-
|
|
569
|
+
board_abs = [int(v) for v in plies[0]["set_position"]]
|
|
570
|
+
else:
|
|
571
|
+
board_abs = _bare_initial_board(g, opening_abs, start)
|
|
572
|
+
if board_abs is not None:
|
|
548
573
|
board = _absolute_to_p1(board_abs)
|
|
549
574
|
fields[3] = _board(board_abs)
|
|
550
575
|
|
|
@@ -832,11 +857,11 @@ class _Match:
|
|
|
832
857
|
fields[11] = b""
|
|
833
858
|
if doc.get("rated"):
|
|
834
859
|
fields[25] = b""
|
|
835
|
-
# Our `site` is
|
|
836
|
-
#
|
|
837
|
-
#
|
|
860
|
+
# Our `site` is free text with no v2 field, so it always rides in its
|
|
861
|
+
# own record -- even when it repeats the city, since the reader no
|
|
862
|
+
# longer derives one from the other.
|
|
838
863
|
site = doc.get("site")
|
|
839
|
-
if site
|
|
864
|
+
if site:
|
|
840
865
|
self.annos.append((SCOPE_MATCH, 0, R.GV_KEY_SITE, R.GV_FORMAT + site))
|
|
841
866
|
variant = doc.get("variant") or 0
|
|
842
867
|
if not _is_uint(variant):
|
|
@@ -1444,12 +1469,21 @@ def _flag_exceptions(doc_obj_by_key: dict, decoded_by_key: dict) -> list[str]:
|
|
|
1444
1469
|
|
|
1445
1470
|
|
|
1446
1471
|
def _chunked(key: str, value: str) -> list[tuple[str, str]]:
|
|
1447
|
-
"""Split
|
|
1448
|
-
|
|
1472
|
+
"""Split a value over ``key``, ``key~1``, ... to stay under ``MAX_STRING``
|
|
1473
|
+
bytes; the reader concatenates them in suffix order. Cuts fall between
|
|
1474
|
+
characters, never inside one's UTF-8 bytes."""
|
|
1449
1475
|
room = MAX_STRING - 16
|
|
1450
|
-
if len(value) <= room:
|
|
1476
|
+
if len(value.encode("utf-8")) <= room:
|
|
1451
1477
|
return [(key, value)]
|
|
1452
|
-
parts = [
|
|
1478
|
+
parts, cur, size = [], [], 0
|
|
1479
|
+
for ch in value:
|
|
1480
|
+
n = len(ch.encode("utf-8"))
|
|
1481
|
+
if size + n > room:
|
|
1482
|
+
parts.append("".join(cur))
|
|
1483
|
+
cur, size = [], 0
|
|
1484
|
+
cur.append(ch)
|
|
1485
|
+
size += n
|
|
1486
|
+
parts.append("".join(cur))
|
|
1453
1487
|
return [(key if i == 0 else f"{key}~{i}", part) for i, part in enumerate(parts)]
|
|
1454
1488
|
|
|
1455
1489
|
|
|
@@ -1844,10 +1878,14 @@ def _encode(ogxm: dict) -> _Parts:
|
|
|
1844
1878
|
# from the records just written, and keep only where the document
|
|
1845
1879
|
# disagrees.
|
|
1846
1880
|
anal_dec = R._decode_anal(anal)
|
|
1881
|
+
decs_dec = R._decode_decs(decs_payload)
|
|
1882
|
+
R.shift_levels(anal_dec, decs_dec, R.PLY_SHIFT)
|
|
1847
1883
|
extra_dec = {ref: R._decode_decs(b"".join(rec for _k, rec in recs))
|
|
1848
1884
|
for ref, recs in extra.items()}
|
|
1885
|
+
for recs in extra_dec.values():
|
|
1886
|
+
R.shift_levels({}, recs, R.PLY_SHIFT)
|
|
1849
1887
|
_info, decoded, _luck = R._v1_block(
|
|
1850
|
-
anal_dec,
|
|
1888
|
+
anal_dec, decs_dec, match.ply_at, match.match_length,
|
|
1851
1889
|
ours={"extra": extra_dec, "exceptions": set(), "illegal": match.illegal,
|
|
1852
1890
|
"frames": R._parse_frames(",".join(frames))})
|
|
1853
1891
|
want = {(match.ref_of[key], key): select(p) for key, p in match.ply_at
|
|
@@ -1860,6 +1898,7 @@ def _encode(ogxm: dict) -> _Parts:
|
|
|
1860
1898
|
k == R.KIND_ROLL for recs in extra.values() for k, _b in recs):
|
|
1861
1899
|
items.append("luck=" + quote(luck_label, safe=_SAFE))
|
|
1862
1900
|
items.append("pr=" + ",".join(tokens))
|
|
1901
|
+
items.append(f"{R.GV_ITEM_PLIES}={R.GV_PLIES_GNUBG}")
|
|
1863
1902
|
if frames:
|
|
1864
1903
|
items.append("frame=" + ",".join(frames))
|
|
1865
1904
|
gv_annos: list[tuple[int, int, str, str]] = []
|
|
@@ -1929,9 +1968,12 @@ def _assemble(parts: _Parts, plan: P.Plan) -> bytes:
|
|
|
1929
1968
|
ensure_ascii=False).encode("utf-8")).decode("ascii")
|
|
1930
1969
|
ours += [(SCOPE_MATCH, 0, key, part)
|
|
1931
1970
|
for key, part in _chunked(R.GV_KEY_ANNOTATIONS, R.GV_FORMAT + text)]
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1971
|
+
# A value of ours past a string's length goes over ``key~1``, ``key~2``, ...
|
|
1972
|
+
# (profile 3), whichever field it carries.
|
|
1973
|
+
for scope, ref, key0, value0 in ours:
|
|
1974
|
+
for key, value in _chunked(key0, value0):
|
|
1975
|
+
entries.append((P.anno_sort_key(scope, ref, None, None, None, key, None, len(entries)),
|
|
1976
|
+
_anno_record(scope, ref, key, value)))
|
|
1935
1977
|
if entries:
|
|
1936
1978
|
entries.sort(key=lambda e: e[0])
|
|
1937
1979
|
payload = b"".join(rec for _k, rec in entries)
|
|
@@ -32,6 +32,8 @@ export const HEADER_FLAG_HAS_ANALYSIS = 0x0001;
|
|
|
32
32
|
// v3: the blanked bytes dropped -- checker record 7->3 B, cube record 6->2 B
|
|
33
33
|
export const GVAN_VERSION = 3;
|
|
34
34
|
|
|
35
|
+
// v1's cap on a decision's alternatives, used by `write_gvab_v1` only. v2
|
|
36
|
+
// holds 1024 (`MAX_ALTS` in ogxm2_writer.js).
|
|
35
37
|
export const MAX_ALTS_PER_DECISION = 50;
|
|
36
38
|
|
|
37
39
|
// The largest hop a ply record can hold. A move step is one byte -- the start
|
|
@@ -146,17 +146,19 @@ function extend(mine, opp, dice, acc, results) {
|
|
|
146
146
|
* possible, and when a non-double allows only one die, the larger one must be
|
|
147
147
|
* played if either alone is playable.
|
|
148
148
|
*
|
|
149
|
-
* @returns {{plays: Set<string>, diceUsed: number}} plays are
|
|
149
|
+
* @returns {{plays: Set<string>, best: Array, diceUsed: number}} plays are
|
|
150
|
+
* normalizePlay keys; best the maximal sequences they came from (empty in a
|
|
151
|
+
* dance, which `isPlayLegal` relies on).
|
|
150
152
|
*/
|
|
151
153
|
export function legalPlays(mine, opp, d1, d2) {
|
|
152
154
|
const results = [];
|
|
153
155
|
const dice = d1 === d2 ? [d1, d1, d1, d1] : [d1, d2];
|
|
154
156
|
extend(mine, opp, dice, [], results);
|
|
155
|
-
if (!results.length) return { plays: new Set(), diceUsed: 0 };
|
|
157
|
+
if (!results.length) return { plays: new Set(), best: [], diceUsed: 0 };
|
|
156
158
|
|
|
157
159
|
let longest = 0;
|
|
158
160
|
for (const s of results) if (s.length > longest) longest = s.length;
|
|
159
|
-
if (longest === 0) return { plays: new Set(), diceUsed: 0 };
|
|
161
|
+
if (longest === 0) return { plays: new Set(), best: [], diceUsed: 0 };
|
|
160
162
|
|
|
161
163
|
let best = results.filter((s) => s.length === longest);
|
|
162
164
|
|