Add homelessness and renting to the housing ladder#105
Merged
Conversation
2 tasks
Previously every player already owned the cheapest home tier for free, so there was no reason to ever be homeless or rent - "owning" the starter tier cost nothing. This extends src/housing into a full ladder: Homeless (free, low energy cap) -> Rented Room (recurring daily cost, no equity) -> owned tiers (Driftwood Shack now actually costs money, through Waterfront Manor). - housing.netCostToMove/moveHome generalize cleanly across the whole ladder: a rung's "price" only applies if it's owned, and "proceeds" only apply if the rung being left was owned, so homeless<->renting<->owned all fall out of the same formula without special-casing. - New housing.runDailyRent, wired into TimeService.increaseDay() alongside bank interest/business production/investment income: rent is charged automatically each morning, and an unaffordable payment evicts the player back to Homeless (mirrors business' "workers quit when payroll can't be met" - shrinks instead of going into debt). - player.homeTier now starts at 0 (Homeless) instead of 1; Stats gains totalRentPaid, and highestHomeTier now starts at 0 to match. - New "Roof Over Your Head" milestone for reaching renting or better; "Waterfront Manor" threshold updated for the extra two rungs. Stacked on feature/home-ownership. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
009b629 to
fd9f809
Compare
dmccoystephenson
added a commit
that referenced
this pull request
Jul 11, 2026
…g stack Multi-angle review of the home-ownership/investments/homeless-and-renting stack (PRs #104, #105) turned up several real issues, all fixed here: Correctness: - Player.__init__ hardcoded energy=100, but a fresh player's homeTier=0 (Homeless) caps at 60 - new players started 40 energy over their own cap. Now derived from housing.HOUSING_TIERS[0]["maxEnergy"]. - moveHome() changed player.homeTier but never reclamped player.energy to the new (possibly lower) cap, so downgrading or being evicted left the player over-cap until their next sleep. Eviction is now routed through moveHome() (instead of duplicating the tier assignment inline), so it gets this reconciliation automatically. - investments.typeInfo()/housing.tierInfo() silently wrapped around via Python's negative-index behavior on an out-of-range id instead of failing loudly; both now raise ValueError, and schemas/player.json gained the matching maximum bounds on homeTier/rentalProperties. UX (eviction was completely silent - the player could lose their room and never be told): - TimeService.increaseDay()/increaseTime() now return {"evicted": bool}. Every place a day can roll over (Home.sleep, Tavern.getDrunk, a multi-hour Docks.fish trip, and the main game loop as a catch-all for everything else) surfaces housing.EVICTION_MESSAGE when it happens. - "Move to Rented Room (free)" was misleading - it now discloses the recurring $10/day rent up front, before the player commits. "+$X" cash-back downgrade label is now "get $X back". - Energy is now shown as "current/cap" (not just current) in all three front-ends (console, pygame, web), so the housing energy-cap benefit is visible without opening Manage Home. Cleanup: - netCostToMove() simplified (dict.get defaults already encode "only owned tiers have a cost/resaleValue", the status-gated ternaries were redundant). - home.py's repeated adjacent-tier-move computation (built separately in _housingStatus() and manageHome()) is now computed once via _availableMoves(). - manageInvestments() combined its two PROPERTY_TYPES loops (buy options, then sell options) into one pass, backed by a new investments.ownedCounts() that computes the whole portfolio's counts in one pass instead of an O(types) list.count() scan per type per render. Deliberately not changed (flagged in review, judged not worth the churn): menu-loop duplication across manageHome/manageInvestments/manageBusiness, investments' 1-based vs housing's 0-based tier indexing, and displayStats()'s hand-rolled optional-block style vs achievements.py's data-driven MILESTONES pattern. Stacked on feature/homeless-and-renting. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
dmccoystephenson
added a commit
that referenced
this pull request
Jul 11, 2026
…g stack (#106) Multi-angle review of the home-ownership/investments/homeless-and-renting stack (PRs #104, #105) turned up several real issues, all fixed here: Correctness: - Player.__init__ hardcoded energy=100, but a fresh player's homeTier=0 (Homeless) caps at 60 - new players started 40 energy over their own cap. Now derived from housing.HOUSING_TIERS[0]["maxEnergy"]. - moveHome() changed player.homeTier but never reclamped player.energy to the new (possibly lower) cap, so downgrading or being evicted left the player over-cap until their next sleep. Eviction is now routed through moveHome() (instead of duplicating the tier assignment inline), so it gets this reconciliation automatically. - investments.typeInfo()/housing.tierInfo() silently wrapped around via Python's negative-index behavior on an out-of-range id instead of failing loudly; both now raise ValueError, and schemas/player.json gained the matching maximum bounds on homeTier/rentalProperties. UX (eviction was completely silent - the player could lose their room and never be told): - TimeService.increaseDay()/increaseTime() now return {"evicted": bool}. Every place a day can roll over (Home.sleep, Tavern.getDrunk, a multi-hour Docks.fish trip, and the main game loop as a catch-all for everything else) surfaces housing.EVICTION_MESSAGE when it happens. - "Move to Rented Room (free)" was misleading - it now discloses the recurring $10/day rent up front, before the player commits. "+$X" cash-back downgrade label is now "get $X back". - Energy is now shown as "current/cap" (not just current) in all three front-ends (console, pygame, web), so the housing energy-cap benefit is visible without opening Manage Home. Cleanup: - netCostToMove() simplified (dict.get defaults already encode "only owned tiers have a cost/resaleValue", the status-gated ternaries were redundant). - home.py's repeated adjacent-tier-move computation (built separately in _housingStatus() and manageHome()) is now computed once via _availableMoves(). - manageInvestments() combined its two PROPERTY_TYPES loops (buy options, then sell options) into one pass, backed by a new investments.ownedCounts() that computes the whole portfolio's counts in one pass instead of an O(types) list.count() scan per type per render. Deliberately not changed (flagged in review, judged not worth the churn): menu-loop duplication across manageHome/manageInvestments/manageBusiness, investments' 1-based vs housing's 0-based tier indexing, and displayStats()'s hand-rolled optional-block style vs achievements.py's data-driven MILESTONES pattern. Stacked on feature/homeless-and-renting. Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
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.
Stacked on #104
This targets
feature/home-ownership(#104), notmain— review it after that one lands, or alongside it.Summary
Previously every player already owned the cheapest home tier for free, so there was no real gap for "being homeless" or "renting somewhere" to fill. This extends
src/housinginto a full 6-rung ladder:Homeless(free, low energy cap) →Rented Room(recurring daily cost, no equity) →Driftwood Shack→Cozy Cottage→Sturdy Cabin→Waterfront Manor(all owned, trade-up economics as before — Driftwood Shack now actually costs money, since it's no longer the free floor).housing.netCostToMove/moveHomegeneralize across the whole ladder with one formula: a rung's price only counts if it's owned, and proceeds only count if the rung being left was owned. Homeless↔renting↔owned all fall out of that without special-casing.housing.runDailyRent, wired intoTimeService.increaseDay()alongside interest/business/investments: rent is charged automatically each morning, and an unaffordable payment evicts the player back to Homeless — mirroring how the fishing business' crew "quits when payroll can't be met" instead of going into debt.player.homeTiernow starts at0(Homeless);StatsgainstotalRentPaid, andhighestHomeTierstarts at0to match.Test plan
python3 -m pytest --verbose -vv --cov=src --cov-report=term-missing— 309 passed (up from 293 onfeature/home-ownership), including a full rewrite oftests/housing/test_housing.pyfor the ladder model, plus updates totests/location/test_home.py,tests/player/*,tests/stats/*,tests/world/test_timeService.py.🤖 Generated with Claude Code