random: port Alea, one generator per character

pqcli used Python's global Mersenne Twister, seeded once from the clock.
Upstream uses Johannes Baagøe's Alea, with Random(n) = uint32() % n, and
keeps the generator state with each character. The port matches the JS
output bit for bit, including UTF-16 seeding of non-BMP characters.

Each Player now owns an Alea that is pickled with the save, and
Simulation.tick draws from it. Saves without one get a fresh generator.
As upstream does, a new character's game continues from the state the
kept stat roll was made from.

The parity traces are re-recorded, since every random number changes.
This commit is contained in:
Dash committed 2026-10-17 15:55:50 +02:00
1 parent f1cdff8140
commit 9c44d69633
11 files changed
+5741 -5452

No files matched your search

@@ -55,7 +55,7 @@ class ChooseCharacterStatsView(BaseView):
elif self._active_widget == 1:
self._stats = self._stats_builder.unroll()
elif self._active_widget == 2:
self.on_confirm(self._stats)
self.on_confirm(self._stats, self._stats_builder.seed)
elif self._active_widget == 3:
self.on_cancel()
else:
@@ -68,7 +68,7 @@ class ChooseCharacterStatsView(BaseView):
self._stats = self._stats_builder.unroll()
elif key == curses.KEY_F10:
self.on_confirm(self._stats)
self.on_confirm(self._stats, self._stats_builder.seed)
elif key in KEYS_CYCLE:
self._active_widget = (self._active_widget + 1) % 4