Skip to content

Add homelessness and renting to the housing ladder#105

Merged
dmccoystephenson merged 1 commit into
mainfrom
feature/homeless-and-renting
Jul 11, 2026
Merged

Add homelessness and renting to the housing ladder#105
dmccoystephenson merged 1 commit into
mainfrom
feature/homeless-and-renting

Conversation

@dmccoystephenson

Copy link
Copy Markdown
Member

Stacked on #104

This targets feature/home-ownership (#104), not main — 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/housing into a full 6-rung ladder:

Homeless (free, low energy cap) → Rented Room (recurring daily cost, no equity) → Driftwood ShackCozy CottageSturdy CabinWaterfront Manor (all owned, trade-up economics as before — Driftwood Shack now actually costs money, since it's no longer the free floor).

  • housing.netCostToMove/moveHome generalize 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.
  • New housing.runDailyRent, wired into TimeService.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.homeTier now starts at 0 (Homeless); Stats gains totalRentPaid, and highestHomeTier starts at 0 to match.
  • New "Roof Over Your Head" milestone (reach renting or better); "Waterfront Manor" threshold bumped for the two new rungs below it.
  • Home menu flavor text and the Manage Home screen are status-aware (different copy for homeless/renting/owned).

Test plan

  • python3 -m pytest --verbose -vv --cov=src --cov-report=term-missing — 309 passed (up from 293 on feature/home-ownership), including a full rewrite of tests/housing/test_housing.py for the ladder model, plus updates to tests/location/test_home.py, tests/player/*, tests/stats/*, tests/world/test_timeService.py.
  • Manual smoke test via direct API calls: started homeless (energy cap 60) → moved to renting (energy cap 90, $10/day) → paid rent for 2 days until broke → evicted back to homeless on the 3rd unaffordable day → bought a Driftwood Shack outright from homeless for $300.

🤖 Generated with Claude Code

Base automatically changed from feature/home-ownership to main July 11, 2026 22:41
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>
@dmccoystephenson dmccoystephenson force-pushed the feature/homeless-and-renting branch from 009b629 to fd9f809 Compare July 11, 2026 22:51
@dmccoystephenson dmccoystephenson marked this pull request as ready for review July 11, 2026 22:53
@dmccoystephenson dmccoystephenson merged commit 46e2014 into main Jul 11, 2026
1 check passed
@dmccoystephenson dmccoystephenson deleted the feature/homeless-and-renting branch July 11, 2026 22:54
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant