| """Per-location special mechanics from DETAILS.md §3.""" |
|
|
| from __future__ import annotations |
|
|
| import json |
| import random |
|
|
| from world.book_of_ages import create_entry, get_entries |
| from world.database import db_session |
| from world.entities import add_tag, get_entity, update_memory |
| from world.locations import get_location_by_slug |
|
|
|
|
| def effective_days_in_realm(entity: dict) -> float: |
| """Clock Forest souls age 1.5× faster for lifecycle logic.""" |
| loc = entity.get("location_slug") |
| if loc is None and entity.get("location_id"): |
| from world.locations import get_location_by_id |
| row = get_location_by_id(entity["location_id"]) |
| loc = row["slug"] if row else None |
| days = entity.get("days_in_realm", 0) |
| if loc == "clock-forest": |
| return days * 1.5 |
| return float(days) |
|
|
|
|
| def process_library_memory_fragments(world_day: int) -> int: |
| """Every 3 days, Library inhabitants gain a half-remembered fragment.""" |
| if world_day % 3 != 0: |
| return 0 |
|
|
| library = get_location_by_slug("library") |
| if not library: |
| return 0 |
|
|
| past = get_entries(limit=30, entry_type=None) |
| if not past: |
| return 0 |
|
|
| fragment_source = random.choice(past) |
| snippet = (fragment_source.get("content") or "")[:120].strip() |
| if not snippet: |
| return 0 |
|
|
| updated = 0 |
| with db_session() as conn: |
| rows = conn.execute( |
| "SELECT * FROM entities WHERE location_id = ? AND status != 'dormant'", |
| (library["id"],), |
| ).fetchall() |
|
|
| for row in rows: |
| entity = dict(row) |
| current = entity.get("memory_summary") or "" |
| addition = f"They half-remember something from the shelves: {snippet}" |
| merged = f"{current} {addition}".strip() |
| if len(merged) > 600: |
| merged = merged[-600:] |
| update_memory(entity["id"], merged) |
| updated += 1 |
|
|
| if updated: |
| create_entry( |
| world_day=world_day, |
| entry_type="milestone", |
| content=( |
| f"The Library breathed out {updated} half-forgotten fragments " |
| "into the minds of those who dwell among unfinished books." |
| ), |
| location_id=library["id"], |
| is_milestone=False, |
| ) |
| return updated |
|
|
|
|
| def process_mirror_contradictions(world_day: int) -> int: |
| """Mirror Bogs: after 5+ interactions, a soul may develop a contradicting trait.""" |
| bogs = get_location_by_slug("mirror-bogs") |
| if not bogs: |
| return 0 |
|
|
| count = 0 |
| with db_session() as conn: |
| entities = conn.execute( |
| "SELECT * FROM entities WHERE location_id = ?", |
| (bogs["id"],), |
| ).fetchall() |
|
|
| opposites = [ |
| ("patient", "restless"), |
| ("gentle", "sharp-tongued"), |
| ("honest", "evasive"), |
| ("hopeful", "cynical"), |
| ("quiet", "loud"), |
| ("generous", "possessive"), |
| ] |
|
|
| for row in entities: |
| entity = dict(row) |
| traits = json.loads(entity["personality_traits"]) if isinstance(entity["personality_traits"], str) else entity["personality_traits"] |
| tags = json.loads(entity["tags"]) if isinstance(entity["tags"], str) else entity["tags"] |
| if "contradiction" in tags: |
| continue |
|
|
| n = conn_count_interactions_at(entity["id"], bogs["id"]) |
| if n < 5: |
| continue |
|
|
| pick_trait = random.choice(traits) |
| opposite = next((b for a, b in opposites if a in pick_trait.lower()), None) |
| if not opposite: |
| opposite = f"secretly unlike their {pick_trait} nature" |
|
|
| new_traits = traits[:] |
| idx = traits.index(pick_trait) |
| new_traits[idx] = f"{pick_trait}, yet {opposite}" |
|
|
| with db_session() as conn: |
| conn.execute( |
| "UPDATE entities SET personality_traits = ? WHERE id = ?", |
| (json.dumps(new_traits), entity["id"]), |
| ) |
| add_tag(entity["id"], "contradiction") |
|
|
| create_entry( |
| world_day=world_day, |
| entry_type="milestone", |
| content=( |
| f"{entity['name']} looked into the bog and came away disagreeing " |
| f"with who they had been." |
| ), |
| entity_ids=[entity["id"]], |
| location_id=bogs["id"], |
| is_milestone=True, |
| title="A Contradiction Surfaces", |
| ) |
| count += 1 |
|
|
| return count |
|
|
|
|
| def conn_count_interactions_at(entity_id: str, location_id: int) -> int: |
| with db_session() as conn: |
| row = conn.execute( |
| """ |
| SELECT COUNT(*) AS c FROM interactions |
| WHERE location_id = ? |
| AND (entity_a_id = ? OR entity_b_id = ?) |
| """, |
| (location_id, entity_id, entity_id), |
| ).fetchone() |
| return row["c"] if row else 0 |
|
|
|
|
| def apply_moon_market_memory_trade( |
| entity_a: dict, |
| entity_b: dict, |
| location_slug: str, |
| memory_a: str, |
| memory_b: str, |
| ) -> tuple[str, str]: |
| """Moon Market: traded memory fragments bleed between two souls.""" |
| if location_slug != "moon-market": |
| return memory_a, memory_b |
|
|
| frag_a = (entity_a.get("memory_summary") or "").split(". ")[-1][:80] |
| frag_b = (entity_b.get("memory_summary") or "").split(". ")[-1][:80] |
|
|
| if frag_b and frag_b not in memory_a: |
| memory_a = f"{memory_a} Carries a traded fragment: {frag_b}.".strip() |
| if frag_a and frag_a not in memory_b: |
| memory_b = f"{memory_b} Carries a traded fragment: {frag_a}.".strip() |
|
|
| return memory_a[:600], memory_b[:600] |
|
|