diff --git a/common_libs/tests/fixtures/tfil_commit_ab_report_runs14.md b/common_libs/tests/fixtures/tfil_commit_ab_report_runs14.md new file mode 100644 index 0000000..74aac59 --- /dev/null +++ b/common_libs/tests/fixtures/tfil_commit_ab_report_runs14.md @@ -0,0 +1,292 @@ + +# TFIL commitment A/B — measured + +POOLED: runs 1-7 (pre-registered primary) + runs 8-14 (independent replication; 70 battles, 490 rounds). The replication can be analysed alone with --first-run 8 --runs 14. + +source commit: `19bf461` + +frozen ModularBot binary sha256: `46e7ce19cc7889dacc0c5add9b29928dadb757fbc6ecbdb7765cddd7813361c0` + +raw artifacts (NOT committed, live only): `/tmp/tfil_ab2/out//run.jsonl` + `...events.jsonl` and `/tmp/tfil_ab2/logs//run.out` + `...commit.jsonl` + +analysed runs per arm: 14 (this file is self-consistent; re-run the tool with --runs N on the raw dir for a different block) + +damage = server BulletHitBotEvent damage, attributed by numeric owner/victim id and verified against the capture's own counters; round wins = final RESULTS `firstPlaces`, cross-checked against BotDeathEvent. + +## 1. Arm table (14 runs x 7 rounds vs real DrussGT, binary frozen from one source commit) + +| arm | env | damage/run | damage taken/run | ROUND WINS | round win % | our shots/run | hits taken/run | our score/run | ticks/run | +|---|---|---:|---:|---:|---:|---:|---:|---:|---:| +| A control (shipped) | `` | 274.44 | 198.95 | 39/98 | 39.8% | 826.7 | 96.5 | 463.6 | 939 | +| B honour commitment | `TR_TFIL_TILE_REPLAN=off` | 274.07 | 192.16 | 43/98 | 43.9% | 810.1 | 89.0 | 476.8 | 915 | +| C B + no-reversal | `TR_TFIL_TILE_REPLAN=off TR_TFIL_NO_REV=1` | 257.34 | 202.16 | 34/98 | 34.7% | 805.4 | 87.2 | 421.9 | 927 | +| D enemy-keyed cancel | `TR_TFIL_TILE_REPLAN=enemy` | 286.61 | 216.09 | 46/98 | 46.9% | 784.6 | 92.5 | 503.4 | 894 | +| E commit 30 ticks | `TR_TFIL_TILE_REPLAN=off TR_TFIL_COMMIT_TICKS=30` | 265.29 | 198.96 | 41/98 | 41.8% | 790.4 | 92.4 | 459.1 | 913 | + +## 2. Per-run values + + +**A control (shipped)** `` + +| run | damage/run | damage taken | wins | round-by-round | hits taken | our shots | our score | enemy score | ticks | +|---:|---:|---:|---:|---|---:|---:|---:|---:|---:| +| 1 | 245.37 | 218.70 | 2/7 | L W L L L W L | 96 | 705 | 387 | 549 | 11166 | +| 2 | 256.58 | 240.50 | 1/7 | W L L L L L L | 101 | 828 | 324 | 640 | 13516 | +| 3 | 283.54 | 197.00 | 2/7 | L L L L W L W | 86 | 788 | 423 | 524 | 12607 | +| 4 | 306.11 | 198.00 | 3/7 | L L W L W W L | 95 | 848 | 509 | 461 | 13146 | +| 5 | 267.51 | 147.60 | 3/7 | W L L W L W L | 92 | 849 | 477 | 404 | 13483 | +| 6 | 271.07 | 138.80 | 4/7 | L W W L W W L | 88 | 885 | 546 | 327 | 13895 | +| 7 | 332.48 | 206.30 | 4/7 | W W L L W L W | 112 | 912 | 559 | 418 | 14461 | +| 8 | 296.97 | 198.00 | 4/7 | W W L L W L W | 101 | 851 | 575 | 397 | 13421 | +| 9 | 305.66 | 207.40 | 4/7 | W L L W W L W | 96 | 870 | 584 | 406 | 14053 | +| 10 | 228.06 | 195.40 | 4/7 | L W L W W L W | 89 | 831 | 501 | 396 | 12967 | +| 11 | 276.63 | 184.30 | 1/7 | L L L W L L L | 100 | 865 | 348 | 575 | 13963 | +| 12 | 238.65 | 245.00 | 2/7 | W W L L L L L | 101 | 751 | 375 | 583 | 11993 | +| 13 | 249.08 | 189.20 | 3/7 | L L L L W W W | 88 | 796 | 458 | 450 | 12709 | +| 14 | 284.47 | 219.10 | 2/7 | L W L L L W L | 106 | 795 | 424 | 550 | 12720 | + +**B honour commitment** `TR_TFIL_TILE_REPLAN=off` + +| run | damage/run | damage taken | wins | round-by-round | hits taken | our shots | our score | enemy score | ticks | +|---:|---:|---:|---:|---|---:|---:|---:|---:|---:| +| 1 | 270.09 | 155.90 | 2/7 | L W L W L L L | 90 | 836 | 410 | 480 | 13267 | +| 2 | 263.83 | 234.60 | 1/7 | L L L L W L L | 111 | 853 | 333 | 631 | 13861 | +| 3 | 280.10 | 189.00 | 5/7 | W L W L W W W | 71 | 721 | 624 | 321 | 11326 | +| 4 | 307.40 | 139.70 | 6/7 | W W W W W W L | 75 | 821 | 707 | 206 | 13196 | +| 5 | 249.24 | 266.20 | 2/7 | L L W L L L W | 99 | 738 | 388 | 594 | 11626 | +| 6 | 275.90 | 161.30 | 4/7 | W L W W L L W | 87 | 812 | 550 | 360 | 12712 | +| 7 | 320.81 | 216.10 | 3/7 | W L L W W L L | 97 | 778 | 511 | 477 | 12428 | +| 8 | 251.62 | 193.80 | 2/7 | W W L L L L L | 81 | 736 | 376 | 519 | 11868 | +| 9 | 292.65 | 159.50 | 4/7 | L W L W W W L | 71 | 834 | 565 | 356 | 12973 | +| 10 | 218.17 | 172.30 | 3/7 | L W W L L L W | 90 | 825 | 367 | 456 | 12792 | +| 11 | 254.60 | 235.00 | 2/7 | L L W L W L L | 90 | 808 | 392 | 563 | 12801 | +| 12 | 275.82 | 172.80 | 3/7 | W W W L L L L | 92 | 880 | 481 | 437 | 13974 | +| 13 | 290.25 | 177.50 | 2/7 | W L L L L W L | 104 | 861 | 419 | 502 | 13750 | +| 14 | 286.48 | 216.60 | 4/7 | W W W L L W L | 88 | 839 | 552 | 419 | 12840 | + +**C B + no-reversal** `TR_TFIL_TILE_REPLAN=off TR_TFIL_NO_REV=1` + +| run | damage/run | damage taken | wins | round-by-round | hits taken | our shots | our score | enemy score | ticks | +|---:|---:|---:|---:|---|---:|---:|---:|---:|---:| +| 1 | 240.31 | 200.90 | 3/7 | W L W L L W L | 89 | 787 | 446 | 467 | 13019 | +| 2 | 298.51 | 138.00 | 3/7 | W L L L W L W | 86 | 830 | 500 | 398 | 13598 | +| 3 | 244.43 | 210.30 | 2/7 | W L L W L L L | 74 | 803 | 379 | 545 | 12935 | +| 4 | 235.60 | 244.40 | 3/7 | L W W L W L L | 80 | 789 | 434 | 501 | 12292 | +| 5 | 268.72 | 193.20 | 3/7 | W L W W L L L | 93 | 839 | 466 | 458 | 13448 | +| 6 | 263.44 | 177.00 | 3/7 | W L L L L W W | 82 | 828 | 470 | 440 | 13385 | +| 7 | 202.55 | 214.70 | 0/7 | L L L L L L L | 96 | 790 | 205 | 676 | 13037 | +| 8 | 289.38 | 208.90 | 3/7 | L L L W L W W | 88 | 756 | 495 | 458 | 12233 | +| 9 | 249.73 | 192.80 | 2/7 | W L L L L L W | 90 | 809 | 388 | 522 | 13027 | +| 10 | 259.55 | 220.60 | 2/7 | L L W W L L L | 86 | 823 | 394 | 539 | 13551 | +| 11 | 274.71 | 189.60 | 3/7 | W W L L L L W | 86 | 842 | 471 | 457 | 13419 | +| 12 | 293.64 | 231.80 | 3/7 | W W W L L L L | 98 | 757 | 504 | 503 | 12005 | +| 13 | 288.62 | 225.00 | 3/7 | L W L W W L L | 94 | 829 | 494 | 481 | 12897 | +| 14 | 193.57 | 183.10 | 1/7 | L W L L L L L | 79 | 794 | 261 | 573 | 12955 | + +**D enemy-keyed cancel** `TR_TFIL_TILE_REPLAN=enemy` + +| run | damage/run | damage taken | wins | round-by-round | hits taken | our shots | our score | enemy score | ticks | +|---:|---:|---:|---:|---|---:|---:|---:|---:|---:| +| 1 | 247.43 | 219.90 | 2/7 | W L W L L L L | 94 | 774 | 386 | 544 | 12775 | +| 2 | 332.70 | 202.50 | 5/7 | W W L W W L W | 104 | 864 | 624 | 350 | 13863 | +| 3 | 264.08 | 201.30 | 4/7 | L W L W L W W | 78 | 765 | 488 | 422 | 12557 | +| 4 | 276.78 | 215.00 | 1/7 | L L L L L W L | 97 | 806 | 332 | 612 | 12857 | +| 5 | 339.18 | 259.40 | 3/7 | W W L W L L L | 104 | 770 | 552 | 536 | 12453 | +| 6 | 321.08 | 161.70 | 4/7 | L L W W W L W | 89 | 809 | 601 | 351 | 12585 | +| 7 | 328.30 | 219.40 | 5/7 | W L W W W L W | 87 | 752 | 669 | 360 | 11942 | +| 8 | 250.60 | 230.60 | 4/7 | W W L L W L W | 90 | 721 | 526 | 436 | 11343 | +| 9 | 266.94 | 189.40 | 3/7 | W W L L L W L | 91 | 790 | 479 | 451 | 12759 | +| 10 | 286.03 | 199.50 | 4/7 | L W W L W W L | 90 | 839 | 560 | 402 | 13223 | +| 11 | 261.72 | 200.40 | 3/7 | W L L W W L L | 94 | 817 | 462 | 466 | 12946 | +| 12 | 218.76 | 290.60 | 1/7 | L L L W L L L | 76 | 636 | 284 | 695 | 10073 | +| 13 | 339.10 | 218.30 | 3/7 | W L W L L L W | 97 | 789 | 531 | 486 | 12430 | +| 14 | 279.86 | 217.20 | 4/7 | W W W L W L L | 104 | 852 | 554 | 421 | 13479 | + +**E commit 30 ticks** `TR_TFIL_TILE_REPLAN=off TR_TFIL_COMMIT_TICKS=30` + +| run | damage/run | damage taken | wins | round-by-round | hits taken | our shots | our score | enemy score | ticks | +|---:|---:|---:|---:|---|---:|---:|---:|---:|---:| +| 1 | 316.56 | 215.20 | 3/7 | W W L L W L L | 110 | 847 | 513 | 483 | 13347 | +| 2 | 316.14 | 206.00 | 3/7 | W W L W L L L | 101 | 776 | 520 | 468 | 12588 | +| 3 | 271.46 | 206.20 | 3/7 | L L W L W W L | 104 | 788 | 441 | 498 | 12580 | +| 4 | 300.07 | 195.70 | 5/7 | L W L W W W W | 87 | 754 | 596 | 352 | 12233 | +| 5 | 256.01 | 188.80 | 3/7 | W L L W W L L | 94 | 828 | 465 | 452 | 13629 | +| 6 | 277.83 | 190.80 | 3/7 | W W L W L L L | 90 | 749 | 485 | 457 | 12302 | +| 7 | 263.14 | 172.90 | 4/7 | W L W W W L L | 87 | 809 | 536 | 374 | 12614 | +| 8 | 267.62 | 202.60 | 2/7 | L L W W L L L | 98 | 833 | 406 | 532 | 13384 | +| 9 | 179.35 | 214.00 | 1/7 | L W L L L L L | 81 | 740 | 247 | 608 | 11998 | +| 10 | 345.39 | 175.40 | 5/7 | W W W W W L L | 94 | 879 | 689 | 303 | 13959 | +| 11 | 174.71 | 200.50 | 0/7 | L L L L L L L | 89 | 793 | 175 | 658 | 13467 | +| 12 | 235.76 | 222.90 | 2/7 | W L W L L L L | 91 | 756 | 367 | 556 | 12571 | +| 13 | 226.19 | 197.60 | 2/7 | W W L L L L L | 92 | 788 | 364 | 531 | 12818 | +| 14 | 283.86 | 196.90 | 5/7 | L W W L W W W | 75 | 726 | 624 | 334 | 11517 | + +### Data integrity: id mapping + round-win cross-check + +Round wins are taken from the final RESULTS `firstPlaces` and cross-checked against the BotDeathEvent of each round (in a TR 1v1 the round loser is the bot that died). A `WARNING` here means the two disagreed, i.e. do not trust that run. + +- A/run1: id 1==ModularBot, id 2==DrussGT; of 1536 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.9% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- A/run2: id 1==ModularBot, id 2==DrussGT; of 1855 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 44.6% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- A/run3: id 1==ModularBot, id 2==DrussGT; of 1741 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.3% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- A/run4: id 2==ModularBot, id 1==DrussGT; of 1806 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 47.0% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- A/run5: id 1==ModularBot, id 2==DrussGT; of 1878 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.2% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- A/run6: id 2==ModularBot, id 1==DrussGT; of 1947 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.5% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- A/run7: id 2==ModularBot, id 1==DrussGT; of 1984 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.0% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- A/run8: id 1==ModularBot, id 2==DrussGT; of 1877 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.3% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- A/run9: id 1==ModularBot, id 2==DrussGT; of 1918 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.4% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- A/run10: id 1==ModularBot, id 2==DrussGT; of 1796 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.3% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- A/run11: id 2==ModularBot, id 1==DrussGT; of 1933 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 44.7% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- A/run12: id 1==ModularBot, id 2==DrussGT; of 1649 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.5% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- A/run13: id 2==ModularBot, id 1==DrussGT; of 1764 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.1% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- A/run14: id 1==ModularBot, id 2==DrussGT; of 1749 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.5% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- B/run1: id 2==ModularBot, id 1==DrussGT; of 1829 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.7% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- B/run2: id 1==ModularBot, id 2==DrussGT; of 1893 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.1% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- B/run3: id 2==ModularBot, id 1==DrussGT; of 1574 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.8% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- B/run4: id 2==ModularBot, id 1==DrussGT; of 1787 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.9% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- B/run5: id 1==ModularBot, id 2==DrussGT; of 1606 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.0% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- B/run6: id 2==ModularBot, id 1==DrussGT; of 1759 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.2% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- B/run7: id 2==ModularBot, id 1==DrussGT; of 1679 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.3% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- B/run8: id 1==ModularBot, id 2==DrussGT; of 1625 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.3% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- B/run9: id 2==ModularBot, id 1==DrussGT; of 1809 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.1% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- B/run10: id 1==ModularBot, id 2==DrussGT; of 1761 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.8% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- B/run11: id 1==ModularBot, id 2==DrussGT; of 1752 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.1% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- B/run12: id 1==ModularBot, id 2==DrussGT; of 1928 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.6% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- B/run13: id 1==ModularBot, id 2==DrussGT; of 1898 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.4% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- B/run14: id 1==ModularBot, id 2==DrussGT; of 1760 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 47.7% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- C/run1: id 1==ModularBot, id 2==DrussGT; of 1768 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 44.5% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- C/run2: id 1==ModularBot, id 2==DrussGT; of 1869 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 44.4% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- C/run3: id 2==ModularBot, id 1==DrussGT; of 1778 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.2% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- C/run4: id 1==ModularBot, id 2==DrussGT; of 1671 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 47.2% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- C/run5: id 2==ModularBot, id 1==DrussGT; of 1831 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.8% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- C/run6: id 1==ModularBot, id 2==DrussGT; of 1823 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.4% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- C/run7: id 1==ModularBot, id 2==DrussGT; of 1776 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 44.5% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- C/run8: id 2==ModularBot, id 1==DrussGT; of 1676 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.1% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- C/run9: id 1==ModularBot, id 2==DrussGT; of 1777 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.5% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- C/run10: id 1==ModularBot, id 2==DrussGT; of 1846 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 44.6% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- C/run11: id 1==ModularBot, id 2==DrussGT; of 1819 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.3% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- C/run12: id 2==ModularBot, id 1==DrussGT; of 1642 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.1% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- C/run13: id 1==ModularBot, id 2==DrussGT; of 1785 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.4% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- C/run14: id 2==ModularBot, id 1==DrussGT; of 1772 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 44.8% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- D/run1: id 1==ModularBot, id 2==DrussGT; of 1739 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 44.5% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- D/run2: id 2==ModularBot, id 1==DrussGT; of 1868 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.3% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- D/run3: id 1==ModularBot, id 2==DrussGT; of 1680 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.5% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- D/run4: id 1==ModularBot, id 2==DrussGT; of 1770 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.5% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- D/run5: id 1==ModularBot, id 2==DrussGT; of 1695 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.4% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- D/run6: id 2==ModularBot, id 1==DrussGT; of 1759 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.0% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- D/run7: id 2==ModularBot, id 1==DrussGT; of 1655 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.4% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- D/run8: id 1==ModularBot, id 2==DrussGT; of 1571 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.9% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- D/run9: id 1==ModularBot, id 2==DrussGT; of 1756 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.0% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- D/run10: id 1==ModularBot, id 2==DrussGT; of 1818 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.1% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- D/run11: id 2==ModularBot, id 1==DrussGT; of 1749 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.7% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- D/run12: id 1==ModularBot, id 2==DrussGT; of 1381 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.1% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- D/run13: id 1==ModularBot, id 2==DrussGT; of 1730 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.6% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- D/run14: id 2==ModularBot, id 1==DrussGT; of 1820 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.8% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- E/run1: id 2==ModularBot, id 1==DrussGT; of 1839 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.1% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- E/run2: id 2==ModularBot, id 1==DrussGT; of 1731 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 44.8% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- E/run3: id 2==ModularBot, id 1==DrussGT; of 1727 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.6% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- E/run4: id 2==ModularBot, id 1==DrussGT; of 1647 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.8% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- E/run5: id 2==ModularBot, id 1==DrussGT; of 1848 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 44.8% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- E/run6: id 2==ModularBot, id 1==DrussGT; of 1657 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.2% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- E/run7: id 2==ModularBot, id 1==DrussGT; of 1760 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.0% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- E/run8: id 2==ModularBot, id 1==DrussGT; of 1835 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.4% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- E/run9: id 2==ModularBot, id 1==DrussGT; of 1636 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.2% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- E/run10: id 2==ModularBot, id 1==DrussGT; of 1896 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.4% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- E/run11: id 1==ModularBot, id 2==DrussGT; of 1800 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 44.1% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- E/run12: id 1==ModularBot, id 2==DrussGT; of 1692 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 44.7% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- E/run13: id 1==ModularBot, id 2==DrussGT; of 1732 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.5% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- E/run14: id 1==ModularBot, id 2==DrussGT; of 1590 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.7% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable + +## 3. Exact two-sided permutation tests vs arm A (control = shipped mover) + +Per-run test: all C(28,14)=40116600 relabellings of the 14+14 per-run values; statistic = difference of means. + +| metric | A mean | arm | arm mean | delta (arm - A) | exact two-sided p | perms | +|---|---:|---|---:|---:|---:|---:| +| damage/run | 274.44 | B | 274.07 | -0.37 | 0.9720 | 40116600 | +| damage/run | 274.44 | C | 257.34 | -17.10 | 0.1559 | 40116600 | +| damage/run | 274.44 | D | 286.61 | 12.17 | 0.3567 | 40116600 | +| damage/run | 274.44 | E | 265.29 | -9.15 | 0.5574 | 40116600 | +| round wins/run | 2.79 | B | 3.07 | 0.29 | 0.6561 | 40116600 | +| round wins/run | 2.79 | C | 2.43 | -0.36 | 0.4720 | 40116600 | +| round wins/run | 2.79 | D | 3.29 | 0.50 | 0.3500 | 40116600 | +| round wins/run | 2.79 | E | 2.93 | 0.14 | 0.8865 | 40116600 | +| hits taken/run | 96.50 | B | 89.00 | -7.50 | 0.0577 | 40116600 | +| hits taken/run | 96.50 | C | 87.21 | -9.29 | 0.0022 | 40116600 | +| hits taken/run | 96.50 | D | 92.50 | -4.00 | 0.2132 | 40116600 | +| hits taken/run | 96.50 | E | 92.36 | -4.14 | 0.2078 | 40116600 | +| our shots/run | 826.71 | B | 810.14 | -16.57 | 0.4099 | 40116600 | +| our shots/run | 826.71 | C | 805.43 | -21.29 | 0.2121 | 40116600 | +| our shots/run | 826.71 | D | 784.57 | -42.14 | 0.0590 | 40116600 | +| our shots/run | 826.71 | E | 790.43 | -36.29 | 0.0675 | 40116600 | +| our score/run | 463.57 | B | 476.79 | 13.21 | 0.7283 | 40116600 | +| our score/run | 463.57 | C | 421.93 | -41.64 | 0.2270 | 40116600 | +| our score/run | 463.57 | D | 503.43 | 39.86 | 0.2919 | 40116600 | +| our score/run | 463.57 | E | 459.14 | -4.43 | 0.9224 | 40116600 | +| ticks/run | 13150.00 | B | 12815.29 | -334.71 | 0.3017 | 40116600 | +| ticks/run | 13150.00 | C | 12985.79 | -164.21 | 0.5526 | 40116600 | +| ticks/run | 13150.00 | D | 12520.36 | -629.64 | 0.0770 | 40116600 | +| ticks/run | 13150.00 | E | 12786.21 | -363.79 | 0.2331 | 40116600 | + +### Round-level test (exact, anti-conservative) + +Sum statistic over all rounds of the arm vs arm A only; exact hypergeometric null. ANTI-CONSERVATIVE: the rounds of an arm are not independent (rounds are clustered within a run, and a run is often 0/7 or 7/7), so this p is smaller than the per-run p by construction. + +| arm | rounds won | rounds lost | all rounds pooled | exact p (round level, vs A) | +|---|---:|---:|---|---:| +| A | 39 | 59 | 39/98 | 1.0000 | +| B | 43 | 55 | 43/98 | 0.6641 | +| C | 34 | 64 | 34/98 | 0.5547 | +| D | 46 | 52 | 46/98 | 0.3872 | +| E | 41 | 57 | 41/98 | 0.8845 | + +## 4. TREATMENT diagnostics (TR_TFIL_COMMIT_LOG — did the arm actually apply?) + +| arm | commit calls | picks | interval (calls) | % tile_self replans | % tile_enemy | % danger | % expiry | % init | reversal-pick rate | mean abs(speed) | +|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:| +| A | 183270 | 36190 | 5.05 | 96.6% | 0.0% | 0.5% | 2.6% | 0.3% | 33.9% | 4.69 | +| B | 178439 | 12029 | 14.77 | 0.0% | 0.0% | 4.8% | 94.3% | 0.9% | 50.9% | 5.37 | +| C | 180987 | 12184 | 14.79 | 0.0% | 0.0% | 4.4% | 94.7% | 0.9% | 42.4% | 5.47 | +| D | 173956 | 32772 | 5.29 | 0.0% | 95.7% | 0.5% | 3.4% | 0.4% | 37.6% | 4.98 | +| E | 177989 | 6640 | 26.62 | 0.0% | 0.0% | 37.0% | 61.2% | 1.7% | 57.0% | 4.91 | + +Same diagnostics from the OFFLINE fixture replay (`nim c -r --path:common_libs common_libs/tests/test_tfil_commit_env.nim`, 20026 ticks, no Java): + +| arm | interval | reversal rate | +|---|---:|---:| +| A control | 5.07 | 33.7% | +| B commit | 14.65 | 41.1% | +| C commit+noRev | 14.65 | 32.9% | +| D enemyTile | 4.94 | 35.6% | +| E commit30 | 27.57 | 42.0% | + +The offline and live diagnostics agree on every arm: the interval moves off ~5 calls exactly where the arm says it should, so every treatment applied. + +### Mean self-speed MAGNITUDE abs(sp) by offset (in calls) around a REVERSAL pick + +This is the series that tests the premise 'the reversal collapses the bot's speed'. `sp` is the SIGNED speed at the call; the signed mean over the whole battle is ~0 because the bot drives backwards about half the time, so the magnitude is the informative statistic. + +| arm | -5 | -4 | -3 | -2 | -1 | +0 | +1 | +2 | +3 | +4 | +5 | +6 | +7 | +8 | +9 | +10 | +|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:| +| A | 4.61 | 4.61 | 4.64 | 4.69 | 4.77 | 4.84 | 4.19 | 3.95 | 4.05 | 4.19 | 4.32 | 4.45 | 4.54 | 4.65 | 4.75 | 4.83 | +| B | 5.96 | 5.95 | 5.85 | 5.73 | 5.55 | 5.28 | 4.32 | 3.91 | 4.10 | 4.49 | 4.90 | 5.24 | 5.53 | 5.76 | 5.92 | 6.01 | +| C | 6.07 | 6.04 | 5.97 | 5.84 | 5.66 | 5.32 | 4.38 | 3.95 | 4.09 | 4.46 | 4.81 | 5.21 | 5.59 | 5.94 | 6.11 | 6.21 | +| D | 5.07 | 5.02 | 5.00 | 5.02 | 5.00 | 4.94 | 4.38 | 4.21 | 4.33 | 4.50 | 4.64 | 4.75 | 4.85 | 4.98 | 5.06 | 5.11 | +| E | 5.00 | 4.93 | 4.84 | 4.78 | 4.67 | 4.52 | 4.19 | 4.08 | 4.22 | 4.42 | 4.58 | 4.77 | 4.98 | 5.18 | 5.32 | 5.40 | + +(reversal samples per arm: A=12250, B=6125, C=5167, D=12336, E=3781) + +## 5. Direct answer + +- control (shipped mover): damage/run 274.44, round wins/run 2.79 (39/98), hits taken/run 96.50, reversal-pick rate 33.9%, mean abs(speed) 4.69 +- arm B (B honour commitment): damage/run 274.07 (delta -0.37, p=0.9720) -> not distinguishable from control; round wins/run 3.07 (delta 0.29, p=0.6561) -> not distinguishable from control; hits taken/run 89.00 (delta -7.50, p=0.0577); reversal-pick rate 50.9% (control 33.9%) +- arm C (C B + no-reversal): damage/run 257.34 (delta -17.10, p=0.1559) -> not distinguishable from control; round wins/run 2.43 (delta -0.36, p=0.4720) -> not distinguishable from control; hits taken/run 87.21 (delta -9.29, p=0.0022); reversal-pick rate 42.4% (control 33.9%) +- arm D (D enemy-keyed cancel): damage/run 286.61 (delta 12.17, p=0.3567) -> not distinguishable from control; round wins/run 3.29 (delta 0.50, p=0.3500) -> not distinguishable from control; hits taken/run 92.50 (delta -4.00, p=0.2132); reversal-pick rate 37.6% (control 33.9%) +- arm E (E commit 30 ticks): damage/run 265.29 (delta -9.15, p=0.5574) -> not distinguishable from control; round wins/run 2.93 (delta 0.14, p=0.8865) -> not distinguishable from control; hits taken/run 92.36 (delta -4.14, p=0.2078); reversal-pick rate 57.0% (control 33.9%) + +**NO ARM improves damage/run or round wins vs the shipped mover at p < 0.05.** Best point estimate on damage/run is arm D (delta 12.17); best on round wins/run is arm D (delta 0.50). Neither is statistically distinguishable from control at this n. + +PREMISE CHECK (the bug report claimed the tile-change replan cancels the commitment, the reversals it causes are what get us hit): + +- The cancel IS real and dominant in the shipped mover: arm A ends its commitment because of OUR tile change for 96.6% of picks at a 5.05-call interval. Arms B/C/E remove it (0.0% tile_self replans, interval 14.77 / 26.62) and arm D re-keys it to the enemy tile (95.7% tile_enemy). +- But honouring the commitment does NOT reduce reversal picks: the reversal rate RISES from 33.9% (control) to 50.9% (B) and 57.0% (E). The no-reversal arm C only claws part of that back (42.4%), still above control. +- The speed dip after a reversal is present in EVERY arm (see the profile table: ~4.9 -> ~4.0 px/tick at offsets +1..+2), i.e. it is a property of turning around, not of the tile replan. Arm A -- the arm carrying the bug -- actually has the LOWEST mean abs(speed). diff --git a/common_libs/tests/fixtures/tfil_commit_ab_report_runs7.md b/common_libs/tests/fixtures/tfil_commit_ab_report_runs7.md new file mode 100644 index 0000000..5f5471f --- /dev/null +++ b/common_libs/tests/fixtures/tfil_commit_ab_report_runs7.md @@ -0,0 +1,222 @@ + +# TFIL commitment A/B — measured + +PRE-REGISTERED PRIMARY BLOCK: runs 1-7 per arm (35 battles, 245 rounds), 5 arms in parallel, 7 runs x 7 rounds each. + +source commit: `19bf461` + +frozen ModularBot binary sha256: `46e7ce19cc7889dacc0c5add9b29928dadb757fbc6ecbdb7765cddd7813361c0` + +raw artifacts (NOT committed, live only): `/tmp/tfil_ab2/out//run.jsonl` + `...events.jsonl` and `/tmp/tfil_ab2/logs//run.out` + `...commit.jsonl` + +analysed runs per arm: 7 (this file is self-consistent; re-run the tool with --runs N on the raw dir for a different block) + +damage = server BulletHitBotEvent damage, attributed by numeric owner/victim id and verified against the capture's own counters; round wins = final RESULTS `firstPlaces`, cross-checked against BotDeathEvent. + +## 1. Arm table (7 runs x 7 rounds vs real DrussGT, binary frozen from one source commit) + +| arm | env | damage/run | damage taken/run | ROUND WINS | round win % | our shots/run | hits taken/run | our score/run | ticks/run | +|---|---|---:|---:|---:|---:|---:|---:|---:|---:| +| A control (shipped) | `` | 280.38 | 192.41 | 19/49 | 38.8% | 830.7 | 95.7 | 460.7 | 1883 | +| B honour commitment | `TR_TFIL_TILE_REPLAN=off` | 281.05 | 194.69 | 23/49 | 46.9% | 794.1 | 90.0 | 503.3 | 1804 | +| C B + no-reversal | `TR_TFIL_TILE_REPLAN=off TR_TFIL_NO_REV=1` | 250.51 | 196.93 | 17/49 | 34.7% | 809.4 | 85.7 | 414.3 | 1871 | +| D enemy-keyed cancel | `TR_TFIL_TILE_REPLAN=enemy` | 301.36 | 211.31 | 24/49 | 49.0% | 791.4 | 93.3 | 521.7 | 1816 | +| E commit 30 ticks | `TR_TFIL_TILE_REPLAN=off TR_TFIL_COMMIT_TICKS=30` | 285.89 | 196.51 | 24/49 | 49.0% | 793.0 | 96.1 | 508.0 | 1822 | + +## 2. Per-run values + + +**A control (shipped)** `` + +| run | damage/run | damage taken | wins | round-by-round | hits taken | our shots | our score | enemy score | ticks | +|---:|---:|---:|---:|---|---:|---:|---:|---:|---:| +| 1 | 245.37 | 218.70 | 2/7 | L W L L L W L | 96 | 705 | 387 | 549 | 11166 | +| 2 | 256.58 | 240.50 | 1/7 | W L L L L L L | 101 | 828 | 324 | 640 | 13516 | +| 3 | 283.54 | 197.00 | 2/7 | L L L L W L W | 86 | 788 | 423 | 524 | 12607 | +| 4 | 306.11 | 198.00 | 3/7 | L L W L W W L | 95 | 848 | 509 | 461 | 13146 | +| 5 | 267.51 | 147.60 | 3/7 | W L L W L W L | 92 | 849 | 477 | 404 | 13483 | +| 6 | 271.07 | 138.80 | 4/7 | L W W L W W L | 88 | 885 | 546 | 327 | 13895 | +| 7 | 332.48 | 206.30 | 4/7 | W W L L W L W | 112 | 912 | 559 | 418 | 14461 | + +**B honour commitment** `TR_TFIL_TILE_REPLAN=off` + +| run | damage/run | damage taken | wins | round-by-round | hits taken | our shots | our score | enemy score | ticks | +|---:|---:|---:|---:|---|---:|---:|---:|---:|---:| +| 1 | 270.09 | 155.90 | 2/7 | L W L W L L L | 90 | 836 | 410 | 480 | 13267 | +| 2 | 263.83 | 234.60 | 1/7 | L L L L W L L | 111 | 853 | 333 | 631 | 13861 | +| 3 | 280.10 | 189.00 | 5/7 | W L W L W W W | 71 | 721 | 624 | 321 | 11326 | +| 4 | 307.40 | 139.70 | 6/7 | W W W W W W L | 75 | 821 | 707 | 206 | 13196 | +| 5 | 249.24 | 266.20 | 2/7 | L L W L L L W | 99 | 738 | 388 | 594 | 11626 | +| 6 | 275.90 | 161.30 | 4/7 | W L W W L L W | 87 | 812 | 550 | 360 | 12712 | +| 7 | 320.81 | 216.10 | 3/7 | W L L W W L L | 97 | 778 | 511 | 477 | 12428 | + +**C B + no-reversal** `TR_TFIL_TILE_REPLAN=off TR_TFIL_NO_REV=1` + +| run | damage/run | damage taken | wins | round-by-round | hits taken | our shots | our score | enemy score | ticks | +|---:|---:|---:|---:|---|---:|---:|---:|---:|---:| +| 1 | 240.31 | 200.90 | 3/7 | W L W L L W L | 89 | 787 | 446 | 467 | 13019 | +| 2 | 298.51 | 138.00 | 3/7 | W L L L W L W | 86 | 830 | 500 | 398 | 13598 | +| 3 | 244.43 | 210.30 | 2/7 | W L L W L L L | 74 | 803 | 379 | 545 | 12935 | +| 4 | 235.60 | 244.40 | 3/7 | L W W L W L L | 80 | 789 | 434 | 501 | 12292 | +| 5 | 268.72 | 193.20 | 3/7 | W L W W L L L | 93 | 839 | 466 | 458 | 13448 | +| 6 | 263.44 | 177.00 | 3/7 | W L L L L W W | 82 | 828 | 470 | 440 | 13385 | +| 7 | 202.55 | 214.70 | 0/7 | L L L L L L L | 96 | 790 | 205 | 676 | 13037 | + +**D enemy-keyed cancel** `TR_TFIL_TILE_REPLAN=enemy` + +| run | damage/run | damage taken | wins | round-by-round | hits taken | our shots | our score | enemy score | ticks | +|---:|---:|---:|---:|---|---:|---:|---:|---:|---:| +| 1 | 247.43 | 219.90 | 2/7 | W L W L L L L | 94 | 774 | 386 | 544 | 12775 | +| 2 | 332.70 | 202.50 | 5/7 | W W L W W L W | 104 | 864 | 624 | 350 | 13863 | +| 3 | 264.08 | 201.30 | 4/7 | L W L W L W W | 78 | 765 | 488 | 422 | 12557 | +| 4 | 276.78 | 215.00 | 1/7 | L L L L L W L | 97 | 806 | 332 | 612 | 12857 | +| 5 | 339.18 | 259.40 | 3/7 | W W L W L L L | 104 | 770 | 552 | 536 | 12453 | +| 6 | 321.08 | 161.70 | 4/7 | L L W W W L W | 89 | 809 | 601 | 351 | 12585 | +| 7 | 328.30 | 219.40 | 5/7 | W L W W W L W | 87 | 752 | 669 | 360 | 11942 | + +**E commit 30 ticks** `TR_TFIL_TILE_REPLAN=off TR_TFIL_COMMIT_TICKS=30` + +| run | damage/run | damage taken | wins | round-by-round | hits taken | our shots | our score | enemy score | ticks | +|---:|---:|---:|---:|---|---:|---:|---:|---:|---:| +| 1 | 316.56 | 215.20 | 3/7 | W W L L W L L | 110 | 847 | 513 | 483 | 13347 | +| 2 | 316.14 | 206.00 | 3/7 | W W L W L L L | 101 | 776 | 520 | 468 | 12588 | +| 3 | 271.46 | 206.20 | 3/7 | L L W L W W L | 104 | 788 | 441 | 498 | 12580 | +| 4 | 300.07 | 195.70 | 5/7 | L W L W W W W | 87 | 754 | 596 | 352 | 12233 | +| 5 | 256.01 | 188.80 | 3/7 | W L L W W L L | 94 | 828 | 465 | 452 | 13629 | +| 6 | 277.83 | 190.80 | 3/7 | W W L W L L L | 90 | 749 | 485 | 457 | 12302 | +| 7 | 263.14 | 172.90 | 4/7 | W L W W W L L | 87 | 809 | 536 | 374 | 12614 | + +### Data integrity: id mapping + round-win cross-check + +Round wins are taken from the final RESULTS `firstPlaces` and cross-checked against the BotDeathEvent of each round (in a TR 1v1 the round loser is the bot that died). A `WARNING` here means the two disagreed, i.e. do not trust that run. + +- A/run1: id 1==ModularBot, id 2==DrussGT; of 1536 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.9% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- A/run2: id 1==ModularBot, id 2==DrussGT; of 1855 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 44.6% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- A/run3: id 1==ModularBot, id 2==DrussGT; of 1741 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.3% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- A/run4: id 2==ModularBot, id 1==DrussGT; of 1806 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 47.0% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- A/run5: id 1==ModularBot, id 2==DrussGT; of 1878 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.2% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- A/run6: id 2==ModularBot, id 1==DrussGT; of 1947 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.5% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- A/run7: id 2==ModularBot, id 1==DrussGT; of 1984 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.0% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- B/run1: id 2==ModularBot, id 1==DrussGT; of 1829 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.7% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- B/run2: id 1==ModularBot, id 2==DrussGT; of 1893 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.1% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- B/run3: id 2==ModularBot, id 1==DrussGT; of 1574 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.8% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- B/run4: id 2==ModularBot, id 1==DrussGT; of 1787 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.9% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- B/run5: id 1==ModularBot, id 2==DrussGT; of 1606 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.0% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- B/run6: id 2==ModularBot, id 1==DrussGT; of 1759 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.2% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- B/run7: id 2==ModularBot, id 1==DrussGT; of 1679 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.3% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- C/run1: id 1==ModularBot, id 2==DrussGT; of 1768 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 44.5% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- C/run2: id 1==ModularBot, id 2==DrussGT; of 1869 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 44.4% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- C/run3: id 2==ModularBot, id 1==DrussGT; of 1778 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.2% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- C/run4: id 1==ModularBot, id 2==DrussGT; of 1671 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 47.2% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- C/run5: id 2==ModularBot, id 1==DrussGT; of 1831 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.8% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- C/run6: id 1==ModularBot, id 2==DrussGT; of 1823 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.4% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- C/run7: id 1==ModularBot, id 2==DrussGT; of 1776 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 44.5% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- D/run1: id 1==ModularBot, id 2==DrussGT; of 1739 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 44.5% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- D/run2: id 2==ModularBot, id 1==DrussGT; of 1868 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.3% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- D/run3: id 1==ModularBot, id 2==DrussGT; of 1680 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.5% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- D/run4: id 1==ModularBot, id 2==DrussGT; of 1770 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.5% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- D/run5: id 1==ModularBot, id 2==DrussGT; of 1695 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.4% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- D/run6: id 2==ModularBot, id 1==DrussGT; of 1759 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.0% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- D/run7: id 2==ModularBot, id 1==DrussGT; of 1655 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.4% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- E/run1: id 2==ModularBot, id 1==DrussGT; of 1839 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.1% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- E/run2: id 2==ModularBot, id 1==DrussGT; of 1731 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 44.8% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- E/run3: id 2==ModularBot, id 1==DrussGT; of 1727 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.6% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- E/run4: id 2==ModularBot, id 1==DrussGT; of 1647 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.8% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- E/run5: id 2==ModularBot, id 1==DrussGT; of 1848 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 44.8% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- E/run6: id 2==ModularBot, id 1==DrussGT; of 1657 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.2% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable +- E/run7: id 2==ModularBot, id 1==DrussGT; of 1760 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.0% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable + +## 3. Exact two-sided permutation tests vs arm A (control = shipped mover) + +Per-run test: all C(14,7)=3432 relabellings of the 7+7 per-run values; statistic = difference of means. + +| metric | A mean | arm | arm mean | delta (arm - A) | exact two-sided p | perms | +|---|---:|---|---:|---:|---:|---:| +| damage/run | 280.38 | B | 281.05 | 0.67 | 0.9650 | 3432 | +| damage/run | 280.38 | C | 250.51 | -29.87 | 0.0880 | 3432 | +| damage/run | 280.38 | D | 301.36 | 20.99 | 0.2617 | 3432 | +| damage/run | 280.38 | E | 285.89 | 5.51 | 0.7121 | 3432 | +| round wins/run | 2.71 | B | 3.29 | 0.57 | 0.6020 | 3432 | +| round wins/run | 2.71 | C | 2.43 | -0.29 | 0.8164 | 3432 | +| round wins/run | 2.71 | D | 3.43 | 0.71 | 0.4312 | 3432 | +| round wins/run | 2.71 | E | 3.43 | 0.71 | 0.3019 | 3432 | +| hits taken/run | 95.71 | B | 90.00 | -5.71 | 0.3904 | 3432 | +| hits taken/run | 95.71 | C | 85.71 | -10.00 | 0.0437 | 3432 | +| hits taken/run | 95.71 | D | 93.29 | -2.43 | 0.6463 | 3432 | +| hits taken/run | 95.71 | E | 96.14 | 0.43 | 0.9510 | 3432 | +| our shots/run | 830.71 | B | 794.14 | -36.57 | 0.2721 | 3432 | +| our shots/run | 830.71 | C | 809.43 | -21.29 | 0.4598 | 3432 | +| our shots/run | 830.71 | D | 791.43 | -39.29 | 0.2110 | 3432 | +| our shots/run | 830.71 | E | 793.00 | -37.71 | 0.2220 | 3432 | +| our score/run | 460.71 | B | 503.29 | 42.57 | 0.4936 | 3432 | +| our score/run | 460.71 | C | 414.29 | -46.43 | 0.3922 | 3432 | +| our score/run | 460.71 | D | 521.71 | 61.00 | 0.3019 | 3432 | +| our score/run | 460.71 | E | 508.00 | 47.29 | 0.2407 | 3432 | +| ticks/run | 13182.00 | B | 12630.86 | -551.14 | 0.3106 | 3432 | +| ticks/run | 13182.00 | C | 13102.00 | -80.00 | 0.8660 | 3432 | +| ticks/run | 13182.00 | D | 12718.86 | -463.14 | 0.3362 | 3432 | +| ticks/run | 13182.00 | E | 12756.14 | -425.86 | 0.3730 | 3432 | + +### Round-level test (exact, anti-conservative) + +Sum statistic over all rounds of the arm vs arm A only; exact hypergeometric null. ANTI-CONSERVATIVE: the rounds of an arm are not independent (rounds are clustered within a run, and a run is often 0/7 or 7/7), so this p is smaller than the per-run p by construction. + +| arm | rounds won | rounds lost | all rounds pooled | exact p (round level, vs A) | +|---|---:|---:|---|---:| +| A | 19 | 30 | 19/49 | 1.0000 | +| B | 23 | 26 | 23/49 | 0.5406 | +| C | 17 | 32 | 17/49 | 0.8342 | +| D | 24 | 25 | 24/49 | 0.4157 | +| E | 24 | 25 | 24/49 | 0.4157 | + +## 4. TREATMENT diagnostics (TR_TFIL_COMMIT_LOG — did the arm actually apply?) + +| arm | commit calls | picks | interval (calls) | % tile_self replans | % tile_enemy | % danger | % expiry | % init | reversal-pick rate | mean abs(speed) | +|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:| +| A | 91795 | 18185 | 5.04 | 96.7% | 0.0% | 0.5% | 2.5% | 0.3% | 34.1% | 4.69 | +| B | 87863 | 5920 | 14.78 | 0.0% | 0.0% | 4.5% | 94.6% | 0.9% | 51.5% | 5.37 | +| C | 91331 | 6142 | 14.80 | 0.0% | 0.0% | 4.4% | 94.7% | 0.9% | 42.4% | 5.45 | +| D | 88392 | 16595 | 5.31 | 0.0% | 95.8% | 0.5% | 3.3% | 0.3% | 37.4% | 4.99 | +| E | 88864 | 3327 | 26.52 | 0.0% | 0.0% | 38.5% | 59.8% | 1.7% | 57.4% | 4.92 | + +Same diagnostics from the OFFLINE fixture replay (`nim c -r --path:common_libs common_libs/tests/test_tfil_commit_env.nim`, 20026 ticks, no Java): + +| arm | interval | reversal rate | +|---|---:|---:| +| A control | 5.07 | 33.7% | +| B commit | 14.65 | 41.1% | +| C commit+noRev | 14.65 | 32.9% | +| D enemyTile | 4.94 | 35.6% | +| E commit30 | 27.57 | 42.0% | + +The offline and live diagnostics agree on every arm: the interval moves off ~5 calls exactly where the arm says it should, so every treatment applied. + +### Mean self-speed MAGNITUDE abs(sp) by offset (in calls) around a REVERSAL pick + +This is the series that tests the premise 'the reversal collapses the bot's speed'. `sp` is the SIGNED speed at the call; the signed mean over the whole battle is ~0 because the bot drives backwards about half the time, so the magnitude is the informative statistic. + +| arm | -5 | -4 | -3 | -2 | -1 | +0 | +1 | +2 | +3 | +4 | +5 | +6 | +7 | +8 | +9 | +10 | +|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:| +| A | 4.67 | 4.67 | 4.71 | 4.75 | 4.84 | 4.89 | 4.24 | 4.00 | 4.10 | 4.25 | 4.39 | 4.52 | 4.59 | 4.68 | 4.79 | 4.87 | +| B | 5.95 | 5.93 | 5.87 | 5.76 | 5.58 | 5.37 | 4.38 | 3.95 | 4.12 | 4.53 | 4.96 | 5.29 | 5.60 | 5.78 | 5.87 | 5.94 | +| C | 6.08 | 6.02 | 5.94 | 5.83 | 5.66 | 5.31 | 4.46 | 4.06 | 4.21 | 4.55 | 4.87 | 5.23 | 5.58 | 5.90 | 6.04 | 6.08 | +| D | 5.15 | 5.10 | 5.07 | 5.06 | 5.03 | 4.98 | 4.42 | 4.27 | 4.39 | 4.56 | 4.70 | 4.82 | 4.90 | 5.04 | 5.12 | 5.15 | +| E | 5.03 | 4.97 | 4.88 | 4.81 | 4.66 | 4.54 | 4.26 | 4.17 | 4.28 | 4.44 | 4.58 | 4.76 | 4.94 | 5.12 | 5.27 | 5.35 | + +(reversal samples per arm: A=6200, B=3047, C=2602, D=6199, E=1910) + +## 5. Direct answer + +- control (shipped mover): damage/run 280.38, round wins/run 2.71 (19/49), hits taken/run 95.71, reversal-pick rate 34.1%, mean abs(speed) 4.69 +- arm B (B honour commitment): damage/run 281.05 (delta 0.67, p=0.9650) -> not distinguishable from control; round wins/run 3.29 (delta 0.57, p=0.6020) -> not distinguishable from control; hits taken/run 90.00 (delta -5.71, p=0.3904); reversal-pick rate 51.5% (control 34.1%) +- arm C (C B + no-reversal): damage/run 250.51 (delta -29.87, p=0.0880) -> not distinguishable from control; round wins/run 2.43 (delta -0.29, p=0.8164) -> not distinguishable from control; hits taken/run 85.71 (delta -10.00, p=0.0437); reversal-pick rate 42.4% (control 34.1%) +- arm D (D enemy-keyed cancel): damage/run 301.36 (delta 20.99, p=0.2617) -> not distinguishable from control; round wins/run 3.43 (delta 0.71, p=0.4312) -> not distinguishable from control; hits taken/run 93.29 (delta -2.43, p=0.6463); reversal-pick rate 37.4% (control 34.1%) +- arm E (E commit 30 ticks): damage/run 285.89 (delta 5.51, p=0.7121) -> not distinguishable from control; round wins/run 3.43 (delta 0.71, p=0.3019) -> not distinguishable from control; hits taken/run 96.14 (delta 0.43, p=0.9510); reversal-pick rate 57.4% (control 34.1%) + +**NO ARM improves damage/run or round wins vs the shipped mover at p < 0.05.** Best point estimate on damage/run is arm D (delta 20.99); best on round wins/run is arm D (delta 0.71). Neither is statistically distinguishable from control at this n. + +PREMISE CHECK (the bug report claimed the tile-change replan cancels the commitment, the reversals it causes are what get us hit): + +- The cancel IS real and dominant in the shipped mover: arm A ends its commitment because of OUR tile change for 96.7% of picks at a 5.04-call interval. Arms B/C/E remove it (0.0% tile_self replans, interval 14.78 / 26.52) and arm D re-keys it to the enemy tile (95.8% tile_enemy). +- But honouring the commitment does NOT reduce reversal picks: the reversal rate RISES from 34.1% (control) to 51.5% (B) and 57.4% (E). The no-reversal arm C only claws part of that back (42.4%), still above control. +- The speed dip after a reversal is present in EVERY arm (see the profile table: ~4.9 -> ~4.0 px/tick at offsets +1..+2), i.e. it is a property of turning around, not of the tile replan. Arm A -- the arm carrying the bug -- actually has the LOWEST mean abs(speed). diff --git a/common_libs/tests/fixtures/tfil_commit_ab_results_runs14.json b/common_libs/tests/fixtures/tfil_commit_ab_results_runs14.json new file mode 100644 index 0000000..3b763eb --- /dev/null +++ b/common_libs/tests/fixtures/tfil_commit_ab_results_runs14.json @@ -0,0 +1,2936 @@ +{ + "meta": { + "source": "19bf461", + "binarySha256": "46e7ce19cc7889dacc0c5add9b29928dadb757fbc6ecbdb7765cddd7813361c0", + "rawRoot": "/tmp/tfil_ab2", + "arms": [ + "", + "TR_TFIL_TILE_REPLAN=off", + "TR_TFIL_TILE_REPLAN=off TR_TFIL_NO_REV=1", + "TR_TFIL_TILE_REPLAN=enemy", + "TR_TFIL_TILE_REPLAN=off TR_TFIL_COMMIT_TICKS=30" + ], + "runsPerArm": 14, + "roundsPerRun": 7, + "note": "POOLED: runs 1-7 (pre-registered primary) + runs 8-14 (independent replication; 70 battles, 490 rounds). The replication can be analysed alone with --first-run 8 --runs 14.", + "roundWinsSource": "final RESULTS firstPlaces, cross-checked with BotDeathEvent", + "damageSource": "BulletHitBotEvent damage, attributed by numeric owner/victim id (verified against the capture's own counters)" + }, + "arms": { + "A": { + "label": "A control (shipped)", + "env": "", + "runs": [ + { + "run": 1, + "rounds": 7, + "wins": 2, + "winsByDeath": 2, + "perRoundWins": [ + 0, + 1, + 0, + 0, + 0, + 1, + 0 + ], + "score": 387, + "enemyScore": 549, + "ticks": 11166, + "damageDealt": 245.36810000000003, + "damageTaken": 218.70000000000016, + "shots": 705, + "enemyShots": 831, + "hits": 71, + "hitsTaken": 96, + "commitCalls": 11107, + "picks": 2202, + "intervalMean": 5.036330608537693, + "revPicks": 764, + "reversalPct": 34.69573115349682, + "speedMean": 4.716623843601673, + "byReason": { + "expiry": 50, + "init": 8, + "danger": 10, + "tile_self": 2134 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1536 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.9% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 2, + "rounds": 7, + "wins": 1, + "winsByDeath": 1, + "perRoundWins": [ + 1, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "score": 324, + "enemyScore": 640, + "ticks": 13516, + "damageDealt": 256.57630000000006, + "damageTaken": 240.5000000000001, + "shots": 828, + "enemyShots": 1027, + "hits": 84, + "hitsTaken": 101, + "commitCalls": 13421, + "picks": 2650, + "intervalMean": 5.056603773584905, + "revPicks": 871, + "reversalPct": 32.867924528301884, + "speedMean": 4.705512235900274, + "byReason": { + "expiry": 74, + "init": 11, + "danger": 16, + "tile_self": 2549 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1855 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 44.6% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 3, + "rounds": 7, + "wins": 2, + "winsByDeath": 2, + "perRoundWins": [ + 0, + 0, + 0, + 0, + 1, + 0, + 1 + ], + "score": 423, + "enemyScore": 524, + "ticks": 12607, + "damageDealt": 283.5444, + "damageTaken": 196.9999999999999, + "shots": 788, + "enemyShots": 953, + "hits": 82, + "hitsTaken": 86, + "commitCalls": 12540, + "picks": 2476, + "intervalMean": 5.058966074313409, + "revPicks": 789, + "reversalPct": 31.865912762520193, + "speedMean": 4.750466125585626, + "byReason": { + "expiry": 68, + "init": 8, + "danger": 10, + "tile_self": 2390 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1741 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.3% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 4, + "rounds": 7, + "wins": 3, + "winsByDeath": 3, + "perRoundWins": [ + 0, + 0, + 1, + 0, + 1, + 1, + 0 + ], + "score": 509, + "enemyScore": 461, + "ticks": 13146, + "damageDealt": 306.11100000000005, + "damageTaken": 198.00000000000006, + "shots": 848, + "enemyShots": 958, + "hits": 88, + "hitsTaken": 95, + "commitCalls": 13084, + "picks": 2634, + "intervalMean": 4.958238420652999, + "revPicks": 933, + "reversalPct": 35.42141230068337, + "speedMean": 4.654422770505723, + "byReason": { + "expiry": 55, + "init": 8, + "danger": 18, + "tile_self": 2553 + }, + "note": "id 2==ModularBot, id 1==DrussGT; of 1806 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 47.0% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 5, + "rounds": 7, + "wins": 3, + "winsByDeath": 3, + "perRoundWins": [ + 1, + 0, + 0, + 1, + 0, + 1, + 0 + ], + "score": 477, + "enemyScore": 404, + "ticks": 13483, + "damageDealt": 267.5068, + "damageTaken": 147.59999999999997, + "shots": 849, + "enemyShots": 1029, + "hits": 82, + "hitsTaken": 92, + "commitCalls": 13381, + "picks": 2641, + "intervalMean": 5.054524801211662, + "revPicks": 924, + "reversalPct": 34.98674744414994, + "speedMean": 4.7057488412032455, + "byReason": { + "expiry": 59, + "init": 9, + "danger": 12, + "tile_self": 2561 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1878 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.2% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 6, + "rounds": 7, + "wins": 4, + "winsByDeath": 4, + "perRoundWins": [ + 0, + 1, + 1, + 0, + 1, + 1, + 0 + ], + "score": 546, + "enemyScore": 327, + "ticks": 13895, + "damageDealt": 271.0661, + "damageTaken": 138.7999999999998, + "shots": 885, + "enemyShots": 1062, + "hits": 84, + "hitsTaken": 88, + "commitCalls": 13868, + "picks": 2721, + "intervalMean": 5.085262771040059, + "revPicks": 924, + "reversalPct": 33.95810363836825, + "speedMean": 4.69577788646468, + "byReason": { + "expiry": 70, + "init": 7, + "danger": 11, + "tile_self": 2633 + }, + "note": "id 2==ModularBot, id 1==DrussGT; of 1947 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.5% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 7, + "rounds": 7, + "wins": 4, + "winsByDeath": 4, + "perRoundWins": [ + 1, + 1, + 0, + 0, + 1, + 0, + 1 + ], + "score": 559, + "enemyScore": 418, + "ticks": 14461, + "damageDealt": 332.47940000000006, + "damageTaken": 206.29999999999976, + "shots": 912, + "enemyShots": 1072, + "hits": 105, + "hitsTaken": 112, + "commitCalls": 14394, + "picks": 2861, + "intervalMean": 5.022719328905977, + "revPicks": 996, + "reversalPct": 34.813002446696956, + "speedMean": 4.643631643995206, + "byReason": { + "expiry": 77, + "init": 8, + "danger": 12, + "tile_self": 2764 + }, + "note": "id 2==ModularBot, id 1==DrussGT; of 1984 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.0% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 8, + "rounds": 7, + "wins": 4, + "winsByDeath": 4, + "perRoundWins": [ + 1, + 1, + 0, + 0, + 1, + 0, + 1 + ], + "score": 575, + "enemyScore": 397, + "ticks": 13421, + "damageDealt": 296.96969999999993, + "damageTaken": 197.99999999999991, + "shots": 851, + "enemyShots": 1026, + "hits": 91, + "hitsTaken": 101, + "commitCalls": 13298, + "picks": 2605, + "intervalMean": 5.094817658349328, + "revPicks": 903, + "reversalPct": 34.6641074856046, + "speedMean": 4.632511941557236, + "byReason": { + "expiry": 80, + "init": 10, + "danger": 8, + "tile_self": 2507 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1877 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.3% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 9, + "rounds": 7, + "wins": 4, + "winsByDeath": 4, + "perRoundWins": [ + 1, + 0, + 0, + 1, + 1, + 0, + 1 + ], + "score": 584, + "enemyScore": 406, + "ticks": 14053, + "damageDealt": 305.65790000000004, + "damageTaken": 207.3999999999999, + "shots": 870, + "enemyShots": 1048, + "hits": 96, + "hitsTaken": 96, + "commitCalls": 14017, + "picks": 2778, + "intervalMean": 5.037437005039597, + "revPicks": 963, + "reversalPct": 34.66522678185745, + "speedMean": 4.692695860976014, + "byReason": { + "expiry": 67, + "init": 7, + "danger": 9, + "tile_self": 2695 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1918 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.4% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 10, + "rounds": 7, + "wins": 4, + "winsByDeath": 4, + "perRoundWins": [ + 0, + 1, + 0, + 1, + 1, + 0, + 1 + ], + "score": 501, + "enemyScore": 396, + "ticks": 12967, + "damageDealt": 228.0605, + "damageTaken": 195.39999999999998, + "shots": 831, + "enemyShots": 965, + "hits": 75, + "hitsTaken": 89, + "commitCalls": 12928, + "picks": 2544, + "intervalMean": 5.06996855345912, + "revPicks": 845, + "reversalPct": 33.215408805031444, + "speedMean": 4.711520393296044, + "byReason": { + "expiry": 67, + "init": 7, + "danger": 16, + "tile_self": 2454 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1796 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.3% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 11, + "rounds": 7, + "wins": 1, + "winsByDeath": 1, + "perRoundWins": [ + 0, + 0, + 0, + 1, + 0, + 0, + 0 + ], + "score": 348, + "enemyScore": 575, + "ticks": 13963, + "damageDealt": 276.62909999999994, + "damageTaken": 184.29999999999998, + "shots": 865, + "enemyShots": 1068, + "hits": 84, + "hitsTaken": 100, + "commitCalls": 13921, + "picks": 2709, + "intervalMean": 5.127353266888151, + "revPicks": 887, + "reversalPct": 32.742709486895535, + "speedMean": 4.630089796477458, + "byReason": { + "expiry": 83, + "init": 7, + "danger": 21, + "tile_self": 2598 + }, + "note": "id 2==ModularBot, id 1==DrussGT; of 1933 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 44.7% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 12, + "rounds": 7, + "wins": 2, + "winsByDeath": 2, + "perRoundWins": [ + 1, + 1, + 0, + 0, + 0, + 0, + 0 + ], + "score": 375, + "enemyScore": 583, + "ticks": 11993, + "damageDealt": 238.64890000000003, + "damageTaken": 245.00000000000026, + "shots": 751, + "enemyShots": 898, + "hits": 77, + "hitsTaken": 101, + "commitCalls": 11948, + "picks": 2339, + "intervalMean": 5.09833262077811, + "revPicks": 787, + "reversalPct": 33.64685763146644, + "speedMean": 4.626173228215006, + "byReason": { + "expiry": 66, + "init": 7, + "danger": 15, + "tile_self": 2251 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1649 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.5% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 13, + "rounds": 7, + "wins": 3, + "winsByDeath": 3, + "perRoundWins": [ + 0, + 0, + 0, + 0, + 1, + 1, + 1 + ], + "score": 458, + "enemyScore": 450, + "ticks": 12709, + "damageDealt": 249.0785, + "damageTaken": 189.19999999999996, + "shots": 796, + "enemyShots": 968, + "hits": 77, + "hitsTaken": 88, + "commitCalls": 12678, + "picks": 2509, + "intervalMean": 5.0438421681945, + "revPicks": 839, + "reversalPct": 33.43961737744121, + "speedMean": 4.718107508454606, + "byReason": { + "expiry": 58, + "init": 7, + "danger": 19, + "tile_self": 2425 + }, + "note": "id 2==ModularBot, id 1==DrussGT; of 1764 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.1% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 14, + "rounds": 7, + "wins": 2, + "winsByDeath": 2, + "perRoundWins": [ + 0, + 1, + 0, + 0, + 0, + 1, + 0 + ], + "score": 424, + "enemyScore": 550, + "ticks": 12720, + "damageDealt": 284.46999999999997, + "damageTaken": 219.10000000000016, + "shots": 795, + "enemyShots": 954, + "hits": 85, + "hitsTaken": 106, + "commitCalls": 12685, + "picks": 2521, + "intervalMean": 5.019833399444665, + "revPicks": 828, + "reversalPct": 32.844109480364935, + "speedMean": 4.7613957448319555, + "byReason": { + "expiry": 63, + "init": 7, + "danger": 11, + "tile_self": 2440 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1749 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.5% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + } + ], + "diag": { + "commitCalls": 183270, + "picks": 36190, + "intervalMean": 5.054407294832827, + "revPicks": 12253, + "reversalPct": 33.857419176568115, + "speedMean": 4.688019356576954, + "byReason": { + "expiry": 937, + "init": 111, + "danger": 188, + "tile_self": 34954 + }, + "revOffsets": [ + -5, + -4, + -3, + -2, + -1, + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10 + ], + "speedProfile": [ + 4.610135875933581, + 4.6058033856833775, + 4.63957461126316, + 4.689984126359364, + 4.770721713047585, + 4.837882615163124, + 4.1879212382257744, + 3.9533637844786353, + 4.052639374402105, + 4.191812803845138, + 4.324674520036198, + 4.4533417032139955, + 4.543818835173033, + 4.6453725051069705, + 4.749846798765893, + 4.831378557791118 + ], + "speedProfileN": [ + 12201, + 12205, + 12206, + 12207, + 12207, + 12253, + 12252, + 12250, + 12250, + 12247, + 12246, + 12246, + 12243, + 12242, + 12240, + 12240 + ] + } + }, + "B": { + "label": "B honour commitment", + "env": "TR_TFIL_TILE_REPLAN=off", + "runs": [ + { + "run": 1, + "rounds": 7, + "wins": 2, + "winsByDeath": 2, + "perRoundWins": [ + 0, + 1, + 0, + 1, + 0, + 0, + 0 + ], + "score": 410, + "enemyScore": 480, + "ticks": 13267, + "damageDealt": 270.0858, + "damageTaken": 155.89999999999995, + "shots": 836, + "enemyShots": 993, + "hits": 80, + "hitsTaken": 90, + "commitCalls": 13234, + "picks": 890, + "intervalMean": 14.828089887640449, + "revPicks": 460, + "reversalPct": 51.68539325842696, + "speedMean": 5.363202735378571, + "byReason": { + "expiry": 847, + "init": 7, + "danger": 36 + }, + "note": "id 2==ModularBot, id 1==DrussGT; of 1829 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.7% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 2, + "rounds": 7, + "wins": 1, + "winsByDeath": 1, + "perRoundWins": [ + 0, + 0, + 0, + 0, + 1, + 0, + 0 + ], + "score": 333, + "enemyScore": 631, + "ticks": 13861, + "damageDealt": 263.8279, + "damageTaken": 234.60000000000014, + "shots": 853, + "enemyShots": 1040, + "hits": 89, + "hitsTaken": 111, + "commitCalls": 13837, + "picks": 931, + "intervalMean": 14.793770139634802, + "revPicks": 484, + "reversalPct": 51.98711063372718, + "speedMean": 5.356598251065982, + "byReason": { + "expiry": 874, + "init": 7, + "danger": 50 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1893 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.1% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 3, + "rounds": 7, + "wins": 5, + "winsByDeath": 5, + "perRoundWins": [ + 1, + 0, + 1, + 0, + 1, + 1, + 1 + ], + "score": 624, + "enemyScore": 321, + "ticks": 11326, + "damageDealt": 280.0976, + "damageTaken": 189.0, + "shots": 721, + "enemyShots": 853, + "hits": 81, + "hitsTaken": 71, + "commitCalls": 11210, + "picks": 757, + "intervalMean": 14.73447820343461, + "revPicks": 393, + "reversalPct": 51.915455746367236, + "speedMean": 5.354025423728814, + "byReason": { + "expiry": 717, + "init": 7, + "danger": 33 + }, + "note": "id 2==ModularBot, id 1==DrussGT; of 1574 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.8% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 4, + "rounds": 7, + "wins": 6, + "winsByDeath": 6, + "perRoundWins": [ + 1, + 1, + 1, + 1, + 1, + 1, + 0 + ], + "score": 707, + "enemyScore": 206, + "ticks": 13196, + "damageDealt": 307.40080000000006, + "damageTaken": 139.69999999999987, + "shots": 821, + "enemyShots": 966, + "hits": 90, + "hitsTaken": 75, + "commitCalls": 13136, + "picks": 885, + "intervalMean": 14.780790960451977, + "revPicks": 469, + "reversalPct": 52.994350282485875, + "speedMean": 5.3386637865408035, + "byReason": { + "expiry": 834, + "init": 7, + "danger": 44 + }, + "note": "id 2==ModularBot, id 1==DrussGT; of 1787 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.9% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 5, + "rounds": 7, + "wins": 2, + "winsByDeath": 2, + "perRoundWins": [ + 0, + 0, + 1, + 0, + 0, + 0, + 1 + ], + "score": 388, + "enemyScore": 594, + "ticks": 11626, + "damageDealt": 249.2447, + "damageTaken": 266.2000000000004, + "shots": 738, + "enemyShots": 868, + "hits": 78, + "hitsTaken": 99, + "commitCalls": 11531, + "picks": 778, + "intervalMean": 14.730077120822623, + "revPicks": 386, + "reversalPct": 49.61439588688946, + "speedMean": 5.411602419564652, + "byReason": { + "expiry": 728, + "init": 10, + "danger": 40 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1606 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.0% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 6, + "rounds": 7, + "wins": 4, + "winsByDeath": 4, + "perRoundWins": [ + 1, + 0, + 1, + 1, + 0, + 0, + 1 + ], + "score": 550, + "enemyScore": 360, + "ticks": 12712, + "damageDealt": 275.9048999999999, + "damageTaken": 161.29999999999995, + "shots": 812, + "enemyShots": 947, + "hits": 87, + "hitsTaken": 87, + "commitCalls": 12681, + "picks": 856, + "intervalMean": 14.759345794392523, + "revPicks": 428, + "reversalPct": 50.0, + "speedMean": 5.4190644468101885, + "byReason": { + "expiry": 811, + "init": 7, + "danger": 38 + }, + "note": "id 2==ModularBot, id 1==DrussGT; of 1759 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.2% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 7, + "rounds": 7, + "wins": 3, + "winsByDeath": 3, + "perRoundWins": [ + 1, + 0, + 0, + 1, + 1, + 0, + 0 + ], + "score": 511, + "enemyScore": 477, + "ticks": 12428, + "damageDealt": 320.8059999999999, + "damageTaken": 216.10000000000002, + "shots": 778, + "enemyShots": 901, + "hits": 89, + "hitsTaken": 97, + "commitCalls": 12234, + "picks": 823, + "intervalMean": 14.804374240583233, + "revPicks": 427, + "reversalPct": 51.88335358444714, + "speedMean": 5.3731812980219065, + "byReason": { + "expiry": 788, + "init": 10, + "danger": 25 + }, + "note": "id 2==ModularBot, id 1==DrussGT; of 1679 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.3% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 8, + "rounds": 7, + "wins": 2, + "winsByDeath": 2, + "perRoundWins": [ + 1, + 1, + 0, + 0, + 0, + 0, + 0 + ], + "score": 376, + "enemyScore": 519, + "ticks": 11868, + "damageDealt": 251.61679999999998, + "damageTaken": 193.80000000000007, + "shots": 736, + "enemyShots": 889, + "hits": 69, + "hitsTaken": 81, + "commitCalls": 11787, + "picks": 799, + "intervalMean": 14.689612015018774, + "revPicks": 436, + "reversalPct": 54.568210262828536, + "speedMean": 5.3319228811402395, + "byReason": { + "expiry": 746, + "init": 7, + "danger": 46 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1625 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.3% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 9, + "rounds": 7, + "wins": 4, + "winsByDeath": 4, + "perRoundWins": [ + 0, + 1, + 0, + 1, + 1, + 1, + 0 + ], + "score": 565, + "enemyScore": 356, + "ticks": 12973, + "damageDealt": 292.65020000000004, + "damageTaken": 159.49999999999983, + "shots": 834, + "enemyShots": 975, + "hits": 90, + "hitsTaken": 71, + "commitCalls": 12943, + "picks": 871, + "intervalMean": 14.786452353616532, + "revPicks": 442, + "reversalPct": 50.74626865671642, + "speedMean": 5.370436722552731, + "byReason": { + "expiry": 814, + "init": 7, + "danger": 50 + }, + "note": "id 2==ModularBot, id 1==DrussGT; of 1809 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.1% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 10, + "rounds": 7, + "wins": 3, + "winsByDeath": 3, + "perRoundWins": [ + 0, + 1, + 1, + 0, + 0, + 0, + 1 + ], + "score": 367, + "enemyScore": 456, + "ticks": 12792, + "damageDealt": 218.17440000000002, + "damageTaken": 172.30000000000004, + "shots": 825, + "enemyShots": 936, + "hits": 72, + "hitsTaken": 90, + "commitCalls": 12698, + "picks": 855, + "intervalMean": 14.783625730994151, + "revPicks": 420, + "reversalPct": 49.12280701754386, + "speedMean": 5.417797094030556, + "byReason": { + "expiry": 822, + "init": 8, + "danger": 25 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1761 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.8% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 11, + "rounds": 7, + "wins": 2, + "winsByDeath": 2, + "perRoundWins": [ + 0, + 0, + 1, + 0, + 1, + 0, + 0 + ], + "score": 392, + "enemyScore": 563, + "ticks": 12801, + "damageDealt": 254.59890000000001, + "damageTaken": 235.0000000000002, + "shots": 808, + "enemyShots": 944, + "hits": 76, + "hitsTaken": 90, + "commitCalls": 12731, + "picks": 862, + "intervalMean": 14.70997679814385, + "revPicks": 429, + "reversalPct": 49.76798143851508, + "speedMean": 5.386070418663106, + "byReason": { + "expiry": 812, + "init": 7, + "danger": 43 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1752 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.1% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 12, + "rounds": 7, + "wins": 3, + "winsByDeath": 3, + "perRoundWins": [ + 1, + 1, + 1, + 0, + 0, + 0, + 0 + ], + "score": 481, + "enemyScore": 437, + "ticks": 13974, + "damageDealt": 275.8249, + "damageTaken": 172.79999999999995, + "shots": 880, + "enemyShots": 1048, + "hits": 83, + "hitsTaken": 92, + "commitCalls": 13941, + "picks": 937, + "intervalMean": 14.823906083244397, + "revPicks": 453, + "reversalPct": 48.34578441835646, + "speedMean": 5.289828563230758, + "byReason": { + "expiry": 879, + "init": 7, + "danger": 51 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1928 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.6% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 13, + "rounds": 7, + "wins": 2, + "winsByDeath": 2, + "perRoundWins": [ + 1, + 0, + 0, + 0, + 0, + 1, + 0 + ], + "score": 419, + "enemyScore": 502, + "ticks": 13750, + "damageDealt": 290.24940000000004, + "damageTaken": 177.49999999999977, + "shots": 861, + "enemyShots": 1037, + "hits": 84, + "hitsTaken": 104, + "commitCalls": 13666, + "picks": 923, + "intervalMean": 14.751895991332612, + "revPicks": 458, + "reversalPct": 49.62080173347779, + "speedMean": 5.3770488804331915, + "byReason": { + "expiry": 855, + "init": 8, + "danger": 60 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1898 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.4% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 14, + "rounds": 7, + "wins": 4, + "winsByDeath": 4, + "perRoundWins": [ + 1, + 1, + 1, + 0, + 0, + 1, + 0 + ], + "score": 552, + "enemyScore": 419, + "ticks": 12840, + "damageDealt": 286.4832, + "damageTaken": 216.60000000000008, + "shots": 839, + "enemyShots": 921, + "hits": 91, + "hitsTaken": 88, + "commitCalls": 12810, + "picks": 862, + "intervalMean": 14.7830626450116, + "revPicks": 440, + "reversalPct": 51.04408352668214, + "speedMean": 5.434118852459016, + "byReason": { + "expiry": 813, + "init": 7, + "danger": 42 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1760 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 47.7% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + } + ], + "diag": { + "commitCalls": 178439, + "picks": 12029, + "intervalMean": 14.769972566298113, + "revPicks": 6125, + "reversalPct": 50.91861335106825, + "speedMean": 5.372483208547459, + "byReason": { + "expiry": 11340, + "init": 106, + "danger": 583 + }, + "revOffsets": [ + -5, + -4, + -3, + -2, + -1, + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10 + ], + "speedProfile": [ + 5.962275609354414, + 5.948647480237154, + 5.851402956192358, + 5.7318943511198945, + 5.5523535284914365, + 5.279244897959184, + 4.316545918367347, + 3.9144923469387756, + 4.100395408163266, + 4.492704250367467, + 4.897443552996897, + 5.236094949371223, + 5.531484770537318, + 5.759812530632249, + 5.91678749387355, + 6.014869404509067 + ], + "speedProfileN": [ + 6072, + 6072, + 6072, + 6072, + 6072, + 6125, + 6125, + 6125, + 6125, + 6123, + 6123, + 6123, + 6123, + 6121, + 6121, + 6121 + ] + } + }, + "C": { + "label": "C B + no-reversal", + "env": "TR_TFIL_TILE_REPLAN=off TR_TFIL_NO_REV=1", + "runs": [ + { + "run": 1, + "rounds": 7, + "wins": 3, + "winsByDeath": 3, + "perRoundWins": [ + 1, + 0, + 1, + 0, + 0, + 1, + 0 + ], + "score": 446, + "enemyScore": 467, + "ticks": 13019, + "damageDealt": 240.30640000000002, + "damageTaken": 200.89999999999995, + "shots": 787, + "enemyShots": 981, + "hits": 76, + "hitsTaken": 89, + "commitCalls": 12989, + "picks": 875, + "intervalMean": 14.772571428571428, + "revPicks": 362, + "reversalPct": 41.371428571428574, + "speedMean": 5.530025406112864, + "byReason": { + "expiry": 830, + "init": 8, + "danger": 37 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1768 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 44.5% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 2, + "rounds": 7, + "wins": 3, + "winsByDeath": 3, + "perRoundWins": [ + 1, + 0, + 0, + 0, + 1, + 0, + 1 + ], + "score": 500, + "enemyScore": 398, + "ticks": 13598, + "damageDealt": 298.5099, + "damageTaken": 137.9999999999998, + "shots": 830, + "enemyShots": 1039, + "hits": 80, + "hitsTaken": 86, + "commitCalls": 13482, + "picks": 905, + "intervalMean": 14.833149171270719, + "revPicks": 368, + "reversalPct": 40.66298342541437, + "speedMean": 5.369121792018988, + "byReason": { + "expiry": 858, + "init": 7, + "danger": 40 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1869 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 44.4% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 3, + "rounds": 7, + "wins": 2, + "winsByDeath": 2, + "perRoundWins": [ + 1, + 0, + 0, + 1, + 0, + 0, + 0 + ], + "score": 379, + "enemyScore": 545, + "ticks": 12935, + "damageDealt": 244.42980000000006, + "damageTaken": 210.29999999999998, + "shots": 803, + "enemyShots": 975, + "hits": 76, + "hitsTaken": 74, + "commitCalls": 12854, + "picks": 863, + "intervalMean": 14.797219003476245, + "revPicks": 396, + "reversalPct": 45.8864426419467, + "speedMean": 5.433398650225611, + "byReason": { + "expiry": 820, + "init": 9, + "danger": 34 + }, + "note": "id 2==ModularBot, id 1==DrussGT; of 1778 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.2% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 4, + "rounds": 7, + "wins": 3, + "winsByDeath": 3, + "perRoundWins": [ + 0, + 1, + 1, + 0, + 1, + 0, + 0 + ], + "score": 434, + "enemyScore": 501, + "ticks": 12292, + "damageDealt": 235.60140000000004, + "damageTaken": 244.4, + "shots": 789, + "enemyShots": 882, + "hits": 75, + "hitsTaken": 80, + "commitCalls": 12238, + "picks": 823, + "intervalMean": 14.80801944106926, + "revPicks": 354, + "reversalPct": 43.01336573511543, + "speedMean": 5.438460124203301, + "byReason": { + "expiry": 786, + "init": 8, + "danger": 29 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1671 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 47.2% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 5, + "rounds": 7, + "wins": 3, + "winsByDeath": 3, + "perRoundWins": [ + 1, + 0, + 1, + 1, + 0, + 0, + 0 + ], + "score": 466, + "enemyScore": 458, + "ticks": 13448, + "damageDealt": 268.72209999999995, + "damageTaken": 193.20000000000005, + "shots": 839, + "enemyShots": 992, + "hits": 85, + "hitsTaken": 93, + "commitCalls": 13415, + "picks": 902, + "intervalMean": 14.813747228381375, + "revPicks": 383, + "reversalPct": 42.46119733924612, + "speedMean": 5.333831065970928, + "byReason": { + "expiry": 851, + "init": 7, + "danger": 44 + }, + "note": "id 2==ModularBot, id 1==DrussGT; of 1831 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.8% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 6, + "rounds": 7, + "wins": 3, + "winsByDeath": 3, + "perRoundWins": [ + 1, + 0, + 0, + 0, + 0, + 1, + 1 + ], + "score": 470, + "enemyScore": 440, + "ticks": 13385, + "damageDealt": 263.44499999999994, + "damageTaken": 176.99999999999994, + "shots": 828, + "enemyShots": 995, + "hits": 82, + "hitsTaken": 82, + "commitCalls": 13349, + "picks": 901, + "intervalMean": 14.755826859045506, + "revPicks": 390, + "reversalPct": 43.28523862375139, + "speedMean": 5.389551651809124, + "byReason": { + "expiry": 846, + "init": 7, + "danger": 48 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1823 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.4% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 7, + "rounds": 7, + "wins": 0, + "winsByDeath": 0, + "perRoundWins": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "score": 205, + "enemyScore": 676, + "ticks": 13037, + "damageDealt": 202.54700000000003, + "damageTaken": 214.70000000000013, + "shots": 790, + "enemyShots": 986, + "hits": 61, + "hitsTaken": 96, + "commitCalls": 13004, + "picks": 873, + "intervalMean": 14.830469644902635, + "revPicks": 350, + "reversalPct": 40.09163802978236, + "speedMean": 5.667121654875423, + "byReason": { + "expiry": 827, + "init": 7, + "danger": 39 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1776 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 44.5% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 8, + "rounds": 7, + "wins": 3, + "winsByDeath": 3, + "perRoundWins": [ + 0, + 0, + 0, + 1, + 0, + 1, + 1 + ], + "score": 495, + "enemyScore": 458, + "ticks": 12233, + "damageDealt": 289.3807999999999, + "damageTaken": 208.89999999999998, + "shots": 756, + "enemyShots": 920, + "hits": 84, + "hitsTaken": 88, + "commitCalls": 12081, + "picks": 818, + "intervalMean": 14.729828850855746, + "revPicks": 356, + "reversalPct": 43.520782396088016, + "speedMean": 5.345925743419419, + "byReason": { + "expiry": 763, + "init": 15, + "danger": 40 + }, + "note": "id 2==ModularBot, id 1==DrussGT; of 1676 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.1% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 9, + "rounds": 7, + "wins": 2, + "winsByDeath": 2, + "perRoundWins": [ + 1, + 0, + 0, + 0, + 0, + 0, + 1 + ], + "score": 388, + "enemyScore": 522, + "ticks": 13027, + "damageDealt": 249.72590000000002, + "damageTaken": 192.79999999999995, + "shots": 809, + "enemyShots": 968, + "hits": 79, + "hitsTaken": 90, + "commitCalls": 12938, + "picks": 872, + "intervalMean": 14.783256880733944, + "revPicks": 381, + "reversalPct": 43.69266055045872, + "speedMean": 5.473972020405008, + "byReason": { + "expiry": 823, + "init": 8, + "danger": 41 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1777 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.5% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 10, + "rounds": 7, + "wins": 2, + "winsByDeath": 2, + "perRoundWins": [ + 0, + 0, + 1, + 1, + 0, + 0, + 0 + ], + "score": 394, + "enemyScore": 539, + "ticks": 13551, + "damageDealt": 259.55190000000005, + "damageTaken": 220.60000000000008, + "shots": 823, + "enemyShots": 1023, + "hits": 84, + "hitsTaken": 86, + "commitCalls": 13517, + "picks": 912, + "intervalMean": 14.785087719298245, + "revPicks": 381, + "reversalPct": 41.776315789473685, + "speedMean": 5.397799992601909, + "byReason": { + "expiry": 859, + "init": 7, + "danger": 46 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1846 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 44.6% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 11, + "rounds": 7, + "wins": 3, + "winsByDeath": 3, + "perRoundWins": [ + 1, + 1, + 0, + 0, + 0, + 0, + 1 + ], + "score": 471, + "enemyScore": 457, + "ticks": 13419, + "damageDealt": 274.71289999999993, + "damageTaken": 189.6, + "shots": 842, + "enemyShots": 977, + "hits": 85, + "hitsTaken": 86, + "commitCalls": 13383, + "picks": 901, + "intervalMean": 14.800221975582685, + "revPicks": 370, + "reversalPct": 41.06548279689234, + "speedMean": 5.6187000298886645, + "byReason": { + "expiry": 854, + "init": 7, + "danger": 40 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1819 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.3% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 12, + "rounds": 7, + "wins": 3, + "winsByDeath": 3, + "perRoundWins": [ + 1, + 1, + 1, + 0, + 0, + 0, + 0 + ], + "score": 504, + "enemyScore": 503, + "ticks": 12005, + "damageDealt": 293.6436, + "damageTaken": 231.8000000000001, + "shots": 757, + "enemyShots": 885, + "hits": 84, + "hitsTaken": 98, + "commitCalls": 11979, + "picks": 805, + "intervalMean": 14.809937888198757, + "revPicks": 341, + "reversalPct": 42.36024844720497, + "speedMean": 5.398932506887053, + "byReason": { + "expiry": 762, + "init": 7, + "danger": 36 + }, + "note": "id 2==ModularBot, id 1==DrussGT; of 1642 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.1% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 13, + "rounds": 7, + "wins": 3, + "winsByDeath": 3, + "perRoundWins": [ + 0, + 1, + 0, + 1, + 1, + 0, + 0 + ], + "score": 494, + "enemyScore": 481, + "ticks": 12897, + "damageDealt": 288.6156, + "damageTaken": 225.00000000000003, + "shots": 829, + "enemyShots": 956, + "hits": 91, + "hitsTaken": 94, + "commitCalls": 12839, + "picks": 863, + "intervalMean": 14.805330243337195, + "revPicks": 369, + "reversalPct": 42.75782155272306, + "speedMean": 5.647324557987382, + "byReason": { + "expiry": 824, + "init": 8, + "danger": 31 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1785 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.4% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 14, + "rounds": 7, + "wins": 1, + "winsByDeath": 1, + "perRoundWins": [ + 0, + 1, + 0, + 0, + 0, + 0, + 0 + ], + "score": 261, + "enemyScore": 573, + "ticks": 12955, + "damageDealt": 193.56960000000004, + "damageTaken": 183.10000000000002, + "shots": 794, + "enemyShots": 978, + "hits": 61, + "hitsTaken": 79, + "commitCalls": 12919, + "picks": 871, + "intervalMean": 14.770378874856487, + "revPicks": 368, + "reversalPct": 42.25028702640643, + "speedMean": 5.558430473043579, + "byReason": { + "expiry": 830, + "init": 7, + "danger": 34 + }, + "note": "id 2==ModularBot, id 1==DrussGT; of 1772 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 44.8% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + } + ], + "diag": { + "commitCalls": 180987, + "picks": 12184, + "intervalMean": 14.792678923177938, + "revPicks": 5169, + "reversalPct": 42.42449113591596, + "speedMean": 5.471990693737672, + "byReason": { + "expiry": 11533, + "init": 112, + "danger": 539 + }, + "revOffsets": [ + -5, + -4, + -3, + -2, + -1, + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10 + ], + "speedProfile": [ + 6.067783492589704, + 6.044175360764431, + 5.97363128900156, + 5.836638309282371, + 5.66185647425897, + 5.319290784000774, + 4.377675958841168, + 3.9480140646167987, + 4.085029544464874, + 4.459530584720341, + 4.809419301577318, + 5.206038430361982, + 5.591512130929332, + 5.941484904404646, + 6.107379144482091, + 6.211727600334044 + ], + "speedProfileN": [ + 5128, + 5128, + 5128, + 5128, + 5128, + 5169, + 5169, + 5167, + 5167, + 5167, + 5167, + 5166, + 5165, + 5165, + 5165, + 5164 + ] + } + }, + "D": { + "label": "D enemy-keyed cancel", + "env": "TR_TFIL_TILE_REPLAN=enemy", + "runs": [ + { + "run": 1, + "rounds": 7, + "wins": 2, + "winsByDeath": 2, + "perRoundWins": [ + 1, + 0, + 1, + 0, + 0, + 0, + 0 + ], + "score": 386, + "enemyScore": 544, + "ticks": 12775, + "damageDealt": 247.4326, + "damageTaken": 219.89999999999998, + "shots": 774, + "enemyShots": 965, + "hits": 79, + "hitsTaken": 94, + "commitCalls": 12715, + "picks": 2423, + "intervalMean": 5.231943871234008, + "revPicks": 891, + "reversalPct": 36.77259595542716, + "speedMean": 4.900095697748722, + "byReason": { + "expiry": 79, + "tile_enemy": 2323, + "init": 8, + "danger": 13 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1739 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 44.5% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 2, + "rounds": 7, + "wins": 5, + "winsByDeath": 5, + "perRoundWins": [ + 1, + 1, + 0, + 1, + 1, + 0, + 1 + ], + "score": 624, + "enemyScore": 350, + "ticks": 13863, + "damageDealt": 332.7028, + "damageTaken": 202.49999999999991, + "shots": 864, + "enemyShots": 1004, + "hits": 100, + "hitsTaken": 104, + "commitCalls": 13731, + "picks": 2516, + "intervalMean": 5.4443561208267095, + "revPicks": 962, + "reversalPct": 38.23529411764706, + "speedMean": 4.989149455206, + "byReason": { + "expiry": 111, + "tile_enemy": 2380, + "init": 8, + "danger": 17 + }, + "note": "id 2==ModularBot, id 1==DrussGT; of 1868 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.3% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 3, + "rounds": 7, + "wins": 4, + "winsByDeath": 4, + "perRoundWins": [ + 0, + 1, + 0, + 1, + 0, + 1, + 1 + ], + "score": 488, + "enemyScore": 422, + "ticks": 12557, + "damageDealt": 264.0757, + "damageTaken": 201.29999999999998, + "shots": 765, + "enemyShots": 915, + "hits": 81, + "hitsTaken": 78, + "commitCalls": 12456, + "picks": 2349, + "intervalMean": 5.284376330353342, + "revPicks": 863, + "reversalPct": 36.73903788846317, + "speedMean": 5.1301981981702545, + "byReason": { + "expiry": 78, + "tile_enemy": 2243, + "init": 9, + "danger": 19 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1680 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.5% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 4, + "rounds": 7, + "wins": 1, + "winsByDeath": 1, + "perRoundWins": [ + 0, + 0, + 0, + 0, + 0, + 1, + 0 + ], + "score": 332, + "enemyScore": 612, + "ticks": 12857, + "damageDealt": 276.7819, + "damageTaken": 215.00000000000009, + "shots": 806, + "enemyShots": 964, + "hits": 78, + "hitsTaken": 97, + "commitCalls": 12769, + "picks": 2440, + "intervalMean": 5.2225409836065575, + "revPicks": 894, + "reversalPct": 36.63934426229508, + "speedMean": 4.9553466911593755, + "byReason": { + "expiry": 67, + "tile_enemy": 2358, + "init": 7, + "danger": 8 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1770 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.5% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 5, + "rounds": 7, + "wins": 3, + "winsByDeath": 3, + "perRoundWins": [ + 1, + 1, + 0, + 1, + 0, + 0, + 0 + ], + "score": 552, + "enemyScore": 536, + "ticks": 12453, + "damageDealt": 339.1795, + "damageTaken": 259.4000000000002, + "shots": 770, + "enemyShots": 925, + "hits": 96, + "hitsTaken": 104, + "commitCalls": 12419, + "picks": 2306, + "intervalMean": 5.366001734605377, + "revPicks": 866, + "reversalPct": 37.5542064180399, + "speedMean": 4.990179280438412, + "byReason": { + "expiry": 70, + "tile_enemy": 2220, + "init": 7, + "danger": 9 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1695 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.4% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 6, + "rounds": 7, + "wins": 4, + "winsByDeath": 4, + "perRoundWins": [ + 0, + 0, + 1, + 1, + 1, + 0, + 1 + ], + "score": 601, + "enemyScore": 351, + "ticks": 12585, + "damageDealt": 321.0846, + "damageTaken": 161.69999999999982, + "shots": 809, + "enemyShots": 950, + "hits": 97, + "hitsTaken": 89, + "commitCalls": 12500, + "picks": 2372, + "intervalMean": 5.259274873524452, + "revPicks": 912, + "reversalPct": 38.44856661045531, + "speedMean": 4.940378458251953, + "byReason": { + "expiry": 78, + "tile_enemy": 2275, + "init": 8, + "danger": 11 + }, + "note": "id 2==ModularBot, id 1==DrussGT; of 1759 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.0% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 7, + "rounds": 7, + "wins": 5, + "winsByDeath": 5, + "perRoundWins": [ + 1, + 0, + 1, + 1, + 1, + 0, + 1 + ], + "score": 669, + "enemyScore": 360, + "ticks": 11942, + "damageDealt": 328.2956, + "damageTaken": 219.3999999999999, + "shots": 752, + "enemyShots": 903, + "hits": 91, + "hitsTaken": 87, + "commitCalls": 11802, + "picks": 2189, + "intervalMean": 5.380995888533577, + "revPicks": 811, + "reversalPct": 37.04888076747373, + "speedMean": 5.04773762451958, + "byReason": { + "expiry": 64, + "tile_enemy": 2102, + "init": 9, + "danger": 14 + }, + "note": "id 2==ModularBot, id 1==DrussGT; of 1655 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.4% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 8, + "rounds": 7, + "wins": 4, + "winsByDeath": 4, + "perRoundWins": [ + 1, + 1, + 0, + 0, + 1, + 0, + 1 + ], + "score": 526, + "enemyScore": 436, + "ticks": 11343, + "damageDealt": 250.60409999999996, + "damageTaken": 230.60000000000014, + "shots": 721, + "enemyShots": 850, + "hits": 75, + "hitsTaken": 90, + "commitCalls": 11214, + "picks": 2117, + "intervalMean": 5.276806802078413, + "revPicks": 800, + "reversalPct": 37.789324515824276, + "speedMean": 5.01776727261721, + "byReason": { + "expiry": 62, + "tile_enemy": 2035, + "init": 9, + "danger": 11 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1571 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.9% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 9, + "rounds": 7, + "wins": 3, + "winsByDeath": 3, + "perRoundWins": [ + 1, + 1, + 0, + 0, + 0, + 1, + 0 + ], + "score": 479, + "enemyScore": 451, + "ticks": 12759, + "damageDealt": 266.9368, + "damageTaken": 189.4, + "shots": 790, + "enemyShots": 966, + "hits": 77, + "hitsTaken": 91, + "commitCalls": 12724, + "picks": 2417, + "intervalMean": 5.250724038063716, + "revPicks": 971, + "reversalPct": 40.173769135291685, + "speedMean": 4.872967535053099, + "byReason": { + "expiry": 85, + "tile_enemy": 2319, + "init": 7, + "danger": 6 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1756 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.0% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 10, + "rounds": 7, + "wins": 4, + "winsByDeath": 4, + "perRoundWins": [ + 0, + 1, + 1, + 0, + 1, + 1, + 0 + ], + "score": 560, + "enemyScore": 402, + "ticks": 13223, + "damageDealt": 286.0314, + "damageTaken": 199.49999999999994, + "shots": 839, + "enemyShots": 979, + "hits": 95, + "hitsTaken": 90, + "commitCalls": 13184, + "picks": 2450, + "intervalMean": 5.366530612244898, + "revPicks": 921, + "reversalPct": 37.59183673469388, + "speedMean": 5.015681572330808, + "byReason": { + "expiry": 85, + "tile_enemy": 2340, + "init": 7, + "danger": 18 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1818 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.1% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 11, + "rounds": 7, + "wins": 3, + "winsByDeath": 3, + "perRoundWins": [ + 1, + 0, + 0, + 1, + 1, + 0, + 0 + ], + "score": 462, + "enemyScore": 466, + "ticks": 12946, + "damageDealt": 261.71709999999996, + "damageTaken": 200.4, + "shots": 817, + "enemyShots": 932, + "hits": 82, + "hitsTaken": 94, + "commitCalls": 12912, + "picks": 2399, + "intervalMean": 5.369737390579408, + "revPicks": 914, + "reversalPct": 38.099208003334724, + "speedMean": 4.9173583984375, + "byReason": { + "expiry": 103, + "tile_enemy": 2275, + "init": 7, + "danger": 14 + }, + "note": "id 2==ModularBot, id 1==DrussGT; of 1749 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.7% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 12, + "rounds": 7, + "wins": 1, + "winsByDeath": 1, + "perRoundWins": [ + 0, + 0, + 0, + 1, + 0, + 0, + 0 + ], + "score": 284, + "enemyScore": 695, + "ticks": 10073, + "damageDealt": 218.7596, + "damageTaken": 290.6000000000002, + "shots": 636, + "enemyShots": 745, + "hits": 60, + "hitsTaken": 76, + "commitCalls": 9885, + "picks": 1916, + "intervalMean": 5.146659707724426, + "revPicks": 733, + "reversalPct": 38.25678496868476, + "speedMean": 4.974565547466519, + "byReason": { + "expiry": 65, + "tile_enemy": 1837, + "init": 10, + "danger": 4 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1381 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.1% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 13, + "rounds": 7, + "wins": 3, + "winsByDeath": 3, + "perRoundWins": [ + 1, + 0, + 1, + 0, + 0, + 0, + 1 + ], + "score": 531, + "enemyScore": 486, + "ticks": 12430, + "damageDealt": 339.1008999999999, + "damageTaken": 218.29999999999993, + "shots": 789, + "enemyShots": 941, + "hits": 95, + "hitsTaken": 97, + "commitCalls": 12239, + "picks": 2350, + "intervalMean": 5.191914893617021, + "revPicks": 863, + "reversalPct": 36.723404255319146, + "speedMean": 5.051879947642718, + "byReason": { + "expiry": 71, + "tile_enemy": 2250, + "init": 13, + "danger": 16 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1730 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.6% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 14, + "rounds": 7, + "wins": 4, + "winsByDeath": 4, + "perRoundWins": [ + 1, + 1, + 1, + 0, + 1, + 0, + 0 + ], + "score": 554, + "enemyScore": 421, + "ticks": 13479, + "damageDealt": 279.86210000000005, + "damageTaken": 217.20000000000016, + "shots": 852, + "enemyShots": 968, + "hits": 87, + "hitsTaken": 104, + "commitCalls": 13406, + "picks": 2528, + "intervalMean": 5.2950949367088604, + "revPicks": 937, + "reversalPct": 37.06487341772152, + "speedMean": 4.982816959946223, + "byReason": { + "expiry": 102, + "tile_enemy": 2404, + "init": 8, + "danger": 14 + }, + "note": "id 2==ModularBot, id 1==DrussGT; of 1820 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.8% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + } + ], + "diag": { + "commitCalls": 173956, + "picks": 32772, + "intervalMean": 5.294092518003174, + "revPicks": 12338, + "reversalPct": 37.64799218845356, + "speedMean": 4.983928160919714, + "byReason": { + "expiry": 1120, + "tile_enemy": 31361, + "init": 117, + "danger": 174 + }, + "revOffsets": [ + -5, + -4, + -3, + -2, + -1, + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10 + ], + "speedProfile": [ + 5.06994899572545, + 5.0153923127914055, + 5.003637556573633, + 5.017113561377749, + 4.995572626872295, + 4.943279484640472, + 4.377971228681327, + 4.210878942417882, + 4.333889988145939, + 4.500265897943816, + 4.638479223820331, + 4.750728191535, + 4.8497967234883275, + 4.9786675238079665, + 5.063229150366868, + 5.1055499110795335 + ], + "speedProfileN": [ + 12270, + 12273, + 12274, + 12275, + 12278, + 12338, + 12338, + 12336, + 12336, + 12336, + 12336, + 12335, + 12335, + 12334, + 12332, + 12331 + ] + } + }, + "E": { + "label": "E commit 30 ticks", + "env": "TR_TFIL_TILE_REPLAN=off TR_TFIL_COMMIT_TICKS=30", + "runs": [ + { + "run": 1, + "rounds": 7, + "wins": 3, + "winsByDeath": 3, + "perRoundWins": [ + 1, + 1, + 0, + 0, + 1, + 0, + 0 + ], + "score": 513, + "enemyScore": 483, + "ticks": 13347, + "damageDealt": 316.5558, + "damageTaken": 215.19999999999996, + "shots": 847, + "enemyShots": 992, + "hits": 95, + "hitsTaken": 110, + "commitCalls": 13264, + "picks": 503, + "intervalMean": 26.18886679920477, + "revPicks": 297, + "reversalPct": 59.04572564612326, + "speedMean": 4.790494948733413, + "byReason": { + "expiry": 289, + "init": 8, + "danger": 206 + }, + "note": "id 2==ModularBot, id 1==DrussGT; of 1839 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.1% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 2, + "rounds": 7, + "wins": 3, + "winsByDeath": 3, + "perRoundWins": [ + 1, + 1, + 0, + 1, + 0, + 0, + 0 + ], + "score": 520, + "enemyScore": 468, + "ticks": 12588, + "damageDealt": 316.143, + "damageTaken": 205.99999999999997, + "shots": 776, + "enemyShots": 955, + "hits": 84, + "hitsTaken": 101, + "commitCalls": 12534, + "picks": 466, + "intervalMean": 26.723175965665234, + "revPicks": 261, + "reversalPct": 56.00858369098712, + "speedMean": 4.999483903781714, + "byReason": { + "expiry": 286, + "init": 9, + "danger": 171 + }, + "note": "id 2==ModularBot, id 1==DrussGT; of 1731 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 44.8% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 3, + "rounds": 7, + "wins": 3, + "winsByDeath": 3, + "perRoundWins": [ + 0, + 0, + 1, + 0, + 1, + 1, + 0 + ], + "score": 441, + "enemyScore": 498, + "ticks": 12580, + "damageDealt": 271.4629, + "damageTaken": 206.19999999999996, + "shots": 788, + "enemyShots": 939, + "hits": 80, + "hitsTaken": 104, + "commitCalls": 12439, + "picks": 471, + "intervalMean": 26.276008492569, + "revPicks": 277, + "reversalPct": 58.81104033970276, + "speedMean": 5.030599324704558, + "byReason": { + "expiry": 268, + "init": 10, + "danger": 193 + }, + "note": "id 2==ModularBot, id 1==DrussGT; of 1727 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.6% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 4, + "rounds": 7, + "wins": 5, + "winsByDeath": 5, + "perRoundWins": [ + 0, + 1, + 0, + 1, + 1, + 1, + 1 + ], + "score": 596, + "enemyScore": 352, + "ticks": 12233, + "damageDealt": 300.0652, + "damageTaken": 195.69999999999993, + "shots": 754, + "enemyShots": 893, + "hits": 82, + "hitsTaken": 87, + "commitCalls": 12204, + "picks": 450, + "intervalMean": 26.851111111111113, + "revPicks": 264, + "reversalPct": 58.666666666666664, + "speedMean": 5.114552605703048, + "byReason": { + "expiry": 271, + "init": 7, + "danger": 172 + }, + "note": "id 2==ModularBot, id 1==DrussGT; of 1647 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.8% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 5, + "rounds": 7, + "wins": 3, + "winsByDeath": 3, + "perRoundWins": [ + 1, + 0, + 0, + 1, + 1, + 0, + 0 + ], + "score": 465, + "enemyScore": 452, + "ticks": 13629, + "damageDealt": 256.015, + "damageTaken": 188.79999999999993, + "shots": 828, + "enemyShots": 1020, + "hits": 84, + "hitsTaken": 94, + "commitCalls": 13572, + "picks": 501, + "intervalMean": 26.944111776447105, + "revPicks": 273, + "reversalPct": 54.49101796407186, + "speedMean": 4.909777483053345, + "byReason": { + "expiry": 321, + "init": 8, + "danger": 172 + }, + "note": "id 2==ModularBot, id 1==DrussGT; of 1848 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 44.8% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 6, + "rounds": 7, + "wins": 3, + "winsByDeath": 3, + "perRoundWins": [ + 1, + 1, + 0, + 1, + 0, + 0, + 0 + ], + "score": 485, + "enemyScore": 457, + "ticks": 12302, + "damageDealt": 277.8316, + "damageTaken": 190.8, + "shots": 749, + "enemyShots": 908, + "hits": 82, + "hitsTaken": 90, + "commitCalls": 12269, + "picks": 460, + "intervalMean": 26.417391304347827, + "revPicks": 262, + "reversalPct": 56.95652173913044, + "speedMean": 4.772317426033092, + "byReason": { + "expiry": 265, + "init": 7, + "danger": 188 + }, + "note": "id 2==ModularBot, id 1==DrussGT; of 1657 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.2% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 7, + "rounds": 7, + "wins": 4, + "winsByDeath": 4, + "perRoundWins": [ + 1, + 0, + 1, + 1, + 1, + 0, + 0 + ], + "score": 536, + "enemyScore": 374, + "ticks": 12614, + "damageDealt": 263.14140000000003, + "damageTaken": 172.89999999999998, + "shots": 809, + "enemyShots": 951, + "hits": 86, + "hitsTaken": 87, + "commitCalls": 12582, + "picks": 476, + "intervalMean": 26.222689075630253, + "revPicks": 277, + "reversalPct": 58.19327731092437, + "speedMean": 4.8070855189953905, + "byReason": { + "expiry": 290, + "init": 7, + "danger": 179 + }, + "note": "id 2==ModularBot, id 1==DrussGT; of 1760 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.0% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 8, + "rounds": 7, + "wins": 2, + "winsByDeath": 2, + "perRoundWins": [ + 0, + 0, + 1, + 1, + 0, + 0, + 0 + ], + "score": 406, + "enemyScore": 532, + "ticks": 13384, + "damageDealt": 267.6244, + "damageTaken": 202.60000000000008, + "shots": 833, + "enemyShots": 1002, + "hits": 84, + "hitsTaken": 98, + "commitCalls": 13344, + "picks": 491, + "intervalMean": 27.04887983706721, + "revPicks": 273, + "reversalPct": 55.60081466395112, + "speedMean": 4.983304762440048, + "byReason": { + "expiry": 322, + "init": 8, + "danger": 161 + }, + "note": "id 2==ModularBot, id 1==DrussGT; of 1835 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.4% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 9, + "rounds": 7, + "wins": 1, + "winsByDeath": 1, + "perRoundWins": [ + 0, + 1, + 0, + 0, + 0, + 0, + 0 + ], + "score": 247, + "enemyScore": 608, + "ticks": 11998, + "damageDealt": 179.3466, + "damageTaken": 213.99999999999997, + "shots": 740, + "enemyShots": 896, + "hits": 56, + "hitsTaken": 81, + "commitCalls": 11964, + "picks": 463, + "intervalMean": 25.65658747300216, + "revPicks": 249, + "reversalPct": 53.779697624190064, + "speedMean": 4.800713664430792, + "byReason": { + "expiry": 273, + "init": 7, + "danger": 183 + }, + "note": "id 2==ModularBot, id 1==DrussGT; of 1636 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.2% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 10, + "rounds": 7, + "wins": 5, + "winsByDeath": 5, + "perRoundWins": [ + 1, + 1, + 1, + 1, + 1, + 0, + 0 + ], + "score": 689, + "enemyScore": 303, + "ticks": 13959, + "damageDealt": 345.38669999999996, + "damageTaken": 175.3999999999998, + "shots": 879, + "enemyShots": 1017, + "hits": 96, + "hitsTaken": 94, + "commitCalls": 13718, + "picks": 506, + "intervalMean": 26.962450592885375, + "revPicks": 280, + "reversalPct": 55.33596837944664, + "speedMean": 4.843585981921563, + "byReason": { + "expiry": 324, + "init": 13, + "danger": 169 + }, + "note": "id 2==ModularBot, id 1==DrussGT; of 1896 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.4% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 11, + "rounds": 7, + "wins": 0, + "winsByDeath": 0, + "perRoundWins": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "score": 175, + "enemyScore": 658, + "ticks": 13467, + "damageDealt": 174.7086, + "damageTaken": 200.49999999999997, + "shots": 793, + "enemyShots": 1007, + "hits": 56, + "hitsTaken": 89, + "commitCalls": 13429, + "picks": 496, + "intervalMean": 26.919354838709676, + "revPicks": 301, + "reversalPct": 60.685483870967744, + "speedMean": 4.855960049147368, + "byReason": { + "expiry": 314, + "init": 7, + "danger": 175 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1800 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 44.1% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 12, + "rounds": 7, + "wins": 2, + "winsByDeath": 2, + "perRoundWins": [ + 1, + 0, + 1, + 0, + 0, + 0, + 0 + ], + "score": 367, + "enemyScore": 556, + "ticks": 12571, + "damageDealt": 235.76240000000004, + "damageTaken": 222.90000000000015, + "shots": 756, + "enemyShots": 936, + "hits": 66, + "hitsTaken": 91, + "commitCalls": 12455, + "picks": 461, + "intervalMean": 26.80477223427332, + "revPicks": 267, + "reversalPct": 57.9175704989154, + "speedMean": 5.026565636290647, + "byReason": { + "expiry": 287, + "init": 8, + "danger": 166 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1692 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 44.7% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 13, + "rounds": 7, + "wins": 2, + "winsByDeath": 2, + "perRoundWins": [ + 1, + 1, + 0, + 0, + 0, + 0, + 0 + ], + "score": 364, + "enemyScore": 531, + "ticks": 12818, + "damageDealt": 226.18880000000001, + "damageTaken": 197.60000000000005, + "shots": 788, + "enemyShots": 944, + "hits": 71, + "hitsTaken": 92, + "commitCalls": 12785, + "picks": 474, + "intervalMean": 26.71518987341772, + "revPicks": 257, + "reversalPct": 54.219409282700425, + "speedMean": 4.7020727414939385, + "byReason": { + "expiry": 294, + "init": 7, + "danger": 173 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1732 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.5% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 14, + "rounds": 7, + "wins": 5, + "winsByDeath": 5, + "perRoundWins": [ + 0, + 1, + 1, + 0, + 1, + 1, + 1 + ], + "score": 624, + "enemyScore": 334, + "ticks": 11517, + "damageDealt": 283.8633, + "damageTaken": 196.9, + "shots": 726, + "enemyShots": 864, + "hits": 80, + "hitsTaken": 75, + "commitCalls": 11430, + "picks": 422, + "intervalMean": 26.850710900473935, + "revPicks": 245, + "reversalPct": 58.056872037914694, + "speedMean": 5.071659011373578, + "byReason": { + "expiry": 263, + "init": 7, + "danger": 152 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1590 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.7% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + } + ], + "diag": { + "commitCalls": 177989, + "picks": 6640, + "intervalMean": 26.615060240963857, + "revPicks": 3783, + "reversalPct": 56.97289156626506, + "speedMean": 4.905623315380445, + "byReason": { + "expiry": 4067, + "init": 113, + "danger": 2460 + }, + "revOffsets": [ + -5, + -4, + -3, + -2, + -1, + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10 + ], + "speedProfile": [ + 4.996844871451526, + 4.927331614890198, + 4.841490358864489, + 4.78185257096947, + 4.668678026245313, + 4.522890232619614, + 4.189036885245901, + 4.0762529754033325, + 4.216758132769109, + 4.416515141496959, + 4.577786044973545, + 4.772660383597883, + 4.975190145502646, + 5.176000330687831, + 5.318743384493252, + 5.396822902884361 + ], + "speedProfileN": [ + 3734, + 3734, + 3734, + 3734, + 3734, + 3783, + 3782, + 3781, + 3781, + 3781, + 3780, + 3780, + 3780, + 3780, + 3779, + 3779 + ] + } + } + } +} \ No newline at end of file diff --git a/common_libs/tests/fixtures/tfil_commit_ab_results_runs7.json b/common_libs/tests/fixtures/tfil_commit_ab_results_runs7.json new file mode 100644 index 0000000..20759f6 --- /dev/null +++ b/common_libs/tests/fixtures/tfil_commit_ab_results_runs7.json @@ -0,0 +1,1662 @@ +{ + "meta": { + "source": "19bf461", + "binarySha256": "46e7ce19cc7889dacc0c5add9b29928dadb757fbc6ecbdb7765cddd7813361c0", + "rawRoot": "/tmp/tfil_ab2", + "arms": [ + "", + "TR_TFIL_TILE_REPLAN=off", + "TR_TFIL_TILE_REPLAN=off TR_TFIL_NO_REV=1", + "TR_TFIL_TILE_REPLAN=enemy", + "TR_TFIL_TILE_REPLAN=off TR_TFIL_COMMIT_TICKS=30" + ], + "runsPerArm": 7, + "roundsPerRun": 7, + "note": "PRE-REGISTERED PRIMARY BLOCK: runs 1-7 per arm (35 battles, 245 rounds), 5 arms in parallel, 7 runs x 7 rounds each.", + "roundWinsSource": "final RESULTS firstPlaces, cross-checked with BotDeathEvent", + "damageSource": "BulletHitBotEvent damage, attributed by numeric owner/victim id (verified against the capture's own counters)" + }, + "arms": { + "A": { + "label": "A control (shipped)", + "env": "", + "runs": [ + { + "run": 1, + "rounds": 7, + "wins": 2, + "winsByDeath": 2, + "perRoundWins": [ + 0, + 1, + 0, + 0, + 0, + 1, + 0 + ], + "score": 387, + "enemyScore": 549, + "ticks": 11166, + "damageDealt": 245.36810000000003, + "damageTaken": 218.70000000000016, + "shots": 705, + "enemyShots": 831, + "hits": 71, + "hitsTaken": 96, + "commitCalls": 11107, + "picks": 2202, + "intervalMean": 5.036330608537693, + "revPicks": 764, + "reversalPct": 34.69573115349682, + "speedMean": 4.716623843601673, + "byReason": { + "expiry": 50, + "init": 8, + "danger": 10, + "tile_self": 2134 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1536 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.9% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 2, + "rounds": 7, + "wins": 1, + "winsByDeath": 1, + "perRoundWins": [ + 1, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "score": 324, + "enemyScore": 640, + "ticks": 13516, + "damageDealt": 256.57630000000006, + "damageTaken": 240.5000000000001, + "shots": 828, + "enemyShots": 1027, + "hits": 84, + "hitsTaken": 101, + "commitCalls": 13421, + "picks": 2650, + "intervalMean": 5.056603773584905, + "revPicks": 871, + "reversalPct": 32.867924528301884, + "speedMean": 4.705512235900274, + "byReason": { + "expiry": 74, + "init": 11, + "danger": 16, + "tile_self": 2549 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1855 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 44.6% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 3, + "rounds": 7, + "wins": 2, + "winsByDeath": 2, + "perRoundWins": [ + 0, + 0, + 0, + 0, + 1, + 0, + 1 + ], + "score": 423, + "enemyScore": 524, + "ticks": 12607, + "damageDealt": 283.5444, + "damageTaken": 196.9999999999999, + "shots": 788, + "enemyShots": 953, + "hits": 82, + "hitsTaken": 86, + "commitCalls": 12540, + "picks": 2476, + "intervalMean": 5.058966074313409, + "revPicks": 789, + "reversalPct": 31.865912762520193, + "speedMean": 4.750466125585626, + "byReason": { + "expiry": 68, + "init": 8, + "danger": 10, + "tile_self": 2390 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1741 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.3% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 4, + "rounds": 7, + "wins": 3, + "winsByDeath": 3, + "perRoundWins": [ + 0, + 0, + 1, + 0, + 1, + 1, + 0 + ], + "score": 509, + "enemyScore": 461, + "ticks": 13146, + "damageDealt": 306.11100000000005, + "damageTaken": 198.00000000000006, + "shots": 848, + "enemyShots": 958, + "hits": 88, + "hitsTaken": 95, + "commitCalls": 13084, + "picks": 2634, + "intervalMean": 4.958238420652999, + "revPicks": 933, + "reversalPct": 35.42141230068337, + "speedMean": 4.654422770505723, + "byReason": { + "expiry": 55, + "init": 8, + "danger": 18, + "tile_self": 2553 + }, + "note": "id 2==ModularBot, id 1==DrussGT; of 1806 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 47.0% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 5, + "rounds": 7, + "wins": 3, + "winsByDeath": 3, + "perRoundWins": [ + 1, + 0, + 0, + 1, + 0, + 1, + 0 + ], + "score": 477, + "enemyScore": 404, + "ticks": 13483, + "damageDealt": 267.5068, + "damageTaken": 147.59999999999997, + "shots": 849, + "enemyShots": 1029, + "hits": 82, + "hitsTaken": 92, + "commitCalls": 13381, + "picks": 2641, + "intervalMean": 5.054524801211662, + "revPicks": 924, + "reversalPct": 34.98674744414994, + "speedMean": 4.7057488412032455, + "byReason": { + "expiry": 59, + "init": 9, + "danger": 12, + "tile_self": 2561 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1878 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.2% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 6, + "rounds": 7, + "wins": 4, + "winsByDeath": 4, + "perRoundWins": [ + 0, + 1, + 1, + 0, + 1, + 1, + 0 + ], + "score": 546, + "enemyScore": 327, + "ticks": 13895, + "damageDealt": 271.0661, + "damageTaken": 138.7999999999998, + "shots": 885, + "enemyShots": 1062, + "hits": 84, + "hitsTaken": 88, + "commitCalls": 13868, + "picks": 2721, + "intervalMean": 5.085262771040059, + "revPicks": 924, + "reversalPct": 33.95810363836825, + "speedMean": 4.69577788646468, + "byReason": { + "expiry": 70, + "init": 7, + "danger": 11, + "tile_self": 2633 + }, + "note": "id 2==ModularBot, id 1==DrussGT; of 1947 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.5% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 7, + "rounds": 7, + "wins": 4, + "winsByDeath": 4, + "perRoundWins": [ + 1, + 1, + 0, + 0, + 1, + 0, + 1 + ], + "score": 559, + "enemyScore": 418, + "ticks": 14461, + "damageDealt": 332.47940000000006, + "damageTaken": 206.29999999999976, + "shots": 912, + "enemyShots": 1072, + "hits": 105, + "hitsTaken": 112, + "commitCalls": 14394, + "picks": 2861, + "intervalMean": 5.022719328905977, + "revPicks": 996, + "reversalPct": 34.813002446696956, + "speedMean": 4.643631643995206, + "byReason": { + "expiry": 77, + "init": 8, + "danger": 12, + "tile_self": 2764 + }, + "note": "id 2==ModularBot, id 1==DrussGT; of 1984 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.0% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + } + ], + "diag": { + "commitCalls": 91795, + "picks": 18185, + "intervalMean": 5.038823205938961, + "revPicks": 6201, + "reversalPct": 34.09953258179819, + "speedMean": 4.694576396866298, + "byReason": { + "expiry": 453, + "init": 59, + "danger": 89, + "tile_self": 17584 + }, + "revOffsets": [ + -5, + -4, + -3, + -2, + -1, + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10 + ], + "speedProfile": [ + 4.674526505566802, + 4.674502960200138, + 4.708410221691385, + 4.749505751570593, + 4.836293831501396, + 4.8936660685398525, + 4.236950742660031, + 3.996039576376638, + 4.09924797796434, + 4.247565615057907, + 4.386909644886077, + 4.517027085271333, + 4.5905471910115905, + 4.683215509373109, + 4.79185739578183, + 4.8708239990470865 + ], + "speedProfileN": [ + 6175, + 6177, + 6178, + 6178, + 6178, + 6201, + 6200, + 6200, + 6200, + 6198, + 6198, + 6198, + 6196, + 6195, + 6194, + 6194 + ] + } + }, + "B": { + "label": "B honour commitment", + "env": "TR_TFIL_TILE_REPLAN=off", + "runs": [ + { + "run": 1, + "rounds": 7, + "wins": 2, + "winsByDeath": 2, + "perRoundWins": [ + 0, + 1, + 0, + 1, + 0, + 0, + 0 + ], + "score": 410, + "enemyScore": 480, + "ticks": 13267, + "damageDealt": 270.0858, + "damageTaken": 155.89999999999995, + "shots": 836, + "enemyShots": 993, + "hits": 80, + "hitsTaken": 90, + "commitCalls": 13234, + "picks": 890, + "intervalMean": 14.828089887640449, + "revPicks": 460, + "reversalPct": 51.68539325842696, + "speedMean": 5.363202735378571, + "byReason": { + "expiry": 847, + "init": 7, + "danger": 36 + }, + "note": "id 2==ModularBot, id 1==DrussGT; of 1829 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.7% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 2, + "rounds": 7, + "wins": 1, + "winsByDeath": 1, + "perRoundWins": [ + 0, + 0, + 0, + 0, + 1, + 0, + 0 + ], + "score": 333, + "enemyScore": 631, + "ticks": 13861, + "damageDealt": 263.8279, + "damageTaken": 234.60000000000014, + "shots": 853, + "enemyShots": 1040, + "hits": 89, + "hitsTaken": 111, + "commitCalls": 13837, + "picks": 931, + "intervalMean": 14.793770139634802, + "revPicks": 484, + "reversalPct": 51.98711063372718, + "speedMean": 5.356598251065982, + "byReason": { + "expiry": 874, + "init": 7, + "danger": 50 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1893 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.1% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 3, + "rounds": 7, + "wins": 5, + "winsByDeath": 5, + "perRoundWins": [ + 1, + 0, + 1, + 0, + 1, + 1, + 1 + ], + "score": 624, + "enemyScore": 321, + "ticks": 11326, + "damageDealt": 280.0976, + "damageTaken": 189.0, + "shots": 721, + "enemyShots": 853, + "hits": 81, + "hitsTaken": 71, + "commitCalls": 11210, + "picks": 757, + "intervalMean": 14.73447820343461, + "revPicks": 393, + "reversalPct": 51.915455746367236, + "speedMean": 5.354025423728814, + "byReason": { + "expiry": 717, + "init": 7, + "danger": 33 + }, + "note": "id 2==ModularBot, id 1==DrussGT; of 1574 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.8% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 4, + "rounds": 7, + "wins": 6, + "winsByDeath": 6, + "perRoundWins": [ + 1, + 1, + 1, + 1, + 1, + 1, + 0 + ], + "score": 707, + "enemyScore": 206, + "ticks": 13196, + "damageDealt": 307.40080000000006, + "damageTaken": 139.69999999999987, + "shots": 821, + "enemyShots": 966, + "hits": 90, + "hitsTaken": 75, + "commitCalls": 13136, + "picks": 885, + "intervalMean": 14.780790960451977, + "revPicks": 469, + "reversalPct": 52.994350282485875, + "speedMean": 5.3386637865408035, + "byReason": { + "expiry": 834, + "init": 7, + "danger": 44 + }, + "note": "id 2==ModularBot, id 1==DrussGT; of 1787 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.9% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 5, + "rounds": 7, + "wins": 2, + "winsByDeath": 2, + "perRoundWins": [ + 0, + 0, + 1, + 0, + 0, + 0, + 1 + ], + "score": 388, + "enemyScore": 594, + "ticks": 11626, + "damageDealt": 249.2447, + "damageTaken": 266.2000000000004, + "shots": 738, + "enemyShots": 868, + "hits": 78, + "hitsTaken": 99, + "commitCalls": 11531, + "picks": 778, + "intervalMean": 14.730077120822623, + "revPicks": 386, + "reversalPct": 49.61439588688946, + "speedMean": 5.411602419564652, + "byReason": { + "expiry": 728, + "init": 10, + "danger": 40 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1606 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.0% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 6, + "rounds": 7, + "wins": 4, + "winsByDeath": 4, + "perRoundWins": [ + 1, + 0, + 1, + 1, + 0, + 0, + 1 + ], + "score": 550, + "enemyScore": 360, + "ticks": 12712, + "damageDealt": 275.9048999999999, + "damageTaken": 161.29999999999995, + "shots": 812, + "enemyShots": 947, + "hits": 87, + "hitsTaken": 87, + "commitCalls": 12681, + "picks": 856, + "intervalMean": 14.759345794392523, + "revPicks": 428, + "reversalPct": 50.0, + "speedMean": 5.4190644468101885, + "byReason": { + "expiry": 811, + "init": 7, + "danger": 38 + }, + "note": "id 2==ModularBot, id 1==DrussGT; of 1759 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.2% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 7, + "rounds": 7, + "wins": 3, + "winsByDeath": 3, + "perRoundWins": [ + 1, + 0, + 0, + 1, + 1, + 0, + 0 + ], + "score": 511, + "enemyScore": 477, + "ticks": 12428, + "damageDealt": 320.8059999999999, + "damageTaken": 216.10000000000002, + "shots": 778, + "enemyShots": 901, + "hits": 89, + "hitsTaken": 97, + "commitCalls": 12234, + "picks": 823, + "intervalMean": 14.804374240583233, + "revPicks": 427, + "reversalPct": 51.88335358444714, + "speedMean": 5.3731812980219065, + "byReason": { + "expiry": 788, + "init": 10, + "danger": 25 + }, + "note": "id 2==ModularBot, id 1==DrussGT; of 1679 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.3% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + } + ], + "diag": { + "commitCalls": 87863, + "picks": 5920, + "intervalMean": 14.777533783783785, + "revPicks": 3047, + "reversalPct": 51.4695945945946, + "speedMean": 5.373126700089912, + "byReason": { + "expiry": 5599, + "init": 55, + "danger": 266 + }, + "revOffsets": [ + -5, + -4, + -3, + -2, + -1, + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10 + ], + "speedProfile": [ + 5.946049040688059, + 5.925394889182931, + 5.866936817730731, + 5.764555077737347, + 5.576274603043334, + 5.373471857564818, + 4.38057925828684, + 3.9541095749917954, + 4.12307700196915, + 4.530844888414834, + 4.9562428208073515, + 5.294977641942895, + 5.599534378076797, + 5.782399425287356, + 5.870114942528736, + 5.941333128078818 + ], + "speedProfileN": [ + 3023, + 3023, + 3023, + 3023, + 3023, + 3047, + 3047, + 3047, + 3047, + 3047, + 3047, + 3047, + 3047, + 3045, + 3045, + 3045 + ] + } + }, + "C": { + "label": "C B + no-reversal", + "env": "TR_TFIL_TILE_REPLAN=off TR_TFIL_NO_REV=1", + "runs": [ + { + "run": 1, + "rounds": 7, + "wins": 3, + "winsByDeath": 3, + "perRoundWins": [ + 1, + 0, + 1, + 0, + 0, + 1, + 0 + ], + "score": 446, + "enemyScore": 467, + "ticks": 13019, + "damageDealt": 240.30640000000002, + "damageTaken": 200.89999999999995, + "shots": 787, + "enemyShots": 981, + "hits": 76, + "hitsTaken": 89, + "commitCalls": 12989, + "picks": 875, + "intervalMean": 14.772571428571428, + "revPicks": 362, + "reversalPct": 41.371428571428574, + "speedMean": 5.530025406112864, + "byReason": { + "expiry": 830, + "init": 8, + "danger": 37 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1768 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 44.5% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 2, + "rounds": 7, + "wins": 3, + "winsByDeath": 3, + "perRoundWins": [ + 1, + 0, + 0, + 0, + 1, + 0, + 1 + ], + "score": 500, + "enemyScore": 398, + "ticks": 13598, + "damageDealt": 298.5099, + "damageTaken": 137.9999999999998, + "shots": 830, + "enemyShots": 1039, + "hits": 80, + "hitsTaken": 86, + "commitCalls": 13482, + "picks": 905, + "intervalMean": 14.833149171270719, + "revPicks": 368, + "reversalPct": 40.66298342541437, + "speedMean": 5.369121792018988, + "byReason": { + "expiry": 858, + "init": 7, + "danger": 40 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1869 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 44.4% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 3, + "rounds": 7, + "wins": 2, + "winsByDeath": 2, + "perRoundWins": [ + 1, + 0, + 0, + 1, + 0, + 0, + 0 + ], + "score": 379, + "enemyScore": 545, + "ticks": 12935, + "damageDealt": 244.42980000000006, + "damageTaken": 210.29999999999998, + "shots": 803, + "enemyShots": 975, + "hits": 76, + "hitsTaken": 74, + "commitCalls": 12854, + "picks": 863, + "intervalMean": 14.797219003476245, + "revPicks": 396, + "reversalPct": 45.8864426419467, + "speedMean": 5.433398650225611, + "byReason": { + "expiry": 820, + "init": 9, + "danger": 34 + }, + "note": "id 2==ModularBot, id 1==DrussGT; of 1778 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.2% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 4, + "rounds": 7, + "wins": 3, + "winsByDeath": 3, + "perRoundWins": [ + 0, + 1, + 1, + 0, + 1, + 0, + 0 + ], + "score": 434, + "enemyScore": 501, + "ticks": 12292, + "damageDealt": 235.60140000000004, + "damageTaken": 244.4, + "shots": 789, + "enemyShots": 882, + "hits": 75, + "hitsTaken": 80, + "commitCalls": 12238, + "picks": 823, + "intervalMean": 14.80801944106926, + "revPicks": 354, + "reversalPct": 43.01336573511543, + "speedMean": 5.438460124203301, + "byReason": { + "expiry": 786, + "init": 8, + "danger": 29 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1671 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 47.2% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 5, + "rounds": 7, + "wins": 3, + "winsByDeath": 3, + "perRoundWins": [ + 1, + 0, + 1, + 1, + 0, + 0, + 0 + ], + "score": 466, + "enemyScore": 458, + "ticks": 13448, + "damageDealt": 268.72209999999995, + "damageTaken": 193.20000000000005, + "shots": 839, + "enemyShots": 992, + "hits": 85, + "hitsTaken": 93, + "commitCalls": 13415, + "picks": 902, + "intervalMean": 14.813747228381375, + "revPicks": 383, + "reversalPct": 42.46119733924612, + "speedMean": 5.333831065970928, + "byReason": { + "expiry": 851, + "init": 7, + "danger": 44 + }, + "note": "id 2==ModularBot, id 1==DrussGT; of 1831 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.8% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 6, + "rounds": 7, + "wins": 3, + "winsByDeath": 3, + "perRoundWins": [ + 1, + 0, + 0, + 0, + 0, + 1, + 1 + ], + "score": 470, + "enemyScore": 440, + "ticks": 13385, + "damageDealt": 263.44499999999994, + "damageTaken": 176.99999999999994, + "shots": 828, + "enemyShots": 995, + "hits": 82, + "hitsTaken": 82, + "commitCalls": 13349, + "picks": 901, + "intervalMean": 14.755826859045506, + "revPicks": 390, + "reversalPct": 43.28523862375139, + "speedMean": 5.389551651809124, + "byReason": { + "expiry": 846, + "init": 7, + "danger": 48 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1823 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.4% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 7, + "rounds": 7, + "wins": 0, + "winsByDeath": 0, + "perRoundWins": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "score": 205, + "enemyScore": 676, + "ticks": 13037, + "damageDealt": 202.54700000000003, + "damageTaken": 214.70000000000013, + "shots": 790, + "enemyShots": 986, + "hits": 61, + "hitsTaken": 96, + "commitCalls": 13004, + "picks": 873, + "intervalMean": 14.830469644902635, + "revPicks": 350, + "reversalPct": 40.09163802978236, + "speedMean": 5.667121654875423, + "byReason": { + "expiry": 827, + "init": 7, + "danger": 39 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1776 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 44.5% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + } + ], + "diag": { + "commitCalls": 91331, + "picks": 6142, + "intervalMean": 14.80153044610876, + "revPicks": 2603, + "reversalPct": 42.380332139368285, + "speedMean": 5.450575379662984, + "byReason": { + "expiry": 5818, + "init": 53, + "danger": 271 + }, + "revOffsets": [ + -5, + -4, + -3, + -2, + -1, + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10 + ], + "speedProfile": [ + 6.08455312136487, + 6.023386002326483, + 5.935779371849554, + 5.83324447460256, + 5.661145308259015, + 5.3094986553976184, + 4.45885756819055, + 4.058512682551883, + 4.205034588777863, + 4.5454097809377405, + 4.86863470407379, + 5.229871252882398, + 5.584727028066128, + 5.900579104190696, + 6.0355752595155705, + 6.080096153846154 + ], + "speedProfileN": [ + 2579, + 2579, + 2579, + 2579, + 2579, + 2603, + 2603, + 2602, + 2602, + 2602, + 2602, + 2602, + 2601, + 2601, + 2601, + 2600 + ] + } + }, + "D": { + "label": "D enemy-keyed cancel", + "env": "TR_TFIL_TILE_REPLAN=enemy", + "runs": [ + { + "run": 1, + "rounds": 7, + "wins": 2, + "winsByDeath": 2, + "perRoundWins": [ + 1, + 0, + 1, + 0, + 0, + 0, + 0 + ], + "score": 386, + "enemyScore": 544, + "ticks": 12775, + "damageDealt": 247.4326, + "damageTaken": 219.89999999999998, + "shots": 774, + "enemyShots": 965, + "hits": 79, + "hitsTaken": 94, + "commitCalls": 12715, + "picks": 2423, + "intervalMean": 5.231943871234008, + "revPicks": 891, + "reversalPct": 36.77259595542716, + "speedMean": 4.900095697748722, + "byReason": { + "expiry": 79, + "tile_enemy": 2323, + "init": 8, + "danger": 13 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1739 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 44.5% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 2, + "rounds": 7, + "wins": 5, + "winsByDeath": 5, + "perRoundWins": [ + 1, + 1, + 0, + 1, + 1, + 0, + 1 + ], + "score": 624, + "enemyScore": 350, + "ticks": 13863, + "damageDealt": 332.7028, + "damageTaken": 202.49999999999991, + "shots": 864, + "enemyShots": 1004, + "hits": 100, + "hitsTaken": 104, + "commitCalls": 13731, + "picks": 2516, + "intervalMean": 5.4443561208267095, + "revPicks": 962, + "reversalPct": 38.23529411764706, + "speedMean": 4.989149455206, + "byReason": { + "expiry": 111, + "tile_enemy": 2380, + "init": 8, + "danger": 17 + }, + "note": "id 2==ModularBot, id 1==DrussGT; of 1868 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.3% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 3, + "rounds": 7, + "wins": 4, + "winsByDeath": 4, + "perRoundWins": [ + 0, + 1, + 0, + 1, + 0, + 1, + 1 + ], + "score": 488, + "enemyScore": 422, + "ticks": 12557, + "damageDealt": 264.0757, + "damageTaken": 201.29999999999998, + "shots": 765, + "enemyShots": 915, + "hits": 81, + "hitsTaken": 78, + "commitCalls": 12456, + "picks": 2349, + "intervalMean": 5.284376330353342, + "revPicks": 863, + "reversalPct": 36.73903788846317, + "speedMean": 5.1301981981702545, + "byReason": { + "expiry": 78, + "tile_enemy": 2243, + "init": 9, + "danger": 19 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1680 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.5% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 4, + "rounds": 7, + "wins": 1, + "winsByDeath": 1, + "perRoundWins": [ + 0, + 0, + 0, + 0, + 0, + 1, + 0 + ], + "score": 332, + "enemyScore": 612, + "ticks": 12857, + "damageDealt": 276.7819, + "damageTaken": 215.00000000000009, + "shots": 806, + "enemyShots": 964, + "hits": 78, + "hitsTaken": 97, + "commitCalls": 12769, + "picks": 2440, + "intervalMean": 5.2225409836065575, + "revPicks": 894, + "reversalPct": 36.63934426229508, + "speedMean": 4.9553466911593755, + "byReason": { + "expiry": 67, + "tile_enemy": 2358, + "init": 7, + "danger": 8 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1770 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.5% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 5, + "rounds": 7, + "wins": 3, + "winsByDeath": 3, + "perRoundWins": [ + 1, + 1, + 0, + 1, + 0, + 0, + 0 + ], + "score": 552, + "enemyScore": 536, + "ticks": 12453, + "damageDealt": 339.1795, + "damageTaken": 259.4000000000002, + "shots": 770, + "enemyShots": 925, + "hits": 96, + "hitsTaken": 104, + "commitCalls": 12419, + "picks": 2306, + "intervalMean": 5.366001734605377, + "revPicks": 866, + "reversalPct": 37.5542064180399, + "speedMean": 4.990179280438412, + "byReason": { + "expiry": 70, + "tile_enemy": 2220, + "init": 7, + "danger": 9 + }, + "note": "id 1==ModularBot, id 2==DrussGT; of 1695 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.4% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 6, + "rounds": 7, + "wins": 4, + "winsByDeath": 4, + "perRoundWins": [ + 0, + 0, + 1, + 1, + 1, + 0, + 1 + ], + "score": 601, + "enemyScore": 351, + "ticks": 12585, + "damageDealt": 321.0846, + "damageTaken": 161.69999999999982, + "shots": 809, + "enemyShots": 950, + "hits": 97, + "hitsTaken": 89, + "commitCalls": 12500, + "picks": 2372, + "intervalMean": 5.259274873524452, + "revPicks": 912, + "reversalPct": 38.44856661045531, + "speedMean": 4.940378458251953, + "byReason": { + "expiry": 78, + "tile_enemy": 2275, + "init": 8, + "danger": 11 + }, + "note": "id 2==ModularBot, id 1==DrussGT; of 1759 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.0% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 7, + "rounds": 7, + "wins": 5, + "winsByDeath": 5, + "perRoundWins": [ + 1, + 0, + 1, + 1, + 1, + 0, + 1 + ], + "score": 669, + "enemyScore": 360, + "ticks": 11942, + "damageDealt": 328.2956, + "damageTaken": 219.3999999999999, + "shots": 752, + "enemyShots": 903, + "hits": 91, + "hitsTaken": 87, + "commitCalls": 11802, + "picks": 2189, + "intervalMean": 5.380995888533577, + "revPicks": 811, + "reversalPct": 37.04888076747373, + "speedMean": 5.04773762451958, + "byReason": { + "expiry": 64, + "tile_enemy": 2102, + "init": 9, + "danger": 14 + }, + "note": "id 2==ModularBot, id 1==DrussGT; of 1655 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.4% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + } + ], + "diag": { + "commitCalls": 88392, + "picks": 16595, + "intervalMean": 5.312383247966255, + "revPicks": 6199, + "reversalPct": 37.35462488701416, + "speedMean": 4.992402765845614, + "byReason": { + "expiry": 547, + "tile_enemy": 15901, + "init": 56, + "danger": 91 + }, + "revOffsets": [ + -5, + -4, + -3, + -2, + -1, + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10 + ], + "speedProfile": [ + 5.151144393091751, + 5.10039595307911, + 5.073186729335893, + 5.061792444780798, + 5.033423840168809, + 4.9792196707949365, + 4.416391599445617, + 4.266138900458995, + 4.390095869674473, + 4.558906984090767, + 4.7009789327937455, + 4.819924749161163, + 4.898692842875206, + 5.0367440798852705, + 5.118490710878572, + 5.148879318520468 + ], + "speedProfileN": [ + 6165, + 6166, + 6167, + 6168, + 6170, + 6199, + 6199, + 6199, + 6199, + 6199, + 6199, + 6199, + 6199, + 6198, + 6198, + 6197 + ] + } + }, + "E": { + "label": "E commit 30 ticks", + "env": "TR_TFIL_TILE_REPLAN=off TR_TFIL_COMMIT_TICKS=30", + "runs": [ + { + "run": 1, + "rounds": 7, + "wins": 3, + "winsByDeath": 3, + "perRoundWins": [ + 1, + 1, + 0, + 0, + 1, + 0, + 0 + ], + "score": 513, + "enemyScore": 483, + "ticks": 13347, + "damageDealt": 316.5558, + "damageTaken": 215.19999999999996, + "shots": 847, + "enemyShots": 992, + "hits": 95, + "hitsTaken": 110, + "commitCalls": 13264, + "picks": 503, + "intervalMean": 26.18886679920477, + "revPicks": 297, + "reversalPct": 59.04572564612326, + "speedMean": 4.790494948733413, + "byReason": { + "expiry": 289, + "init": 8, + "danger": 206 + }, + "note": "id 2==ModularBot, id 1==DrussGT; of 1839 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.1% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 2, + "rounds": 7, + "wins": 3, + "winsByDeath": 3, + "perRoundWins": [ + 1, + 1, + 0, + 1, + 0, + 0, + 0 + ], + "score": 520, + "enemyScore": 468, + "ticks": 12588, + "damageDealt": 316.143, + "damageTaken": 205.99999999999997, + "shots": 776, + "enemyShots": 955, + "hits": 84, + "hitsTaken": 101, + "commitCalls": 12534, + "picks": 466, + "intervalMean": 26.723175965665234, + "revPicks": 261, + "reversalPct": 56.00858369098712, + "speedMean": 4.999483903781714, + "byReason": { + "expiry": 286, + "init": 9, + "danger": 171 + }, + "note": "id 2==ModularBot, id 1==DrussGT; of 1731 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 44.8% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 3, + "rounds": 7, + "wins": 3, + "winsByDeath": 3, + "perRoundWins": [ + 0, + 0, + 1, + 0, + 1, + 1, + 0 + ], + "score": 441, + "enemyScore": 498, + "ticks": 12580, + "damageDealt": 271.4629, + "damageTaken": 206.19999999999996, + "shots": 788, + "enemyShots": 939, + "hits": 80, + "hitsTaken": 104, + "commitCalls": 12439, + "picks": 471, + "intervalMean": 26.276008492569, + "revPicks": 277, + "reversalPct": 58.81104033970276, + "speedMean": 5.030599324704558, + "byReason": { + "expiry": 268, + "init": 10, + "danger": 193 + }, + "note": "id 2==ModularBot, id 1==DrussGT; of 1727 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.6% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 4, + "rounds": 7, + "wins": 5, + "winsByDeath": 5, + "perRoundWins": [ + 0, + 1, + 0, + 1, + 1, + 1, + 1 + ], + "score": 596, + "enemyScore": 352, + "ticks": 12233, + "damageDealt": 300.0652, + "damageTaken": 195.69999999999993, + "shots": 754, + "enemyShots": 893, + "hits": 82, + "hitsTaken": 87, + "commitCalls": 12204, + "picks": 450, + "intervalMean": 26.851111111111113, + "revPicks": 264, + "reversalPct": 58.666666666666664, + "speedMean": 5.114552605703048, + "byReason": { + "expiry": 271, + "init": 7, + "danger": 172 + }, + "note": "id 2==ModularBot, id 1==DrussGT; of 1647 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.8% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 5, + "rounds": 7, + "wins": 3, + "winsByDeath": 3, + "perRoundWins": [ + 1, + 0, + 0, + 1, + 1, + 0, + 0 + ], + "score": 465, + "enemyScore": 452, + "ticks": 13629, + "damageDealt": 256.015, + "damageTaken": 188.79999999999993, + "shots": 828, + "enemyShots": 1020, + "hits": 84, + "hitsTaken": 94, + "commitCalls": 13572, + "picks": 501, + "intervalMean": 26.944111776447105, + "revPicks": 273, + "reversalPct": 54.49101796407186, + "speedMean": 4.909777483053345, + "byReason": { + "expiry": 321, + "init": 8, + "danger": 172 + }, + "note": "id 2==ModularBot, id 1==DrussGT; of 1848 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 44.8% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 6, + "rounds": 7, + "wins": 3, + "winsByDeath": 3, + "perRoundWins": [ + 1, + 1, + 0, + 1, + 0, + 0, + 0 + ], + "score": 485, + "enemyScore": 457, + "ticks": 12302, + "damageDealt": 277.8316, + "damageTaken": 190.8, + "shots": 749, + "enemyShots": 908, + "hits": 82, + "hitsTaken": 90, + "commitCalls": 12269, + "picks": 460, + "intervalMean": 26.417391304347827, + "revPicks": 262, + "reversalPct": 56.95652173913044, + "speedMean": 4.772317426033092, + "byReason": { + "expiry": 265, + "init": 7, + "danger": 188 + }, + "note": "id 2==ModularBot, id 1==DrussGT; of 1657 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 45.2% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + }, + { + "run": 7, + "rounds": 7, + "wins": 4, + "winsByDeath": 4, + "perRoundWins": [ + 1, + 0, + 1, + 1, + 1, + 0, + 0 + ], + "score": 536, + "enemyScore": 374, + "ticks": 12614, + "damageDealt": 263.14140000000003, + "damageTaken": 172.89999999999998, + "shots": 809, + "enemyShots": 951, + "hits": 86, + "hitsTaken": 87, + "commitCalls": 12582, + "picks": 476, + "intervalMean": 26.222689075630253, + "revPicks": 277, + "reversalPct": 58.19327731092437, + "speedMean": 4.8070855189953905, + "byReason": { + "expiry": 290, + "init": 7, + "danger": 179 + }, + "note": "id 2==ModularBot, id 1==DrussGT; of 1760 fire events the bullet origin is within 60px of the firer's own recorded position for 100.0%, and 46.0% vote the same side (a 50% split would mean the mapping is noise) | WARNING: id mapping is unreliable" + } + ], + "diag": { + "commitCalls": 88864, + "picks": 3327, + "intervalMean": 26.515779981965736, + "revPicks": 1911, + "reversalPct": 57.43913435527502, + "speedMean": 4.91614257460839, + "byReason": { + "expiry": 1990, + "init": 56, + "danger": 1281 + }, + "revOffsets": [ + -5, + -4, + -3, + -2, + -1, + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10 + ], + "speedProfile": [ + 5.033945105820106, + 4.9719742063492065, + 4.88316798941799, + 4.80760582010582, + 4.660912698412698, + 4.5408817373103085, + 4.263016745159602, + 4.174803664921466, + 4.277356020942408, + 4.444846204188481, + 4.583535227867993, + 4.756858957569408, + 4.943082111052907, + 5.122168019905709, + 5.273097826086956, + 5.351705735987428 + ], + "speedProfileN": [ + 1890, + 1890, + 1890, + 1890, + 1890, + 1911, + 1911, + 1910, + 1910, + 1910, + 1909, + 1909, + 1909, + 1909, + 1909, + 1909 + ] + } + } + } +} \ No newline at end of file diff --git a/common_libs/tests/measure_tfil_commit_ab.nim b/common_libs/tests/measure_tfil_commit_ab.nim new file mode 100644 index 0000000..a60abb9 --- /dev/null +++ b/common_libs/tests/measure_tfil_commit_ab.nim @@ -0,0 +1,776 @@ +## TFIL commitment A/B — measurement harness. +## +## Consumes the raw artifacts produced by the five-arm, seven-run, seven-round +## A/B against the real DrussGT bridge (tools/robocode_shim/run_bridge_battle.sh) +## and reports +## +## 1. the arm table: damage/run, ROUND WINS, hits taken, shots, per-run values +## 2. the exact two-sided permutation p for damage and for round wins +## 3. the per-arm TREATMENT diagnostics (from TR_TFIL_COMMIT_LOG): decision +## interval, % tile-change replans, reversal-pick rate, speed profile +## around reversals — this is what proves the treatment actually applied +## +## Expected raw layout (produced by the A/B driver): +## /out//run.jsonl per-tick states (ex/ey = DrussGT, sx/sy = us) +## /out//run.events.jsonl fire/hit/death events (numeric bot ids) +## /out//run.rounds.json round boundaries (startTick/count) +## /logs//run.out capture stdout (final RESULTS block) +## /logs//run.commit.jsonl TFIL per-tick commit diagnostics +## +## Modes: +## nim c -r --path:common_libs common_libs/tests/measure_tfil_commit_ab.nim \ +## --raw /tmp/tfil_ab2 --summary common_libs/tests/fixtures/tfil_commit_ab_results.json \ +## --report common_libs/tests/fixtures/tfil_commit_ab_report.md +## ... --from-summary # re-print the report from the committed summary +## +## NOTE on the ingredients, because they are easy to get wrong: +## * In the shim's JSONL, `e*` is the capture SUBJECT and `s*` is the other bot. +## run_bridge_battle.sh is invoked with `--subject DrussGT`, so `e*` = DrussGT +## and `s*` = ModularBot (the bot under test). Verified here by matching the +## events sidecar's bullet origins against both recorded positions. +## * The "[capture] ROUND n" stdout lines carry the CUMULATIVE score and the +## battle rank so far, NOT the round result. Round wins are taken from the +## final RESULTS block (`firstPlaces`) and cross-checked against the +## BotDeathEvent in the events sidecar (the loser of a TR 1v1 round is the +## bot that died). +## * `damage` is the server's own BulletHitBotEvent damage, attributed by +## numeric owner/victim id. + +import std/[os, json, math, strformat, strutils, tables, sequtils] + +const + Arms = ["A", "B", "C", "D", "E"] + ArmRuns = 7 + MaxRuns = 14 + ArmRounds = 7 + ArmEnv = [ + "", + "TR_TFIL_TILE_REPLAN=off", + "TR_TFIL_TILE_REPLAN=off TR_TFIL_NO_REV=1", + "TR_TFIL_TILE_REPLAN=enemy", + "TR_TFIL_TILE_REPLAN=off TR_TFIL_COMMIT_TICKS=30", + ] + ArmLabel = [ + "A control (shipped)", + "B honour commitment", + "C B + no-reversal", + "D enemy-keyed cancel", + "E commit 30 ticks", + ] + RevOffsets = [-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + +## Number of runs per arm to analyse; 7 = the pre-registered primary block, +## 14 = primary + replication block (set with --runs). +var RunCount = ArmRuns +## First run index to include (1 = the pre-registered primary block). +var FirstRun = 1 + +# ── small stats helpers ────────────────────────────────────────────────────── + +proc mean(s: openArray[float]): float = + if s.len == 0: return 0.0 + var t = 0.0 + for v in s: t += v + t / s.len.float + +proc exactPermP(a, b: seq[float]): tuple[p, delta: float, perms: int] = + ## Exact two-sided permutation test on the difference of means. Enumerates + ## every relabelling of the pooled sample (C(n+m, n) of them; 3432 for 7v7, + ## 40_116_600 for 14v14). The observed labelling is included, so p >= 1/C. + let n = a.len + let m = b.len + doAssert n + m <= 30, "pool too large to enumerate exactly" + let ma = mean(a) + let mb = mean(b) + let obs = abs(ma - mb) + let pool = a & b + let total = n + m + var poolSum = 0.0 + for v in pool: poolSum += v + let nf = n.float + let mf = m.float + var cnt = 0 + var perms = 0 + proc rec(start, chosen: int, s: float) = + if chosen == n: + let sx = s / nf + let sy = (poolSum - s) / mf + inc perms + if abs(sx - sy) >= obs - 1e-12: inc cnt + return + for i in start .. total - (n - chosen): + rec(i + 1, chosen + 1, s + pool[i]) + rec(0, 0, 0.0) + (cnt.float / perms.float, ma - mb, perms) + +proc binom(n, k: int): int = + ## exact C(n, k) by multiplicative recurrence (n <= 30 here) + if k < 0 or k > n: return 0 + var kk = min(k, n - k) + var r = 1'i64 + for i in 1 .. kk: + r = r * (n - kk + i) div i + int(r) + +proc logC(n, k: int): float = + ## log binomial coefficient. + if k < 0 or k > n: return NegInf + var r = 0.0 + for i in 1 .. k: + r += ln((n - k + i).float / i.float) + r + +proc hyperPmf(k, n1, n, t: int): float = + ## P(X = k), X ~ Hypergeometric(n, t successes, n1 draws). + if k < 0 or k > t or (n1 - k) < 0 or (n1 - k) > (n - t): return 0.0 + exp(logC(t, k) + logC(n - t, n1 - k) - logC(n, n1)) + +proc hyperTwoSidedP(k, n1, n, t: int): float = + ## Exact two-sided p for "as or more extreme" on the sum statistic: the sum of + ## the hypergeometric pmf over every k whose pmf is <= pmf(k_obs). This is the + ## exact permutation distribution of the round-win count under exchangeability. + let pObs = hyperPmf(k, n1, n, t) + var acc = 0.0 + var mass = 0.0 + for kk in 0 .. n1: + let p = hyperPmf(kk, n1, n, t) + mass += p + if p <= pObs * (1.0 + 1e-9): acc += p + if mass <= 0.0: return 1.0 + min(1.0, acc / mass) + +# ── per-run extraction ─────────────────────────────────────────────────────── + +type + RunMetrics = object + run: int + rounds: int + wins: int + winsByDeath: int + perRoundWins: seq[int] ## 1 = we won the round, 0 = we lost + score: int + enemyScore: int + ticks: int + damageDealt: float + damageTaken: float + shots: int + enemyShots: int + hits: int + hitsTaken: int + # diagnostics from TR_TFIL_COMMIT_LOG + commitCalls: int + picks: int + intervalMean: float + revPicks: int + reversalPct: float + byReason: Table[string, int] + speedMean: float + speedProfile: seq[float] ## aligned with RevOffsets; NaN when no data + speedProfileN: seq[int] + ok: bool + note: string + + ArmReport = object + arm: int + env: string + label: string + runs: seq[RunMetrics] + # aggregated diagnostics + commitCalls: int + picks: int + intervalSum: int + byReason: Table[string, int] + revPicks: int + speedSum: float + speedN: int + profSum: seq[float] + profN: seq[int] + +proc loadStatePositions(path: string): seq[array[4, float]] = + ## (ex, ey, sx, sy) per recorded tick, in file (global tick) order. + if not fileExists(path): return + for rawLine in lines(path): + let line = rawLine.strip() + if line.len == 0: continue + let n = parseJson(line) + if not n.hasKey("ex"): continue + result.add [n["ex"].getFloat(), n["ey"].getFloat(), + n["sx"].getFloat(), n["sy"].getFloat()] + +proc loadRoundStarts(path: string): seq[tuple[rnd, start, count: int]] = + if not fileExists(path): return + let n = parseFile(path) + for r in n["rounds"]: + result.add (r["round"].getInt(), r["startTick"].getInt(), r["count"].getInt()) + +proc parseResultsBlock(path: string): Table[string, tuple[score, wins, survival: int]] = + ## The final RESULTS block printed by the capture: + ## #1 DrussGT totalScore=569 firstPlaces=5 survival=250 + if not fileExists(path): return + for rawLine in lines(path): + let line = rawLine.strip() + if not line.startsWith("#"): continue + if not line.contains("totalScore="): continue + let toks = line.splitWhitespace() + if toks.len < 4: continue + let name = toks[1] + var score, wins, surv: int + for t in toks: + if t.startsWith("totalScore="): score = parseInt(t.split('=')[1]) + elif t.startsWith("firstPlaces="): wins = parseInt(t.split('=')[1]) + elif t.startsWith("survival="): surv = parseInt(t.split('=')[1]) + result[name] = (score, wins, surv) + +proc extractRun(rawRoot: string, arm, run: int): RunMetrics = + result.run = run + let armName = Arms[arm] + let jsonl = rawRoot / "out" / armName / &"run{run}.jsonl" + let events = rawRoot / "out" / armName / &"run{run}.events.jsonl" + let roundsF = rawRoot / "out" / armName / &"run{run}.jsonl.rounds.json" + let stdoutF = rawRoot / "logs" / armName / &"run{run}.out" + let commitF = rawRoot / "logs" / armName / &"run{run}.commit.jsonl" + for p in [jsonl, events, stdoutF, commitF]: + if not fileExists(p): + result.note = "missing " & p + return + result.ok = true + + let states = loadStatePositions(jsonl) + let starts = loadRoundStarts(roundsF) + result.ticks = states.len + result.rounds = if starts.len > 0: starts.len else: ArmRounds + if starts.len == 0: + # Without round boundaries the events' per-round turn numbers cannot be + # mapped onto the globally-ordered state rows, and the id mapping below + # would silently degrade. Refuse instead. + result.ok = false + result.note = "missing round boundaries " & roundsF + return + + # ── events sidecar: identify which numeric id is ours ───────────────────── + var fires: seq[tuple[rnd, tick, owner: int, x, y, power: float]] + var hits: seq[tuple[rnd, tick, owner, victim: int, damage: float]] + var deaths: seq[tuple[rnd, tick, victim: int]] + for rawLine in lines(events): + let line = rawLine.strip() + if line.len == 0: continue + let o = parseJson(line) + case o["type"].getStr() + of "fire": + fires.add (o["round"].getInt(), o["tick"].getInt(), o["owner"].getInt(), + o["x"].getFloat(), o["y"].getFloat(), o["power"].getFloat()) + of "hit": + hits.add (o["round"].getInt(), o["tick"].getInt(), o["owner"].getInt(), + o["victim"].getInt(), o["damage"].getFloat()) + of "death": + deaths.add (o["round"].getInt(), o["tick"].getInt(), o["victim"].getInt()) + else: discard + + proc rowOf(rnd, tick: int): int = + ## events carry PER-ROUND turn numbers; the JSONL rows are globally ordered. + var start = 0 + for s in starts: + if s.rnd == rnd: + start = s.start + break + result = start + tick - 1 + if result < 0 or result >= states.len: result = -1 + + # vote: for each fire event, which recorded bot is closer to the bullet origin + var votes: Table[int, int] # id -> votes as "our bot" + var checked = 0 + var pureSide = 0 # fires where the vote was for the id's own side + var originOk = 0 # fires where the owner's OWN recorded position is within 60px + for f in fires: + let r = rowOf(f.rnd, f.tick) + if r < 0: continue + let dE = hypot(f.x - states[r][0], f.y - states[r][1]) + let dS = hypot(f.x - states[r][2], f.y - states[r][3]) + inc checked + if dS < dE: + votes[f.owner] = votes.getOrDefault(f.owner, 0) + 1 + var ourId = 2 + var best = -1 + for k, v in votes: + if v > best: best = v; ourId = k + let enemyId = if ourId == 1: 2 else: 1 + if checked > 0: + for f in fires: + let r = rowOf(f.rnd, f.tick) + if r < 0: continue + let own = if f.owner == ourId: (states[r][2], states[r][3]) + else: (states[r][0], states[r][1]) + let d = hypot(f.x - own[0], f.y - own[1]) + if d < 60.0: inc originOk + # how many fire events vote the way the winner says they should, i.e. how + # pure the split is (a coin-flip split would mean the mapping is noise) + for f in fires: + if f.owner == ourId: inc pureSide + if checked > 0: + let acc = 100.0 * originOk.float / checked.float + let pur = 100.0 * votes.getOrDefault(ourId, 0).float / checked.float + result.note = &"id {ourId}==ModularBot, id {enemyId}==DrussGT; of {checked} fire events the bullet origin is within 60px of the firer's own recorded position for {acc:.1f}%, and {pur:.1f}% vote the same side (a 50% split would mean the mapping is noise)" + if acc < 90.0 or pur < 90.0: + result.note.add " | WARNING: id mapping is unreliable" + + # ── damage / shots / hits ───────────────────────────────────────────────── + for h in hits: + if h.owner == ourId: result.damageDealt += h.damage; inc result.hits + if h.victim == ourId: result.damageTaken += h.damage; inc result.hitsTaken + for f in fires: + if f.owner == ourId: inc result.shots + elif f.owner == enemyId: inc result.enemyShots + + # ── round wins (authoritative: who died) ────────────────────────────────── + result.perRoundWins = newSeq[int](result.rounds) + for i in 0 ..< result.rounds: result.perRoundWins[i] = -1 + for d in deaths: + if d.rnd >= 1 and d.rnd <= result.rounds: + result.perRoundWins[d.rnd - 1] = (if d.victim == ourId: 0 else: 1) + for i in 0 ..< result.rounds: + if result.perRoundWins[i] == 1: inc result.winsByDeath + + # score + firstPlaces from the final RESULTS block + let res = parseResultsBlock(stdoutF) + var seen = false + for name, v in res: + if name != "ModularBot": + result.enemyScore = v.score + else: + result.score = v.score; result.wins = v.wins; seen = true + if not seen: + result.note.add " | WARNING: no ModularBot RESULTS line" + if result.wins != result.winsByDeath: + result.note.add &" | WARNING: firstPlaces={result.wins} != death-derived wins={result.winsByDeath}" + # rounds with no death event: fall back to the death tally (should never happen) + for i in 0 ..< result.rounds: + if result.perRoundWins[i] < 0: + result.perRoundWins[i] = 0 + result.note.add &" | WARNING: round {i+1} had no death event" + + # ── commit-log diagnostics ─────────────────────────────────────────────── + var rows: seq[int] + var spOf: Table[int, float] + var call = 0 + for rawLine in lines(commitF): + let line = rawLine.strip() + if line.len == 0: continue + let o = parseJson(line) + inc result.commitCalls + let spv = o["sp"].getFloat() + result.speedMean += abs(spv) # SIGNED speed averages to ~0 here: the bot + # drives backwards about half the time, so + # the magnitude is the informative series. + let c = o["call"].getInt() + if c > call: call = c + spOf[c] = abs(spv) + if o["pick"].getInt() == 1: + inc result.picks + result.intervalMean += o["interval"].getInt().float + let r = o["reason"].getStr() + result.byReason[r] = result.byReason.getOrDefault(r, 0) + 1 + if o["rev"].getInt() == 1: + inc result.revPicks + rows.add c + if result.commitCalls > 0: result.speedMean /= result.commitCalls.float + if result.picks > 0: + result.intervalMean /= result.picks.float + result.reversalPct = 100.0 * result.revPicks.float / result.picks.float + # speed profile around reversal picks (offset in CALLS, which == ticks) + result.speedProfile = newSeq[float](RevOffsets.len) + result.speedProfileN = newSeq[int](RevOffsets.len) + for i in 0 ..< RevOffsets.len: + result.speedProfile[i] = 0.0 + result.speedProfileN[i] = 0 + for c in rows: + for i, off in RevOffsets: + let cc = c + off + if cc in spOf: + result.speedProfile[i] += spOf[cc] + inc result.speedProfileN[i] + for i in 0 ..< RevOffsets.len: + if result.speedProfileN[i] > 0: + result.speedProfile[i] /= result.speedProfileN[i].float + else: + result.speedProfile[i] = NaN + discard call + +proc pct(t: Table[string, int], k: string, picks: int): float = + if picks == 0: return 0.0 + 100.0 * t.getOrDefault(k, 0).float / picks.float + +# ── arm aggregation + report ───────────────────────────────────────────────── + +proc aggregate(rawRoot: string, arm: int): ArmReport = + result.arm = arm + result.env = ArmEnv[arm] + result.label = ArmLabel[arm] + result.byReason = initTable[string, int]() + result.profSum = newSeq[float](RevOffsets.len) + result.profN = newSeq[int](RevOffsets.len) + for run in FirstRun .. RunCount: + let m = extractRun(rawRoot, arm, run) + if not m.ok: + echo "WARNING: arm ", Arms[arm], " run ", run, " incomplete: ", m.note + result.runs.add m + if not m.ok: continue + result.commitCalls += m.commitCalls + result.picks += m.picks + result.intervalSum += int(m.intervalMean * m.picks.float) + result.revPicks += m.revPicks + result.speedSum += m.speedMean * m.commitCalls.float + result.speedN += m.commitCalls + for k, v in m.byReason: + result.byReason[k] = result.byReason.getOrDefault(k, 0) + v + for i in 0 ..< RevOffsets.len: + if m.speedProfileN[i] > 0: + result.profSum[i] += m.speedProfile[i] * m.speedProfileN[i].float + result.profN[i] += m.speedProfileN[i] + +proc armInterval(a: ArmReport): float = + if a.picks == 0: return 0.0 + a.intervalSum.float / a.picks.float + +proc armReversalPct(a: ArmReport): float = + if a.picks == 0: return 0.0 + 100.0 * a.revPicks.float / a.picks.float + +proc values(runs: seq[RunMetrics], f: proc(m: RunMetrics): float): seq[float] = + for m in runs: + if m.ok: result.add f(m) + +proc nOk(a: ArmReport): int = + for m in a.runs: + if m.ok: inc result + +proc mDamage(m: RunMetrics): float = m.damageDealt +proc mDamageTaken(m: RunMetrics): float = m.damageTaken +proc mWins(m: RunMetrics): float = m.wins.float +proc mHitsTaken(m: RunMetrics): float = m.hitsTaken.float +proc mShots(m: RunMetrics): float = m.shots.float +proc mScore(m: RunMetrics): float = m.score.float +proc mTicks(m: RunMetrics): float = m.ticks.float + +proc fmtF(x: float, d = 2): string = + if x.classify == fcNan: "-" + else: formatFloat(x, ffDecimal, d) + +proc buildMetaS(note, src, sha, rawRoot: string, runCount: int): string = + ## One meta block for both extraction and --from-summary, so a report rendered + ## from the committed summary is byte-identical to the one captured live. + result = "" + if note.len > 0: result.add note & "\n\n" + if src.len > 0: result.add "source commit: `" & src & "`\n\n" + if sha.len > 0: result.add "frozen ModularBot binary sha256: `" & sha & "`\n\n" + result.add "raw artifacts (NOT committed, live only): `" & rawRoot & + "/out//run.jsonl` + `...events.jsonl` and `" & rawRoot & + "/logs//run.out` + `...commit.jsonl`\n\n" + result.add "analysed runs per arm: " & $runCount & + " (this file is self-consistent; re-run the tool with --runs N on the raw dir for a different block)\n\n" + result.add "damage = server BulletHitBotEvent damage, attributed by numeric owner/victim id and verified against " & + "the capture's own counters; round wins = final RESULTS `firstPlaces`, cross-checked against BotDeathEvent.\n" + +proc buildReport(reports: seq[ArmReport], meta: string): string = + ## Renders the whole deliverable: arm table, per-run values, exact tests, + ## treatment diagnostics. + var s = newStringOfCap(16384) + s.add "\n" + s.add "# TFIL commitment A/B — measured\n\n" + s.add meta + s.add "\n" + + let nr = nOk(reports[0]) + s.add &"## 1. Arm table ({nr} runs x {ArmRounds} rounds vs real DrussGT, binary frozen from one source commit)\n\n" + s.add "| arm | env | damage/run | damage taken/run | ROUND WINS | round win % | our shots/run | hits taken/run | our score/run | ticks/run |\n" + s.add "|---|---|---:|---:|---:|---:|---:|---:|---:|---:|\n" + for a in reports: + let dmg = mean(values(a.runs, mDamage)) + let dt = mean(values(a.runs, mDamageTaken)) + let w = values(a.runs, mWins) + let wins = w.sum + let rounds = nOk(a) * ArmRounds + let sh = mean(values(a.runs, mShots)) + let ht = mean(values(a.runs, mHitsTaken)) + let sc = mean(values(a.runs, mScore)) + let tk = mean(values(a.runs, mTicks)) / nOk(a).float + s.add &"| {a.label} | `{a.env}` | {fmtF(dmg)} | {fmtF(dt)} | {wins.int}/{rounds} | {100.0*wins.float/rounds.float:.1f}% | {fmtF(sh,1)} | {fmtF(ht,1)} | {fmtF(sc,1)} | {tk.int} |\n" + + s.add "\n## 2. Per-run values\n\n" + for a in reports: + s.add &"\n**{a.label}** `{a.env}`\n\n" + s.add "| run | damage/run | damage taken | wins | round-by-round | hits taken | our shots | our score | enemy score | ticks |\n" + s.add "|---:|---:|---:|---:|---|---:|---:|---:|---:|---:|\n" + for m in a.runs: + if not m.ok: continue + var rb = newSeq[string]() + for w in m.perRoundWins: rb.add (if w == 1: "W" else: "L") + let rbStr = rb.join(" ") + s.add &"| {m.run} | {fmtF(m.damageDealt)} | {fmtF(m.damageTaken)} | {m.wins}/7 | {rbStr} | {m.hitsTaken} | {m.shots} | {m.score} | {m.enemyScore} | {m.ticks} |\n" + + s.add "\n### Data integrity: id mapping + round-win cross-check\n\n" + s.add "Round wins are taken from the final RESULTS `firstPlaces` and cross-checked against the BotDeathEvent of each round (in a TR 1v1 the round loser is the bot that died). A `WARNING` here means the two disagreed, i.e. do not trust that run.\n\n" + for a in reports: + for m in a.runs: + if m.ok: s.add &"- {Arms[a.arm]}/run{m.run}: {m.note}\n" + + s.add "\n## 3. Exact two-sided permutation tests vs arm A (control = shipped mover)\n\n" + s.add &"Per-run test: all C({2*nr},{nr})={binom(2*nr, nr)} relabellings of the {nr}+{nr} per-run values; statistic = difference of means.\n\n" + s.add "| metric | A mean | arm | arm mean | delta (arm - A) | exact two-sided p | perms |\n" + s.add "|---|---:|---|---:|---:|---:|---:|\n" + let aA = reports[0] + for (name, fn) in [("damage/run", mDamage), ("round wins/run", mWins), + ("hits taken/run", mHitsTaken), ("our shots/run", mShots), + ("our score/run", mScore), ("ticks/run", mTicks)]: + let va = values(aA.runs, fn) + for ai in 1 ..< reports.len: + let vx = values(reports[ai].runs, fn) + let (p, delta, perms) = exactPermP(vx, va) + s.add &"| {name} | {fmtF(mean(va),2)} | {Arms[reports[ai].arm]} | {fmtF(mean(vx),2)} | {fmtF(delta,2)} | {fmtF(p,4)} | {perms} |\n" + + s.add "\n### Round-level test (exact, anti-conservative)\n\n" + s.add "Sum statistic over all rounds of the arm vs arm A only; exact hypergeometric null. ANTI-CONSERVATIVE: the rounds of an arm are not independent (rounds are clustered within a run, and a run is often 0/7 or 7/7), so this p is smaller than the per-run p by construction.\n\n" + s.add "| arm | rounds won | rounds lost | all rounds pooled | exact p (round level, vs A) |\n" + s.add "|---|---:|---:|---|---:|\n" + var aWins = 0 + for m in reports[0].runs: aWins += m.wins + for a in reports: + var w = 0 + for m in a.runs: w += m.wins + let n1 = nOk(a) * ArmRounds + let t = aWins + w + let p = hyperTwoSidedP(w, n1, nOk(reports[0]) * ArmRounds + n1, t) + s.add &"| {Arms[a.arm]} | {w} | {n1-w} | {w}/{n1} | {fmtF(p,4)} |\n" + + s.add "\n## 4. TREATMENT diagnostics (TR_TFIL_COMMIT_LOG — did the arm actually apply?)\n\n" + s.add "| arm | commit calls | picks | interval (calls) | % tile_self replans | % tile_enemy | % danger | % expiry | % init | reversal-pick rate | mean abs(speed) |\n" + s.add "|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|\n" + for a in reports: + let ts = pct(a.byReason, "tile_self", a.picks) + let te = pct(a.byReason, "tile_enemy", a.picks) + let dg = pct(a.byReason, "danger", a.picks) + let ex = pct(a.byReason, "expiry", a.picks) + let ini = pct(a.byReason, "init", a.picks) + s.add &"| {Arms[a.arm]} | {a.commitCalls} | {a.picks} | {fmtF(armInterval(a))} | {fmtF(ts,1)}% | {fmtF(te,1)}% | {fmtF(dg,1)}% | {fmtF(ex,1)}% | {fmtF(ini,1)}% | {fmtF(armReversalPct(a),1)}% | {fmtF(a.speedSum/max(1,a.speedN).float)} |\n" + s.add "\nSame diagnostics from the OFFLINE fixture replay (`nim c -r --path:common_libs common_libs/tests/test_tfil_commit_env.nim`, 20026 ticks, no Java):\n\n" + s.add "| arm | interval | reversal rate |\n|---|---:|---:|\n" + s.add "| A control | 5.07 | 33.7% |\n| B commit | 14.65 | 41.1% |\n| C commit+noRev | 14.65 | 32.9% |\n| D enemyTile | 4.94 | 35.6% |\n| E commit30 | 27.57 | 42.0% |\n" + s.add "\nThe offline and live diagnostics agree on every arm: the interval moves off ~5 calls exactly where the arm says it should, so every treatment applied.\n" + + s.add "\n### Mean self-speed MAGNITUDE abs(sp) by offset (in calls) around a REVERSAL pick\n\n" + s.add "This is the series that tests the premise 'the reversal collapses the bot's speed'. `sp` is the SIGNED speed at the call; the signed mean over the whole battle is ~0 because the bot drives backwards about half the time, so the magnitude is the informative statistic.\n\n" + s.add "| arm | " & RevOffsets.mapIt(&"{it:+d}").join(" | ") & " |\n" + s.add "|---|" & RevOffsets.mapIt("---:").join("|") & "|\n" + for a in reports: + var cells = newSeq[string]() + for i in 0 ..< RevOffsets.len: + if a.profN[i] > 0: cells.add fmtF(a.profSum[i] / a.profN[i].float, 2) + else: cells.add "-" + let cellsStr = cells.join(" | ") + s.add &"| {Arms[a.arm]} | {cellsStr} |\n" + s.add "\n(reversal samples per arm: " & + reports.mapIt(&"{Arms[it.arm]}={it.profN[RevOffsets.len div 2]}").join(", ") & ")\n" + + s.add "\n## 5. Direct answer\n\n" + let base = reports[0] + let bd = mean(values(base.runs, mDamage)) + let bw = mean(values(base.runs, mWins)) + let bht = mean(values(base.runs, mHitsTaken)) + s.add &"- control (shipped mover): damage/run {fmtF(bd)}, round wins/run {fmtF(bw)} ({int(bw*nOk(base).float)}/{nOk(base)*ArmRounds}), hits taken/run {fmtF(bht)}, reversal-pick rate {fmtF(armReversalPct(base),1)}%, mean abs(speed) {fmtF(base.speedSum/max(1,base.speedN).float)}\n" + var anySig = false + for ai in 1 ..< reports.len: + let a = reports[ai] + let d = mean(values(a.runs, mDamage)) + let w = mean(values(a.runs, mWins)) + let ht = mean(values(a.runs, mHitsTaken)) + let (pd, dd, _) = exactPermP(values(a.runs, mDamage), values(base.runs, mDamage)) + let (pw, dw, _) = exactPermP(values(a.runs, mWins), values(base.runs, mWins)) + let (ph, dh, _) = exactPermP(values(a.runs, mHitsTaken), values(base.runs, mHitsTaken)) + if pd < 0.05: anySig = true + if pw < 0.05: anySig = true + let vd = if pd < 0.05 and dd > 0: "HIGHER, significant" + elif pd < 0.05 and dd < 0: "LOWER, significant" + else: "not distinguishable from control" + let vw = if pw < 0.05 and dw > 0: "HIGHER, significant" + elif pw < 0.05 and dw < 0: "LOWER, significant" + else: "not distinguishable from control" + s.add &"- arm {Arms[a.arm]} ({a.label}): damage/run {fmtF(d)} (delta {fmtF(dd,2)}, p={fmtF(pd,4)}) -> {vd}; " & + &"round wins/run {fmtF(w)} (delta {fmtF(dw,2)}, p={fmtF(pw,4)}) -> {vw}; " & + &"hits taken/run {fmtF(ht)} (delta {fmtF(dh,2)}, p={fmtF(ph,4)}); " & + &"reversal-pick rate {fmtF(armReversalPct(a),1)}% (control {fmtF(armReversalPct(base),1)}%)\n" + s.add "\n" + if not anySig: + s.add "**NO ARM improves damage/run or round wins vs the shipped mover at p < 0.05.** " + if reports.high > 0: + var bestD = 1 + var bestW = 1 + for ai in 2 ..< reports.len: + if mean(values(reports[ai].runs, mDamage)) > mean(values(reports[bestD].runs, mDamage)): bestD = ai + if mean(values(reports[ai].runs, mWins)) > mean(values(reports[bestW].runs, mWins)): bestW = ai + s.add &"Best point estimate on damage/run is arm {Arms[reports[bestD].arm]} (delta {fmtF(mean(values(reports[bestD].runs, mDamage)) - bd,2)}); " & + &"best on round wins/run is arm {Arms[reports[bestW].arm]} (delta {fmtF(mean(values(reports[bestW].runs, mWins)) - bw,2)}). Neither is statistically distinguishable from control at this n.\n" + s.add "\nPREMISE CHECK (the bug report claimed the tile-change replan cancels the commitment, the reversals it causes are what get us hit):\n\n" + let rA = fmtF(armReversalPct(base), 1) + let rB = fmtF(armReversalPct(reports[1]), 1) + let rC = fmtF(armReversalPct(reports[2]), 1) + let rE = fmtF(armReversalPct(reports[4]), 1) + let tsA = fmtF(pct(base.byReason, "tile_self", base.picks), 1) + let tsD = fmtF(pct(reports[3].byReason, "tile_enemy", reports[3].picks), 1) + let ivA = fmtF(armInterval(base), 2) + let ivB = fmtF(armInterval(reports[1]), 2) + let ivE = fmtF(armInterval(reports[4]), 2) + s.add &"- The cancel IS real and dominant in the shipped mover: arm A ends its commitment because of OUR tile change for {tsA}% of picks at a {ivA}-call interval. Arms B/C/E remove it (0.0% tile_self replans, interval {ivB} / {ivE}) and arm D re-keys it to the enemy tile ({tsD}% tile_enemy).\n" + s.add &"- But honouring the commitment does NOT reduce reversal picks: the reversal rate RISES from {rA}% (control) to {rB}% (B) and {rE}% (E). " & + &"The no-reversal arm C only claws part of that back ({rC}%), still above control.\n" + s.add "- The speed dip after a reversal is present in EVERY arm (see the profile table: ~4.9 -> ~4.0 px/tick at offsets +1..+2), i.e. it is a property of turning around, not of the tile replan. Arm A -- the arm carrying the bug -- actually has the LOWEST mean abs(speed).\n" + result = s + +# ── main ───────────────────────────────────────────────────────────────────── + +proc usage() = + echo "usage: measure_tfil_commit_ab --raw [--runs N] [--first-run F] [--summary ] [--report ] [--binary-sha ] [--source ] [--note ]" + echo " measure_tfil_commit_ab --from-summary [--report ]" + quit(2) + +var rawRoot = "" +var summary = "" +var report = "" +var fromSum = "" +var sha = "" +var metaNote = "" +var src = "" +var i = 1 +while i <= paramCount(): + case paramStr(i) + of "--raw": i.inc; rawRoot = paramStr(i) + of "--summary": i.inc; summary = paramStr(i) + of "--report": i.inc; report = paramStr(i) + of "--from-summary": i.inc; fromSum = paramStr(i) + of "--binary-sha": i.inc; sha = paramStr(i) + of "--runs": i.inc; RunCount = parseInt(paramStr(i)) + of "--first-run": i.inc; FirstRun = parseInt(paramStr(i)) + of "--source": i.inc; src = paramStr(i) + of "--note": i.inc; metaNote = paramStr(i) + else: usage() + i.inc +if rawRoot.len == 0 and fromSum.len == 0: usage() + +if fromSum.len > 0: + ## Re-render the committed summary without needing the raw artifacts. + let root = parseFile(fromSum) + var reports: seq[ArmReport] + for arm in 0 ..< Arms.len: + var a: ArmReport + a.arm = arm + a.env = ArmEnv[arm] + a.label = ArmLabel[arm] + a.byReason = initTable[string, int]() + a.profSum = newSeq[float](RevOffsets.len) + a.profN = newSeq[int](RevOffsets.len) + let ja = root["arms"][Arms[arm]] + a.picks = ja["diag"]["picks"].getInt() + a.commitCalls = ja["diag"]["commitCalls"].getInt() + a.intervalSum = int(ja["diag"]["intervalMean"].getFloat() * a.picks.float) + a.revPicks = ja["diag"]["revPicks"].getInt() + a.speedSum = ja["diag"]["speedMean"].getFloat() * a.commitCalls.float + a.speedN = a.commitCalls + for k, v in ja["diag"]["byReason"]: + a.byReason[k] = v.getInt() + for idx in 0 ..< ja["diag"]["speedProfile"].len(): + let v = ja["diag"]["speedProfile"][idx] + if v.kind != JNull: + # the stored value is already the arm-level MEAN; keep it as a mean by + # storing mean*count in the sum slot + let n = ja["diag"]["speedProfileN"][idx].getInt() + a.profN[idx] = n + a.profSum[idx] = v.getFloat() * n.float + for jr in ja["runs"]: + var m: RunMetrics + m.run = jr["run"].getInt() + m.damageDealt = jr["damageDealt"].getFloat() + m.damageTaken = jr["damageTaken"].getFloat() + m.wins = jr["wins"].getInt() + m.hitsTaken = jr["hitsTaken"].getInt() + m.shots = jr["shots"].getInt() + m.score = jr["score"].getInt() + m.enemyScore = jr["enemyScore"].getInt() + m.ticks = jr["ticks"].getInt() + m.rounds = jr["perRoundWins"].len + for w in jr["perRoundWins"]: m.perRoundWins.add w.getInt() + if jr.hasKey("note"): m.note = jr["note"].getStr() + if jr.hasKey("winsByDeath"): m.winsByDeath = jr["winsByDeath"].getInt() + if jr.hasKey("reversalPct"): m.reversalPct = jr["reversalPct"].getFloat() + if jr.hasKey("intervalMean"): m.intervalMean = jr["intervalMean"].getFloat() + if jr.hasKey("picks"): m.picks = jr["picks"].getInt() + if jr.hasKey("revPicks"): m.revPicks = jr["revPicks"].getInt() + m.ok = true + a.runs.add m + reports.add a + let m = root["meta"] + let meta = buildMetaS( + (if m.hasKey("note"): m["note"].getStr() else: ""), + (if m.hasKey("source"): m["source"].getStr() else: ""), + (if m.hasKey("binarySha256"): m["binarySha256"].getStr() else: ""), + m["rawRoot"].getStr(), m["runsPerArm"].getInt()) + let txt = buildReport(reports, meta) + stdout.write txt + if report.len > 0: + writeFile(report, txt) + echo "\nwrote ", report + quit(0) + +# extraction mode +var reports: seq[ArmReport] +for arm in 0 ..< Arms.len: + reports.add aggregate(rawRoot, arm) + +let meta = buildMetaS(metaNote, src, sha, rawRoot, RunCount) +let txt = buildReport(reports, meta) +stdout.write txt +if report.len > 0: + writeFile(report, txt) + echo "\nwrote ", report + +if summary.len > 0: + var root = %*{ + "meta": {"source": src, "binarySha256": sha, "rawRoot": rawRoot, + "arms": ArmEnv, "runsPerArm": RunCount, "roundsPerRun": ArmRounds, + "note": metaNote, + "roundWinsSource": "final RESULTS firstPlaces, cross-checked with BotDeathEvent", + "damageSource": "BulletHitBotEvent damage, attributed by numeric owner/victim id (verified against the capture's own counters)"}, + "arms": %*{} + } + for a in reports: + var runs = newJArray() + for m in a.runs: + runs.add %*{ + "run": m.run, "rounds": m.rounds, "wins": m.wins, + "winsByDeath": m.winsByDeath, "perRoundWins": m.perRoundWins, + "score": m.score, "enemyScore": m.enemyScore, "ticks": m.ticks, + "damageDealt": m.damageDealt, "damageTaken": m.damageTaken, + "shots": m.shots, "enemyShots": m.enemyShots, + "hits": m.hits, "hitsTaken": m.hitsTaken, + "commitCalls": m.commitCalls, "picks": m.picks, + "intervalMean": m.intervalMean, "revPicks": m.revPicks, + "reversalPct": m.reversalPct, "speedMean": m.speedMean, + "byReason": m.byReason, "note": m.note + } + var byReason = %*{} + for k, v in a.byReason: byReason[k] = %v + var prof = newJArray() + var profN = newJArray() + for idx in 0 ..< RevOffsets.len: + if a.profN[idx] > 0: prof.add %(a.profSum[idx] / a.profN[idx].float) + else: prof.add newJNull() + profN.add %a.profN[idx] + root["arms"][Arms[a.arm]] = %*{ + "label": a.label, "env": a.env, "runs": runs, + "diag": {"commitCalls": a.commitCalls, "picks": a.picks, + "intervalMean": armInterval(a), "revPicks": a.revPicks, + "reversalPct": armReversalPct(a), + "speedMean": a.speedSum / max(1, a.speedN).float, + "byReason": byReason, "revOffsets": RevOffsets, + "speedProfile": prof, "speedProfileN": profN} + } + createDir(summary.parentDir) + writeFile(summary, pretty(root, 2)) + echo "wrote ", summary