Bump MVP map to 80², lock world-view camera, resolve doc rot

Resolves the rimlike docs audit. Decisions made this session:

- Map: 40² → 80² for MVP slice; architecture sized to ~120² ceiling.
  Pawn count unchanged (3 start / 6 cap) — 'split-the-difference'
  sizing rather than frontier-feel scale.
- World-view camera (locked): pinch + drag-pan + double-tap-centre.
  Storyteller alerts include 'Go there' tap. No minimap, no
  follow-cam in MVP. New ui.md section captures this.
- Storyteller cooldowns: per-event AND per-category — both gates
  must pass. Resolves design.md/architecture.md disagreement.
- MAX_STACKS_PER_THOUGHT = 5 (was named-but-unset).
- Hauling no-destination fallback: drop after 3 retry passes,
  surface a passive 'No stockpile accepts X' alert.
- Container all-neighbors-blocked: hold then drop after ~5 sim s
  to avoid deadlock.
- Auto-roof big-room UX: emits room_too_large signal when BFS hits
  the ≤8-cell cap; UI surfaces a 'split with an interior wall'
  banner. Threshold + exact wording added to memory.md TODOs.

Doc rot cleaned in design.md and architecture.md: removed the stale
'8 work categories' intermediate sections (canonical 9-list lives
later in each file). Updated architecture.md perf claims for the new
map size.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
megaproxy 2026-05-10 19:45:29 +01:00
parent d233cef12f
commit c5dadedab0
4 changed files with 52 additions and 19 deletions

View file

@ -97,15 +97,17 @@ Multipliers drive sim tick stepping per render frame. At 60 Hz render with 20 Hz
- Ultra (12×): 240 ticks/sec (4 ticks per render frame)
- Pause: zero ticks queued
Per-tick budget at Ultra: ~4 ms to stay in 60 fps render. Achievable for our scope (36 pawns, < 500 entities, 60×60 tiles).
Per-tick budget at Ultra: ~4 ms to stay in 60 fps render. Achievable for our scope (36 pawns, < 500 entities, **80×80 MVP map; sized to a 120² ceiling**). At 80² the AStarGrid2D has 6.4k nodes; longest path 160 tiles; full A* runs sub-millisecond on a mid-range phone. At 120² (22.5k nodes) it's still well under a millisecond per query.
### Pawn movement
| Speed | Real-time crossing of 40-tile map | Feel |
| Speed | Real-time crossing of 80-tile map (corner-to-corner) | Feel |
|---|---|---|
| 1× (1 tile / 0.5 s) | ~20 s | Watchable, deliberate |
| Fast (5×) | ~4 s | Snappy, default cadence |
| Ultra (12×) | ~1.7 s | Almost teleporting |
| 1× (1 tile / 0.5 s) | ~80 s | Watchable, deliberate |
| Fast (5×) | ~16 s | Snappy, default cadence |
| Ultra (12×) | ~7 s | Almost teleporting |
Travel times are roughly 2× the original 40-tile estimates. A wolf spawning at the map edge gives ~20 s real-time lead at Fast — meaningful warning, not tedious.
This mostly absorbs the "walking-speed problem" surfaced earlier — Fast as default keeps a 5-min day from feeling like watching paint dry.
@ -260,6 +262,10 @@ walk_to(item.cell) → pick_up(item, capped_at_carry_capacity)
Carry capacity capped at one stack of one type. Multi-type carry is v2.
**No-destination fallback.** If `find_best_destination_for(item)` returns null on three consecutive periodic-flow passes (~15 sim seconds), the item is dropped where it lies, taken off the dirty set, and a passive "*No stockpile accepts X*" alert is queued. This prevents `_dirty_items` from cycling forever when the player has zero matching storage. The alert clears the moment a matching destination opens up (it re-enters the dirty set on the next storage-change event).
**Container all-neighbors-blocked.** `best_drop_position(from)` returns the closest free 4-neighbor of the container. If all four are blocked, the hauler holds the carry and re-tries on the next pass; if still blocked after ~5 sim seconds the carry gets dropped on the hauler's current tile (same fallback as above) so it doesn't deadlock.
### Container build flow
1. Player taps Build → Furniture → Crate.
@ -322,6 +328,8 @@ func compute_mood(p: Pawn) -> float:
return clamp(m, 0, 100)
```
`MAX_STACKS_PER_THOUGHT = 5`. Caps "saw corpse" / "ate without table" / "slept on floor" pile-ups so a single thought type can't deterministically tank mood by itself; the player still loses points from variety, which matches the Rimworld feel.
Thought registry (data file) drives content. Each thought entry knows:
- Trigger: what game event creates it (saw_corpse from EntityProximity, slept_well from JobComplete, hungry from NeedThreshold, …)
- Lifetime: persistent (driven by ongoing state) or event (decays after N hours)
@ -375,7 +383,7 @@ func recompute_light(affected_cells: Array[Vector2i]):
Recomputed only when a light source is added/removed/state-changes (turned off due to fuel, etc.). Cheap.
**Render side:** at night, world rendered with darkness shader; the shader samples `light_map` to brighten tiles. Phone GPU runs this comfortably for 60×60 tiles.
**Render side:** at night, world rendered with darkness shader; the shader samples `light_map` to brighten tiles. Phone GPU runs this comfortably for the MVP 80×80 map (and the 120² ceiling) — light recompute touches only cells inside the affected light's radius (max_radius=8 → ≤ 200 cells), not the whole map.
**Mood integration:** `is_lit(cell)` returns `light_map[cell] > 0.2`. Fires "In darkness" thought for pawns in unlit tiles at night.
@ -450,10 +458,6 @@ Decay: zero — dirtiness only goes down via Cleaning.
8th WorkProvider in the existing pawn AI. Scans dirty tiles in/near rooms with priority bias toward indoor tiles. Job toils: walk → clean (timed by Manual Labor skill) → set dirtiness to 0.
### Updated WorkProvider list (8)
Construction · Mining · Hauling · **Cleaning** · Cooking · Plant · Doctor · Combat
## Production: workbenches, recipes, bills
For player rules and recipe lists, see [`design.md`](./design.md). This section is data + AI plug-in.
@ -889,6 +893,9 @@ func build_weighted_pool() -> Dictionary:
var pool = {}
for ev in EVENT_REGISTRY.values():
if not ev.trigger.matches(World.state, current_day): continue
# Both gates must pass: per-event AND per-category cooldown.
# Per-event prevents the *same* event repeating within its individual cooldown.
# Per-category enforces the design.md pacing rule (no two threats within 3 days, etc.).
if recently_fired(ev.id, ev.cooldown_days): continue
if recently_fired_category(ev.category, CATEGORY_COOLDOWN[ev.category]): continue
var w = ev.weight * tension_modifier(ev.category)
@ -982,6 +989,8 @@ func on_wall_change(cell: Vector2i):
`bfs_finds_exit(start)`: flood from `start`, treating walls as blockers; return true if we reach the map edge or a No-Roof designated cell within 8 steps. Fast at our scale (8² = 64 cells worst case per BFS, called on each candidate within the affected radius — still microseconds).
**Big-room feedback.** When `bfs_finds_exit` returns true *because the BFS ran out of steps without escaping or roofing* (i.e. the area is enclosed but larger than the cap), the EnclosureDetector emits a `room_too_large` signal carrying the centroid cell. The UI raises a one-shot ambient banner: "*This area is too large to roof. Split it with an interior wall.*" Threshold and exact UX wording TBD — see `memory.md` Open questions.
When a wall is destroyed: candidates are re-evaluated; some may flip from roof to no-roof.
### No-Roof designation
@ -1070,7 +1079,7 @@ Children of a `World` node, tile-aligned positions:
### Pathfinding
`AStarGrid2D`, one grid the size of the map. Walkable derived from TileMap data + furniture occupancy. Update affected cells on build/destroy/door-state-change. Microseconds at 60×60.
`AStarGrid2D`, one grid the size of the map. Walkable derived from TileMap data + furniture occupancy. Update affected cells on build/destroy/door-state-change (one cell per change → O(1)). Sub-millisecond per path query at 80²; still well under a millisecond at the 120² ceiling.
### Saving
@ -1078,7 +1087,7 @@ Per layer, `tilemap.get_used_cells_by_id(layer)` → JSON. Plus furniture entiti
### Performance
60×60 × 5 layers = 18k tiles, GPU-batched in one draw call per layer. ~100500 entity nodes at peak. Comfortable 60fps on a mid-range phone with headroom. iOS export needs Mac/Xcode; Android export from Linux is fine.
80×80 × 5 layers = 32k tiles (MVP); 120×120 × 5 = 72k tiles (ceiling). GPU-batched in one draw call per layer regardless of cell count, so the bump is essentially free for the renderer. ~100500 entity nodes at peak. Comfortable 60 fps on a mid-range phone with headroom. iOS export needs Mac/Xcode; Android export from Linux is fine.
## What lives elsewhere

View file

@ -175,10 +175,6 @@ Floor tiles accumulate `dirtiness: float (0..100)`:
Mostly low-skill chore work — gives Manual-Labor pawns something to do when not building/mining/hauling.
### Updated work category list (now 8)
Construction · Mining · Hauling · **Cleaning** · Cooking · Plant · Doctor · Combat
## Production chains & workbenches
2-step chains where it makes medieval sense; 1-step where simpler is fine. Going Medieval / Banished flow without runaway recipe authoring.
@ -540,6 +536,8 @@ When walls form an enclosed area ≤ 8 cells across, the interior **auto-roofs**
Matches the player's mental model: *"I built four walls, now there's a room."*
**Big-room UX (open):** the ≤8-cell cap silently fails on rooms larger than 8×8. When the player encloses a too-large area, we should surface "this area is too large to roof — split it with an interior wall." Exact threshold + UI treatment is TBD (see `memory.md` Open questions).
### "No-roof" designation
Designation type: paint cells inside an enclosure to forbid auto-roofing. Useful for courtyards, fire pits, gardens. Same paint UX as stockpile zones.

View file

@ -610,11 +610,24 @@ Top bar also shows season + day-of-season ("Spring 4/12"). Tap → seasonal fore
Wet pawns have a subtle drip particle + slight sprite tint until they dry off. Players can quickly spot "Bob is soaked, why?" without opening the pawn detail screen.
## World view camera (locked)
The camera/navigation model on the main world view, decided 2026-05-10 alongside the bump from a 40² to an 80² map.
- **Pinch-zoom**: between a "strategic" zoom (whole-map-ish on tablet, ~1/4 of the map on phone) and a "close" zoom (~16 tiles wide on phone, sprite-readable). No fixed zoom levels — smooth.
- **Drag-pan**: one-finger drag on empty world tiles. Drag on a pawn = select-and-drag-issue-order (long-press-then-drag for multi-select rectangle).
- **Double-tap to centre**: double-tap on a pawn portrait, alert banner, or the world centres the camera there with a brief animated pan.
- **No follow-camera**: selecting a pawn does **not** lock the view to them. Selection persists across pans so the player can scroll to a build site and issue an order without losing their pawn.
- **Jump-to-alert**: every storyteller alert / banner / event modal includes a *Go there* tap that pans-and-centres the camera on the relevant tile (raid spawn, downed pawn, fire, etc.). Replaces the "where is this happening?" minimap need.
- **No minimap in MVP**. Reasoning: phone screen real estate is precious, and *Jump-to-alert* + double-tap-on-portrait covers the navigation needs at 80². Revisit if playtest shows people getting lost.
- **Speed / pause buttons** stay fixed at the top regardless of camera state.
Layered on the world view: select (tap empty pawn), inspect (long-press anything), build mode (bottom-sheet → paint), designation paint (bottom-sheet → designate type → paint).
## Screens still to design
These are open work for future sessions. Listed roughly in importance.
- **World view** — camera/select/orders. The screen the player sees most. Tap targets, long-press menus, pinch-zoom, multi-select (for multi-pawn orders).
- **Build drawer** — bottom-sheet tabs (Walls / Floors / Furniture / Production / Designate). Designation paint mode. Material-pick UI when a build can use multiple materials.
- **Alerts / storyteller event** — modal vs. ambient, dismissal, history of past prompts.
- **Day-summary / end-of-day** — a recap card showing what changed today; gives short sessions a stopping point.

View file

@ -37,7 +37,7 @@ Distilled from the brainstorm. Each lock has a "why" — change with deliberate
| **Goal scaffolding** | Light storyteller prompts | Soft, dismissible nudges give each short session shape without forcing scenarios. |
| **Combat** | Realtime with auto-pause on threat | Rimworld-feel; touch-friendly. |
| **Health** | Single HP per pawn + status effects (Bleeding/Sick/Tired/Hungry/Wet/Cold/Downed/...) | ~80% of the drama for ~5% of Rimworld's health-system code. |
| **Scale** | 36 pawns, 30×30 to 60×60 tile maps | Readable on a phone; cheap to simulate. |
| **Scale** | 36 pawns, **80×80** MVP map (architecture sized to ~120² ceiling) | Roughly Stardew-farm size; readable when zoomed in, doesn't fit a phone screen — forces the world-view camera (pinch / pan / jump-to-alert) rather than strategic-overview-on-phone. |
| **Priority levels** | 5 (Critical / High / Normal / When idle / Off) | Matches Rimworld + Going Medieval contract. |
| **Failure state** | Ghost colony — no game over; storyteller drops wanderer in 35 days | Mobile-friendly; preserves player investment. |
@ -72,6 +72,7 @@ Distilled from the brainstorm. Each lock has a "why" — change with deliberate
- **Stockpile/container UI**: 4×4 chip grid for the 16 filter categories; same UI for floor zones and crates.
- **Storyteller events**: ambient banners for nudges/seasonal/lore; modal auto-pause for wanderer/threat/disease/milestone. Events log + "while you were away" digest at resume.
- **Indoor tint** marks "this is inside" without needing roof rendering — matches Stardew/Rimworld convention.
- **World-view camera**: pinch-zoom + drag-pan + double-tap-to-center; selected pawn does **not** force-follow. Storyteller alerts/banners include a *Go there* tap that pans the camera to the event tile. **No minimap in MVP** — revisit if playtest shows people getting lost on the 80² map.
### Art strategy
@ -94,6 +95,7 @@ These are concrete checks to run before serious construction begins. Total ~75 m
### Design topics still open
- [ ] **Auto-roof big-room UX**: the ≤8-cell BFS cap silently fails on rooms larger than 8×8. Decide whether to (a) keep the cap and surface "this area is too large to roof — split with an interior wall" hint when the player encloses one, (b) bump the cap to ~16 with the same hint at the new threshold, or (c) detect any enclosed area regardless of size. Affects `EnclosureDetector` + a new room-feedback UI.
- [ ] **Onboarding / first 60 seconds**: mobile-specific. Don't copy Rimworld's tutorial.
- [ ] **Touch UI for non-priority screens**: world view, build drawer, alerts, day-summary, pawn detail. See `docs/ui.md` "Screens still to design."
- [ ] **Background time / "while you were away" mechanic** — locked to no background simulation in MVP; revisit if it feels bad in playtest.
@ -120,7 +122,7 @@ These are concrete checks to run before serious construction begins. Total ~75 m
Same scope as locked in `~/claude/ideas/rimlike/plan.md`. Realistic timeline 36 months solo for an MVP this rich.
- **1 biome** (temperate forest, ElvGames Forest Tileset 4 Seasons; Ventilatore foliage accents).
- **1 map**, ~40×40 tiles, fixed seed for now.
- **1 map**, **80×80 tiles**, fixed seed for now.
- **3 starting pawns** ("settlers" / "villagers"), each with name + portrait + one-sentence backstory.
- **Verbs**: chop wood, mine stone & ore, build walls/floors/furniture/crates, plant/harvest crops (34 types), cook meals (recipes), haul, repair, clean.
- **Needs**: hunger, sleep, mood (~13 distinct thoughts, soft breaks at sustained mood < 25).
@ -143,6 +145,17 @@ Same scope as locked in `~/claude/ideas/rimlike/plan.md`. Realistic timeline 3
### 2026-05-10
- Promoted from `~/claude/ideas/rimlike/` (single multi-hour brainstorm session).
- Scaffolded `projects/rimlike/` from `_templates/project/`. Customized `CLAUDE.md`. Distilled `plan.md` into this `memory.md`. Moved companion files (design / architecture / ui / art) into `docs/`. `git init`, first commit "Initial scaffold". Created Forgejo repo `rimlike` (private), set HTTPS remote, pushed `main`. Archived idea folder to `~/claude/archive/ideas/rimlike/`.
- Wired Godot MCP Pro: `.mcp.json`, `.claude/settings.local.json` allowlist, three project-local subagents (`quick-edit`, `researcher`, `gdscript-refactor`). Added the MCP-Pro and tiered-delegation sections to `CLAUDE.md`. Editor plugin / `addons/godot_mcp/` re-copy still pending Godot project scaffold.
- Reviewed `docs/` against `memory.md` and `CLAUDE.md`. Decisions made:
- **Map size bumped from 40² → 80²** for the MVP slice; architecture sized to ~120² ceiling. Pawn count stays at 3 start / 6 cap (frontier feel was rejected in favor of "split-the-difference" sizing — Stardew-farm scale, not Going-Medieval scale).
- **World-view camera** = pinch-zoom + drag-pan + double-tap-to-center; storyteller alerts include a *Go there* tap. No minimap in MVP. No follow-cam (avoids fighting the player when issuing build orders across the map).
- Storyteller cooldown semantics: **per-event AND per-category, both gates must pass** (resolves the design.md vs architecture.md disagreement).
- `MAX_STACKS_PER_THOUGHT = 5` (was named-but-unset).
- Hauling-loop fallback: items that fail to find any valid destination after 3 retry passes drop on the floor and surface as a passive "No stockpile accepts X" alert.
- Cleaned up doc rot in `docs/design.md` and `docs/architecture.md` — both had stale "8 work categories" intermediate sections still next to the canonical "9" lists. Removed the (8) snapshots.
- Updated `docs/architecture.md` perf assumptions (60² → 80² with 120² ceiling).
- `docs/ui.md` got a new **World view camera (locked)** section; removed the world-view bullet from "Screens still to design".
- Open: auto-roof big-room UX (added to TODOs above) — the ≤8-cell BFS cap silently fails on bigger rooms; player feedback path needs a decision before EnclosureDetector lands.
## External references