Repository navigation
Releases
Releases · Sparks-code1/SparksGaming
0.3.7
A comeback power is claimed against the copy it is written to. The pool is exclusive: no two factions may ever hold the same power, and it is a permanent campaign mark. Both claim paths took the first id THEIR OWN copy said was free, and that copy is a snapshot. Two players knocked out by one stroke choose on two machines. Neither has heard of the other's claim, so both modals offer the same power and both take it -- and the merge cannot help, because nothing about the result looks wrong to it: one faction key added on each side, kept, and one id added to claimedComebackPowers twice, deduped. Two factions holding one power and a claimed list saying it went once. Nothing downstream can tell that happened. claimComebackPower makes the pick against whatever copy it is handed. As `reapply` that is the freshly-read row, so a preference still free is honoured, one taken since is replaced by the next free power, and a faction that already holds one is returned untouched -- which is what makes it safe to re-run. RE-PICKING RATHER THAN REFUSING is a decision, not an oversight: refusing leaves an eliminated player with no comeback power at all, which is worse than a different one. It is not silent either -- the swap says so on their screen, because it is a permanent mark on their own faction. Both paths go through the one rule now, so the AI's automatic pick and the human's choice cannot drift apart on exclusivity. The rule is in gameLogic, parameterised by the pool so lib does not import a component, and unit-tested rather than pinned by source. Six sabotages: the preference trusted when taken, the already-holds guard removed, an exhausted pool handing out the last power again, the human path deciding from a snapshot, and each of the two rebuilds unwired separately. One note on the tests. Unwiring the AI's rebuild passed at first: both paths spell it identically, so `.test()` was satisfied by the human's and the pin sat green over the bug. It counts the occurrences now, and each path fails on its own. Found by the sabotage meant to prove the pin worked -- which is the argument for running it. 113/113 unit, 24/24 e2e, build green, edge 16/16. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
0.3.6
v0.3.6 Two-seat online harness: the helpers work, the spec stops one click s…
0.3.5
The delete refuses, says so, and is no longer offered. An RLS-refused DELETE is not an error: it matches no rows and returns success. deleteCampaign checked only `error`, so the picker's ✕ opened a confirmation, took the press, refreshed the list, and left the campaign sitting there — a control that appeared to do the one irreversible thing in the app and did nothing at all. BOTH HALVES. deleteCampaign counts what it removed and raises on zero, so the API tells the truth to whatever calls it. And the ✕ is gone: with no DELETE policy nobody is entitled, not even the campaign's owner, so there is nobody to offer it to. The confirmation row, the busy state and the handler went with it rather than being left behind implying a feature that no longer exists. deleteCampaign is kept, uncalled, because the refusal it now reports is the useful part — if a policy is ever added, or a script or admin path wants it, it behaves correctly instead of lying. Its doc says so. The RLS check gained the assertion behind all of it: a refused delete is silent but countable — no error, count zero — which is the exact shape the fixed deleteCampaign reads. Three sabotages on the client pins, all red: dropping the count option, dropping the raise, and putting a delete call back in the picker. The migration records the decision where the two options were written down, and its breakage list no longer warns about a button that is gone. 110 suites, 23 browser tests, 12 RLS checks. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
0.3.4
The AI's draft fallback stops being silent cover. The reinforce step drops the draft on the first territory the player owns when aiReinforcePlacements offers nothing. It has to: troopsToPlace > 0 blocks the phase advance, so a troop that never lands is a wedged turn. BUT IT IS COVER FOR A PLANNER BUG, and it was silent. That is how it was found: a browser spec stubbing aiReinforcePlacements, aiAttackPlan and aiFortifyMove all to nothing passed GREEN. The console proved the stubs were called and the roster proved what happened — territories 1 to 1, so the attack stub really did bite, and troops 8 to 11, exactly the three-troop draft, placed by this line. A broken planner would not stall the game. It would quietly draft onto the same square every turn for the rest of a campaign, playing worse and worse while looking like it worked. In unmodified code the fallback never fires: the planner's one empty return is owned.length === 0 || troops <= 0, this is its only caller, it passes troops: 1 and only asks while troops are owed. So the planner comes back empty only when the player owns nothing — and then the fallback is empty too. Cover that is dead in play and alive only when something is wrong is exactly the cover that should speak up. It warns now, naming the territory it fell back to. Verified both ways in the browser: silent across a healthy AI turn, three warnings across a turn with the planner stubbed. Pinned in aitest so the warn cannot be deleted while the `??` stays — that is the precise edit that restores the silence — and so it cannot be made unconditional, which would be noise nobody reads. Three sabotages, three reds. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
0.3.3
v0.3.3 The note on the basic Bene Gesserit follow stops saying the advanced …