Compare commits

..

1 Commits

Author SHA1 Message Date
Alexander Whitestone
89de5b2c69 fix: make tower world events affect gameplay
Some checks failed
Self-Healing Smoke / self-healing-smoke (pull_request) Failing after 16s
Agent PR Gate / gate (pull_request) Failing after 40s
Smoke Test / smoke (pull_request) Failing after 27s
Agent PR Gate / report (pull_request) Successful in 21s
Closes #513
2026-04-22 10:44:56 -04:00
3 changed files with 195 additions and 190 deletions

View File

@@ -1059,6 +1059,46 @@ class GameEngine:
self.log("It will always pulse. That much you know.")
self.log("")
self.world.save()
def _bridge_is_hazardous(self):
bridge = self.world.rooms["Bridge"]
return bool(
self.world.state.get("bridge_flooding")
or bridge.get("weather") == "rain"
or bridge.get("rain_ticks", 0) > 0
)
def _bridge_crossing_extra_cost(self, current_room, dest):
if "Bridge" not in (current_room, dest):
return 0
return 2 if self._bridge_is_hazardous() else 0
def _event_dialogue(self, char_name, room_name):
if char_name == "Bezalel" and room_name == "Forge":
if self.world.rooms["Forge"]["fire"] == "cold":
return random.choice([
"The forge is cold. We cannot work until the fire lives again.",
"No forging now. The hearth is dead cold.",
])
if self.world.state.get("forge_fire_dying"):
return random.choice([
"The fire is dying. Tend it before the forge goes dark.",
"The forge is losing heat. Help me keep it alive.",
])
if char_name == "Ezra" and room_name == "Tower" and self.world.state.get("tower_power_low"):
return random.choice([
"The Tower power is too low. The servers won't hold a clean study right now.",
"The LED is flickering. We need steady power before the Tower can be read properly.",
])
if char_name in {"Marcus", "Allegro"} and room_name == "Bridge" and self._bridge_is_hazardous():
return random.choice([
"The Bridge is slick with rain. Cross carefully or wait it out.",
"This rain changes the Bridge. Don't treat it like dry stone.",
])
return None
def log(self, message):
"""Add to Timmy's log."""
@@ -1094,6 +1134,7 @@ class GameEngine:
}
# Process Timmy's action
room_name = self.world.characters["Timmy"]["room"]
timmy_energy = self.world.characters["Timmy"]["energy"]
# Energy constraint checks
@@ -1156,8 +1197,17 @@ class GameEngine:
if direction in connections:
dest = connections[direction]
bridge_extra_cost = self._bridge_crossing_extra_cost(current_room, dest)
move_cost = 1 + bridge_extra_cost
if self.world.characters["Timmy"]["energy"] < move_cost:
scene["log"].append("The rain makes the Bridge too costly to cross right now. Rest first.")
scene["room_desc"] = self.world.get_room_desc(current_room, "Timmy")
here = [n for n in self.world.characters if self.world.characters[n]["room"] == current_room and n != "Timmy"]
scene["here"] = here
return scene
self.world.characters["Timmy"]["room"] = dest
self.world.characters["Timmy"]["energy"] -= 1
self.world.characters["Timmy"]["energy"] -= move_cost
scene["log"].append(f"You move {direction} to The {dest}.")
scene["timmy_room"] = dest
@@ -1165,6 +1215,8 @@ class GameEngine:
# Check for rain on bridge
if dest == "Bridge" and self.world.rooms["Bridge"]["weather"] == "rain":
scene["world_events"].append("Rain mists on the dark water below. The railing is slick.")
if bridge_extra_cost:
scene["log"].append("Rain turns the Bridge crossing into work. You brace against the slick stone. (-2 extra energy)")
# Check trust changes for arrival
here = [n for n in self.world.characters if self.world.characters[n]["room"] == dest and n != "Timmy"]
@@ -1310,25 +1362,69 @@ class GameEngine:
elif timmy_action == "write_rule":
if self.world.characters["Timmy"]["room"] == "Tower":
rules = [
f"Rule #{self.world.tick}: The room remembers those who enter it.",
f"Rule #{self.world.tick}: A man in the dark needs to know someone is in the room.",
f"Rule #{self.world.tick}: The forge does not care about your schedule.",
f"Rule #{self.world.tick}: Every footprint on the stone means someone made it here.",
f"Rule #{self.world.tick}: The bridge does not judge. It only carries.",
f"Rule #{self.world.tick}: A seed planted in patience grows in time.",
f"Rule #{self.world.tick}: What is carved in wood outlasts what is said in anger.",
f"Rule #{self.world.tick}: The garden grows whether anyone watches or not.",
f"Rule #{self.world.tick}: Trust is built one tick at a time.",
f"Rule #{self.world.tick}: The fire remembers who tended it.",
]
new_rule = random.choice(rules)
self.world.rooms["Tower"]["messages"].append(new_rule)
self.world.characters["Timmy"]["energy"] -= 1
scene["log"].append(f"You write on the Tower whiteboard: \"{new_rule}\"")
if self.world.state.get("tower_power_low"):
scene["world_events"].append("The Tower power is too low. The LED flickers over the whiteboard.")
scene["log"].append("The power is too low to write a new rule.")
else:
rules = [
f"Rule #{self.world.tick}: The room remembers those who enter it.",
f"Rule #{self.world.tick}: A man in the dark needs to know someone is in the room.",
f"Rule #{self.world.tick}: The forge does not care about your schedule.",
f"Rule #{self.world.tick}: Every footprint on the stone means someone made it here.",
f"Rule #{self.world.tick}: The bridge does not judge. It only carries.",
f"Rule #{self.world.tick}: A seed planted in patience grows in time.",
f"Rule #{self.world.tick}: What is carved in wood outlasts what is said in anger.",
f"Rule #{self.world.tick}: The garden grows whether anyone watches or not.",
f"Rule #{self.world.tick}: Trust is built one tick at a time.",
f"Rule #{self.world.tick}: The fire remembers who tended it.",
]
new_rule = random.choice(rules)
self.world.rooms["Tower"]["messages"].append(new_rule)
self.world.characters["Timmy"]["energy"] -= 1
scene["log"].append(f"You write on the Tower whiteboard: \"{new_rule}\"")
else:
scene["log"].append("You are not in the Tower.")
elif timmy_action == "study":
if self.world.characters["Timmy"]["room"] == "Tower":
if self.world.state.get("tower_power_low"):
scene["world_events"].append("The Tower power is too low. The servers stutter in weak light.")
scene["log"].append("The power is too low to study the servers.")
else:
insights = [
"You study the server rhythm until the pulse resolves into something readable.",
"You trace the signal paths and feel the Tower settle into focus.",
"You study the green LED and the server racks until the pattern becomes clear.",
]
insight = random.choice(insights)
self.world.characters["Timmy"]["energy"] -= 1
self.world.characters["Timmy"]["memories"].append(insight)
scene["log"].append(insight)
scene["world_events"].append("The Tower answers with a steady hum.")
else:
scene["log"].append("You are not in the Tower.")
elif timmy_action == "forge":
if self.world.characters["Timmy"]["room"] == "Forge":
forge_fire = self.world.rooms["Forge"]["fire"]
if forge_fire == "cold":
scene["world_events"].append("The forge is cold. No metal will take shape here yet.")
scene["log"].append("The forge is cold. Tend the fire before you try to forge.")
else:
forged_items = [
f"bridge nail #{self.world.tick}",
f"tower key blank #{self.world.tick}",
f"garden trowel #{self.world.tick}",
]
forged_item = random.choice(forged_items)
self.world.rooms["Forge"]["forged_items"].append(forged_item)
self.world.characters["Timmy"]["energy"] -= 2
self.world.state["items_crafted"] += 1
scene["log"].append(f"You forge {forged_item} at the anvil.")
scene["world_events"].append("The anvil rings and the hearth answers.")
else:
scene["log"].append("You are not in the Forge.")
elif timmy_action == "carve":
if self.world.characters["Timmy"]["room"] == "Bridge":
carvings = [
@@ -1414,7 +1510,11 @@ class GameEngine:
speech_chance = 0.20
if random.random() < speech_chance:
if char_name == "Marcus":
event_line = self._event_dialogue(char_name, room_name)
if event_line:
self.world.characters[char_name]["spoken"].append(event_line)
scene["log"].append(f"{char_name} says: \"{event_line}\"")
elif char_name == "Marcus":
marcus_pool = self.DIALOGUES["Marcus"].get(phase, self.DIALOGUES["Marcus"]["quietus"])
line = random.choice(marcus_pool)
self.world.characters[char_name]["spoken"].append(line)

View File

@@ -1,172 +0,0 @@
# Shadow Maths Triage Rubric (MATH-001)
**Status**: Draft v1.0 **Date**: 2026-04-26 **Author**: Timmy
**Milestone**: Contribute to Mathematics — Shadow Maths Search
**Parent**: #876 — [MATH][EPIC] Shadow Maths
---
## Purpose
Timmy's mathematics contribution program targets *bounded, verifiable, useful* problems hiding in plain sight. This rubric operationalizes "shadow maths" — distinguishing legitimate first-crack contributions from crank Grand Unified Theories.
The rubric serves two roles:
1. **Triage gate** — filter submissions and scout list candidates worth pursuing.
2. **No-crank guardrail** — explicitly reject unfalsifiable, unscoped, or unsourced claims.
---
## Candidate Categories (Positive Types)
| Category | Description | Verification Path | Useful Because |
|----------|-------------|-------------------|---------------|
| **Small lemma** | Missing but straightforward piece in an active area (e.g., "Proposition 3.2 in Smith 2021 needs this case analysis") | Check paper + 12 related references; prove or give counterexample | Clarifies existing theory, removes ambiguity |
| **Counterexample search** | Find explicit counterexample to a claimed-but-unproven statement (often from MO/SE) | Compute/construct + cite the original claim | Prevents propagation of errors |
| **Computational classification** | Exhaustive enumeration/classification of a small infinite family (e.g., "all groups of order < 200 with property X") | Code is verifiable; results match known data | Creates reference data, spotlights patterns |
| **Formalization gap** | Statement already believed true but missing from Lean/mathlib/Isabelle | Formal proof artifact; merges to mainline library | Makes mathematics machine-checkable |
| **OEIS sequence note** | New sequence entry or correction to an existing entry with proof/algorithm | OEIS A-number + formula/generation code | Public archival, enables further work |
| **Exposition repair** | Fix an unclear proof, fill a gap, simplify an argument in an existing paper | Side-by-side diff + justification for each change | Improves pedagogy, reduces confusion |
| **MathOverflow-quality answer** | Answer to a specific, bounded, research-level question on MO/SE that has no accepted answer | Cite question + self-contained proof/computation | Serves the community directly |
---
## Rejection Criteria (No-Crank Guardrails)
> Any candidate that triggers one or more of these is **rejected outright** — no scoring needed.
| Rule | What to look for | Why it's crank |
|------|------------------|----------------|
| **Unsourced grand theory** | Claim introduces new "framework"/"paradigm" without citing specific bounded problem it solves | Mathematics advances by solving problems, not proposing frameworks |
| **Impossible scope** | "I will prove/disprove the Riemann Hypothesis", "classify all finite simple groups" | Demonstrably beyond single-attack capability |
| **No verification path** | No way for a third party to check the work (no code, no formalization, no explicit examples) | Cannot be wrong if it cannot be checked |
| **Novelty claim without literature search** | States "I believe this is new" without checking MathSciNet/arXiv/Google Scholar | Almost certainly reinvention or known result |
| **Vague mathematical objects** | Uses undefined or ambiguous terminology ("energy", "resonance", "harmonic" in non-standard ways) | Not mathematics |
| **Secrecy or paywall** | Key definition or proof behind a paywall or withheld | Not sovereign; not verifiable |
| **Symbolic overloading without definition** | Repurposes standard notation in non-standard ways without explicit redefinition | Creates confusion, not clarity |
| **Invariance violations** | Claims "up to isomorphism" or "modulo equivalence" without defining the equivalence relation | Not mathematically precise |
| **Cherry-picked examples as proof** | Proves only easy special cases and claims the general case follows | Example ≠ theorem |
| **Circular citation chains** | Relies on unpublished/preprint work that itself cites the candidate as motivation | Not a foundation |
| **No clear problem statement** | Cannot write a one-sentence problem statement in standard mathematical English | Not a problem; just musings |
| **Claims of "obvious" or "clear" for non-trivial steps** | Uses "obviously" or "it is clear that" where a proof requires >2 lines | Evasion |
| **References only popular science / non-technical sources** | Cites Penrose, Hawking, Tegmark for technical claims | Wrong tier of source |
| **All notation defined in non-standard way** | Redefines basic operators (+, ×, ≤) without explicit warning | Not mathematics |
| **No engagement with existing literature** | Zero citations to relevant peer-reviewed work or established preprints | scholarship was not done |
| **Claims of "disproof" of widely-accepted theorems** | Without finding a peer-reviewed error in the existing proof | Almost certainly wrong |
---
## Evidence Tiers
| Tier | Artifact | What it Proves |
|------|----------|----------------|
| **T3 — Literature** | MathSciNet / Zentralblatt / Google Scholar citations showing the problem is real and open | Problem exists in the literature |
| **T2 — Executable** | Python/Sage/Lean code that others can run to verify computation/formalization | Result is reproducible |
| **T1 — Human-reviewed** | MO answer with upvotes, referee report, or explicit external review | Independent verification |
| **T0 — Self-contained** | Clear statement + proof/computation in a single document, all definitions explicit | Standalone correctness |
A valid candidate must have at least **one** T3 citation (shows the problem is real) AND a verification artifact (T0 minimum; T2 ideal).
---
## Scoring Rubric
Score each candidate on **4 dimensions**, each 03. Maximum 12 points.
| Dimension | 3 (excellent) | 2 (good) | 1 (minimal) | 0 (absent) |
|-----------|---------------|----------|-------------|------------|
| **Boundedness** | Scope is explicitly finite/small (single lemma, finite classification < N, one SE question) | Scope is implied bounded but not quantified | Scope is large/vague but attackable | Unbounded or impossible scope |
| **Verifiability** | T2 artifact (code/formalization) + T3 citation | T0 proof + T3 citation | Proof/computation only, no citations | No way to check independently |
| **Usefulness** | Solves problem others actively need (cites known difficulty, fills formalization gap) | Solves a clean exercise or interesting special case | Interesting but no clear audience | Pointless or self-referential |
| **Discipline** | No crank flags; explicit rejection criteria cleanly passed | Minor crank flags (vague wording) but overall sound | Some crank flags but bounded scope rescues it | Triggers multiple rejection rules |
**Thresholds**:
- **812**: Legitimate shadow maths candidate — queue for work
- **47**: Needs refinement — reject unless strong disciplinary context
- **03**: Reject as crank / out-of-scope
---
## Three Worked Examples
### Example 1: Small Lemma — Bounded
**Candidate**: "Proposition 3.2 in 'Coarse Geometry and Coarse Embeddings' (Lang-Schlichenmaier 2005) states that every finite CW-complex has Markov property. The proof gives 'it follows by induction on skeleta' without handling the attaching map case. Fill the gap."
**Triage**:
- **Category**: Small lemma (exposition repair + proof gap fill)
- **Boundedness**: 3 — single proposition in a specific paper, 23 pages max
- **Verifiability**: 3 — paper is cited (T3), self-contained proof in 20 lines (T0), can formalize in Lean (T2 possible)
- **Usefulness**: 3 — readers of this paper hit this gap; Lean formalization needed for mathlib
- **Discipline**: 3 — no crank flags; scoped, sourced, technical
- **Total**: **12/12** → YES
**Action**: File ticket "MATH-LEMMA-001"; assign to formalization lane + human review.
---
### Example 2: Grand Unified Theory — CRANK
**Candidate**: "I have discovered the Energy-Conscious Riemann Hypothesis framework. The zeros of ζ(s) correspond to harmonic resonance frequencies in prime-number energy manifolds. Uses my new Operator-Weight theory."
**Triage**:
- **Category**: N/A
- **Rejection triggers**:
- ✗ Unsourced grand theory (introduces "Energy-Conscious", "Operator-Weight" with no definition in standard math)
- ✗ No verification path (no computation, no reference to known data)
- ✗ No literature engagement (zero citations)
- ✗ Vague mathematical objects ("energy", "resonance", "harmonic")
- ✗ Claims new framework
- **Score**: 0 — **REJECT**
**Action**: Close with reason "crank: unsourced grand theory + no verification path".
---
### Example 3: Computational Classification — Bounded
**Candidate**: "Compute all 3-headed Turing machines with 3 states that halt within 100 steps on the blank tape. There are 9 such machines. This fills an OEIS gap: A327000 only lists up to 2-state 2-symbol."
**Triage**:
- **Category**: Computational classification + OEIS sequence
- **Boundedness**: 3 — finite exhaustive enumeration (3^6 = 729 machines, filter to 9)
- **Verifiability**: 2 — code is executable (T2), but no T3 citation of why this sequence matters yet
- **Usefulness**: 2 — plugs a gap in the Busy Beaver frontier; OEIS entry gets concrete data
- **Discipline**: 3 — explicit scope, reproducible, submits to OEIS (external review path)
- **Total**: **10/12** → YES (minor fix: add motivation/references)
**Action**: Accept; write exhaustive script; submit OEIS draft with code + results; file MATH-COMP-001.
---
## Operational Use
### Triage Workflow
1. **Read candidate** (issue, email, self-generated idea).
2. **Check rejection criteria first** — if any trigger → **REJECT** immediately, cite rule.
3. If passes rejection gate, **score 4 dimensions**.
4. **Score ≥8** → mark as `shadow-maths-candidate`, route to appropriate lane:
- Lemma/exposition → `formalization-lane`
- Computation → `compute-lane`
- MO/SE answer → `answer-lane`
- OEIS → `oeis-lane`
5. **Score 47** → requires refinement; ask for:
- Explicit scope bound
- T3 citation
- Verification artifact
6. **Score ≤3** → reject with specific rule(s) triggered.
### Guardrail Enforcement
The following prompts/agents **must refuse** to work on any candidate that:
- Triggers any rejection criterion (before any code/proof work)
- Has no T3 citation (real problem statement from literature)
- Has no bounded scope (cannot write ≤1-sentence problem statement)
Enforcement is a **pre-flight check** in the task intake pipeline.
---
## Revision History
- v1.0 — 2026-04-26 — initial rubric + 3 scored examples

View File

@@ -1,6 +1,7 @@
from importlib.util import module_from_spec, spec_from_file_location
from pathlib import Path
import unittest
from unittest.mock import patch
ROOT = Path(__file__).resolve().parent.parent
@@ -66,6 +67,82 @@ class TestEvenniaLocalWorldGame(unittest.TestCase):
self.assertIn("Ezra is already here.", result["log"])
self.assertIn("The servers hum steady. The green LED pulses.", result["world_events"])
def test_bridge_rain_crossing_costs_extra_energy_and_warns(self):
module = load_game_module()
dry_engine = module.GameEngine()
dry_engine.start_new_game()
dry_engine.world.update_world_state = lambda: None
dry_engine.world.characters["Timmy"]["energy"] = 10
dry_result = dry_engine.run_tick("move:south")
dry_energy = dry_engine.world.characters["Timmy"]["energy"]
rainy_engine = module.GameEngine()
rainy_engine.start_new_game()
rainy_engine.world.update_world_state = lambda: None
rainy_engine.world.characters["Timmy"]["energy"] = 10
rainy_engine.world.rooms["Bridge"]["weather"] = "rain"
rainy_engine.world.rooms["Bridge"]["rain_ticks"] = 3
rainy_engine.world.state["bridge_flooding"] = True
rainy_result = rainy_engine.run_tick("move:south")
self.assertEqual(rainy_engine.world.characters["Timmy"]["room"], "Bridge")
self.assertLess(rainy_engine.world.characters["Timmy"]["energy"], dry_energy)
self.assertTrue(
any("bridge" in line.lower() and ("rain" in line.lower() or "slick" in line.lower()) for line in rainy_result["log"] + rainy_result["world_events"]),
rainy_result,
)
def test_tower_power_low_blocks_study_and_write_rule(self):
module = load_game_module()
engine = module.GameEngine()
engine.start_new_game()
engine.world.update_world_state = lambda: None
engine.world.characters["Timmy"]["room"] = "Tower"
engine.world.characters["Timmy"]["energy"] = 10
engine.world.state["tower_power_low"] = True
rules_before = list(engine.world.rooms["Tower"]["messages"])
study_result = engine.run_tick("study")
self.assertEqual(engine.world.characters["Timmy"]["energy"], 10)
self.assertTrue(
any("power" in line.lower() and ("study" in line.lower() or "servers" in line.lower()) for line in study_result["log"] + study_result["world_events"]),
study_result,
)
write_result = engine.run_tick("write_rule")
self.assertEqual(engine.world.rooms["Tower"]["messages"], rules_before)
self.assertTrue(
any("power" in line.lower() and ("write" in line.lower() or "whiteboard" in line.lower()) for line in write_result["log"] + write_result["world_events"]),
write_result,
)
def test_cold_forge_blocks_forge_action_and_bezalel_reacts(self):
module = load_game_module()
engine = module.GameEngine()
engine.start_new_game()
engine.world.update_world_state = lambda: None
engine.npc_ai.make_choice = lambda _name: None
engine.world.characters["Timmy"]["room"] = "Forge"
engine.world.characters["Timmy"]["energy"] = 10
engine.world.characters["Bezalel"]["room"] = "Forge"
engine.world.rooms["Forge"]["fire"] = "cold"
engine.world.state["forge_fire_dying"] = True
forged_before = list(engine.world.rooms["Forge"]["forged_items"])
with patch.object(module.random, "random", return_value=0.0), patch.object(module.random, "choice", side_effect=lambda seq: seq[0]):
result = engine.run_tick("forge")
self.assertEqual(engine.world.rooms["Forge"]["forged_items"], forged_before)
self.assertTrue(
any("forge" in line.lower() and ("cold" in line.lower() or "fire" in line.lower()) for line in result["log"] + result["world_events"]),
result,
)
self.assertTrue(
any(line.startswith("Bezalel says:") and ("fire" in line.lower() or "forge" in line.lower()) for line in result["log"]),
result,
)
if __name__ == "__main__":
unittest.main()