Report level gains as events and add LearnSpell for spell-book growth - #37
Merged
Conversation
An XP award that crossed a level threshold was invisible as a moment: the gain surfaced only as level_after on the XP award event, so a front end had no event to announce the level on and no hit-point or title facts to announce it with. Every apply_xp call site in the session (the end-of-adventure award, the immediate timing, and the referee's AwardXP) now reports through one module helper, _xp_award_events, which emits the member's XpAwardedEvent and, when the award leveled them, a CharacterLeveledUpEvent (session.level.gained, player visibility) immediately after it — levels before and after, hp gained, the raw hit die roll (None past name level), whether CON applied, and the class's level title at the new level via the new level_title helper. Leveling also opens spell-book capacity with no in-session way to use it, so LearnSpell drives the core add_spell_to_book through the command loop: town and exploring, zero game time, the existing SpellBookUpdatedEvent, no new rejection codes. The capacity rule itself moves into one home, open_book_capacity (row capacity minus spells held, floored at zero, () for non-arcane classes), which add_spell_to_book now consults with behavior unchanged. The phase5_milestone golden regenerates because the milestone's return award now reports the thief's level gain: the diff is exactly one inserted leveled_up event and its transcript line (plus the engine_version stamp of the producing engine moving 1.3.0 -> 1.4.0); no dice roll or other value changed anywhere — the level-up hp draw already existed. The hoard and NPC goldens regenerated byte-identical. Claude-Session: https://claude.ai/code/session_01TW7yQtphaXFdex8ZutgDLd
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Levels have always happened silently: every XP award surface discarded
XpAwardResult.level_up, so the only level signal on the wire waslevel_afterburied in the XP line — no hit points, no title, no moment. And a leveled arcane caster had no in-session path to the new spell their widened book allows:add_spell_to_bookexisted but no command wrapped it, so any front end calling it directly would bypass the command log and break replay.CharacterLeveledUpEvent(session.level.gained, player visibility): fires immediately after the member's ownXpAwardedEventwhenever an award crosses a threshold, on all three award surfaces (end-of-adventure, immediate timing, refereeAwardXP) — routed through one_xp_award_eventshelper so the ordering is structural. Carrieslevel_before/level_after,hp_gained,hp_roll(Nonepast name level),con_applied, and the class's leveltitle(Nonepast the printed list).LearnSpellcommand (town + exploring, no time cost): drivesadd_spell_to_bookthrough the command loop so book growth is validated, logged, and replayed like everything else. Emits the existingSpellBookUpdatedEvent; no new rejection codes.open_book_capacity(per-spell-level open book slots, zero-floored for over-capacity drained books,()for non-arcane) —add_spell_to_book's capacity check now consults it, giving front ends the same rule for rendering learnable picks. Pluslevel_titlefor the IndexError-guarded title lookup.Golden:
phase5_milestone.jsonregenerated — the diff is exactly one insertedleveled_upevent (the thief's level 2, Footpad, +4 hp), its transcript line, and theengine_versionstamp catching up to 1.4.0. No dice value, stream state, checkpoint, or command-log byte moved; all other goldens byte-identical.Verification: 1644 passed / 58 skipped;
ruff format --checkclean andruff checkat exact parity with main;mkdocs build(strict) green, which proves the message-template and command-docstring gates accept the additions.https://claude.ai/code/session_01TW7yQtphaXFdex8ZutgDLd