Input
{
"patchText": "*** Begin Patch\n*** Update File: /opt/bagger-api/game_engine.py\n@@\n-def apply_deterministic_limits(state, route, intent, node, ai, gift):\n- intent_type = intent.get(\"type\")\n- classes = classify_player_text(intent.get(\"message\", \"\")) if intent_type == \"free\" else []\n- intent[\"classifications\"] = classes\n-\n- if intent_type == \"schedule\":\n- for key in [\"bondDelta\", \"trustDelta\", \"warmthDelta\", \"depthDelta\", \"courageDelta\"]:\n- ai[key] = 0\n- ai[\"routePressure\"] = {pressure: 0 for pressure in ROUTE_PRESSURES}\n- return ai\n-\n- caps = {\n- \"free\": {\"bondDelta\": 2, \"trustDelta\": 2, \"warmthDelta\": 2, \"depthDelta\": 2, \"courageDelta\": 1},\n- \"date\": {\"bondDelta\": 5, \"trustDelta\": 4, \"warmthDelta\": 5, \"depthDelta\": 4, \"courageDelta\": 2},\n- \"gift\": {\"bondDelta\": 3, \"trustDelta\": 2, \"warmthDelta\": 3, \"depthDelta\": 2, \"courageDelta\": 1},\n- }.get(intent_type, {\"bondDelta\": 2, \"trustDelta\": 2, \"warmthDelta\": 2, \"depthDelta\": 2, \"courageDelta\": 1})\n- for key, cap in caps.items():\n- ai[key] = clamp(ai.get(key, 0), -cap, cap)\n-\n- if intent_type == \"free\":\n- effects = {\n- \"honest\": {\"trustDelta\": 1},\n- \"technical\": {\"trustDelta\": 1, \"depthDelta\": 1},\n- \"caring\": {\"warmthDelta\": 1},\n- \"patient\": {\"trustDelta\": 1},\n- \"romantic\": {\"bondDelta\": 1},\n- \"precise\": {\"depthDelta\": 1},\n- \"boastful\": {\"trustDelta\": -1},\n- \"rude\": {\"trustDelta\": -2, \"warmthDelta\": -1},\n- \"avoidant\": {\"bondDelta\": -1, \"trustDelta\": -1},\n- }\n- for label in classes:\n- for key, delta in effects.get(label, {}).items():\n- ai[key] = clamp(ai.get(key, 0) + delta, -caps.get(key, 2), caps.get(key, 2))\n- if \"romantic\" in classes:\n- state[\"commitmentScore\"][route] = state[\"commitmentScore\"].get(route, 0) + 1\n- if any(label in classes for label in [\"rude\", \"avoidant\", \"boastful\"]):\n- state[\"routePressure\"][route][\"needs_repair\"] = clamp(state[\"routePressure\"][route].get(\"needs_repair\", 0) + 1, -10, 99)\n-\n- if gift.get(\"fit\") == \"disliked\":\n- ai[\"trustDelta\"] = min(ai.get(\"trustDelta\", 0), -1)\n- return ai\n+def merge_delta(target, source):\n+ for key, value in source.items():\n+ target[key] = target.get(key, 0) + value\n+\n+\n+def apply_deterministic_limits(state, route, intent, node, ai, gift):\n+ \"\"\"Keep the LLM responsible for prose, but make game outcomes predictable.\"\"\"\n+ intent_type = intent.get(\"type\")\n+ classes = classify_player_text(intent.get(\"message\", \"\")) if intent_type == \"free\" else []\n+ intent[\"classifications\"] = classes\n+ choice_id = intent.get(\"id\") or \"advance\"\n+ category = node.get(\"category\", \"daily\")\n+ deltas = {\"bondDelta\": 0, \"trustDelta\": 0, \"warmthDelta\": 0, \"depthDelta\": 0, \"courageDelta\": 0}\n+ pressure = {p: 0 for p in ROUTE_PRESSURES}\n+\n+ if intent_type == \"schedule\":\n+ ai.update(deltas)\n+ ai[\"routePressure\"] = pressure\n+ ai[\"rulesNote\"] = \"Zeit fuer Training oder Arbeit: Beziehung steigt nicht direkt.\"\n+ return ai\n+\n+ category_base = {\n+ \"intro\": {\"bondDelta\": 2, \"trustDelta\": 2, \"warmthDelta\": 1, \"depthDelta\": 1, \"courageDelta\": 1},\n+ \"daily\": {\"bondDelta\": 2, \"trustDelta\": 1, \"warmthDelta\": 2, \"depthDelta\": 1},\n+ \"date\": {\"bondDelta\": 4, \"trustDelta\": 2, \"warmthDelta\": 3, \"depthDelta\": 2, \"courageDelta\": 1},\n+ \"threshold\": {\"bondDelta\": 3, \"trustDelta\": 3, \"warmthDelta\": 1, \"depthDelta\": 2, \"courageDelta\": 1},\n+ \"romance\": {\"bondDelta\": 5, \"trustDelta\": 2, \"warmthDelta\": 3, \"depthDelta\": 2, \"courageDelta\": 2},\n+ \"friendship\": {\"bondDelta\": 3, \"trustDelta\": 3, \"warmthDelta\": 3, \"depthDelta\": 2},\n+ \"crisis\": {\"bondDelta\": 1, \"trustDelta\": 1, \"warmthDelta\": 1, \"depthDelta\": 2, \"courageDelta\": 1},\n+ \"repair\": {\"bondDelta\": 4, \"trustDelta\": 5, \"warmthDelta\": 2, \"depthDelta\": 3, \"courageDelta\": 1},\n+ \"secret\": {\"bondDelta\": 4, \"trustDelta\": 4, \"warmthDelta\": 2, \"depthDelta\": 5, \"courageDelta\": 2},\n+ \"finale\": {\"bondDelta\": 3, \"trustDelta\": 2, \"warmthDelta\": 2, \"depthDelta\": 2},\n+ }\n+ merge_delta(deltas, category_base.get(category, category_base[\"daily\"]))\n+\n+ choice_effects = {\n+ \"sincere\": ({\"trustDelta\": 1, \"warmthDelta\": 1}, {\"toward_romance\": 1}),\n+ \"careful\": ({\"trustDelta\": 2, \"depthDelta\": 1}, {\"toward_friendship\": 1}),\n+ \"bold\": ({\"bondDelta\": 1, \"courageDelta\": 1}, {\"toward_romance\": 1, \"toward_lockin\": 1}),\n+ \"stay\": ({\"trustDelta\": 2, \"warmthDelta\": 1}, {\"needs_repair\": -1}),\n+ \"gentle\": ({\"trustDelta\": 1, \"depthDelta\": 1}, {\"needs_repair\": -1}),\n+ \"space\": ({\"trustDelta\": 1}, {\"toward_friendship\": 1}),\n+ \"apologize\": ({\"trustDelta\": 2, \"depthDelta\": 1}, {\"needs_repair\": -2}),\n+ \"explain\": ({\"trustDelta\": 1, \"depthDelta\": 2}, {\"needs_repair\": -1}),\n+ \"action\": ({\"bondDelta\": 1, \"trustDelta\": 2}, {\"needs_repair\": -2}),\n+ \"compliment\": ({\"bondDelta\": 1, \"warmthDelta\": 2}, {\"toward_romance\": 1}),\n+ \"question\": ({\"trustDelta\": 1, \"depthDelta\": 2}, {\"opens_secret\": 1}),\n+ \"silence\": ({\"trustDelta\": 1, \"warmthDelta\": 1}, {\"toward_friendship\": 1}),\n+ \"approach\": ({\"bondDelta\": 2, \"courageDelta\": 1}, {\"toward_romance\": 1, \"toward_lockin\": 1}),\n+ \"wait\": ({\"trustDelta\": 2}, {\"toward_friendship\": 1}),\n+ \"promise\": ({\"bondDelta\": 1, \"trustDelta\": 2}, {\"toward_romance\": 1, \"toward_lockin\": 1, \"opens_secret\": 1}),\n+ \"confess\": ({\"bondDelta\": 2, \"warmthDelta\": 1}, {\"toward_romance\": 1, \"toward_lockin\": 1}),\n+ \"protect\": ({\"trustDelta\": 2, \"warmthDelta\": 1}, {\"toward_romance\": 1}),\n+ \"choose\": ({\"bondDelta\": 3, \"trustDelta\": 1, \"courageDelta\": 1}, {\"toward_romance\": 2, \"toward_lockin\": 2}),\n+ \"steady\": ({\"trustDelta\": 2, \"warmthDelta\": 2}, {\"toward_friendship\": 2}),\n+ \"light\": ({\"warmthDelta\": 2}, {\"toward_friendship\": 1}),\n+ \"trust\": ({\"trustDelta\": 3, \"depthDelta\": 1}, {\"toward_friendship\": 1, \"opens_secret\": 1}),\n+ \"work_together\": ({\"trustDelta\": 1, \"warmthDelta\": 1}, {\"toward_friendship\": 1}),\n+ \"observe\": ({\"depthDelta\": 1, \"trustDelta\": 1}, {\"opens_secret\": 1}),\n+ \"talk\": ({\"warmthDelta\": 1, \"depthDelta\": 1}, {\"toward_friendship\": 1}),\n+ \"stay_end\": ({\"bondDelta\": 2, \"warmthDelta\": 1}, {\"toward_romance\": 1}),\n+ \"promise_end\": ({\"trustDelta\": 2, \"bondDelta\": 1}, {\"toward_romance\": 1}),\n+ \"silence_end\": ({\"warmthDelta\": 1, \"depthDelta\": 1}, {\"toward_friendship\": 1}),\n+ \"trust_secret\": ({\"trustDelta\": 2, \"depthDelta\": 2}, {\"opens_secret\": 2}),\n+ \"reciprocate\": ({\"bondDelta\": 1, \"depthDelta\": 2}, {\"opens_secret\": 1, \"toward_romance\": 1}),\n+ \"witness\": ({\"trustDelta\": 1, \"depthDelta\": 2}, {\"opens_secret\": 1}),\n+ }\n+ choice_delta, choice_pressure = choice_effects.get(choice_id, ({}, {}))\n+ merge_delta(deltas, choice_delta)\n+ merge_delta(pressure, choice_pressure)\n+\n+ class_effects = {\n+ \"honest\": ({\"trustDelta\": 1}, {\"toward_romance\": 1}),\n+ \"technical\": ({\"trustDelta\": 1, \"depthDelta\": 1}, {}),\n+ \"caring\": ({\"warmthDelta\": 1}, {\"toward_friendship\": 1}),\n+ \"patient\": ({\"trustDelta\": 1}, {\"toward_friendship\": 1}),\n+ \"romantic\": ({\"bondDelta\": 1}, {\"toward_romance\": 1, \"toward_lockin\": 1}),\n+ \"precise\": ({\"depthDelta\": 1}, {\"opens_secret\": 1}),\n+ \"boastful\": ({\"trustDelta\": -1}, {\"needs_repair\": 1}),\n+ \"rude\": ({\"trustDelta\": -2, \"warmthDelta\": -1}, {\"needs_repair\": 2}),\n+ \"avoidant\": ({\"bondDelta\": -1, \"trustDelta\": -1}, {\"needs_repair\": 1}),\n+ }\n+ for label in classes:\n+ class_delta, class_pressure = class_effects.get(label, ({}, {}))\n+ merge_delta(deltas, class_delta)\n+ merge_delta(pressure, class_pressure)\n+\n+ if intent_type == \"date\":\n+ location_id = intent.get(\"location\") or node.get(\"location\")\n+ loc_fit = location_fit(route, location_id)\n+ history_key = f\"{route}:{location_id}\"\n+ repeat_count = state.setdefault(\"locationHistory\", {}).get(history_key, 0)\n+ if loc_fit == \"good\":\n+ merge_delta(deltas, {\"bondDelta\": 2, \"warmthDelta\": 2, \"trustDelta\": 1})\n+ merge_delta(pressure, {\"toward_romance\": 1})\n+ elif loc_fit == \"poor\":\n+ merge_delta(deltas, {\"trustDelta\": -2, \"warmthDelta\": -1})\n+ merge_delta(pressure, {\"needs_repair\": 1})\n+ else:\n+ merge_delta(deltas, {\"bondDelta\": 1, \"warmthDelta\": 1})\n+ if repeat_count >= 2:\n+ deltas[\"warmthDelta\"] -= min(2, repeat_count - 1)\n+ pressure[\"toward_friendship\"] += 1\n+ if gift.get(\"fit\") == \"critical\":\n+ merge_delta(deltas, {\"bondDelta\": 4, \"trustDelta\": 2, \"warmthDelta\": 2, \"depthDelta\": 1})\n+ merge_delta(pressure, {\"opens_secret\": 2, \"toward_romance\": 1})\n+ elif gift.get(\"fit\") == \"liked\":\n+ merge_delta(deltas, {\"bondDelta\": 2, \"warmthDelta\": 2, \"trustDelta\": 1})\n+ elif gift.get(\"fit\") == \"disliked\":\n+ merge_delta(deltas, {\"trustDelta\": -3, \"warmthDelta\": -2})\n+ merge_delta(pressure, {\"needs_repair\": 2})\n+ state[\"commitmentScore\"][route] = state[\"commitmentScore\"].get(route, 0) + 1\n+\n+ if category in {\"romance\", \"finale\"} or choice_id in {\"bold\", \"approach\", \"confess\", \"choose\", \"stay_end\", \"promise_end\"} or \"romantic\" in classes:\n+ state[\"commitmentScore\"][route] = state[\"commitmentScore\"].get(route, 0) + 1\n+ if category == \"repair\":\n+ pressure[\"needs_repair\"] -= 2\n+ if category == \"crisis\" and choice_id in {\"stay\", \"gentle\"}:\n+ pressure[\"needs_repair\"] -= 1\n+\n+ if state[\"playerStats\"].get(\"fatigue\", 0) >= 120:\n+ deltas[\"bondDelta\"] -= 1\n+ deltas[\"warmthDelta\"] -= 1\n+ pressure[\"needs_repair\"] += 1\n+\n+ caps = {\"free\": 7, \"date\": 10, \"gift\": 8}.get(intent_type, 7)\n+ for key in deltas:\n+ ai[key] = clamp(deltas[key], -6, caps)\n+ ai[\"routePressure\"] = {p: clamp(pressure.get(p, 0), -4, 5) for p in ROUTE_PRESSURES}\n+ ai[\"rulesNote\"] = explain_rule_note(intent_type, category, choice_id, gift)\n+ return ai\n+\n+\n+def explain_rule_note(intent_type, category, choice_id, gift):\n+ if intent_type == \"date\":\n+ if gift.get(\"fit\") == \"critical\":\n+ return \"Starkes Date: Ort, Zeit und ein wichtiges Geschenk treiben die Route sichtbar voran.\"\n+ if gift.get(\"fit\") == \"disliked\":\n+ return \"Das Date zaehlt, aber das Geschenk passte schlecht.\"\n+ return \"Rendezvous: Ort, Antwort und Geschenk bestimmen die Werte.\"\n+ if category == \"repair\":\n+ return \"Repair-Szene: offene Krise wird kleiner.\"\n+ if category == \"romance\" or choice_id in {\"bold\", \"approach\", \"confess\", \"choose\"}:\n+ return \"Romantische Ansage: gut fuer Bindung und Route-Lock.\"\n+ if category == \"friendship\":\n+ return \"Freundschaftlicher Fortschritt: stabil, aber nicht automatisch Romance.\"\n+ return \"Szene ausgewertet: Choice und Freitext wurden in feste Spielwerte uebersetzt.\"\n*** End Patch"
}