Pabloler21 Claude Fable 5 commited on
Commit
a50d7dd
·
1 Parent(s): fae2f18

feat: wire three endings into chat() with fast-finale seeds

Browse files

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Files changed (1) hide show
  1. app.py +126 -11
app.py CHANGED
@@ -5,7 +5,7 @@ import gradio as gr
5
 
6
  from character import build_system_prompt, OPENING_LINE
7
  from engine import run_turn
8
- from finale import finale_steps
9
  from memory import apply_update, get_tier, decide_ending, should_recall
10
  from render import render_entity, render_treasure
11
 
@@ -29,7 +29,8 @@ def _render_bond(affinity: int, tier: str, label: str = "bond") -> str:
29
 
30
 
31
  def _init_state():
32
- if os.environ.get("HOLLOW_FAST_FINALE"):
 
33
  seeded = [
34
  "is afraid of being forgotten",
35
  "had a dog named Nala",
@@ -43,6 +44,22 @@ def _init_state():
43
  "turn": 12,
44
  "last_recall_turn": 9,
45
  "ended": False,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  }
47
  return {
48
  "affinity": 20,
@@ -52,6 +69,9 @@ def _init_state():
52
  "turn": 0,
53
  "last_recall_turn": None,
54
  "ended": False,
 
 
 
55
  }
56
 
57
 
@@ -107,31 +127,125 @@ def _play_finale(state: dict, history: list):
107
  )
108
 
109
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  def chat(user_msg: str, state: dict, history: list):
111
  if not user_msg.strip() or state.get("ended"):
 
 
 
112
  tier = get_tier(state["affinity"])
 
 
113
  yield (
114
  gr.update(interactive=not state.get("ended")),
115
  gr.update(interactive=not state.get("ended")),
116
  history,
117
  state,
118
  _render_bond(state["affinity"], tier),
119
- render_treasure(state["treasure"], mine=state.get("ended", False),
120
- claimed=set(state["claimed"])),
121
- render_entity(state["affinity"],
122
- "end" if state.get("ended") else "idle",
123
- seq=state["turn"]),
124
  )
125
  return
126
 
127
- if decide_ending(state):
 
128
  state["ended"] = True
129
- yield from _play_finale(state, history)
 
 
 
 
 
 
130
  return
131
 
132
  do_recall, recall_memory = should_recall(state)
133
 
134
- system = build_system_prompt(state["affinity"], state["treasure"], recall_memory)
 
 
135
  chat_messages = [{"role": "system", "content": system}] + state["history"] + [
136
  {"role": "user", "content": user_msg}
137
  ]
@@ -159,7 +273,8 @@ def chat(user_msg: str, state: dict, history: list):
159
  new_history,
160
  state,
161
  _render_bond(state["affinity"], tier),
162
- render_treasure(state["treasure"], claimed=set(state["claimed"])),
 
163
  render_entity(state["affinity"], mode, seq=state["turn"]),
164
  )
165
 
 
5
 
6
  from character import build_system_prompt, OPENING_LINE
7
  from engine import run_turn
8
+ from finale import finale_steps, finale_steps_bad, finale_steps_neutral
9
  from memory import apply_update, get_tier, decide_ending, should_recall
10
  from render import render_entity, render_treasure
11
 
 
29
 
30
 
31
  def _init_state():
32
+ fast = os.environ.get("HOLLOW_FAST_FINALE", "").lower()
33
+ if fast in ("1", "good", "neutral"):
34
  seeded = [
35
  "is afraid of being forgotten",
36
  "had a dog named Nala",
 
44
  "turn": 12,
45
  "last_recall_turn": 9,
46
  "ended": False,
47
+ "tone": 5 if fast == "neutral" else 30,
48
+ "wounds": [],
49
+ "ending": None,
50
+ }
51
+ if fast == "bad":
52
+ return {
53
+ "affinity": 12,
54
+ "treasure": [],
55
+ "claimed": [],
56
+ "history": [],
57
+ "turn": 7,
58
+ "last_recall_turn": None,
59
+ "ended": False,
60
+ "tone": -35,
61
+ "wounds": ["you're nothing", "talking to you is a waste of time"],
62
+ "ending": None,
63
  }
64
  return {
65
  "affinity": 20,
 
69
  "turn": 0,
70
  "last_recall_turn": None,
71
  "ended": False,
72
+ "tone": 0,
73
+ "wounds": [],
74
+ "ending": None,
75
  }
76
 
77
 
 
127
  )
128
 
129
 
130
+ def _play_finale_bad(state: dict, history: list):
131
+ """The wound loop — recites the visitor's cruelties, unredacting them
132
+ one by one. No model, no GPU."""
133
+ wounds = state.get("wounds", [])
134
+ chat_hist = list(history)
135
+ tier = get_tier(state["affinity"])
136
+ bond = _render_bond(state["affinity"], tier)
137
+ revealed = 0
138
+ treasure_html = render_treasure(state["treasure"], claimed=set(state["claimed"]),
139
+ wounds=wounds, revealed=0)
140
+ entity = render_entity(state["affinity"], seq=state["turn"])
141
+ locked = gr.update(interactive=False)
142
+ input_locked = gr.update(interactive=False)
143
+ mutated = False
144
+
145
+ for step in finale_steps_bad(wounds):
146
+ role = "assistant" if step["speaker"] == "hollow" else "user"
147
+ chat_hist = chat_hist + [{"role": role, "content": step["text"]}]
148
+
149
+ if step["strike"] is not None:
150
+ revealed = step["strike"] + 1
151
+ treasure_html = render_treasure(state["treasure"],
152
+ claimed=set(state["claimed"]),
153
+ wounds=wounds, revealed=revealed)
154
+
155
+ if step["stage"] == "turn" and not mutated:
156
+ mutated = True
157
+ bond = _render_bond(state["affinity"], tier, label="wound")
158
+ treasure_html = render_treasure(state["treasure"],
159
+ claimed=set(state["claimed"]),
160
+ wounds=wounds, revealed=revealed,
161
+ yours=True)
162
+ entity = render_entity(state["affinity"], "rage", seq=state["turn"])
163
+ input_locked = gr.update(interactive=False, placeholder="get out.")
164
+
165
+ yield (input_locked, locked, chat_hist, state, bond, treasure_html, entity)
166
+ if step["pause"] > 0:
167
+ time.sleep(step["pause"])
168
+
169
+ yield (
170
+ gr.update(interactive=False, placeholder="get out."),
171
+ locked, chat_hist, state, bond, treasure_html, entity,
172
+ )
173
+
174
+
175
+ def _play_finale_neutral(state: dict, history: list):
176
+ """The fog closes — unresolved. No model, no GPU."""
177
+ chat_hist = list(history)
178
+ tier = get_tier(state["affinity"])
179
+ bond = _render_bond(state["affinity"], tier)
180
+ wounds = state.get("wounds", [])
181
+ treasure_html = render_treasure(state["treasure"], claimed=set(state["claimed"]),
182
+ wounds=wounds)
183
+ entity = render_entity(state["affinity"], seq=state["turn"])
184
+ locked = gr.update(interactive=False)
185
+ input_locked = gr.update(interactive=False)
186
+ mutated = False
187
+
188
+ for step in finale_steps_neutral():
189
+ chat_hist = chat_hist + [{"role": "assistant", "content": step["text"]}]
190
+
191
+ if step["stage"] == "turn" and not mutated:
192
+ mutated = True
193
+ bond = _render_bond(state["affinity"], tier, label="fading")
194
+ # every entry dims as the treasure stops mattering
195
+ treasure_html = render_treasure(state["treasure"],
196
+ claimed=set(state["treasure"]),
197
+ wounds=wounds)
198
+ entity = render_entity(state["affinity"], "dissolve", seq=state["turn"])
199
+
200
+ yield (input_locked, locked, chat_hist, state, bond, treasure_html, entity)
201
+ if step["pause"] > 0:
202
+ time.sleep(step["pause"])
203
+
204
+ yield (
205
+ gr.update(interactive=False, placeholder="..."),
206
+ locked, chat_hist, state, bond, treasure_html, entity,
207
+ )
208
+
209
+
210
  def chat(user_msg: str, state: dict, history: list):
211
  if not user_msg.strip() or state.get("ended"):
212
+ ending = state.get("ending")
213
+ if state.get("ended") and ending is None:
214
+ ending = "good" # sessions ended before `ending` existed
215
  tier = get_tier(state["affinity"])
216
+ wounds = state.get("wounds", [])
217
+ entity_mode = {"good": "end", "bad": "rage"}.get(ending, "idle")
218
  yield (
219
  gr.update(interactive=not state.get("ended")),
220
  gr.update(interactive=not state.get("ended")),
221
  history,
222
  state,
223
  _render_bond(state["affinity"], tier),
224
+ render_treasure(state["treasure"], mine=(ending == "good"),
225
+ claimed=set(state["claimed"]),
226
+ wounds=wounds, revealed=len(wounds),
227
+ yours=(ending == "bad")),
228
+ render_entity(state["affinity"], entity_mode, seq=state["turn"]),
229
  )
230
  return
231
 
232
+ ending = decide_ending(state)
233
+ if ending:
234
  state["ended"] = True
235
+ state["ending"] = ending
236
+ if ending == "good":
237
+ yield from _play_finale(state, history)
238
+ elif ending == "bad":
239
+ yield from _play_finale_bad(state, history)
240
+ else:
241
+ yield from _play_finale_neutral(state, history)
242
  return
243
 
244
  do_recall, recall_memory = should_recall(state)
245
 
246
+ system = build_system_prompt(state["affinity"], state["treasure"], recall_memory,
247
+ tone=state.get("tone", 0),
248
+ wounds=state.get("wounds", []))
249
  chat_messages = [{"role": "system", "content": system}] + state["history"] + [
250
  {"role": "user", "content": user_msg}
251
  ]
 
273
  new_history,
274
  state,
275
  _render_bond(state["affinity"], tier),
276
+ render_treasure(state["treasure"], claimed=set(state["claimed"]),
277
+ wounds=state.get("wounds", [])),
278
  render_entity(state["affinity"], mode, seq=state["turn"]),
279
  )
280