ui/phase-27: root-cause aggregation of duplicate (race, className) rows

Legacy reports list the same `(race, className)` pair across several
roster rows; the engine likewise creates one ShipGroup per arrival.
Both the legacy parser and `TransformBattle` were keyed on shipClass
without summing — only the last row / group's counts survived, so a
protocol's destroy count appeared to exceed the recorded initial
roster. The UI worked around this with phantom-frame logic.

Both parser and engine now SUM `Number`/`NumberLeft` across rows /
groups sharing the same class; the phantom-frame workaround is gone.
KNNTS041 turn 41 planet #7 reconciles: `Nails:pup` 1168 initial −
86 survivors = 1082 destroys.

The engine's previously latent nil-map write on `bg.Tech` (would
have paniced on any group with non-empty Tech) is fixed in the same
patch — it blocked the aggregation regression test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ilia Denisov
2026-05-13 18:52:40 +02:00
parent 2e7478f5ea
commit bd11cd80da
9 changed files with 344 additions and 133 deletions
+61 -61
View File
@@ -14743,10 +14743,10 @@
"race": "KnightErrants",
"className": "PeaceShip",
"tech": {
"DRIVE": 9.1
"DRIVE": 9.09
},
"num": 50,
"numLeft": 50,
"num": 52,
"numLeft": 52,
"loadType": "",
"loadQuantity": 0,
"inBattle": true
@@ -15498,11 +15498,11 @@
"race": "Frightners",
"className": "moan",
"tech": {
"DRIVE": 7.79,
"SHIELDS": 5.15
"DRIVE": 7.5,
"SHIELDS": 4.85
},
"num": 85,
"numLeft": 85,
"num": 259,
"numLeft": 259,
"loadType": "",
"loadQuantity": 0,
"inBattle": true
@@ -15988,13 +15988,13 @@
"race": "Slimes",
"className": "Fly_1",
"tech": {
"DRIVE": 5.79
"DRIVE": 6.02
},
"num": 105,
"numLeft": 105,
"num": 348,
"numLeft": 348,
"loadType": "",
"loadQuantity": 0,
"inBattle": true
"inBattle": false
},
"6": {
"race": "Slimes",
@@ -16325,10 +16325,10 @@
"race": "KnightErrants",
"className": "PeaceShip",
"tech": {
"DRIVE": 9.1
"DRIVE": 10.62
},
"num": 99,
"numLeft": 99,
"num": 100,
"numLeft": 100,
"loadType": "",
"loadQuantity": 0,
"inBattle": true
@@ -16497,10 +16497,10 @@
"race": "Enoxes",
"className": "Gnat",
"tech": {
"DRIVE": 11.4
"DRIVE": 9.07
},
"num": 26,
"numLeft": 26,
"num": 167,
"numLeft": 167,
"loadType": "",
"loadQuantity": 0,
"inBattle": true
@@ -18583,8 +18583,8 @@
"tech": {
"DRIVE": 5.16
},
"num": 19,
"numLeft": 19,
"num": 1026,
"numLeft": 1026,
"loadType": "",
"loadQuantity": 0,
"inBattle": true
@@ -19025,10 +19025,10 @@
"CARGO": 1.1,
"DRIVE": 10.85
},
"num": 1,
"num": 3,
"numLeft": 0,
"loadType": "COL",
"loadQuantity": 1.4,
"loadQuantity": 1.855,
"inBattle": true
},
"12": {
@@ -19140,9 +19140,9 @@
"race": "Koreans",
"className": "d",
"tech": {
"DRIVE": 9.87
"DRIVE": 2.4
},
"num": 112,
"num": 113,
"numLeft": 0,
"loadType": "",
"loadQuantity": 0,
@@ -29824,8 +29824,8 @@
"SHIELDS": 3.19,
"WEAPONS": 3.97
},
"num": 1,
"numLeft": 1,
"num": 2,
"numLeft": 2,
"loadType": "",
"loadQuantity": 0,
"inBattle": true
@@ -29893,10 +29893,10 @@
"race": "Nails",
"className": "pup",
"tech": {
"DRIVE": 4.98
"DRIVE": 4.97
},
"num": 88,
"numLeft": 6,
"num": 1168,
"numLeft": 86,
"loadType": "",
"loadQuantity": 0,
"inBattle": true
@@ -46450,10 +46450,10 @@
"race": "Ricksha",
"className": "Dron",
"tech": {
"DRIVE": 7.63
"DRIVE": 3.2
},
"num": 647,
"numLeft": 647,
"num": 1211,
"numLeft": 1211,
"loadType": "",
"loadQuantity": 0,
"inBattle": true
@@ -46462,11 +46462,11 @@
"race": "Ricksha",
"className": "HDron",
"tech": {
"DRIVE": 7.63,
"DRIVE": 6.88,
"SHIELDS": 3.95
},
"num": 88,
"numLeft": 88,
"num": 112,
"numLeft": 112,
"loadType": "",
"loadQuantity": 0,
"inBattle": true
@@ -46690,10 +46690,10 @@
"race": "KnightErrants",
"className": "PeaceShip",
"tech": {
"DRIVE": 9.09
"DRIVE": 9.1
},
"num": 2,
"numLeft": 2,
"num": 164,
"numLeft": 163,
"loadType": "",
"loadQuantity": 0,
"inBattle": true
@@ -48389,10 +48389,10 @@
"className": "FS-6",
"tech": {
"DRIVE": 11.4,
"SHIELDS": 5.64
"SHIELDS": 5.1
},
"num": 48,
"numLeft": 48,
"num": 72,
"numLeft": 72,
"loadType": "",
"loadQuantity": 0,
"inBattle": true
@@ -48449,10 +48449,10 @@
"race": "Enoxes",
"className": "Gnat",
"tech": {
"DRIVE": 11.4
"DRIVE": 8.4
},
"num": 100,
"numLeft": 100,
"num": 101,
"numLeft": 101,
"loadType": "",
"loadQuantity": 0,
"inBattle": true
@@ -48466,10 +48466,10 @@
"SHIELDS": 2.13,
"WEAPONS": 4.22
},
"num": 1,
"numLeft": 1,
"num": 2,
"numLeft": 2,
"loadType": "COL",
"loadQuantity": 5.73,
"loadQuantity": 5.375,
"inBattle": true
},
"8": {
@@ -48479,10 +48479,10 @@
"CARGO": 1,
"DRIVE": 11.4,
"SHIELDS": 5.1,
"WEAPONS": 5.77
"WEAPONS": 5.44
},
"num": 1,
"numLeft": 1,
"num": 2,
"numLeft": 2,
"loadType": "COL",
"loadQuantity": 1.05,
"inBattle": true
@@ -48539,10 +48539,10 @@
"race": "Flagist",
"className": "Drone",
"tech": {
"DRIVE": 8.49
"DRIVE": 6.08
},
"num": 1,
"numLeft": 1,
"num": 2,
"numLeft": 2,
"loadType": "",
"loadQuantity": 0,
"inBattle": false
@@ -48587,10 +48587,10 @@
"race": "Enoxes",
"className": "Gnat",
"tech": {
"DRIVE": 11.4
"DRIVE": 9.07
},
"num": 49,
"numLeft": 49,
"num": 50,
"numLeft": 50,
"loadType": "",
"loadQuantity": 0,
"inBattle": true
@@ -48662,10 +48662,10 @@
"race": "KnightErrants",
"className": "PeaceShip",
"tech": {
"DRIVE": 9.09
"DRIVE": 8.71
},
"num": 1,
"numLeft": 1,
"num": 63,
"numLeft": 63,
"loadType": "",
"loadQuantity": 0,
"inBattle": true
@@ -48918,10 +48918,10 @@
"race": "KnightErrants",
"className": "PeaceShip",
"tech": {
"DRIVE": 8.71
"DRIVE": 5.6
},
"num": 1,
"numLeft": 1,
"num": 158,
"numLeft": 158,
"loadType": "",
"loadQuantity": 0,
"inBattle": true