File size: 5,586 Bytes
781b9f7 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 | """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]
|