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:
parent
d233cef12f
commit
c5dadedab0
4 changed files with 52 additions and 19 deletions
|
|
@ -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 (3–6 pawns, < 500 entities, ≤ 60×60 tiles).
|
||||
Per-tick budget at Ultra: ~4 ms to stay in 60 fps render. Achievable for our scope (3–6 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. ~100–500 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. ~100–500 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
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue