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]