Curses screens, windows and pads are typed as curses.window, and the
widgets that may get no parent window take curses.window | None.
- EventHandler is generic over a ParamSpec, so each handler declares
the arguments it passes, and MenuView is generic over the item its
on_confirm hands out. Choice callbacks take no arguments.
- Scrollable is generic over its items, which DataTable and ListBox
fix to tuple[str, str] and str.
- random.choice() and choice_low() are generic over the sequence, so
the casts at their call sites are gone. BasicUserInterface.menu()
and util.first() are generic too.
- Signal callbacks and their payloads are Callable[..., object] and
object, since emit() only forwards them.
- JSON values in game_data are object and narrowed by isinstance,
and data files are Traversable.
- Signal handlers take an int and a FrameType | None, and
list_players() takes a TextIO.
- The focus_standout() context managers return
Generator[None, None, None].
Any remains in three places: Player.__setstate__, whose pickled
__dict__ holds values of every type; Focusable.__init__, a mixin that
passes *args and **kwargs through to the next class; and the values
load_game_data() collects, whose element type depends on the field
each one fills.
Python 3.10 is the minimum, so annotations use `X | None`, `list[X]`
and friends, enforced by ruff's pyupgrade rules. The remaining typing
names are imported directly, with abstract types coming from
collections.abc. first() is now generic over the item and default.
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.
The word lists (monsters, spells, equipment, races, classes, titles and
so on) now live in pqcli/data/*.json instead of pqcli/config.py. A
GameData object loaded at startup is passed to the simulation and UIs.
Users can replace any list by putting NAME.json in
$XDG_CONFIG_HOME/pqcli/data/ or a directory given with --data-dir.
--dump-data DIR copies the built-in files out for editing. Malformed
files are reported and pqcli exits before starting the UI.
Combat task descriptions come from the new combat_verbs list, with
{target} standing for the enemy. The built-in list holds only "Executing
{target}", and a single verb consumes no random roll, so stock gameplay
is unchanged. Recorded traces in tests/fixtures pin this down.
Monster, Race and Class stay in pqcli.config so existing saves still
load.
Resolves#3