From 8c159812a0de2b2456a3105ea7bc1059dc0590fb Mon Sep 17 00:00:00 2001 From: megaproxy Date: Sun, 10 May 2026 19:19:19 +0100 Subject: [PATCH] Initial scaffold Promoted from ~/claude/ideas/rimlike after a single multi-hour brainstorm session. memory.md distilled from plan.md; companion design / architecture / ui / art docs preserved under docs/. Co-Authored-By: Claude Opus 4.7 (1M context) --- .gitignore | 42 ++ CLAUDE.md | 24 + docs/architecture.md | 1088 ++++++++++++++++++++++++++++++++++++++++++ docs/art.md | 150 ++++++ docs/design.md | 701 +++++++++++++++++++++++++++ docs/ui.md | 630 ++++++++++++++++++++++++ memory.md | 156 ++++++ 7 files changed, 2791 insertions(+) create mode 100644 .gitignore create mode 100644 CLAUDE.md create mode 100644 docs/architecture.md create mode 100644 docs/art.md create mode 100644 docs/design.md create mode 100644 docs/ui.md create mode 100644 memory.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4e649ae --- /dev/null +++ b/.gitignore @@ -0,0 +1,42 @@ +# Secrets — never commit +.env +.env.* +!.env.example +*.pem +*.key +*.p12 +*.pfx +secrets/ +credentials/ +.aws/ +.ssh/ + +# Dependencies / build artifacts +node_modules/ +__pycache__/ +*.py[cod] +.venv/ +venv/ +env/ +dist/ +build/ +target/ +*.egg-info/ + +# Editor / OS noise +.DS_Store +Thumbs.db +.vscode/ +.idea/ +*.swp +*.swo + +# Logs / caches +*.log +.cache/ +.pytest_cache/ +.mypy_cache/ +.ruff_cache/ +coverage/ +.coverage +.nyc_output/ diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..a3039ce --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,24 @@ +# Project: rimlike + +A 2D, tile-based **cute-farming-RPG-meets-colony-sim** for mobile / handheld. Rimworld DNA, Going Medieval × Stardew lodestars. Touch-first UI, sandbox + light storyteller, Rimworld-fidelity depth (5-priority work matrix, 16-chip stockpiles, mood + soft breaks, lighting, room beauty, Quality, dirtiness, production chains, combat, burial). Built on Godot 4 with the ElvGames "Ultimate Farming RPG" Humble bundle as primary art and Ventilatore Fantasy Tileset as medieval accent. + +Working title `rimlike` — rename TBD. + +## Working agreement + +- This is a git repo with `origin` on Forgejo at `https://git.rdx4.com/megaproxy/rimlike.git` (private). HTTPS auth uses the token in `~/.git-credentials` — pushes are non-interactive. +- Commit after each logical change with a one-line imperative message; `git push` after each commit (or at minimum before ending the session). +- Read `memory.md` at session start. Update it before ending the session. +- Read the relevant `docs/` companion (design / architecture / ui / art) for any task in that area — `memory.md` is the index, the companions are the depth. +- Never commit secrets — see `.gitignore` and the rules in `~/claude/CLAUDE.md`. + +## Project-specific notes + +- **Engine:** Godot 4 (GDScript). 2D-first. Mobile and Steam-Deck export targets; iOS export needs Mac/Xcode, Android from Linux is fine. +- **Tile size:** 16×16 pixel art (locked). +- **Art:** ElvGames "Ultimate Farming RPG" Humble bundle is the primary visual layer; local path `/mnt/d/godot/assets/humble set new/`. Ventilatore Fantasy Tileset Bundle is owned and used as medieval accent. License attribution is required — maintain a credits string for every pack used (see `docs/art.md`). +- **Tick rate:** sim 20 Hz, render 60 Hz decoupled. Default speed Fast (5×) — 1 in-game day ≈ 5 min real time. Auto-pause on threats / pawn-down / dialog events. +- **Save format:** Mid-tick suspend safe; we save **between** sim ticks. JobRunner state must round-trip from day one. +- **i18n from day one:** all player-visible strings live in a string table; English-only ship plan but no hardcoded copy in code. +- **Conventions:** GDScript with Godot's standard idioms. Class-per-file. Snake_case for files and variables. Tilesets imported from the bundle's `Tilesets/*.png` directly (skip the .unitypackage / .yymps / RPG Maker variants). +- **Open scope:** the `Vertical slice` in `memory.md` is the MVP target — realistic timeline 3–6 months solo. Do not silently expand scope; fork choices live in `memory.md` Open questions. diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..1a6cc44 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,1088 @@ +# rimlike — architecture + +> Companion to [`memory.md`](../memory.md). This file is the **technical architecture**: pawn AI, job system, time/tick model, Godot 4 engine layout. Game design (mechanics, simplifications, vertical slice) lives in [`design.md`](./design.md). + +## Pawn AI / job system + +Five-layer design. Slimmer than Rimworld's ThinkTree+JobGiver+WorkGiver+JobDriver, hits ~80% of the behavior. ~800–1500 lines of GDScript for full MVP, single-developer comprehensible. + +### Layers (top wins) + +| # | Layer | Role | +|---|---|---| +| 1 | **Decision pipeline** | Priority-ordered "what do I do RIGHT NOW?" walk: Incapacitated → Forced → Critical need → Mental break (v2) → Work → Recreation (v2) → Idle. First match wins. | +| 2 | **WorkProvider** | One per work category. Scans world for jobs in its domain, returns best for this pawn. Pawn iterates its own priority list calling `find_best_for(self)`. | +| 3 | **Job + JobRunner** | Job is plain data (type, target, materials, duration, skill). JobRunner is a small state machine running the toils (steps): walk → pick up → walk → work → complete. | +| 4 | **Status interrupts** | Bleeding/Tired/Hungry/Sick at threshold flag the pawn for re-decision. Each status declares its interrupt urgency (mid-walk vs. between-toils). | +| 5 | **Player overrides** | Tap-pawn → "do this" issues a forced one-shot job into the pawn's queue. Runs at Layer 1 step 2. | + +### MVP WorkProviders + +Construction · Mining · Hauling · Cooking · Plant · Doctor · Combat (later). + +### Locked design defaults + +- **Priority levels: 5 (1 Critical / 2 High / 3 Normal / 4 When idle / Off)** — matches Rimworld + Going Medieval contract. Pawn iterates priorities 1→4, scanning display-order categories within each tier. +- Within-tier order = display order (draggable, default hand-curated). +- Job claiming: a Job picked up by a pawn is `claimed_by = self`; others ignore. Released on cancel/fail. +- Single-pawn jobs only in MVP. No co-op hauling/building. +- Skills modify duration and quality, never permission. +- Interrupted runners drop carried items at the current cell — visible and recoverable in the world. + +### Pseudocode shape + +```gdscript +# Pawn.gd (excerpt) +func _on_sim_tick(dt: float) -> void: + update_statuses(dt) # bleed, hunger, fatigue grow + + if current_job and should_interrupt_for_status(): + cancel_current_job() + + if current_job == null: + current_job = decide_next_job() + if current_job: + job_runner = JobRunner.new(self, current_job) + + if job_runner: + match job_runner.tick(dt): + JobResult.COMPLETE, JobResult.FAILED: + _finish_job() + +func decide_next_job() -> Job: + if has_status(Status.DOWNED): return null + if not forced_queue.is_empty(): return forced_queue.pop_front() + var critical = check_critical_needs() + if critical: return critical + return find_work_by_priority() + +func find_work_by_priority() -> Job: + for category in WorkCategories.in_priority_order(work_priorities): + var job = WorkRegistry.get(category).find_best_for(self) + if job: return job + return null +``` + +### Open AI design questions + +- Skill depth — committed to **minimal**: 5 skills × 0–10, level by use, multiplicative speed/quality bonus. +- Walking-speed problem — see Time / tick model below; mostly absorbed by Fast being default. +- Job chaining (cook = fetch + fire + cook) — one Job with multi-step toils, not nested sub-jobs. +- Animal AI — separate predatory state machine, not the pawn pipeline. +- Idle behavior — punt to v2; pawns stand still or wander locally. +- Save round-trip — JobRunner mid-toil state must serialize cleanly from day one. + +## Time / tick model + +The simulation runs on a **fixed-rate sim tick** decoupled from render. Speed multipliers compress wall-clock time by stepping multiple sim ticks per render frame. + +### Locked numbers + +| Parameter | Value | Notes | +|---|---|---| +| **Sim tick rate** | **20 Hz** (50 ms per tick at 1×) | Smooth enough for animation interp, low enough to be cheap on phone | +| **Render rate** | 60 Hz, decoupled | Pawn sprites lerp between last and next sim tick positions | +| **In-game day** | 24 in-game hours | Game uses a 24h clock; storyteller hooks at sunrise/sunset | +| **1× speed** | 1 in-game minute = 1 real second | 1 day = 24 min real time at 1× — used for combat / fine work | +| **Fast (default)** | 5× | 1 day ≈ **5 min real time** — matches the 5–15 min session target | +| **Ultra** | 12× | 1 day ≈ 2 min real time — for skipping quiet stretches | +| **Pause** | 0× | Time stops; UI fully interactive | + +### Speed multiplier mechanics + +Multipliers drive sim tick stepping per render frame. At 60 Hz render with 20 Hz sim: + +- 1×: render frame queues 1 sim tick every 3 render frames (20 ticks/sec) +- Fast (5×): render frame queues sim ticks at 100 ticks/sec (~1.67 ticks per render frame) +- 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). + +### Pawn movement + +| Speed | Real-time crossing of 40-tile map | 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 | + +This mostly absorbs the "walking-speed problem" surfaced earlier — Fast as default keeps a 5-min day from feeling like watching paint dry. + +### Auto-pause triggers + +Default-on; player can disable individually in settings. + +- New threat appears (wolf seen, raid begins) +- Pawn becomes Downed +- Pawn dies +- Storyteller fires a player-prompt event (modal) +- (Optional) low health threshold, low food threshold + +### Suspend / resume + +- **Save between sim ticks**, never mid-tick. We own the loop. +- Save snapshot includes: current sim tick number, all entity state, job state (including JobRunner mid-toil), time-of-day, storyteller state. +- On resume: restore, re-establish render lerp from the last sim tick. +- **No background simulation.** App backgrounded = sim paused. Avoids "lost colony to a raid while at work" rage and saves battery. +- On resume from a long absence: brief "you've been away X minutes" toast, then sim resumes from where it stopped. No fast-forward in MVP. + +### Why 20 Hz, not 10 Hz + +10 Hz works for the sim itself but creates visible jitter in pawn animation unless render-side interpolation is heavily tuned. 20 Hz halves the lerp distance, halves the perceived snap on direction changes, and the per-tick cost is still trivial. If profiling on a low-end phone shows trouble, 10 Hz with rich interp is the fallback. + +### Render-vs-sim animation + +- Sprite/walk animations driven by **render time**, not sim ticks. They look smooth even at low sim Hz. +- Pawn world-position lerps from `last_sim_tick_pos` to `next_sim_tick_pos` based on `accumulator / tick_duration`. +- World tile changes (build complete, wall placed) snap on sim tick — no need to lerp tiles. + +## Storage: zones, containers, hauling + +For player-facing rules (categories, priorities, stack sizes), see [`design.md`](./design.md). This section is data structures + AI plug-in. + +### Storage destinations: a unified concept + +Two things accept items: **floor zones** and **containers**. The hauling AI treats them as a single pool of `StorageDestination` candidates, scored identically. Internally they share a small interface: + +```gdscript +interface StorageDestination: + var filter: int # bitmask of 16 categories + var priority: int # 0..4 (Low..Critical) + var name: String + + func accepts(item: Item) -> bool + func has_capacity_for(item: Item) -> bool + func best_drop_position(from: Vector2i) -> Vector2i # tile a pawn drops at + func deposit(item: Item) -> int # qty actually deposited +``` + +### StockpileZone + +```gdscript +class StockpileZone extends StorageDestination: + var id: int + var name: String # "Kitchen", default "Stockpile #N" + var cells: Array[Vector2i] + var filter: int # bitmask of 16 categories + var priority: int # CRITICAL=4, IMPORTANT=3, PREFERRED=2, NORMAL=1, LOW=0 + var contents: Dictionary # cell → ItemStack (one stack per cell, one type per cell) +``` + +Stored as `World.zones: Array[StockpileZone]`. Rendered as a transparent colored overlay (priority-tinted) **only** when the Zones panel is open. Zero render cost otherwise. + +### Container + +A Furniture entity. Built via Build → Furniture → Crate. One generic type in MVP (4-stack capacity). + +```gdscript +class Container extends Furniture, StorageDestination: + var name: String # "Crate", "Crate (kitchen)" etc. + var position: Vector2i # 1 tile, impassable + var filter: int # bitmask of 16 categories + var priority: int # 0..4 + var stacks: Array[ItemStack] # max 4 in MVP +``` + +Pawns walk to any 4-neighbor of `position` to deposit (`best_drop_position` returns a free neighbor). Container tile itself is non-walkable. + +Stored as `World.containers: Array[Container]`. Inspect screen accessible by tapping the entity. + +### HaulingProvider + +Slots into the existing 5-layer pawn AI as a `WorkProvider`. Considers BOTH zones and containers via the unified `StorageDestination` interface. + +```gdscript +# WorkProvider_Hauling.gd +func find_best_for(pawn: Pawn) -> Job: + var best: Job = null + var best_score: float = -INF + + for item in World.items_needing_haul(): + var target = World.find_best_destination_for(item) + if target == null: continue + + var score = priority_delta(item.current_priority, target.priority) * 1000.0 \ + + completeness_bonus(item, target) \ + - distance(pawn.position, item.position) + + if score > best_score: + best_score = score + best = Job.new(JobType.HAUL, item, target, ...) + + return best + +func find_best_destination_for(item: Item) -> StorageDestination: + var candidates: Array[StorageDestination] = [] + for z in World.zones: + if z.accepts(item) and z.has_capacity_for(item): + candidates.append(z) + for c in World.containers: + if c.accepts(item) and c.has_capacity_for(item): + candidates.append(c) + candidates.sort_by(|d| (d.priority * -1000) + distance(item.position, d.best_drop_position(item.position))) + return candidates[0] if candidates else null +``` + +**Score components, weighted high → low:** + +1. **Priority delta** — Low (0) → Critical (4) is the biggest delta. Moving items between equal-priority destinations gives 0 delta and is dropped. +2. **Completeness bonus** — finishing a partial stack beats opening a new one. Counters the sprawl problem. +3. **Distance** — tiebreak. + +### Priority flow (Rimworld semantics) + +Items in any storage destination at priority `p` may be re-hauled to a destination at priority > `p` if the higher destination has space and accepts the type. This is what enables "items flow Low → Critical." + +To avoid hot loops: + +- Newly-placed items go straight to the highest-priority valid destination (no flow needed). +- Existing items in stored destinations are re-considered every ~5 sim seconds via a periodic dirty-marking pass: `World.mark_low_priority_items_dirty()`. +- When `World.find_best_destination_for(item)` returns a higher-priority destination than the item's current one, the item joins `_dirty_items` and HaulingProvider picks it up. + +### items_needing_haul + +A dirty-set on World, not a full scan: + +- When an item spawns (workbench drop, designation completes, corpse drops, etc.) → added. +- When an item enters its highest-priority valid destination → removed. +- Periodic priority-flow pass (every ~5 sim sec) re-marks items in lower-priority destinations if higher options have opened up. + +O(dirty × destinations) per tick. Trivial at our scale (worst case ~100 items × ~20 destinations). + +### Hauling Job toils + +``` +walk_to(item.cell) → pick_up(item, capped_at_carry_capacity) +→ walk_to(target.best_drop_position(item.cell)) → deposit(item) +→ on_complete: update destination contents, mark partial-stack-still-needs-haul if applicable +``` + +Carry capacity capped at one stack of one type. Multi-type carry is v2. + +### Container build flow + +1. Player taps Build → Furniture → Crate. +2. Designation paint mode, but single-tile (each tap places one crate ghost). +3. Confirm → BuildJob queued, materials cost (e.g. 8 wood per crate). +4. Construction-priority pawn picks up: haul materials → walk → work N ticks → place Container entity. +5. New crate defaults: filter = all on, priority = Normal, name = "Crate #N". +6. Tap to inspect/configure. + +### Container operations + +- **Inspect** — opens UI screen (filter chips, priority picker, contents, Empty/Move/Demolish). +- **Empty** — all stacks become loose Items at the crate's neighbor cells; `_dirty_items` updated. +- **Move** — designate new position; pawn deconstructs + reconstructs (materials preserved). Or simpler: empty + demolish + rebuild manually. MVP: empty + demolish. +- **Demolish** — like a wall: contents drop as loose items, entity removed, materials partially refunded. + +### Workbench output + +Workbench entity has `output_cell: Vector2i` (one tile in front). Products drop there; haulers clear via the normal pipeline. + +### Forbid / allow + +Items have `is_forbidden: bool`. HaulingProvider skips forbidden items. UI exposes via long-press. + +### Zone editing operations + +- **Resize** — re-paint cells; update `cells` array. +- **Delete** — items left behind become loose Items, added to `_dirty_items`. +- **Filter change** — items now disallowed become loose, marked dirty. +- **Conflict** — disallow paint where cells overlap another zone (red overlay). + +### Save format + +Zones, containers, items, all serialize via existing per-entity save logic. ItemStack contents per-cell-or-per-container. JobRunner mid-toil state (carrying X of Y, walking to destination Z) round-trips. + +## Mood, lighting, rooms, beauty, quality, cleaning + +The full-fidelity colonist systems. Mechanics in [`design.md`](./design.md); this section is data + algorithms. + +### MoodSystem + +Per-pawn mood is a single 0–100 score recomputed each tick (or when thought modifiers change): + +```gdscript +class Thought: + var id: String # "saw_corpse", "slept_well_indoors", ... + var modifier: float # +/- mood points + var stacks: int # for thoughts that compound + var expires_at_tick: int # 0 = persistent + var source_ref: Variant # the corpse, the bed, etc. + +class Pawn: + var thoughts: Array[Thought] + var mood: float # cached, recomputed when thoughts change + +func compute_mood(p: Pawn) -> float: + var m = 50.0 # base + for t in p.thoughts: + m += t.modifier * min(t.stacks, MAX_STACKS_PER_THOUGHT) + return clamp(m, 0, 100) +``` + +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) +- Stacking rule: stack on repeat, or refresh duration + +Add a thought = add a registry entry + a trigger handler. ~10 lines per thought. + +### Soft break behavior + +```gdscript +class Pawn: + var break_state: BreakState = BreakState.NONE # NONE / SULKING / WANDERING + var low_mood_since_tick: int = -1 + +func _on_sim_tick(...): + if mood < 25: + if low_mood_since_tick == -1: low_mood_since_tick = current_tick + elif current_tick - low_mood_since_tick > THIRTY_MIN_TICKS: + if break_state == BreakState.NONE: + break_state = randf() < 0.5 ? SULKING : WANDERING + else: + low_mood_since_tick = -1 + + if break_state == SULKING: + # override Decision pipeline: walk to nearest quiet tile, sit + elif break_state == WANDERING: + # walk to random nearby tiles, refuse work + + if mood >= 35: break_state = BreakState.NONE +``` + +Break state slots into the Decision pipeline at Layer 1 step 2.5 (between FORCED and CRITICAL NEED) — sulking pawns ignore non-life-threatening things; critical bleeding still pulls them out. + +### LightingSystem + +Each `LightSource` (Furniture subtype) has `position`, `radius`, optional `color`. The system maintains a per-tile `light_level: float (0..1)`: + +```gdscript +# LightingSystem.gd +var light_map: PackedFloat32Array # one float per tile, indexed by cell + +func recompute_light(affected_cells: Array[Vector2i]): + for cell in affected_cells: + var v = 0.0 + for src in nearby_light_sources(cell, max_radius=8): + var d = cell.distance_to(src.position) + if d <= src.radius: + v += 1.0 - (d / src.radius) + light_map[index(cell)] = clamp(v, 0, 1) +``` + +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. + +**Mood integration:** `is_lit(cell)` returns `light_map[cell] > 0.2`. Fires "In darkness" thought for pawns in unlit tiles at night. + +### RoomDetector — extends EnclosureDetector + +Triggered after enclosure changes; identifies discrete enclosed areas as `Room` records: + +```gdscript +class Room: + var id: int + var cells: Array[Vector2i] + var furniture_in_room: Array[Furniture] + var inferred_type: String # "Bedroom" / "Kitchen" / "Dining Hall" / "Hallway" / "Room" + var beauty: float # cached, recomputed on furniture change + var dirtiness_avg: float # cached + var owners: Array[Pawn] # pawns who own a bed here + +func recompute_rooms(): + # Flood-fill all interior cells, group into Room records + # Categorize by dominant furniture + ... +``` + +Updated when walls / furniture change. Uses the same enclosure machinery as auto-roof — just one more pass over the same data. + +### Beauty score + +Each Furniture/Floor entity has `beauty: int` baked into its data definition. Quality multiplies it (Masterwork = ×3 beauty contribution). Room beauty is a weighted average of cell-beauty contributions with falloff for items more than 4 tiles away (so a statue helps the whole room). + +### Quality system + +```gdscript +enum Quality { SHODDY, NORMAL, EXCELLENT, MASTERWORK, LEGENDARY } + +func roll_quality(skill: int, base_chance_modifier: float = 0.0) -> Quality: + # Higher skill shifts the distribution + var roll = randf() + skill * 0.04 + base_chance_modifier + if roll > 1.4: return LEGENDARY + elif roll > 1.1: return MASTERWORK + elif roll > 0.7: return EXCELLENT + elif roll > 0.3: return NORMAL + else: return SHODDY +``` + +Quality multipliers (applied on item create): + +``` +SHODDY 0.7× +NORMAL 1.0× +EXCELLENT 1.25× +MASTERWORK 1.5× +LEGENDARY 2.0× +``` + +Stored as `quality: Quality` on every craftable Item entity. UI color-codes item name by quality. + +### Dirtiness simulation + +```gdscript +# DirtinessSystem.gd +var dirty_map: PackedFloat32Array # per-tile dirtiness 0..100 + +func _on_pawn_walk_through(cell: Vector2i, pawn: Pawn): + # Pawns track in mud, blood, etc. Outdoor → indoor pawns track more. + dirty_map[index(cell)] += pawn.tracking_amount * dt +``` + +Spike sources: combat blood, corpse near tile, spilled food. +Decay: zero — dirtiness only goes down via Cleaning. + +### CleaningProvider + +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. + +### Recipe registry (data) + +```gdscript +class Recipe: + var id: String + var workbench_type: String # "smithy", "cooking_hearth", ... + var inputs: Dictionary # {item_id: qty} + var outputs: Dictionary # {item_id: qty}; quality rolled at create + var skill: SkillType # Crafting / Cooking + var duration_ticks: int + var min_skill_required: int = 0 +``` + +Loaded from a JSON/Godot resource at startup. Adding a recipe = adding a row. + +### Workbench (Furniture subtype) + +```gdscript +class Workbench extends Furniture: + var workbench_type: String # "smithy", "carpenter_bench", ... + var output_cell: Vector2i # cell in front for product drop + var bills: Array[Bill] # queue + var current_bill: Bill = null # actively being worked + var current_pawn: Pawn = null # who's working it +``` + +### Bill (queue entry) + +```gdscript +class Bill: + var recipe_id: String + var mode: BillMode # ONE_SHOT / FOREVER / UNTIL_N + var count_remaining: int # for ONE_SHOT + var stockpile_threshold: int # for UNTIL_N + var ingredient_quality_min: Quality = Quality.SHODDY + var pawn_skill_min: int = 0 + var paused: bool = false + +func is_active() -> bool: + if paused: return false + if mode == ONE_SHOT: return count_remaining > 0 + if mode == FOREVER: return true + if mode == UNTIL_N: return World.count_in_storage(recipe.outputs) < stockpile_threshold +``` + +### CraftingProvider — 9th WorkProvider + +```gdscript +# WorkProvider_Crafting.gd +func find_best_for(pawn: Pawn) -> Job: + if pawn.skills[Crafting] < required: return null # respects pawn skill + var best: (Workbench, Bill) = null + var best_score = -INF + + for bench in World.workbenches: + if bench.skill != Crafting: continue + for bill in bench.bills: + if not bill.is_active(): continue + if pawn.skills[Crafting] < bill.pawn_skill_min: continue + if not ingredients_available(bill, bench): continue + + var score = bill_priority_in_queue(bill, bench) * 100 \ + - distance(pawn.position, bench.position) + if score > best_score: + best_score = score + best = (bench, bill) + + return best ? Job_Craft(bench, bill) : null +``` + +A separate **CookingProvider** mirrors this for cooking workbenches (hearth) using the Cooking skill. + +### Crafting Job toils + +``` +walk_to(workbench) +→ for each input: walk_to(stockpile-or-crate-with-input), pick_up, walk_back, drop_at_workbench +→ work(duration_ticks, modified_by_skill) +→ on_complete: roll_quality(skill), spawn output Item with quality, place at workbench.output_cell +→ if mode == ONE_SHOT: count_remaining -= 1; if 0 remove bill +→ if mode == UNTIL_N: bill auto-deactivates next tick when threshold met +→ HaulingProvider clears the output via normal pipeline +``` + +### Ingredient acquisition radius + +For MVP: pawn searches **all** stockpiles + containers globally for ingredients. Real restriction (per-bench radius) is a v2 polish item — bench can be configured to only pull from stockpiles within K tiles. + +### Skill-rolled quality on output + +Reuses the Quality system already designed. `roll_quality(pawn.skills[recipe.skill])` returns Quality enum, multiplied into item stats on creation. UI color-codes the resulting name. + +### Bill UI state — saved per workbench + +Bills round-trip in saves as part of the workbench entity serialization. Mid-job state (pawn currently fetching iron ingot 2 of 4) is JobRunner state, already serializable. + +### Updated WorkProvider list (9) + +Construction · Mining · Hauling · Cleaning · **Crafting** · Cooking · Plant · Doctor · Combat + +## Combat system + +For weapon/armor stats, hit math, and combat priority semantics, see [`design.md`](./design.md). This section is tech. + +### Equipment data + +```gdscript +class Weapon extends Item: + var weapon_type: String # "sword" / "axe" / "bow" + var base_damage: int + var range_tiles: int # 1 for melee, 8 for bow + var attacks_per_sec: float + var quality: Quality + func effective_damage() -> float: + return base_damage * QUALITY_MULTIPLIER[quality] + +class Armor extends Item: + var slot: ArmorSlot # HELMET / CUIRASS / BOOTS + var base_armor: int + var quality: Quality + func effective_armor() -> float: + return base_armor * QUALITY_MULTIPLIER[quality] + +class Pawn: + var equipped_weapon: Weapon = null + var equipped_armor: Dictionary = {} # slot -> Armor + var locked_assignments: Dictionary # for player-overridden auto-equip +``` + +### Auto-equip system + +Each tick (cheap), if a Combat-enabled pawn has no weapon equipped and a valid one is in reach, queues a `Job_Equip`. Toils: walk → pick up → equip. Player long-press → "Assign to X" overrides this with a locked assignment. + +### CombatSystem — auto-pause + threat detection + +Singleton: + +```gdscript +# CombatSystem.gd +signal threat_appeared(threat: Entity) +signal pawn_downed(pawn: Pawn) + +func _on_sim_tick(): + var threats = scan_for_threats() # hostile pawns/animals near colonists + for t in threats: + if not _known_threats.has(t): + emit_signal("threat_appeared", t) # Time/tick model auto-pauses on this + _known_threats[t] = current_tick +``` + +### Hit / damage resolution + +```gdscript +# CombatJob toil (per attack tick of attacker) +func resolve_attack(attacker: Pawn, target: Entity): + var dist = attacker.position.distance_to(target.position) + if dist > attacker.equipped_weapon.range_tiles: + return # out of range, attacker walks closer + + var hit_chance = 0.5 \ + + attacker.skills[Combat] * 0.05 \ + - cover_penalty(attacker.position, target.position) \ + - range_penalty(dist, attacker.equipped_weapon) + hit_chance = clamp(hit_chance, 0.05, 0.95) + + if randf() < hit_chance: + var dmg = attacker.equipped_weapon.effective_damage() \ + - target_total_armor(target) + dmg = max(1, dmg) + target.hp -= dmg + if target.hp <= 0: + handle_down_or_death(target) + emit_signal("attack_resolved", attacker, target, true, dmg) + else: + emit_signal("attack_resolved", attacker, target, false, 0) + # If projectile: chance to hit something else along LOS path + if attacker.equipped_weapon.weapon_type == "bow": + check_friendly_fire(attacker, target) +``` + +### Cover detection + +```gdscript +func cover_penalty(attacker: Vector2i, target: Vector2i) -> float: + var path = bresenham_line(attacker, target) + var max_cover = 0.0 + for cell in path[1..-2]: # exclude endpoints + if World.has_wall_at(cell): max_cover = max(max_cover, 0.4) + elif World.has_tree_at(cell): max_cover = max(max_cover, 0.2) + return max_cover +``` + +Bresenham is microseconds at our distances. + +### Downed system + +```gdscript +# DownedSystem.gd +func handle_down_or_death(pawn: Pawn): + if pawn.has_status(Status.DOWNED): + # Already downed; this attack causes death + pawn.die() + else: + pawn.add_status(Status.DOWNED) + pawn.drop_weapon() + World.mark_downed_pawn(pawn) # Doctors prioritize + World.start_bleed_timer(pawn, BLEED_OUT_TICKS) + +func _on_bleed_timer_expired(pawn: Pawn): + if pawn.has_status(Status.DOWNED) and not pawn.in_bed: + pawn.die() # death + +func handle_pawn_in_bed(pawn: Pawn): + if pawn.has_status(Status.DOWNED): + pawn.cancel_bleed_timer() + pawn.add_status(Status.RESTING) + pawn.heal_over_time = true +``` + +### Doctor work — Downed priority + +`WorkProvider_Doctor` already exists in the 9-WorkProvider list. Adds a high-priority scan: any Downed pawn within reasonable distance gets a Job_Rescue ahead of normal treatment work. + +Job toils: +``` +walk_to(downed) → pick_up → walk_to(nearest_unoccupied_bed) → place_in_bed +→ optionally: walk to medical supplies, walk back, treat → reduce bleeding/infection +``` + +### Combat priority — "defends if cornered" + +```gdscript +# When threat appears + pawn has Combat=Off: +func handle_threat_for_off_pawn(pawn: Pawn, threat: Entity): + var escape = find_path_away_from(pawn, threat, max_steps=5) + if escape: pawn.queue_job(Job_FleeTo(escape)) + else: pawn.engage(threat) # cornered, fight back with whatever is at hand +``` + +### Wolf AI (MVP threat) + +`Wolf` is an `Animal` entity with its own state machine, separate from the pawn ThinkTree: + +```gdscript +states: APPROACH → ENGAGE → FLEE (at HP < 30%) → DEAD +``` + +Spawned at map edge by storyteller wolf event. Native attack: 6 damage, 1.0/sec. Tough hide armor: 2. Drops a butcherable corpse. + +## Failure state, death, corpses, burial + +For mechanics see [`design.md`](./design.md). This section is data + AI plug-in. + +### Ghost colony state + +```gdscript +# World.gd +enum WorldState { ACTIVE, GHOST } +var state: WorldState = ACTIVE +var ghost_started_at_tick: int = 0 + +func _on_pawn_died(pawn: Pawn): + if World.living_pawns.is_empty(): + enter_ghost_state() + +func enter_ghost_state(): + state = GHOST + ghost_started_at_tick = current_tick + sim.set_speed_factor(0.5) # half-time during ghost + StorytellerSystem.schedule_event("wanderer_arrival", + min_delay_days = 3, + max_delay_days = 5) + UI.show_ghost_banner("Your colony has fallen silent. Watch for travelers.") + +func on_recruit_wanderer(new_pawn: Pawn): + state = ACTIVE + sim.set_speed_factor(1.0) + UI.hide_ghost_banner() +``` + +While in ghost state, render normally — buildings/items persist. Pawn-AI sim does nothing because no pawns. Storyteller still ticks for wanderer arrival. + +### Corpse entity + +```gdscript +class Corpse extends Item: + var pawn_record: PawnRecord # the dead pawn's full record + var decay_severity: float = 0.0 # 0..100 + var death_cause: String # "killed by wolf", "starvation", "bleed out", ... + var death_day: int + +func _on_in_game_hour(): + decay_severity += 33.0 / 24.0 # ~33 per day + if decay_severity >= 50 and not has_status("Rotting"): + add_status("Rotting") + update_mood_penalty() +``` + +Butchering disabled when `decay_severity >= 50` (cooking-hearth recipe checks). + +### PawnRecord (preserved after death) + +```gdscript +class PawnRecord: # not a node; data only + var name: String + var backstory: String + var skills_at_death: Dictionary + var death_day: int + var death_cause: String + var days_lived: int + # ... statuses at death, equipment at death, etc. +``` + +Stored on the Corpse and on any GraveMarker built from this corpse. Pawn-detail UI for deceased pawns reads from PawnRecord. + +### BurialJob + +A new job type. Created by HaulingProvider when: +- A Graveyard zone exists (a Stockpile with ONLY the Corpses category enabled) +- An unburied corpse exists in the world + +Toils: + +``` +walk_to(corpse_cell) → pick_up(corpse) +→ walk_to(target_grave_cell) # an empty tile in the graveyard +→ dig_grave (duration_ticks = 600 / manual_labor_skill_modifier) +→ place_grave_marker(pawn_record) +→ destroy(corpse) +→ on_complete: GraveMarker entity placed at grave_cell +``` + +The DigGraveJob uses Construction work category (it's manual digging + structure placement). + +### GraveMarker entity + +```gdscript +class GraveMarker extends Furniture: + var pawn_record: PawnRecord + var beauty: int = 1 + func on_tap() -> void: + UI.show_pawn_detail(pawn_record) # deceased view +``` + +Persists indefinitely. Cluster of markers in a graveyard zone contributes ~+1 beauty per marker to the room/area. Tap → pawn-detail showing the deceased's record. + +### Cremation pyre — Furniture + Workbench + +Build via Build → Furniture → Pyre. Stats: + +- Cost: 10 stone + 5 wood +- Output cell: 1 tile in front +- Recipes: just "Cremate corpse" + +Cremate recipe: +``` +inputs: [{corpse: 1}, {wood: 5}] +outputs: [] # corpse destroyed; no item produced +skill: CRAFTING +duration_ticks: 400 +``` + +Player queues a "Cremate forever" bill on the pyre; a Crafting-priority pawn auto-hauls a corpse from anywhere, performs the rite. Visual: 5-second flame + smoke FX. + +### Storyteller wanderer event + +```gdscript +# StorytellerSystem.gd +func fire_wanderer_event(): + var wanderer = generate_pawn() # random name + skills + UI.show_modal_event( + title = "A traveler appears", + body = "%s wandered to your colony. They are tired and looking for shelter." % wanderer.name, + choices = [ + { label = "Welcome them", on_pick = lambda(): World.add_pawn(wanderer) }, + { label = "Send them away", on_pick = lambda(): pass } + ] + ) +``` + +If sent away: schedule another wanderer event 3–5 days later. The event always eventually succeeds — ghost state is recoverable, never terminal. + +## Storyteller system + +For mechanism rules and the prompt corpus, see [`design.md`](./design.md). This section is data + picker. + +### Prompt registry (data) + +```gdscript +class StorytellerEvent: + var id: String + var category: Category # NUDGE / SEASONAL / WANDERER / THREAT / DISEASE / RESOURCE / LORE / MILESTONE + var title_key: String # i18n key + var body_key: String # i18n key, may contain %pawn% substitution + var trigger: TriggerSpec # how this fires + var effect: EffectSpec # what happens + var weight: float # base pool weight + var cooldown_days: int # min days before this exact event can fire again +``` + +Loaded from a JSON registry at startup. Adding events is a row in the file. + +### TriggerSpec + +```gdscript +class TriggerSpec: + var type: TriggerType # RANDOM / STATE / TIME / SEASONAL_EVENT + var min_day: int = 0 + var state_predicate: Callable = null # for STATE triggers + var season_filter: Array[Season] = [] + var requires_pawn_count: int = 0 +``` + +Examples: +- "First Beds" → STATE, predicate: `World.bed_count < World.living_pawns.size()`, min_day=2. +- "Spring Awakens" → SEASONAL_EVENT, season=SPRING, fires-on-season-start. +- "Wolves at the Edge" → RANDOM, season-weighted. + +### Picker — daily roll + +```gdscript +# StorytellerSystem.gd +func _on_daily_tick(): + var pool = build_weighted_pool() + var event = weighted_pick(pool) + if event: fire_event(event) + +func build_weighted_pool() -> Dictionary: + var pool = {} + for ev in EVENT_REGISTRY.values(): + if not ev.trigger.matches(World.state, current_day): continue + 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) + pool[ev] = w + return pool + +func tension_modifier(category: Category) -> float: + if category == THREAT and tension_score > HIGH_THRESHOLD: return 0.3 + if category == THREAT and tension_score < LOW_THRESHOLD: return 2.0 + return 1.0 +``` + +### Tension model + +```gdscript +var tension_score: float = 0.0 # 0..100 + +# On each event fire: +match event.category: + THREAT: tension_score = min(100, tension_score + 30) + DISEASE: tension_score = min(100, tension_score + 20) + WANDERER (Welcomed): tension_score = max(0, tension_score - 10) + SEASONAL: pass + NUDGE: pass + +# Decay over time: +func _on_in_game_hour(): + tension_score = max(0, tension_score - 0.5) # ~12/day decay +``` + +Hand-tuned from playtest. + +### State-trigger predicates + +A handful of common state predicates (Callable on `World`): + +```gdscript +func no_beds(): return World.bed_count < World.living_pawns.size() +func no_farm(): return World.farm_zones.is_empty() +func no_walls(): return World.wall_count == 0 +func no_fire(): return World.light_sources.is_empty() +func sick_pawn_exists(): return World.living_pawns.any(|p| p.has_status(SICK)) +func is_winter(): return World.current_season == Season.WINTER +``` + +### Event firing & UI dispatch + +```gdscript +func fire_event(ev: StorytellerEvent): + var ctx = build_context(ev) # %pawn% substitution etc. + match ev.category: + NUDGE, SEASONAL, LORE: + UI.show_ambient_banner(ev.title_key, ev.body_key, ctx) + WANDERER, THREAT, DISEASE, MILESTONE: + sim.auto_pause() + UI.show_modal_event(ev.title_key, ev.body_key, ev.choices, ctx) + RESOURCE: + UI.show_ambient_banner(...) + apply_effect(ev.effect, ctx) +``` + +Effects are applied either immediately (resource bonuses, status applications) or scheduled (wolves spawn 2 in-game hours later). + +### Save format + +Storyteller state: tension score, recent-event log (last 30 days), per-category cooldown timers, scheduled-effect queue. Trivial to round-trip. + +## Roofing, enclosure, weather + +For mechanics (mood, weather table, what roofs protect against) see [`design.md`](./design.md). This section covers data + algorithms. + +### Roof flag on Layer 4 + +Layer 4 of the TileMap stores a single tile id "roofed" (or unset). The set/unset flag is the authoritative "is this tile indoors?" — no further enclosure check at sim-read time. + +### EnclosureDetector — auto-roof when walls change + +Triggered on any Layer 2 (Wall) edit: + +```gdscript +# pseudocode +func on_wall_change(cell: Vector2i): + var affected = neighbors_within(cell, RADIUS=8) + for candidate in affected: + if is_already_wall_or_roofed(candidate): continue + if bfs_finds_exit(candidate, max_steps=8): + unset_roof(candidate) # area opened up, was roof, isn't now + else: + set_roof(candidate) +``` + +`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). + +When a wall is destroyed: candidates are re-evaluated; some may flip from roof to no-roof. + +### No-Roof designation + +A separate persistent set on the World scene: `no_roof_cells: Dictionary[Vector2i, true]`. EnclosureDetector treats no-roof cells as map-exits during BFS, so they never become roofed. Painted via the standard designation paint mode. + +### Indoor tint + +Render-side: indoor tiles (roof flag set) get a subtle dark-blue overlay on the floor layer. Outdoor tiles render normally. Cheap shader uniform driven by tile metadata. + +### WeatherSystem — singleton + +```gdscript +# WeatherSystem.gd (autoload singleton) +var current: Weather = Weather.CLEAR +var current_season: Season = Season.SPRING + +func _on_sunrise(): + var weights = SEASON_WEATHER_WEIGHTS[current_season] + current = Weather.weighted_pick(weights) + emit_signal("weather_changed", current) + +# Subscribers: +# PawnSystem — applies Wet status to outdoor pawns at tick rate +# Renderer — adjusts sky tint, rain particles +# JobSystem — disables harvesting jobs in Storm +``` + +Storyteller can pre-empt: `WeatherSystem.set_for_next_day(Weather.STORM)` for narrative events. + +### Wet status integration + +`Wet` is just another status in the existing pawn AI ([Pawn AI / job system](#pawn-ai--job-system)). Each tick, for each pawn: + +``` +if outdoors and weather in {RAIN, STORM}: + wet_severity += accumulation_rate(weather) * dt +elif indoors: + wet_severity = max(0, wet_severity - 5 * dt_in_game_hours) +``` + +Mood thoughts derived from severity bands (see design.md table). + +### Snow accumulation + +Optional polish: per-tile `snow_level: float (0..3)` on outdoor tiles in winter. Visual decal that slows movement when ≥ 3. Cheap dirty-set update at sunrise during winter. + +### Save format + +- Roof: serialized via Layer 4 `get_used_cells_by_id` like other layers. +- No-roof designations: `Array[Vector2i]`. +- Weather: current state + current season + day-of-season counter. +- Wet status: per-pawn severity (already in pawn save). + +## Engine architecture (Godot 4) + +**Architectural split:** static, grid-aligned stuff is `TileMap`. Stateful, named, or multi-tile things are scene-instanced nodes (`PackedScene`). Don't try to put everything in the TileMap. + +### TileMap layers + +| Layer | Purpose | Source | +|---|---|---| +| 0 Terrain | grass / dirt / stone / water | world-gen, mostly static | +| 1 Floor | built wood / stone / carpet | player-placed | +| 2 Wall | walls — Godot terrain autotile | player-placed | +| 3 Designation | blueprint ghosts, chop / mine marks | overlay | +| 4 Roof | flag tiles for "is this roofed?" | mostly invisible, sim use | +| 5 Fog | optional fog-of-war / unseen | overlay | + +### Entity nodes + +Children of a `World` node, tile-aligned positions: + +- **Furniture** — bed, stove, workbench, door, lamp. PackedScene per type. Tracked in a `furniture_by_cell` dictionary for fast lookup. Multi-tile shapes occupy multiple cells. +- **Pawn** — `CharacterBody2D` + state-machine/utility AI. Named, persistent. +- **Item** — dropped logs, stone, food, corpses. Stack qty + decay timers. +- **EffectFX** — muzzle flashes, blood, smoke. Transient. + +### Build flow + +1. Player taps **Build** → bottom-sheet drawer (Walls / Floors / Furniture / Production). +2. Pick "Wall" → designation mode; finger-drag paints ghosts on Layer 3, green-if-placeable / red-if-blocked. +3. Confirm → each ghost becomes a `BuildJob` in a queue with material cost. +4. Idle Construction-priority pawn picks nearest job: haul materials → walk to blueprint → work N ticks → clear Layer 3, set Layer 2 (terrain autotile fixes neighbors), update pathfinder. +5. Same flow generalizes to floors, mining (terrain → log/stone drop), chopping (tree entity → log drop), deconstruction (reverse). + +### 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. + +### Saving + +Per layer, `tilemap.get_used_cells_by_id(layer)` → JSON. Plus furniture entities, pawns, items, job queue, time, storyteller state. Mid-tick suspend is safe as long as we save **between** sim ticks (we own the tick loop). JobRunner state must be serializable — design constraint from day one. + +### 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. + +## What lives elsewhere + +- **Game design / mechanics / simplifications** — see [`design.md`](./design.md). +- **Touch UI** — see [`ui.md`](./ui.md). +- **Tileset / art** — see [`art.md`](./art.md). +- **Pillars and vertical slice** — see [`memory.md`](../memory.md). diff --git a/docs/art.md b/docs/art.md new file mode 100644 index 0000000..9167fa7 --- /dev/null +++ b/docs/art.md @@ -0,0 +1,150 @@ +# rimlike — art / tileset strategy + +> Companion to [`memory.md`](../memory.md). This file is the **art strategy**: owned tilesets (now substantial), aesthetic anchor, gaps, license notes, autotile gotchas. + +## Aesthetic anchor + +**Primary: ElvGames "Ultimate Farming RPG" bundle (Humble) — cute-farming-RPG palette.** + +The aesthetic shifted on 2026-05-10 when the bundle was found. Previously locked as "vibrant medieval-fantasy" via Ventilatore + planned Mana Seed; now the spine is ElvGames' bright, cheerful, Stardew-adjacent farming-RPG style. Project tone: **"Going Medieval × Stardew × Rimworld" — cozier than Rimworld, deeper than Stardew, mobile-first.** + +Ventilatore Fantasy Tileset Bundle (already owned) stays as a **secondary** layer for biome variety, decorative props, and medieval-flavor accents. Mana Seed series is **dropped** from the buy plan entirely (the bundle covers what we'd planned to purchase). + +## Owned art foundation + +### Primary — ElvGames "Ultimate Farming RPG" Humble Bundle + +Local path: `/mnt/d/godot/assets/humble set new/`. Three tiers, ~70 packs, ~2.8 GB. All 16×16 pixel art (matches our locked spec). License: commercial use OK with credit ("ElvGames"), no NFT/crypto, no resale of pack itself, modification permitted. + +**MVP-essential packs (the working art set):** + +| Need | Pack | Tier | +|---|---|---| +| Default biome (forest) | Forest Tileset 4 Seasons | T3 | +| Alt biome (open ground) | Farm Game Grasslands 4 Seasons | T1 | +| Mining + caves + ores + crystals + minerals | Mines Tileset 16x16 | T3 | +| Crops with growth stages (16+ crops) | Crops with Stages 01 / 02 / 03 | T2/T3 | +| Pawn sprites (15 characters with anim) | Farming Characters Pack | T1 | +| Animals / livestock (chicken, cow, duck, goat, pig) | Animal Sprites Pixelart | T2 | +| Item icons | Item Icons 16x16 | T2 | +| Workbench / decoration | House Interior Tileset | T3 | +| House exteriors (2 seasons) | Houses Tileset 2 Seasons | T3 | +| Castle / fortress / keep | Fortress Tileset 2 Seasons | T3 | +| Defensive structures (sandbags, towers) | Pixel Tower Defense 1 | T2 | +| Music — cozy ambient | Cozy Melodies Pack 1, Retro Farming Music 1 | T1 | +| SFX — combat / crafting / mining / monsters / UI / sword | Multiple SFX packs | T3 | + +**Bonus / post-MVP packs:** + +| Use | Pack | +|---|---| +| Monster variety (15 species) for v2 threat events | EvoMonster Pack 01–15 | +| RPG-style enemies for combat polish | Turn-Based RPG Monsters Pack 7–16 | +| Modular building components | Modular Pixel Houses Pack 01–05 | +| Alt aesthetic option | Retro Kingdom Explorer 16x16 series (Castle/Forest/Graveyard/Grotto/Plains/Town) | +| Additional biomes | Mountains 2 Seasons / Marketplace / Village / Frozen Caves / Magma Caves / Dark Castle | +| Effects / spells | Magic and Spells SFX 5/6 | + +### Secondary — Ventilatore Fantasy Tileset Complete Bundle + +Owner: us. Bundle URL: . Six packs, 16×16, vibrant medieval-fantasy. + +**Use as:** decorative flourish — animated water, encampment props, medieval fences, foliage variety, bridges. Where ElvGames' farming-RPG palette feels too cute, slot in a Ventilatore prop. + +**Skip:** prefab houses (wrong paradigm), most exterior buildings (ElvGames covers them). + +## Pack file structure + +ElvGames packs are consistently structured: + +``` +/ +├── Characters/ # NPC sprite variants per pack +├── Item Icons/ # category-relevant icons +├── Objects/ # decorative / interactive objects +├── Tilesets/ # main tilesheet PNGs +├── RPG Maker/ # RPG-Maker import format (ignore) +├── License.txt +├── Read Me.txt +├── *.unitypackage # Unity import (ignore) +└── *.yymps # GameMaker import (ignore) +``` + +For Godot: import the **Tilesets/`*.png`** directly as TileSet sources. Objects + Item Icons are individual sprite assets. + +## Gaps still uncovered + +Even with the bundle, these need authoring or sourcing: + +1. **Autotile-ready buildable wall sets** — UNKNOWN. The bundle's house and fortress sheets need an autotile audit (count corners / T-junctions / cap pieces). If insufficient, options are: + - Custom-author terrain bits onto bundle wall tiles (~half-day per material) + - Source a dedicated construction tileset (Mana Seed Iconic Homestead remains available as fallback at $19.99) +2. **Designation overlays** — blueprint ghosts, chop-this-tree marks, mine-this marks. ~4–8 tiles, custom (~2 hrs). +3. **Indoor tint shader** — a subtle blue-grey overlay on Layer 0/1 tiles when Roof flag set. Not asset, just shader work. +4. **Lighting shader** — for visual lighting at night (we locked this in mood/lighting). Not asset. +5. **Cleaning visualization** — dirty-tile decals. Some bundle packs (Mines? grass spring etc.) may have grunge tiles we can repurpose. +6. **Wolf sprite** — bundle's Animal Sprites pack does NOT include wolves. Options: + - Reskin a different animal (a darker dog?) + - Pick a suitable wild creature from EvoMonster Pack series + - Custom wolf sprite (a few hours of pixel art) or external pack +7. **Cremation pyre furniture** — may be in House Interior or Marketplace; otherwise custom. +8. **Grave marker tile** — likely needs custom or graveyard-themed pack (Retro Graveyard 16×16 in Tier 3 *might* have it). + +## Audit list (action items before any release build) + +- [ ] Autotile coverage of `Houses Tileset 2 Seasons/FG_Houses.png` — count corner/T-junction/cap pieces for player-built walls. +- [ ] Autotile coverage of `Fortress Tileset 2 Seasons/FG_Fortress.png` (alt material). +- [ ] Aesthetic harmony test: open one tile from ElvGames Forest 4 Seasons + one from Ventilatore + render in same scene. Is the palette cohesive enough to mix? Decides whether Ventilatore stays as accent or gets shelved. +- [ ] Wolf sprite source — confirm what's available from EvoMonster / Turn-Based Monster packs; if none, plan a custom. +- [ ] Confirm Retro Graveyard 16×16 has grave-marker tile we can use. +- [ ] License attribution credits document — list every pack we use, credit string for game credits screen. + +## Tileset prep (the gotcha) + +ElvGames packs are RPG-style — designed primarily for RPG Maker / GameMaker hand-painted maps, not tile-by-tile colony construction. Same caveat as Mana Seed: walls and floors may not have full 47-tile autotile coverage. Plan for ~half-day per buildable material to author Godot terrain bits onto the existing tiles. + +Designation overlays still custom. Estimated 4–8 tiles, ~2 hours of pixel art. + +## License notes + +**ElvGames terms (most packs in the bundle):** + +> You can: use in personal or commercial projects. Edit/modify to fit your game. Credits to ElvGames. +> You cannot: use in NFT/crypto projects. Sell this asset pack (even modified). Claim it as your own. + +Source: `License.txt` in each pack. Full terms: + +**Some packs may be by other authors** (e.g. Retro Kingdom Explorer series). License for each is in the pack's own `License.txt`. **Audit and honor each.** + +**Ventilatore terms:** confirmed commercial-friendly during prior owner-of-bundle research; verify before any commercial release build. + +**Credits screen requirement:** maintain a list of pack names + creator names; display in game's credits UI. + +## What was dropped from the plan + +- **Mana Seed series purchases** — Smithing Gear, Crops #1, Iconic Homestead. Bundle's coverage replaces them. Free Mana Seed forest packs and Character Base remain optional fallbacks if Bundle palettes mismatch a specific need. +- **Gold Rush / Pixel Farm RPG / LimeZu / Cute Fantasy / Mars Colony** — all dropped from active consideration; bundle covers their roles. + +## Comprehensive survey (kept for reference) + +Earlier-session survey of itch.io / OpenGameArt candidates (now superseded). Maintained so future sessions don't redo the search. + +- **Mana Seed by Seliel the Shaper** ([itch profile](https://seliel-the-shaper.itch.io/)) — only tileset family explicitly authored for tile-by-tile player construction. Free seasonal forest + Farmer Base remain optional fallbacks. +- **[Iconic Homestead](https://seliel-the-shaper.itch.io/iconic-homestead)** ($19.99) — fallback if bundle's wall autotile coverage is insufficient. Stone/wood/plaster wall variants explicitly construction-authored. +- **[Modern Interiors / Exteriors](https://limezu.itch.io/moderninteriors)** by LimeZu — modern aesthetic, wrong direction. +- **[Cute Fantasy RPG](https://kenmi-art.itch.io/cute-fantasy-rpg)** by Kenmi — Stardew-adjacent (similar to bundle's tone, could supplement). +- **[Base Building Tileset](https://elihaun.itch.io/base-building-tileset)** by Eli Haun — Rimworld-genre match but small. +- **[Colony Sim Assets](https://opengameart.org/content/colony-sim-assets)** by Buch — CC0 fallback. + +## Reading list (TBD) + +- ElvGames license fine-print on each pack used. +- Godot 4 docs: TileMap, TileSet terrains, AStarGrid2D. +- Rimworld design talks (Tynan Sylvester) — storyteller / drama / interest curve. + +## What lives elsewhere + +- **Game design / mechanics** — see [`design.md`](./design.md). +- **Engine architecture and TileMap layer plan** — see [`architecture.md`](./architecture.md). +- **Touch UI** — see [`ui.md`](./ui.md). +- **Pillars and vertical slice** — see [`memory.md`](../memory.md). diff --git a/docs/design.md b/docs/design.md new file mode 100644 index 0000000..b443d21 --- /dev/null +++ b/docs/design.md @@ -0,0 +1,701 @@ +# rimlike — game design + +> Companion to [`memory.md`](../memory.md). Read `memory.md` first for project north-star (pillars, vertical slice, open questions). This file is the **game design**: core loop, mechanics, simplifications from Rimworld. + +## Problem / motivation + +A 2D, tile-based **medieval-fantasy** colony management sim — Rimworld DNA, **Going Medieval** as the closest lodestar — but **slimmer** and **shaped for mobile / handheld**. Most of Rimworld's fundamentals are present (pawns with needs and work priorities, base building, food production, hostile events, progression), but simulation, UI, and session length are tuned for short bursts on a small touch screen. + +The aesthetic anchor is **cute farming RPG meets medieval colony sim** (peasants, wood/stone/iron, swords/bows, wild beasts) — bright, cheerful, Stardew-adjacent in palette while keeping the colony-sim depth of Rimworld and the medieval scope of Going Medieval. Driven by owned art (see [`art.md`](./art.md): ElvGames "Ultimate Farming RPG" bundle as primary, Ventilatore Fantasy Tileset as medieval accent secondary). + +## Core loop (one play session) + +1. **Open app** → instant resume from autosave, brief "while you were away" summary (if any background events fired during the timeskip). +2. **Triage** → storyteller prompt(s) waiting (e.g. "Stranger arrived. Recruit?", "Winter in 4 days"). +3. **Plan** → adjust work priorities, queue construction/research, designate areas. The slow, deliberate phase — pause is fine here. +4. **Run time** → game speed defaults to fast; auto-pauses on threats / pawn-down / dialog events. Player watches a few in-game hours fly by. +5. **React** → 1–2 events resolve. +6. **Stop** → close app at any time, autosave is always current. + +## Simplifications from Rimworld + +What we deliberately *do not* build: + +- **No body-part injury system.** Single HP per pawn + status effects (Bleeding, Infected, Sick, Tired, Hungry, Downed). ~80% of the drama for ~5% of the code. +- **No deep social web.** Relationships exist as +/- mood thoughts; no schism / lover triangles in MVP. +- **No temperature simulation in MVP.** Winter = food penalty + storyteller pressure, not a heat-leak simulation. +- **~4–5 skill categories** (Manual Labor, Crafting, Cooking, Medicine, Combat) instead of 12. +- **No mods/scripting in MVP** — punt. +- **No multi-Z-level / mountain-base.** Single ground layer; "roofed" is a flag, not a Z. +- **No prefab-house hand-painting.** Players build tile-by-tile from walls/floors. + +## Skills + +5 categories per pawn, 0–10 each. Level by use. Multiplicative speed/quality bonus on relevant work. **Skills modify duration and quality, never permission** — Bob with Cooking 0 can still cook, slowly and slightly worse. + +| Skill | Affects | +|---|---| +| Manual Labor | Mining, hauling, construction, chopping | +| Crafting | Workbench output, repair quality | +| Cooking | Meal speed and food poisoning rate | +| Medicine | Treatment success, infection clear rate | +| Combat | Hit chance, damage | + +## Health & status effects + +Single HP per pawn (default 100). Statuses tracked separately, each with severity 0–100, decay/grow rates, and an interrupt urgency flag (see [`architecture.md`](./architecture.md) for how statuses become AI interrupts). + +| Status | Source | Effect | +|---|---|---| +| Hungry | Time without food | Slow movement/work; eventually damages HP | +| Tired | Time awake | Slow work; eventually forces sleep | +| Bleeding | Combat / accident | Continuous HP loss; high-priority interrupt | +| Sick | Storyteller / hygiene | Reduces work speed; needs rest + treatment | +| Infected | Untreated wound | Can escalate; treat with Medicine | +| Downed | HP near 0 | Cannot act; carry to bed; bleeds out if untreated | + +## Mood system + +Per-pawn mood is a single 0–100 score, computed each tick as `BASE + sum(thought_modifiers)`. Thoughts come from current state (persistent) or recent events (decay over time). + +### Mood bands & break risk + +| Band | Range | Effect | +|---|---|---| +| Inspired | 90+ | +20% work speed (rare positive event windows) | +| Happy | 65–90 | +10% work speed | +| Content | 35–65 | normal | +| Stressed | 15–35 | −30% work speed | +| Breaking | < 25 sustained ≥ 30 in-game min | **soft break** — see below | + +### Soft breaks + +When a pawn's mood is < 25 for 30 in-game minutes (sustained — momentary dips don't trigger), they enter a soft break, randomly **sulking** or **wandering**: + +- **Sulking** — pawn walks to a quiet corner, sits there, won't accept work. Ends at mood ≥ 35 or after 8 in-game hours. +- **Wandering** — pawn paces aimlessly, refuses work, may eat/sleep. Ends same conditions. + +Soft breaks are a player signal: "address this pawn's needs." No destruction, no violence in MVP. + +### Thought list (~13) + +**State-driven (persistent while condition holds):** + +| Thought | Effect | Source | +|---|---|---| +| Hungry | −5 → −15 (severity ramp) | Hunger need ≥ 60 | +| Tired | −3 → −10 | Sleep need ≥ 70 | +| Damp / Soaked | −3 / −6 | Wet status (see weather) | +| Cold | −10 | Outdoors during a Cold Snap | +| In darkness | −3 | At night, in unlit tile (see Lighting) | +| Cramped quarters | −3 | Sleeping in a room < 4 tiles (see Rooms) | +| Beautiful room | +3 | Sleeping in a room with high beauty score | +| Ugly room | −3 | Sleeping in a low-beauty / dirty room | + +**Event-driven (fire and decay):** + +| Thought | Effect | Decay | +|---|---|---| +| Slept well / poorly | +5 / +0 / −2 / −5 / −8 (sleep gradient, locked) | next sleep | +| Saw corpse | −3 (stacks per corpse) | 8h | +| Ate raw food | −5 | 6h | +| Ate fine meal | +3 | 6h | +| Witnessed death | −10 | 2 days | +| Recently injured | −3 (while wound healing) | wound heal | + +> Numbers are placeholders — tune in prototype. + +The system is **data-driven**: thoughts live in a registry, easy to add/remove. We ship with these 13; expand by playtest. + +## Lighting + +Each tile tracks "is this lit at night?" Lights are emitting furniture (torch, hearth, candle, oil lamp). Visual: lit tiles render in normal color at night; unlit tiles render with a darkening overlay. Players physically see darkness — atmosphere ahead of pure simulation. + +**Light sources (initial):** + +| Source | Radius | Fuel | Build cost | +|---|---|---|---| +| Torch (wall-mounted) | 3 tiles | none | 2 wood | +| Candle | 2 tiles | none | 1 cloth | +| Hearth | 5 tiles + warmth flavor | wood (slow burn) | 10 stone, 5 wood | +| Oil lamp | 6 tiles | oil (post-MVP fuel sim) | 5 metal, 1 cloth | + +Fuel system simplified for MVP — most lights don't deplete; hearth consumes wood from adjacent stockpiles slowly. Full fuel-sim is post-MVP. + +## Rooms + +Auto-detected from walls. Each enclosed area is a Room with: + +- `cells: Array[Vector2i]` +- `furniture: Array[Furniture]` (everything inside) +- `beauty: float` (computed; see Beauty) +- `dirtiness: float` (computed; see Cleaning) +- `inferred_type: String` ("Bedroom" if contains bed; "Kitchen" if contains stove; else "Room") +- `owners: Array[Pawn]` (pawns whose beds are here) + +Used by mood thoughts (Cramped quarters, Beautiful/Ugly room) and the day-summary screen ("Bob slept in his bedroom (beautiful)"). + +Rooms recompute when walls or furniture change; cheap given enclosure detection already runs. + +## Beauty & Quality + +### Beauty + +Each furniture/floor/decor item has a `beauty: int`. Room aggregates as average beauty across cells; some items have wide influence (a statue contributes to a 5-tile radius even if it's one tile). + +Categories displayed to player: +``` +Beauty < −2 Ugly "−3 mood Ugly room" +−2 ≤ B < 0 Dingy slight negative +0 ≤ B < 3 Plain no thought +3 ≤ B < 6 Nice "+1 mood Nice room" +6+ Beautiful "+3 mood Beautiful room" +``` + +### Quality + +All crafted items roll a Quality on creation: **Shoddy / Normal / Excellent / Masterwork / Legendary.** + +- Roll function of crafter's relevant skill + RNG. Shoddy at low skill is common; Masterwork at high skill is rare. +- Quality multiplies item stats (weapon damage, armor coverage, bed comfort, beauty contribution to room). +- Item names are color-coded: grey/white/green/blue/purple. +- Affects all crafted goods: weapons, armor, furniture, meals. + +The "Bob crafted a Masterwork chair" / "Mira's Legendary sword" moments are core Rimworld magic; this system lights them up. + +## Dirtiness & Cleaning + +Floor tiles accumulate `dirtiness: float (0..100)`: + +- Dirtiness grows over time, faster on high-traffic tiles (pawns walking through), faster outdoors-tracked-in. +- Specific events spike dirtiness: blood from combat (~20), corpse decay (~5/h), spilled food. +- Dirtiness above 30 reduces room beauty; above 60 contributes "Ugly room" thought directly. + +**Cleaning** is an 8th work category in the priority matrix. Cleaning Job toils: walk to dirty tile, perform timed cleaning (modified by Manual Labor skill), reduce dirtiness to 0. + +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. + +### Material flow + +``` +Raw material Intermediate Finished +──────────────────────────────────────────────── +Wood log — Walls/floors (1-step), furniture +Stone — Walls (1-step) +Iron ore Iron ingot Iron tools/weapons/armor +Copper ore Copper ingot (decor/wires future) +Silver / Gold ore Silver/Gold ingot (currency / luxury items future) +Cloth / hide — Beds, curtains (1-step furniture) +Grain Flour Bread +Vegetables — Meals (cooked) +Meat — Meals (cooked) +Corpse Meat (butchering) → +``` + +### Workbenches (5 in MVP) + +| Workbench | Skill | Recipes | +|---|---|---| +| **Carpenter's bench** | Crafting | Chair, table, bed, crate, barrel — wood goods | +| **Smelter** | Crafting | Iron / Copper / Silver / Gold ore → ingots (consumes wood as fuel) | +| **Smithy / Anvil** | Crafting | Iron tools (axe, pickaxe, hammer), iron weapons (sword), iron armor (helm/cuirass/boots) | +| **Cooking hearth** | Cooking | Basic meal, fine meal, bread, butcher (corpse → meat) | +| **Millstone** | Crafting | Grain → flour | + +Skipped for MVP: stonecutter (raw stone builds walls fine), tailor (no cloth clothing — only metal armor), drug lab, brewery. + +### Recipe authoring (~22 for MVP) + +Each `Recipe` is a data record: + +``` +Recipe: + id: "forge_iron_sword" + workbench: "smithy" + inputs: [{iron_ingot: 4}, {wood: 1}] + outputs: [{iron_sword: 1}] # quality rolled on creation + skill: CRAFTING + duration_ticks: 200 + min_skill_required: 0 // optional, default 0 +``` + +Output quality is rolled at job completion based on **crafter skill + RNG only**. Input quality doesn't influence output (standard Rimworld/Going Medieval — keeps balance simpler). + +### Bills (recipe queue per workbench) + +Each workbench has a list of **Bills** (queued recipes). A pawn with Crafting (or Cooking) work picks a bench with active bills and runs the next. + +**Bill modes (full Rimworld fidelity):** + +- **Do X count** — run this recipe N times, then remove the bill. +- **Forever** — keep running until paused or removed. +- **Until N in stockpile** — most-used; bill is dormant when stockpile + crate count of output ≥ N, becomes active when below. + +**Optional filters per bill:** + +- **Ingredient quality minimum** — "use Excellent+ iron ingots only." Bill waits if none qualify. +- **Pawn skill minimum** — "only Crafting ≥ 8 takes this bill." For Masterwork-attempting bills. + +### Bill priority within a workbench + +Bills run in display order (top to bottom). Drag to reorder. The active pawn finishes the current job, then re-checks priorities. + +### Updated skill list (5) + +Same five skills; Crafting now spans more bench types: + +| Skill | Affects | +|---|---| +| Manual Labor | Mining, hauling, construction, chopping, **cleaning** | +| Crafting | **Carpenter, smelter, smithy, millstone** — quality of crafted goods | +| Cooking | Cooking hearth, butchering, food poisoning rate | +| Medicine | Treatment success, infection clear rate | +| Combat | Hit chance, damage | + +### Updated work category list (9) + +Construction · Mining · Hauling · Cleaning · **Crafting** · Cooking · Plant · Doctor · Combat + +The priority matrix is now 9 columns wide. Still scrollable on a phone (sticky pawn-name column, swipe horizontally). + +## Storyteller + +Sandbox + light storyteller prompts — soft, dismissible nudges that give each short session shape without forcing scenarios. Per Tynan Sylvester's design talks: drama from situation, mix of opportunity and threat, quiet/threat alternation, player choice for big events. + +### Picker mechanism + +- **Daily roll** at 6am in-game time. Storyteller picks one event from a weighted pool. +- **Cooldowns per category**: no two threats within 3 days; no two wanderers within 5 days; nudges fire at most once per pawn-need-state. +- **Tension model**: a running "tension score" tracks recent dramatic events. High tension → reduce threat weight. Low tension → boost threat weight. Keeps the pacing breathing. +- **State-triggered events** (e.g. "First Beds" fires while no beds exist) get higher weight than random ones. +- **Display flavor**: + - **Nudges** → ambient top banner, no pause, dismissible. + - **Seasonal/ambient beats** → ambient banner with seasonal art tint. + - **Modal events** (wanderer, threat, disease, choice) → auto-pause + dialog. + +### Prompt corpus (MVP — 25 prompts) + +#### 🌾 Soft nudges (dismiss-only, low priority) + +| ID | Title | Body | Trigger | Effect | +|---|---|---|---|---| +| 1 | First Beds | *"Your settlers slept on the cold ground again. They are starting to ache."* | day 2+, beds < pawn count | nudge only | +| 2 | Empty Larder | *"The larder is bare. Spring won't last forever."* | day 3+, no farm zone | nudge | +| 3 | No Fire | *"Without a hearth, the cold will bite by night."* | day 4+, no hearth/torch | nudge | +| 4 | Walls? | *"Sleeping under stars is romantic until the wolves arrive."* | day 5+, no walls | nudge | + +#### 🍂 Seasonal beats (ambient banner) + +| ID | Title | Body | Trigger | Effect | +|---|---|---|---|---| +| 5 | Spring Awakens | *"The thaw runs in every stream. Crops will grow fast now."* | spring start | +10% crop growth season | +| 6 | Summer's Heat | *"The sun beats down. Unsheltered work will tire faster."* | summer start | outdoor work −5% | +| 7 | Autumn's Harvest | *"The fields are heavy with the last of the year's bounty."* | autumn start | +15% harvest yield | +| 8 | Winter's Edge | *"Frost has come. The road is closed; you are alone."* | winter start | no wanderers for 5 days; threat weight ↑ | + +#### 🚶 Wanderer events (modal, choice) + +| ID | Title | Body | Trigger | Choices | +|---|---|---|---|---| +| 9 | A Traveler | *"A weary traveler stumbles toward your gate. They look hungry. Will you welcome them?"* | ghost state OR ~8 days | Welcome (+1 pawn) / Send away | +| 10 | The Refugee Family | *"A family fleeing bandits arrives. They have nothing, but they would work hard."* | post-day-15 | Welcome (+2 pawns, low skills) / Refuse (−2 mood colony 1 day) | +| 11 | The Old Soldier | *"A retired soldier offers his blade for a place by your fire. Combat 8, but old and tired."* | post-day-20 | Welcome / Refuse | +| 12 | The Wandering Healer | *"A traveling healer asks for shelter. She brings knowledge of medicine."* | any pawn Sick | Welcome (+1, Medicine 6) / Refuse | + +#### 🐺 Threat events (modal, auto-pause) + +| ID | Title | Body | Trigger | Effect | +|---|---|---|---|---| +| 13 | Wolves at the Edge | *"Wolves howl in the distance. They will be here by nightfall."* | season-weighted threat slot | 1–3 wolves at edge in 2h | +| 14 | Lone Wolf | *"A starving wolf circles your livestock."* | low-threat random | 1 wolf | +| 15 | Pack Hunt | *"A hunting pack moves through the forest. They smell your colony."* | post-day-30 threat | 4–6 wolves | +| 16 | Bandit Scouts | *"Strange figures watched from the treeline at dusk. Bandits, perhaps."* | post-day-25 | flavor; raises threat-likelihood (foreshadow for v2 raids) | + +#### 🤒 Disease & misfortune (modal or nudge) + +| ID | Title | Body | Trigger | Effect | +|---|---|---|---|---| +| 17 | Fever | *"%pawn% woke with a fever. The sickness may spread."* | random ~30 days | Sick on random pawn; 20% daily spread to neighbors | +| 18 | A Bad Cut | *"%pawn% gashed their hand chopping wood. The wound looks deep."* | chop job, low chance | Bleeding on pawn | +| 19 | The Sleeplessness | *"%pawn% has barely slept. Something weighs on them."* | random, low-mood pawn | Tired worsens; mood penalty 2 days | + +#### 🌾 Resource & opportunity + +| ID | Title | Body | Trigger | Effect | +|---|---|---|---|---| +| 20 | Bountiful Harvest | *"Your fields exceeded the season. The granary swells."* | harvest, random | +25% yield this harvest | +| 21 | Lumberjack's Luck | *"%pawn% found a copse of unusually thick trees."* | chop job, random | next 3 trees +50% wood | +| 22 | Veins of Iron | *"A miner reports a rich vein, deeper than expected."* | mine job, random | next mining yield ×2 | + +#### 🏰 Discovery / lore (flavor, dismiss) + +| ID | Title | Body | Trigger | Effect | +|---|---|---|---|---| +| 23 | Strange Stones | *"Settlers report finding carved stones in the wood — older than any memory."* | random, post-day-15 | flavor only | +| 24 | An Old Map | *"%pawn% found a tattered map. Roads to the north, half-faded."* | random, post-recruit | flavor (seeds v2 trade/exploration) | + +#### 🌟 Milestone (rare, celebratory) + +| ID | Title | Body | Trigger | Effect | +|---|---|---|---|---| +| 25 | One Year Survived | *"A full year. The first frost feels different now — yours is a real settlement."* | end of first winter | +5 mood "We made it" colony 2 days | + +### Authoring guidelines + +- **Voice**: medieval, a touch of melancholy/grit. Avoid modern phrasing. +- **Length**: 1–2 sentences. Anything longer feels like a wall on a phone. +- **Use `%pawn%` substitution** for personalized events. Game replaces with the relevant pawn's name. +- **All copy is in a string table** — i18n-ready from day one. +- **New prompts = new rows in a JSON registry**. Trivial to expand post-MVP. + +## Combat + +Realtime with auto-pause on threat. Two-roll resolution (hit, then damage). Cover from walls + trees. Downed-then-rescue death model for sim drama. + +### Weapons (3 in MVP) + +| Weapon | Type | Damage | Range | Speed | Recipe | +|---|---|---|---|---|---| +| Sword | Melee | 8 | 1 (adjacent) | 1.0 atk/sec | 4 iron ingot + 1 wood | +| Axe | Melee | 12 | 1 | 0.7 atk/sec | 3 iron ingot + 1 wood | +| Bow | Ranged | 10 | 8 tiles | 0.5 atk/sec | 1 wood + 1 cloth | + +Quality multiplies all stats (Masterwork sword = 12 dmg @ 1.5 atk/sec). + +Unarmed pawns fight with fists: 3 damage, 0.5/sec. Used when cornered without a weapon. + +### Armor (3 slots) + +| Slot | Iron variant armor value | Recipe | +|---|---|---| +| Helmet | 3 | 3 iron ingot | +| Cuirass | 8 | 5 iron ingot | +| Boots | 2 | 3 iron ingot | + +Total iron set = 13. Quality multiplies. Each slot independent — pawn can wear partial armor. Leather/cloth variants post-MVP (lower armor, lower mood penalty for hot weather). + +### Cover (walls + trees) + +A target is "in cover" if a wall or tree tile sits between the target and the attacker along line-of-sight. + +- **Wall** — full cover, **−40% to hit** +- **Tree** — partial cover, **−20% to hit** + +Cover is checked per-attack. Multiple cover sources don't stack (highest applies). + +### Hit / damage resolution (two roll) + +```python +# Roll 1: hit +hit_chance = 50% + (combat_skill × 5%) − cover_penalty − (range_penalty if ranged) +range_penalty = max(0, (distance − 2) × 5%) +hit = (random() < hit_chance) + +# Roll 2: damage (only if hit) +damage = weapon_damage × quality_multiplier − target_total_armor +damage = max(1, damage) # always at least 1 damage on a hit +target_hp -= damage +``` + +Skill 10 pawn at adjacent range with no cover: 50% + 50% = ~100% hit before clamps. Same pawn at range 8 with bow against treed target: 50% + 50% − 20% (cover) − 30% (range) = 50%. Tunable. + +> All numbers placeholders — tune in prototype. + +### Downed & death + +When HP reaches 0, pawn enters **Downed** status (not dead). Behavior: + +- Drops their weapon to the ground. +- Cannot move or take any job (Layer 1 of decision pipeline). +- Bleeding continues if untreated → HP stays at 0 → death after **6 in-game hours** untreated. + +**Rescue flow:** + +- Doctors auto-prioritize Downed colonists (Doctor work). +- Job: walk to Downed pawn → carry → walk to nearest bed → place in bed. +- Pawn in bed gets `Resting` status; doctor visits to treat (clears bleeding, infection); HP regenerates over ~2 in-game days. + +If no doctor reaches the Downed pawn in time → death → corpse entity → handled by stockpile system (Corpses filter category). + +### Combat priority semantics — "Off = defends if cornered" + +A pawn with Combat priority **Off**: + +- Won't accept the player's forced "Attack target" job. +- Won't autonomously move toward threats. +- *Will* fight back if an enemy is adjacent and no escape path is reachable (5-tile pathfinder check). +- Tries to flee toward map interior / nearest indoor zone when threats appear. + +A pawn with Combat priority **High** or **Critical**: actively engages threats in range, holds position, takes player commands. + +### Forced commands + +Player taps a pawn → bottom sheet → **Attack target** → tap an enemy. Issues a forced job (Layer 5 of pawn AI). Pawn engages with priority. + +Same flow for **Carry to bed** (manually rescuing a downed pawn) and **Patrol** (post-MVP). + +### Auto-equip + +Pawns with Combat > Off auto-equip the best available weapon they can carry, prioritizing higher quality. Player can override via long-press on a weapon item → **Assign to Bob**. Locks the assignment until cleared. + +### Friendly fire — ON + +Projectiles travel along their LOS path; any pawn in the path is a potential hit (with reduced chance vs the intended target). Standard Rimworld semantic. Adds tension to ranged formations. + +### Wolf AI (MVP's one threat type) + +- Storyteller fires wolf events at night, weighted by season (more in winter). +- Wolves spawn at map edge in a small pack (1–4). +- Approach the colony, engage pawns or animals. +- Have natural weapons (bite: 6 damage, 1.0 atk/sec) and minor armor (tough hide: 2 armor). +- Flee at HP < 30% — pacing keeps them from being trivial swarm fodder. +- Death = corpse, butcherable for ~5 meat at the cooking hearth. + +## Stockpiles & hauling + +The unsung 30% of pawn-time. Storage has two coexisting forms: **floor stockpile zones** (areas painted on the world) and **containers** (furniture you build). Both share the same filter + priority model; haulers treat them as a unified pool of destinations. + +### Filter categories — 16 chips + +| Materials | Food | Equipment | Misc | +|---|---|---|---| +| 🪵 Wood | 🥕 Vegetables | 💊 Medicine | ⚰ Corpses | +| 🪨 Stone | 🍖 Meat | ⚒ Tools | | +| ⚙ Iron ore | 🌾 Grain | ⚔ Weapons | | +| 🟤 Copper ore | 🍞 Cooked meals | 🛡 Armor | | +| ⚪ Silver ore | | | | +| 🟡 Gold ore | | | | +| 🧵 Cloth & hides | | | | + +Tap-toggle in a 4×4 chip grid per stockpile or container. (Future expansion: split Wood by species, etc. — punted.) + +### Priorities — 5, Rimworld semantics + +**Critical / Important / Preferred / Normal / Low.** Items flow from low priority to high automatically: + +- **Critical** — pulls items from anywhere, immediately. The "I want this filled NOW" zone. +- **Important** — pulls from Preferred and below. +- **Preferred** — default for "nice" stockpiles (e.g. kitchen-adjacent food). +- **Normal** — standard. +- **Low** — dump zone. Items here move OUT to higher zones when those have space. + +Pawns periodically rescan (every ~5 sim seconds) to migrate items low→high without burning CPU. + +### Containers + +A **Crate** is a furniture entity, built like any other (Build → Furniture → Crate). MVP ships **one generic crate type**: 4 stacks capacity, any allowed item types (configurable post-build). Different sizes / specialized crates are post-MVP. + +- Independent of zones — placed anywhere, has its own filter + priority. +- Same 16-chip filter and 5-level priority as floor zones. +- Tap a crate → inspect screen (filter, priority, contents, Empty / Move / Demolish). +- Pawns walk adjacent (4-neighbor) to drop into a crate; container tile itself is impassable. +- Capacity: 4 stacks of allowed types. + +Crates exist alongside floor stockpiles. Use floor zones for high-volume bulk (logs in a lumber yard); use crates for organized indoor storage (kitchen pantry, armory). + +### Stacks — one per tile, one item type per tile + +Mixed tiles disallowed. Visual quantity badge per tile. Stack maxes: + +| Item | Max stack | +|---|---| +| Wood, stone, cloth, ores | 50 | +| Vegetables, meat, grain | 25 | +| Cooked meals, medicine | 10 | +| Tools, weapons, armor, corpses | 1 | + +### Pawn carry + +One stack at a time, capped at pawn's carry capacity (~25 wood per trip on the average pawn). Multi-type carry is v2. + +### Workbench output drop tile + +Each workbench has one designated cell in front. Products land there; haulers clear it via the normal pipeline. Avoids the Rimworld kitchen-mess problem. + +### Forbid / allow + +Long-press item → toggle hauling. Forbidden items get a 🚫 badge; haulers ignore them. + +For data structure + hauling AI, see [`architecture.md`](./architecture.md). For zone painting and crate inspect UI, see [`ui.md`](./ui.md). + +## Roofing, indoor, weather + +Temperature simulation is cut (locked). What remains is the indoor/outdoor divide that powers shelter motivation. + +### Indoor = roofed + +A tile is **indoors** if it has the Roof flag set on Layer 4 (see [`architecture.md`](./architecture.md)). No further enclosure check at sim-time — we trust the auto-roof system to only place roofs in sensible places. + +### Auto-roof, not player-placed + +When walls form an enclosed area ≤ 8 cells across, the interior **auto-roofs**. No "place roof tile" verb. BFS from interior cells when walls change; if every direction hits a wall within 8 cells, mark Roof. Cap prevents a fence around a meadow from roofing a stadium. + +Matches the player's mental model: *"I built four walls, now there's a room."* + +### "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. + +### Roof rendering — none in MVP + +Roofs are sim data only. Indoor tiles get a subtle tint (faint blue-grey overlay). No X-ray toggle. This is the genre convention for top-down sims (Rimworld does the same). Visible roofs = v2 polish. + +### What roofing protects against + +| Effect | Outdoor | Indoor (roofed) | +|---|---|---| +| Mood penalty for being in rain | −3 (Wet) | none | +| Mood for sleeping | −5/−2 | +0/+5 | +| Snow accumulates | yes | no | +| Food spoilage in heat wave | faster | slower | +| **Plants grow** | yes | **no** | + +Crucially: **plants don't grow indoors.** No skylights / hydroponics in medieval scope. Gardens must be outside, so weather matters even after walls go up. + +### Weather — 4 types, daily roll + +Simple state machine. At sunrise, storyteller picks the day's weather, weighted by season. + +| Weather | Effect | +|---|---| +| **Clear** | nothing special | +| **Rain** | outdoor pawns/items accumulate Wet; food spoilage slows | +| **Storm** | heavier rain, faster Wet, mood penalty outside, no harvesting jobs assigned | +| **Cold snap** | outdoor pawns: −10 mood, hunger grows faster | + +### Seasons + +4 seasons × 12 in-game days = **48-day year** (~4 real-time hours per year at default speed). + +| Season | Clear | Rain | Storm | Cold snap | +|---|---|---|---|---| +| Spring | 50% | 35% | 10% | 5% | +| Summer | 70% | 20% | 10% | 0% | +| Autumn | 50% | 30% | 15% | 5% | +| Winter | 30% | 25% (as snow) | 25% | 20% | + +> **Weights are placeholders — tune in prototype.** The shape (winter rough, summer kind) is locked; numbers are first guesses. + +Storyteller can override for narrative beats ("a hard winter approaches"). + +### Wet status + +Pawns in rain accumulate **Wet** (0–100). Decays indoors, grows outside. + +``` +Wet 0–25: no effect +Wet 25–60: −3 mood "Damp" +Wet 60+: −6 mood "Soaked", small movement-speed penalty +``` + +Decay ~5/in-game-hour indoors; accumulation ~10/h in rain, ~20/h in storm. + +> **Numbers are placeholders — tune in prototype.** Thresholds, decay rates, and accumulation rates are starting values, not locked decisions. + +### Sleep mood (the strong gradient) + +``` ++5 Slept in bed, indoors ++0 Slept in bed, outdoors (under stars) +−2 Slept on bedroll/ground, outdoors with no bed +−5 Slept on the ground, indoors +−8 Slept on the ground, outdoors +``` + +Forces players to build (a) beds and (b) walls/roof to house them. Simple, legible gradient. + +> **Numbers are placeholders — tune in prototype.** The shape (worse outdoors, worse on ground, both compound) is the design intent; specific values get tuned by playtest. + +### Explicitly not simulated + +- Temperature (locked). +- Wall-material insulation — all walls equivalent. +- Lightning, fires — defer. +- Wind, projectile drift — defer. +- Sunlight angles — plants either get sky (grow) or don't. + +## Failure state — Ghost colony + +When all colonists die or leave, the world enters **ghost state.** No game-over screen. + +- Buildings, stockpiles, containers, items, beds — all persist exactly as they were. +- Time continues at slow speed; pawn-side simulation is paused. +- Within **3–5 in-game days**, the storyteller fires a **Wanderer event**: a stranger arrives at the map edge, prompting recruit/refuse. +- If accepted: new pawn enters, you control them, ghost state ends. +- If refused: another wanderer eventually appears; world stays ghost until accepted. + +**Why this works for mobile:** "lost my colony of 200 hours" on a phone is brutal UX. Ghost state preserves the world, the player's investment, and lets a session end on a softer note. Permadeath drama still lives in *individual* pawn deaths. + +## Death & corpses + +### Cause of death tracking + +Each pawn entity stores `death_cause: String` and `death_day: int`. Used by pawn-detail screen ("Bob the Carpenter — died Day 27 of Spring 1, killed by wolf") and by grave markers (tap a grave → see that screen). + +### Corpse decay + +Corpses decay over time. Each corpse has `decay_severity: float (0..100)`, growing ~33/in-game-day: + +| Severity | State | Effect | +|---|---|---| +| 0–50 | Fresh | Butcherable for ~5 meat. Standard −3 mood per stack. | +| 50–100 | Rotting | No longer butcherable. Mood penalty grows: −5 per stack. Visible green-grey tint. | +| 100 | Rotted | Still removable (bury or cremate); butcher disabled. | + +Rotting corpses don't attract predators in MVP (clean cut for scope) — but rot creates urgency: bury or cremate fast. + +### Burial mechanic + +Designate a **Graveyard** zone — a special stockpile that only accepts Corpses (filter has a single chip on, Corpses). + +Burial Job toils (Construction-priority pawns): + +``` +walk to corpse → carry → walk to graveyard cell +→ dig grave (~30 sim sec, modified by Manual Labor skill) +→ place corpse → place grave marker tile +``` + +Result: corpse entity destroyed, grave marker placed. Marker is a 1-tile decorative entity: + +- Beauty: +1 (modest contribution to room beauty) +- Persistent until demolished +- Tap → opens pawn-detail showing the deceased's record + +### Cremation pyre + +Buildable furniture (Build → Furniture → Pyre, costs ~10 stone + 5 wood). Plays the role of a workbench but accepts a single recipe: + +- **Cremate corpse** — input: 1 corpse + 5 wood. Output: nothing (corpse gone). Time: ~20 sim sec. Skill: Crafting. + +Player adds a "Cremate" bill to the pyre; a Crafting-priority pawn auto-hauls a corpse, walks to the pyre, performs the rite (visual: brief flame + smoke), corpse destroyed. + +Faster than burial; doesn't preserve a memorial. Clean for "send the wolves we killed back into the world." + +### Grave markers + +Single-tile decorative entity placed by burial. Beauty +1 each. Cluster in a graveyard zone gives a small "Beautiful (somber) garden" feel — beauty bonus stacks. + +Tap a marker → pawn-detail screen for the deceased: name, role, death day, cause, days-lived, statuses-at-death. + +### What we deliberately don't build + +- **Resurrection** — no MVP feature. Death is permanent. +- **Hauntings / ghosts** — flavor for v2, not needed. +- **Funeral ritual** with attendance + mood — interesting but adds a system. +- **Decay attracts predators** — was an option; cut for MVP scope (creates negative-feedback-spiral risk). + +## What lives elsewhere + +- **Pawn AI / job system, work priorities mechanics** — see [`architecture.md`](./architecture.md). +- **Touch UI for work priorities + other screens** — see [`ui.md`](./ui.md). +- **Tileset strategy + assets + art license** — see [`art.md`](./art.md). +- **Vertical slice spec** — see [`memory.md`](../memory.md). diff --git a/docs/ui.md b/docs/ui.md new file mode 100644 index 0000000..675e85a --- /dev/null +++ b/docs/ui.md @@ -0,0 +1,630 @@ +# rimlike — touch UI + +> Companion to [`memory.md`](../memory.md). This file is the **touch UI** design: gestures, layout philosophy, screen mockups. Engine-side implementation lives in [`architecture.md`](./architecture.md); game mechanics in [`design.md`](./design.md). + +## UX philosophy (locked) + +- **Bottom-sheet menus** replace Rimworld's right-side panels. Thumbs reach the bottom of a phone screen. +- **Long-press** = inspect / context menu (the right-click replacement). +- **Tap world** = select tile / pawn / item. +- **Pawn list panel** along one edge — tap a portrait to focus the camera and act on that pawn directly. +- **Build menu** = bottom drawer with tabs (Furniture / Walls / Floors / Production / …); designation paint mode for placing. +- **Speed/pause** controls fixed at top; large, finger-sized. +- **Default cadence is fast + auto-pause** (see [`architecture.md`](./architecture.md) Time / tick model). The UI should reflect this — no element should require careful fine-targeting at 1× speed. + +## Work priority UI + +THE macro-management surface for this genre — Rimworld and Going Medieval players spend significant time here. Touch translation matters. + +### Three views, same backing data + +#### View A — Matrix (default) + +Spreadsheet pawn × work, horizontally scrollable, sticky pawn-name column. Cells show priority with colored background. Tap to cycle (4→3→2→1→Off→4); long-press for 5-chip direct picker. Swipe a column = bulk-set everyone to the same priority for that job. Each pawn row shows primary skill under name. Sort by name / total skill / single-job skill. Pinch to zoom. + +``` +┌──────────────────────────────────────────────────┐ +│ Priorities Sort: skill ▼ Auto ⓘ │ +├──────────────────────────────────────────────────┤ +│ │Cnstr│Mine │Cook │ Doc │Plant│Haul │ │ +│ Bob 🔧8 │ 🟠2 │ 🔵4 │ 🔴1 │ 🟡3 │ 🟡3 │ 🔵4 │ │ +│ Mira ⚕12 │ 🟡3 │ 🟡3 │ 🔵4 │ 🔴1 │ 🟠2 │ 🟡3 │ │ +│ Tom ⛏9 │ 🔵4 │ 🔴1 │ 🟡3 │ 🔵4 │ 🟡3 │ 🟠2 │ │ +└──────────────────────────────────────────────────┘ +``` + +#### View B — Per-pawn (tap a pawn row) + +Skills bars + full priority list as a vertical, finger-friendly list. Easy to plan one pawn's role. + +``` +┌──────────────────────────────────┐ +│ ← Bob │ +│ "Carpenter from Eastvale, 27" │ +├──────────────────────────────────┤ +│ Skills │ +│ Construction ████████░░ 8/10 │ +│ Cooking ██████░░░░ 6/10 │ +│ Combat ████░░░░░░ 4/10 │ +│ Medicine ███░░░░░░░ 3/10 │ +│ Mining ██░░░░░░░░ 2/10 │ +├──────────────────────────────────┤ +│ Job priorities │ +│ 🔴 Construction [ 2 ] │ +│ 🟠 Cooking [ 1 ] │ +│ 🟡 Medicine [ 3 ] │ +│ 🟡 Plant [ 3 ] │ +│ 🔵 Hauling [ 4 ] │ +│ 🔵 Mining [ 4 ] │ +│ ⚫ Doctor [ Off ] │ +│ │ +│ [ Auto-assign from skills ] │ +└──────────────────────────────────┘ +``` + +#### View C — Per-job (tap a column header) + +Pawns sorted by skill in that job, descending. Surfaces "best cook is set to lowest" insights — genre-specific gold; saves players from a Rimworld trap. + +``` +┌──────────────────────────────────┐ +│ ← Cooking │ +├──────────────────────────────────┤ +│ Mira ⚕12 Cook ████████░░ 9│ 4 │ +│ Bob 🔧8 Cook ██████░░░░ 6│ 1 │ +│ Tom ⛏9 Cook ███░░░░░░░ 3│ 3 │ +│ │ +│ ⚠ Mira is best but set to 4. │ +│ [ Promote Mira to 1 ] │ +└──────────────────────────────────┘ +``` + +### Smart defaults & QoL + +- **New-pawn auto-assign:** highest skill → 2, second skill → 3, others 3 / 4 / Off based on skill level. Player commits the "1" themselves. +- **Templates:** save a configuration ("Cook role") and apply with one tap. Useful for new arrivals. +- **Bulk ops:** long-press column header → "Disable for everyone" / "Reset"; long-press row → "Pause this pawn" (everything Off). +- **Contextual nudges in View C:** "⚠ best cook is set to 4 — promote?" Cheap and high-value. +- **Schedule integration** (different priorities for morning/night) — post-MVP. + +### Edge cases worth surfacing in UI + +- A pawn with Doctor=Off **won't** treat a bleeding colonist. Warn in View C: "no one is set to Doctor; emergencies will be ignored." +- Forced jobs (Layer 5 of pawn AI) bypass priorities entirely — the grid is autonomous behavior only. +- Low-skill assignments to risky jobs (Doctor with Medicine 0) → soft warning at chip-pick time, not a block. + +## Stockpile zone UI + +For mechanics (categories, priorities, stack rules) see [`design.md`](./design.md); for data + hauling AI see [`architecture.md`](./architecture.md). This section is the screens. + +### Designation flow (creating a zone) + +1. Player taps **Zones** (top-level button, peer to Build). +2. Bottom sheet shows existing zones list + **+ New Stockpile**. +3. Tap + → designation paint mode; finger-drag rectangles or freeform cells. (Rectangles for MVP unless freeform is requested.) +4. Confirm tray slides up: + +``` +┌────────────────────────────────────────┐ +│ New stockpile · 12 tiles │ +│ Filter: [✓ all 7 categories] │ +│ Priority: [ Normal ▼ ] │ +│ Name: "Stockpile #3" │ +│ [ Cancel ] [ Create ] │ +└────────────────────────────────────────┘ +``` + +5. After creation, the zone is editable via tap-zone-tile. + +### Editing an existing zone + +Tap any zone tile → bottom sheet: + +``` +┌────────────────────────────────────────┐ +│ ← Kitchen Stockpile │ +│ 12 tiles · Priority: Preferred ▼ │ +├────────────────────────────────────────┤ +│ Accepts (4×4 chip grid) │ +│ [✓🪵Wd] [✗🪨St] [✗⚙Ir] [✗🟤Cu] │ +│ [✗⚪Ag] [✗🟡Au] [✗🧵Cl] [✓🥕Veg] │ +│ [✗🍖Mt] [✓🌾Gr] [✓🍞Ck] [✗💊Md] │ +│ [✗⚒Tl] [✗⚔Wp] [✗🛡Ar] [✗⚰Co] │ +├────────────────────────────────────────┤ +│ Contains │ +│ 🌾 Wheat ×18 (1 stack) │ +│ 🥕 Carrot ×8 (1 stack) │ +│ 🍞 Bread ×5 (1 stack) │ +│ Empty: 9 tiles │ +├────────────────────────────────────────┤ +│ [ Resize ] [ Delete ] [ Priority ] │ +└────────────────────────────────────────┘ +``` + +**Filter grid (16 chips, 4×4):** tap any chip to toggle. Top-row long-press → "category-only mode" (toggle off everything except the row's column). + +**Priority picker (5-chip):** tap the priority dropdown → row of 5 chips (Critical / Important / Preferred / Normal / Low) with color coding (🔴🟠🟡⬜🔵). + +### Container inspect screen + +Tap any Crate entity in the world → bottom sheet: + +``` +┌────────────────────────────────────────┐ +│ ← Crate │ +│ 2/4 stacks · Priority: Normal ▼ │ +├────────────────────────────────────────┤ +│ Accepts (same 4×4 chip grid) │ +│ [✓🪵Wd] [✗🪨St] [✗⚙Ir] [✗🟤Cu] │ +│ [✗⚪Ag] [✗🟡Au] [✗🧵Cl] [✓🥕Veg] │ +│ [✗🍖Mt] [✗🌾Gr] [✗🍞Ck] [✗💊Md] │ +│ [✗⚒Tl] [✗⚔Wp] [✗🛡Ar] [✗⚰Co] │ +├────────────────────────────────────────┤ +│ Contains │ +│ 🪵 Wood log ×42 (1 stack) │ +│ 🥕 Carrot ×18 (1 stack) │ +│ Empty: 2 stack slots │ +├────────────────────────────────────────┤ +│ [ Empty ] [ Demolish ] │ +└────────────────────────────────────────┘ +``` + +Same filter and priority controls as zones — players can transfer mental model. "Empty" drops contents as loose items at neighbor cells; "Demolish" deconstructs (partial material refund). + +### Zone overlay rendering + +Zones are a transparent colored overlay rendered **only** when the Zones panel is open. Color-coded by priority: + +- 🔴 Critical — strong red tint +- 🟠 Important — warm orange +- 🟡 Preferred — amber +- ⬜ Normal — neutral / faint +- 🔵 Low — cool blue + +Outside the Zones panel, the world view shows item stacks and crate sprites but not zone boundaries (less visual clutter). + +### Smart defaults & nudges + +- **New zone defaults:** filter on for everything, priority Normal. +- **"Suggest filter from contents"** — if 80% of items in a zone match one category, suggest enabling only that. +- **Auto-categorize hint on first drop** — when a pawn drops the first wood log into a new zone, prompt: "Make this a Wood-only stockpile?" +- **Items-not-being-hauled warning** — if items sit outside any zone for > 2 in-game days, show a soft alert: "Lots of wood waiting outside — make a stockpile?" + +### Forbid / allow + +Long-press an item in the world → context menu → "Forbid hauling" / "Allow hauling." Forbidden items get a red 🚫 badge. Hauling pawns ignore them. + +### Per-tile inspection + +Tap a non-zone tile holding an item → inspect tray ("3× Wood log · Loose · Will be hauled by next idle pawn"). Tap a zone tile → zone editing tray (above) plus "tile contents." + +### Edge cases worth surfacing in UI + +- **Zone overlap** — paint mode shows red overlay where cells already belong to another zone; can't confirm until resolved. +- **No accepting zone** — if an item's category isn't accepted anywhere, surface a soft warning ("Cooked meals piling up — no kitchen stockpile yet"). +- **Full zone** — the Contains list shows "Full · X tiles" so the player notices. + +## Mood, lighting, rooms, beauty, quality, cleaning — UX + +Most of these surfaces are passive (visualization in-world). For mechanics see [`design.md`](./design.md); algorithms in [`architecture.md`](./architecture.md). + +### Mood visualization + +- **Pawn portrait** in the pawn list shows a small mood-band emoji: 🙂 Content / 😐 Stressed / 😟 Breaking. Tap → pawn detail. +- **Pawn detail screen** shows the mood bar (0–100) with color zones, plus a list of active thoughts: + ``` + Mood: ████████░░░░░░░░ 38/100 [Stressed] + Thoughts: + +5 Slept well, indoors (next sleep) + +3 Beautiful room (persistent) + −5 Hungry (persistent) + −3 Saw corpse ×2 (decays 7h) + −10 Witnessed death (decays 38h) + ``` +- **Soft break alert** — when a pawn enters a break, a top-screen alert: "Bob is sulking. Address his needs." Camera pans to him on tap. + +### Lighting visualization + +At night, world renders with a darkness shader. Lit tiles inside torch/hearth/candle radii brighten back toward normal color. Player physically sees safe lit zones vs scary dark. Daytime renders normally regardless of lighting. + +Light sources are visible as glowing tile sprites. Hearth has flicker animation. + +### Room labels + +When the **Rooms** panel is opened (a top-level toggle), each detected room gets a floating label: + +``` + ╔═══════════╗ + ║ Bedroom ║ + ║ Bob ║ + ║ Beauty: 4 ║ + ╚═══════════╝ + Beautiful +``` + +Labels show: inferred type, owner(s) if any, beauty score, dirtiness if > 30. Outside the Rooms panel: labels hidden, just the indoor tint. + +### Beauty indicator on rooms + +In the Rooms panel, room overlay tints by beauty: green-tint for Beautiful, neutral for Plain, brown-tint for Ugly. Player reads the colony quality at a glance. + +### Quality color-coding + +Item names everywhere display with quality color: +``` +Wooden chair (Normal — white) +Excellent wooden chair (green) +Masterwork iron sword (blue) +Legendary oak throne (purple) +``` +Quality icon prepended to item name in stockpile lists, pawn equipment, container contents. + +### Dirtiness visualization + +Dirty tiles render with a subtle brown overlay tint (dirtier = darker). Blood spots, spilled food show as decals on top of the floor. Cleaning Job in progress: a small "sweeping" animation on the pawn. + +### Cleaning in the work-priority matrix + +Cleaning becomes column 8 in the priority matrix (and the per-pawn / per-job views). Otherwise no UI change — slots into the existing system. + +## Workbench bill queue UI + +For mechanics and bill modes, see [`design.md`](./design.md); for data structures see [`architecture.md`](./architecture.md). This section is screens. + +### Workbench inspect + +Tap any workbench → bottom sheet: + +``` +┌──────────────────────────────────────┐ +│ ← Smithy │ +│ Bob is working · 2/3 active │ +├──────────────────────────────────────┤ +│ Bills (drag to reorder) │ +│ 1. ▶ Iron sword × 3 │ +│ 2. Iron axe — until 5 in storage │ +│ 3. Iron helmet × 1 ⏸ paused │ +│ 4. Masterwork sword (skill ≥ 8) │ +│ │ +│ [ + Add Bill ] │ +├──────────────────────────────────────┤ +│ Output drop: → 1 tile │ +│ [ Demolish ] │ +└──────────────────────────────────────┘ +``` + +Each row shows: recipe name, mode (count / until-N / forever), ingredient or skill filters if set, paused state. Long-press a bill → Edit / Pause / Remove. Drag reorder (top = highest priority). + +### Add Bill flow + +1. Tap **+ Add Bill** → recipe picker (categorized: Tools / Weapons / Armor / Furniture / Food / etc.). +2. Tap a recipe → bill config: + +``` +┌──────────────────────────────────────┐ +│ ← Add Bill: Iron Sword │ +│ Inputs: 4 Iron ingot, 1 Wood │ +│ Skill: Crafting │ +├──────────────────────────────────────┤ +│ Mode: │ +│ ◯ Do count: [ 1 ] │ +│ ◯ Forever │ +│ ◉ Until in storage: [ 5 ] │ +├──────────────────────────────────────┤ +│ Filters (optional): │ +│ Ingredient quality min: [ Normal ▼ ] │ +│ Pawn skill min: [ 0 ] │ +├──────────────────────────────────────┤ +│ [ Cancel ] [ Add ] │ +└──────────────────────────────────────┘ +``` + +3. Tap Add → bill enters the queue at the bottom; drag to reorder. + +### Active-bill indicator + +A workbench in the world view shows a small icon when bills are active and a pawn is or could be working it (a hammer for Smithy, a cooking pot for hearth, etc.). Smoke / sparks particle when actively in use. + +### Recipe picker organization + +Recipes live in categorized lists: by output category, then by required skill threshold. A Masterwork-attempting bill (skill ≥ 8) shows a faint star icon — visual signal of "this is power-user content." + +### Crafting in the work-priority matrix + +Crafting is column 5 (between Cleaning and Cooking) in the priority matrix — 9 columns total. Same tap-to-cycle and long-press picker as the others. + +## Combat UX + +For mechanics, weapons, armor, and AI, see [`design.md`](./design.md). This section is screens + visualization. + +### Auto-pause on threat + +When a wolf is detected approaching the colony, time auto-pauses and a banner appears: + +``` +⚠ Threat: Wolves approaching from the east (3 of them) +[ Continue ] [ Pause ] +``` + +Camera pans to show the threat. Player adjusts orders, then resumes. + +### Forced commands + +Tap a pawn → bottom sheet → action menu: + +``` +┌──────────────────────────────────┐ +│ ← Bob (Combat 7, Sword) │ +├──────────────────────────────────┤ +│ • Move here [tap world] │ +│ • Attack target [tap enemy] │ +│ • Carry to bed [tap downed] │ +│ • Drop weapon │ +│ • Pause work (sit) │ +└──────────────────────────────────┘ +``` + +Forced jobs run at Layer 5 (player override) of the AI. Combat=Off pawns refuse Attack target. + +### Equipment in pawn detail + +Pawn detail screen adds an Equipment panel: + +``` +┌──────────────────────────────────┐ +│ Equipment │ +│ Weapon: ⚔ Iron sword (Excellent) │ +│ Helmet: 🪖 Iron helm (Normal) │ +│ Cuirass: 🛡 Iron cuirass (Excel.) │ +│ Boots: 👢 Iron boots (Shoddy) │ +│ │ +│ Total armor: 14 Total dmg: 10 │ +└──────────────────────────────────┘ +``` + +Tap a slot → swap from inventory; long-press item → "Lock to Bob" (override auto-equip). + +### Combat readability — visual cues + +- Pawn currently engaged in combat: red outline pulse +- Hit on target: damage number floats up briefly +- Miss: small "miss" puff +- Bleeding pawn: red drip particle +- Downed pawn: lying-down sprite, red "!" icon above + +### Downed alerts + +When a pawn enters Downed status, top-screen alert (auto-pause): + +``` +⚠ Mira is down. Bleeding out in 6 hours. +[ Continue ] +``` + +Camera pans. The Doctor priority system kicks in automatically; player can manually issue Carry-to-bed via forced command if they want a specific bed. + +### Cover indicator + +When a pawn is selected and threats exist, tiles providing cover near the pawn light up briefly. Helps the player position pawns tactically without reading wiki pages. + +## Death, burial, ghost state UX + +### Pawn death event + +When a pawn dies, top-screen modal alert (auto-pause): + +``` +┌────────────────────────────────────┐ +│ ✟ Bob the Carpenter has died. │ +│ Killed by wolf, day 27 of Spring │ +│ │ +│ The colony mourns. │ +│ [ Continue ] [ View record ] │ +└────────────────────────────────────┘ +``` + +"View record" opens the pawn-detail screen for the deceased. + +### Pawn-detail for deceased + +Same screen as living pawns, with a black band at the top reading "Deceased — Day X." Skills shown as final values; equipment as final equipped items; cause of death prominent. + +Accessed from: tap a corpse, tap a grave marker, alert "View record" link, or via a colony memorial list (post-MVP feature). + +### Graveyard zone + +Designate via Zones → New Stockpile → enable only the **Corpses** chip. The zone is automatically labeled "Graveyard" if it contains only corpses or grave markers. Beauty calculation includes graves' +1 each. + +### Cremation pyre — bill queue + +Tap the pyre → bill-queue screen (same UI as other workbenches): + +``` +┌─────────────────────────────────────┐ +│ ← Pyre │ +│ No active worker │ +├─────────────────────────────────────┤ +│ Bills │ +│ 1. Cremate corpse — Forever │ +│ │ +│ [ + Add Bill ] │ +└─────────────────────────────────────┘ +``` + +Default bill (suggested on placement): "Cremate corpse — Forever." Player can pause if they want to leave a body for burial instead. + +### Rotting visual + +Corpses tint progressively green-grey as `decay_severity` rises. Once Rotting (≥50), a small fly-cluster particle. Once fully Rotted (100), the corpse sprite is solidly grey-green. + +### Ghost state UI + +When the colony enters Ghost state: + +``` +┌────────────────────────────────────┐ +│ 🕯 Your colony has fallen silent. │ +│ Watch for travelers. │ +└────────────────────────────────────┘ +``` + +Persistent banner at top of screen until the next wanderer event resolves. Game continues at half-speed; player can pan/inspect the abandoned base. Works as both a real-time soft-stop and an evocative narrative moment. + +### Wanderer event + +Modal (auto-pause): + +``` +┌────────────────────────────────────┐ +│ A traveler appears │ +│ │ +│ Mira wandered to your settlement. │ +│ She is tired and looking for │ +│ shelter. │ +│ │ +│ Skills: Crafting 7, Cooking 4, │ +│ Manual Labor 3 │ +│ │ +│ [ Welcome ] [ Send away ] │ +└────────────────────────────────────┘ +``` + +## Storyteller events UX + +For corpus and mechanism see [`design.md`](./design.md); for tech see [`architecture.md`](./architecture.md). This section is screens. + +### Ambient banner (nudges, seasonal, resource, lore) + +Top of screen, slides in from above, persists for 5 seconds or until tapped: + +``` +┌──────────────────────────────────────────┐ +│ 🌾 Spring Awakens │ +│ The thaw runs in every stream. │ +│ Crops will grow fast now. │ +└──────────────────────────────────────────┘ +``` + +Stacks if multiple fire close together (rare). Tappable → dismiss; long-press → "see history." + +### Modal event (wanderer, threat, disease, milestone, choice) + +Auto-pauses the sim. Centered modal: + +``` +┌────────────────────────────────────────┐ +│ 🚶 A Traveler │ +├────────────────────────────────────────┤ +│ A weary traveler stumbles toward your │ +│ gate. They look hungry. │ +│ │ +│ Mira — Cooking 5, Manual Labor 3 │ +│ │ +│ [ Welcome ] [ Send away ] │ +└────────────────────────────────────────┘ +``` + +Title icon by category (🚶 wanderer, 🐺 threat, 🤒 disease, ✟ death, ⭐ milestone, 🌾 seasonal). Choices below body. Resume sim only when player picks. + +### Threat events with delayed spawn + +Some threats announce, then spawn later (e.g. "Wolves arriving at nightfall"). Player gets a banner *and* a marker on the map edge showing where they'll arrive: + +``` +On map edge: ⚠ ← (orange marker, pulsing) +``` + +### Event history + +Top-bar icon (📜 scroll) opens an events log: + +``` +┌─────────────────────────────────────┐ +│ ← Events history │ +├─────────────────────────────────────┤ +│ Day 28 ⭐ One Year Survived │ +│ Day 26 🐺 Pack Hunt (3 wolves) │ +│ Day 25 🚶 The Old Soldier (welcome) │ +│ Day 24 🌾 Bountiful Harvest │ +│ Day 22 🤒 Fever (Bob) │ +│ Day 18 🌾 Autumn's Harvest │ +└─────────────────────────────────────┘ +``` + +Tap any entry → re-show that event's text. Useful as "what happened while I was at work?" recap. + +### "While you were away" digest + +When the player resumes after closing the app, a brief digest summarizes events that fired since last session. (Sim is paused while app is closed, but day-rolls happen on resume up to a max of N days to prevent accumulating events.) + +``` +┌────────────────────────────────────────┐ +│ Welcome back │ +│ While you were away (12 hrs): │ +│ • Day 22 — Fever broke out (Bob) │ +│ • Day 23 — Spring Awakens │ +│ 2 events handled automatically. │ +│ [ Continue ] │ +└────────────────────────────────────────┘ +``` + +For events with player choices that fired during the away period: those auto-default to the safe option (e.g. wanderers default to Send Away — the player can still recruit later events). Surface clearly: "1 wanderer was sent away." + +## Roofing, weather, indoor cues + +Most of this is rendering, not screens. For mechanics see [`design.md`](./design.md); for data/algorithms [`architecture.md`](./architecture.md). + +### Indoor tint + +Indoor tiles render with a subtle blue-grey overlay on the floor layer. Outdoor tiles unchanged. Players read indoor/outdoor at a glance; no toggle needed. + +### Roofs are invisible in MVP + +We don't render roof tiles. Floors and pawns render normally even "under" the roof. Genre convention; matches Rimworld. + +### No-Roof designation + +New designation type alongside Build / Mine / Chop / Stockpile. + +1. Player taps **Designate → No Roof** (in the bottom-sheet). +2. Paint cells inside an enclosure to forbid auto-roofing. +3. Cells show a dashed outline + open-sky icon. +4. EnclosureDetector skips these cells when assigning Roof. + +Useful for courtyards, gardens, fire pits. + +### Weather rendering + +- **Clear** — default sky. +- **Rain** — falling-particle overlay, 30%-darken sky tint, gentle ambient sound loop. +- **Storm** — heavier particles, deeper darken, occasional flash, louder rain. +- **Cold snap** — pale-blue sky tint, breath puffs from outdoor pawns (cheap particle), no rain. + +A small **weather indicator** in the top bar shows the current state with a tappable tooltip ("Rain · Outdoor pawns will get wet"). + +### Season indicator + +Top bar also shows season + day-of-season ("Spring 4/12"). Tap → seasonal forecast tooltip ("Winter starts in 8 days"). + +### Pawn wet visual + +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. + +## 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. +- **Pawn detail** — beyond priorities: needs bars, status effects, current job, equipment, mood thoughts. Tap-pawn-portrait → this screen. +- **Onboarding / first 60 seconds** — mobile-specific. New player bounces if not productive in a minute. Open question, see [`memory.md`](../memory.md). +- **Settings** — speeds, auto-pause toggles, audio, accessibility. + +## What lives elsewhere + +- **Pawn AI / job system / how priorities map to behavior** — see [`architecture.md`](./architecture.md). +- **Game mechanics / skills / statuses** — see [`design.md`](./design.md). +- **Tilesets and art** — see [`art.md`](./art.md). +- **Pillars and vertical slice** — see [`memory.md`](../memory.md). diff --git a/memory.md b/memory.md new file mode 100644 index 0000000..4b81fd6 --- /dev/null +++ b/memory.md @@ -0,0 +1,156 @@ +# memory — rimlike + +Durable memory for this project. Read at session start, update before session end. Date format: `YYYY-MM-DD`. + +A 2D, tile-based **cute-farming-RPG-meets-colony-sim** — Rimworld DNA, Going Medieval × Stardew lodestars — shaped for mobile and handheld. Promoted from `~/claude/ideas/rimlike` on 2026-05-10 after a single deep brainstorm session. Realistic MVP timeline: **3–6 months solo**. + +## How to read this project + +`memory.md` is the index of decisions and open questions. The deep specs live alongside in `docs/`: + +| File | Contents | +|---|---| +| [`docs/design.md`](./docs/design.md) | Game design — core loop, simplifications, skills, statuses, mood, weather, stockpiles, production, combat, death/burial, storyteller corpus | +| [`docs/architecture.md`](./docs/architecture.md) | Tech — pawn AI / job system, time/tick model, Godot 4 engine layout, TileMap split, all subsystems (mood, lighting, rooms, hauling, production, combat, storyteller) | +| [`docs/ui.md`](./docs/ui.md) | Touch UX — work-priority matrix, stockpile/container screens, mood/lighting/rooms cues, combat banners, storyteller event UI, screens still to design | +| [`docs/art.md`](./docs/art.md) | Owned assets (ElvGames bundle primary, Ventilatore secondary), license, autotile gotcha, audit list, candidates kept for record | + +When working on a feature, read `memory.md` first, then the relevant `docs/` file(s). + +## Decisions & rationale + +Distilled from the brainstorm. Each lock has a "why" — change with deliberate intent. + +### Pillars + +| Decision | Choice | Why | +|---|---|---| +| **View** | Top-down, grid-aligned tilemap | Closest to Rimworld feel; matches owned tilesets. | +| **Primary platforms** | iOS + Android touch, then Steam Deck / ROG Ally gamepad. Desktop falls out for free. | Mobile is the hard constraint; Deck inherits. | +| **Ambition** | itch.io + TestFlight release. Real artifact, small audience. | Drives engine choice; no app-store polish-tax. | +| **Engine** | Godot 4 (GDScript) | 2D-first, free, exports everywhere we need, fast iteration. | +| **Tile size / style** | 16×16 pixel art, **cute-farming-RPG primary** (ElvGames bundle), Ventilatore as medieval accent | ElvGames "Ultimate Farming RPG" Humble bundle owned (~2.8 GB, 70+ packs). Tone: Stardew × Going Medieval × Rimworld. | +| **Setting** | Medieval fantasy with cute palette | Owned-art alignment + clearer scope (no electricity / hydroponics / energy weapons) + underexplored on mobile. | +| **Run shape** | Open-ended sandbox, autosave, persistent world | Player picks up where they left off. | +| **Session length** | 5–15 min target | Drives every UI and pacing choice. | +| **Default speed** | Fast (5×), with auto-pause on event | 1 in-game day ≈ 5 min real time. Solves the "watch a pawn walk for 60s" problem; 1× exists for combat / fine work. | +| **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** | 3–6 pawns, 30×30 to 60×60 tile maps | Readable on a phone; cheap to simulate. | +| **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 3–5 days | Mobile-friendly; preserves player investment. | + +### Architecture (tech) + +- **Sim tick 20 Hz, render 60 Hz, decoupled.** Pawn positions lerp between sim ticks. (`docs/architecture.md` Time / tick model) +- **Pawn AI: 5-layer pipeline** (Decision → WorkProvider → Job + JobRunner → Status interrupts → Player overrides). Slimmer than Rimworld's ThinkTree. ~800–1500 LOC GDScript for full MVP. +- **TileMap layers**: 0 Terrain · 1 Floor · 2 Wall · 3 Designation · 4 Roof · 5 Fog. Furniture / Pawn / Item / EffectFX as scene-instanced entities (not TileMap). +- **Pathfinding**: `AStarGrid2D` (built-in), updated on wall/door/furniture changes. +- **No background simulation** — app backgrounded = sim paused. Avoids "lost colony to a raid while at work." +- **Save format**: between sim ticks only; JobRunner mid-toil state round-trips from day one. + +### Game design + +- **5 skills** (Manual Labor / Crafting / Cooking / Medicine / Combat), 0–10 each, level by use, multiplicative speed/quality bonus. Skills modify duration and quality, never permission. +- **9 work categories** (Construction / Mining / Hauling / Cleaning / Crafting / Cooking / Plant / Doctor / Combat). 5-level priority matrix per pawn. +- **Storage**: floor zones AND independent crate furniture (4 stacks each), unified by `StorageDestination` interface. **16 filter chips** (Wd/St/Ir/Cu/Ag/Au/Cl/Veg/Mt/Gr/Ck/Md/Tl/Wp/Ar/Co), 5 priorities with Rimworld flow semantics. One stack per tile, one type per tile. +- **Production**: 2-step where medieval-sense (Iron→Ingot→Weapon, Grain→Flour→Bread); 1-step otherwise. 5 workbenches (Carpenter, Smelter, Smithy, Cooking hearth, Millstone), ~22 recipes. Full Rimworld bill semantics (one-shot count / forever / until-N + ingredient-quality filter + skill threshold). +- **Quality system** (Shoddy/Normal/Excellent/Masterwork/Legendary) on every crafted item; multiplicative stat bonus. Quality from crafter skill + RNG only (inputs are just resources). +- **Mood**: ~13 thoughts (data-driven registry, mix persistent + event-driven). Soft breaks at sustained mood < 25 for 30 in-game min — Sulking or Wandering, recover at mood ≥ 35. +- **Lighting** (real shader at night), **auto-detected rooms** (named by furniture, scored for beauty + dirtiness), **dirtiness + Cleaning** (8th work category), **beauty score** with Quality multiplier. +- **Roofing**: indoor = Layer-4 Roof flag, sim-data only (no rendering, just an indoor tint on floors). Auto-roof when walls enclose ≤8 cells (BFS); No-Roof designation for courtyards. Plants don't grow indoors. +- **Weather**: 4 types (Clear / Rain / Storm / Cold snap), daily roll, season-weighted. Wet status accumulates outside in rain, decays indoors. 4 seasons × 12 days = 48-day year. +- **Combat**: 3 weapons (sword/axe/bow), 3 armor slots (helm/cuirass/boots). Walls + trees provide cover. Two-roll resolution (hit, then damage with armor reduction). Downed-then-rescue death model; doctors auto-prioritize. Combat=Off "defends if cornered, won't volunteer." Friendly fire ON. +- **Death / corpses**: Both burial AND cremation. Graveyard = special stockpile (Corpses-only); pawns dig graves, place permanent grave markers (tap → deceased pawn-detail). Cremation pyre = furniture with single recipe (1 corpse + 5 wood). Corpses decay 0–50 fresh / 50–100 rotting (no butcher) / 100 rotted. +- **Storyteller**: 25 prompts written in `docs/design.md`. Daily 6am roll, weighted pool, per-category cooldowns, tension model alternates quiet/threat per Tynan Sylvester pacing. Ambient banner for low-stakes, modal auto-pause for choice events. + +### Touch UX + +- **Bottom-sheet menus** instead of right-side panels. **Long-press** = inspect/context. **Tap world** = select. Speed/pause buttons fixed top. +- **Work-priority matrix**: 9 columns × N pawns, sticky pawn-name column, horizontal scroll on phone. Tap-to-cycle priority, long-press for 5-chip picker, swipe column for bulk-set. Per-pawn and per-job views layered on top. +- **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. + +### Art strategy + +- **ElvGames "Ultimate Farming RPG" Humble bundle** is the primary art (`/mnt/d/godot/assets/humble set new/`, ~2.8 GB, 70+ packs, all 16×16, ElvGames license: commercial OK with credit). +- **Ventilatore Fantasy Tileset Complete Bundle** stays as medieval accent for biome variety / decorative props / animated water. +- Mana Seed series considered but **dropped** from buy plan (the bundle covers what we'd planned). +- **Authoring still required**: designation overlays (~2 hrs custom), possibly autotile bits on bundle wall sheets, possibly wolf sprite, possibly grave marker. + +## Open questions / TODOs + +### Audit / unblock-the-prototype action items + +These are concrete checks to run before serious construction begins. Total ~75 min. + +- [ ] **Aesthetic harmony test** — open one tile from ElvGames Forest 4 Seasons + one from Ventilatore + view side-by-side. Decide whether they mesh (use both) or clash (drop Ventilatore from active use). ~15 min. +- [ ] **ElvGames autotile audit** — count corner / T-junction / cap pieces in `Houses Tileset 2 Seasons/FG_Houses.png` and `Fortress Tileset 2 Seasons/FG_Fortress.png`. ≥16 wall variants per material → autotile is solvable; <8 → fall back to Mana Seed Iconic Homestead ($19.99) or custom-author. ~15–30 min. +- [ ] **Wolf sprite source** — bundle's Animal Sprites pack lacks wolves. Browse EvoMonster Packs 01–15 or Turn-Based RPG Monster packs for a suitable predator. Or plan custom (~few hours pixel art). ~15 min. +- [ ] **Grave marker source** — Retro Graveyard 16×16 (Tier 3) probably has it; verify. ~10 min. +- [ ] **License compilation** — maintain credits string for every pack used (ElvGames + Ventilatore + any others). Display in game's credits screen. Confirm specific terms per pack before any commercial release. + +### Design topics still open + +- [ ] **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. +- [ ] **Audio direction** — who/where for SFX + ambient track? Bundle has 11 music + 8 SFX packs covering most needs. +- [ ] **Steam Deck input parity** — gamepad-driven cursor, or full menu navigation by D-pad? Probably both. +- [ ] **Localization stance** — English-only for MVP, but architect strings for i18n (already locked in CLAUDE.md). +- [ ] **Pawn name/backstory generation** — hand-curated list vs simple generator. +- [ ] **Naming the game** — "rimlike" is a working title. +- [ ] **Monetization stance** — free? PWYW? Premium? +- [ ] **Tech / research progression** — medieval tech tree shape. +- [ ] **Map / world generation** — fixed seed for slice; procgen later. + +### Tunable in prototype (not real "open Qs", just numbers to playtest) + +- Sleep mood gradient values (`+5/+0/−2/−5/−8`) +- Wet status thresholds (25 / 60) and accumulation rates +- Season weather weights (Spring/Summer/Autumn/Winter distributions) +- Hit-chance bonuses (skill ×5%, range ×5%, cover 40/20%) +- Bleed-out timer (6 in-game hours) +- Various mood thought magnitudes and decay times + +## Vertical slice (MVP target) + +Same scope as locked in `~/claude/ideas/rimlike/plan.md`. Realistic timeline 3–6 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. +- **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 (3–4 types), cook meals (recipes), haul, repair, clean. +- **Needs**: hunger, sleep, mood (~13 distinct thoughts, soft breaks at sustained mood < 25). +- **Status effects**: Hungry, Tired, Bleeding, Sick, Downed, Wet (Damp/Soaked), Cold. +- **Storage**: floor zones AND containers, 16 filter chips, 5 priorities. +- **Quality system** on all crafted items. +- **Lighting** — torches + hearths emit light; visual darkness at night. +- **Rooms** — auto-detected, named by contents, beauty + dirtiness scored. +- **Cleaning** as 8th work category. +- **Day/night cycle**, ~5 min per day at default speed. **Seasons** (Spring/Summer/Autumn/Winter, 12 days each). +- **Weather**: Clear / Rain / Storm / Cold snap. +- **One disaster type**: wolves at night (bandit raids deferred). +- **One storyteller**: random quiet/threat alternation, 25 prompt corpus. +- **Save/load**, autosave on suspend, single slot. +- **Touch UI** end-to-end (no desktop-only gestures). +- **Sound**: minimal — UI clicks, ambient day/night loop, alert sting. + +## Session log + +### 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/`. + +## External references + +- **Forgejo repo:** https://git.rdx4.com/megaproxy/rimlike (private) +- **Owned art bundle (primary):** ElvGames "Ultimate Farming RPG" Humble bundle, local at `/mnt/d/godot/assets/humble set new/`. License: ElvGames terms (). Commercial OK with credit; no NFT/crypto/resale. +- **Owned art bundle (secondary):** Ventilatore Fantasy Tileset Complete Bundle — +- **Mana Seed catalog (fallback if bundle gaps surface):** +- **Iconic Homestead (autotile-ready wall fallback, $19.99):** +- **Lodestars:** Rimworld (Tynan Sylvester / Ludeon Studios), Going Medieval (Foxy Voxel), Stardew Valley (ConcernedApe). +- **Original brainstorm history:** archived to `~/claude/archive/ideas/rimlike/` after promotion. The session log there captures every decision's rationale. +- **Brainstorming-mode preference:** see `~/.claude/projects/-home-megaproxy-claude-ideas/memory/brainstorm-ask-dont-decide.md` (not directly applicable to projects but captures interaction style).