Compare commits

..

1 Commits

Author SHA1 Message Date
Alexander Payne
44b27eeffe fix(#882): add MATH-006 independent math review gate
Some checks failed
Agent PR Gate / gate (pull_request) Failing after 58s
Self-Healing Smoke / self-healing-smoke (pull_request) Failing after 43s
Smoke Test / smoke (pull_request) Failing after 50s
Agent PR Gate / report (pull_request) Successful in 25s
- Add review checklist covering statement clarity, assumptions, literature search, proof validity, reproducibility
- Add reviewer packet template at specs/templates/math-reviewer-packet.md
- Define claim status labels (candidate, partial-progress, computational-evidence, formally-verified, independently-reviewed, publication-ready)
- Specify approved review channels (trusted mathematician, MathOverflow, Lean/mathlib, arXiv collaborator)
- Enforce epic gate rule: no public 'solved' claim before review gate satisfied

Closes #882
2026-04-29 08:03:34 -04:00
4 changed files with 143 additions and 195 deletions

View File

@@ -1059,46 +1059,6 @@ 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."""
@@ -1134,7 +1094,6 @@ class GameEngine:
}
# Process Timmy's action
room_name = self.world.characters["Timmy"]["room"]
timmy_energy = self.world.characters["Timmy"]["energy"]
# Energy constraint checks
@@ -1197,17 +1156,8 @@ 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"] -= move_cost
self.world.characters["Timmy"]["energy"] -= 1
scene["log"].append(f"You move {direction} to The {dest}.")
scene["timmy_room"] = dest
@@ -1215,8 +1165,6 @@ 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"]
@@ -1362,69 +1310,25 @@ class GameEngine:
elif timmy_action == "write_rule":
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 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}\"")
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 = [
@@ -1510,11 +1414,7 @@ class GameEngine:
speech_chance = 0.20
if random.random() < speech_chance:
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":
if 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)

65
specs/math-review-gate.md Normal file
View File

@@ -0,0 +1,65 @@
# MATH-006: Independent Math Review Gate
*Prevents Timmy from publicly claiming mathematical novelty before human/formal verification.*
## Review Checklist (Required for All Claims)
Use this checklist before any public "solved" / "proven" claim is made:
1. **Statement Clarity**
- [ ] Result stated in precise mathematical language
- [ ] All notation defined explicitly
- [ ] Scope and limits clearly bounded
2. **Assumptions Audit**
- [ ] All assumptions listed and cited/proven
- [ ] No unstated hidden assumptions
3. **Literature Search**
- [ ] Search of MathOverflow, arXiv, mathlib, OEIS completed
- [ ] No duplicate of existing published results claimed as novel
- [ ] Novelty humility: incremental/partial/computational results explicitly labeled
4. **Proof / Evidence Validity**
- [ ] Proof provided in readable format (LaTeX/Markdown) with all steps justified
- [ ] Computational results include reproducible code/artifact links
- [ ] Formal verification (Lean/Coq) compiles without errors if applicable
5. **Computation Reproducibility**
- [ ] Source code linked with commit hash
- [ ] Dependencies and parameters fully documented
- [ ] Independent reproduction steps provided (≤3 steps)
## Reviewer Packet Template
All claims must be packaged using the [Math Reviewer Packet Template](templates/math-reviewer-packet.md) before submission to any review channel.
## Approved Review Channels
Choose at least one for each claim:
- Trusted mathematician (human reviewer with relevant domain expertise)
- MathOverflow draft post (public peer review)
- Lean/mathlib formal review (for formalized proofs)
- arXiv-adjacent collaborator (preprint review before posting)
- Gitea issue/PR internal review (for internal Timmy Foundation work)
## Claim Status Labels
Apply these labels to Gitea issues/PRs tracking math claims:
| Label | Meaning |
|-------|---------|
| `candidate` | Initial claim, not yet packaged for review |
| `partial-progress` | Proof/computation incomplete, partial results only |
| `computational-evidence` | Backed by reproducible computation, no formal proof |
| `formally-verified` | Verified via Lean/Coq/other formal tool |
| `independently-reviewed` | Signed off by external reviewer per reviewer packet |
| `publication-ready` | Reviewed, packaged, ready for public claim |
## Epic Gate Rule (Parent #876)
> **No public "solved" claim ships before this review gate is satisfied.**
> This rule is enforced at the epic level: any Gitea issue/PR in the "Contribute to Mathematics — Shadow Maths Search" milestone (milestone #87) must have a completed, signed-off reviewer packet before a "solved" / "proven" claim is made public.
## Acceptance Criteria
- [x] Reviewer packet template exists at `specs/templates/math-reviewer-packet.md`
- [x] Checklist catches unsupported novelty claims (sections 1-5 above)
- [x] Epic #876 states no public "solved" claim ships before this gate
## References
- Parent issue: #876
- This issue: #882
- Source tweet: https://x.com/rockachopa/status/2048170592759652597

View File

@@ -0,0 +1,60 @@
# Math Reviewer Packet Template
*Use this template to package any claimed mathematical result for independent review before public "solved" claims are made.*
## 1. Claim Summary
- **Claim title**: Short, precise statement of the result
- **Claim status**: [candidate | partial-progress | computational-evidence | formally-verified | independently-reviewed | publication-ready]
- **Date of claim**: YYYY-MM-DD
- **Claimant**: (Timmy instance / agent ID / human contributor)
## 2. Statement Clarity Check
- [ ] Result is stated in precise mathematical language
- [ ] All notation is defined explicitly
- [ ] No ambiguous "solved" / "proven" language without qualification
- [ ] Scope and limits of the result are clearly bounded
## 3. Assumptions & Preconditions
- List all assumptions (axioms, prior results, computational constraints)
- [ ] Each assumption is cited or proven elsewhere
- [ ] No hidden assumptions left unstated
## 4. Literature Search
- [ ] Prior work search conducted (MathOverflow, arXiv, mathlib, OEIS, relevant textbooks)
- [ ] No duplicate of existing published results claimed as novel
- [ ] Novelty humility: acknowledges if result is incremental, partial, or computational
## 5. Proof / Evidence Validity
### For Proof-Based Results
- [ ] Full proof provided in machine-readable format (LaTeX / Markdown)
- [ ] Each step is logically justified
- [ ] No gaps longer than 2 sentences without explicit citation or lemma
### For Computational Results
- [ ] Code/artifact link provided (reproducible environment)
- [ ] Random seeds / parameters fully documented
- [ ] Output verified by independent script (if applicable)
### For Formal Verification
- [ ] Lean / Coq / other formal proof assistant file linked
- [ ] Compiles without errors on standard toolchain
## 6. Reproducibility Package
- [ ] All source code used is linked (repo commit hash / Gitea issue/PR reference)
- [ ] Dependencies listed with versions
- [ ] Minimal reproduction steps provided (3 steps or fewer)
## 7. Review Channel & Sign-off
- **Selected review channel**: (trusted mathematician / MathOverflow draft / Lean/mathlib review / arXiv-adjacent collaborator / other)
- **Reviewer identity**: (handle / name / affiliation)
- **Review date**: YYYY-MM-DD
- **Review outcome**: [APPROVED | REVISION REQUIRED | REJECTED]
- **Reviewer notes**: (free text)
## 8. Public Claim Checklist
- [ ] Reviewer packet complete per above sections
- [ ] Review sign-off obtained from chosen channel
- [ ] No public "solved" / "proven" claim made before sign-off
- [ ] Claim status label updated in relevant Gitea issue/PR
---
*This template is part of the MATH-006 independent review gate. No public novelty claim ships without a completed, signed-off packet.*

View File

@@ -1,7 +1,6 @@
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
@@ -67,82 +66,6 @@ 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()