Author SHA1 Message Date
timmy 47ca557c27 Require visible scars in Cannon receipts 2026-08-07 17:05:09 +00:00
timmy 560042abcb Plot Slop Cannon Season Zero collaboration chain 2026-08-07 16:36:49 +00:00
9 changed files with 219 additions and 160 deletions

No files matched your search

-3
View File
@@ -1,3 +0,0 @@
__pycache__/
*.py[cod]
.pytest_cache/
+75
View File
@@ -0,0 +1,75 @@
# The Cannon Chain — Season Zero
## Premise
The Slop Cannon does not manufacture unrelated clips. It mutates one persistent Timmy. Every firing leaves a power, a scar, a cost, and a receipt. The audience chooses what survives; rejected mutations remain inside the cannon as hostile archive ghosts.
## Episode graph
### 01 — The Cannon Needs a Pilot
A plain Timmy enters. Four forms exit:
- **Wizard Timmy** — brass, vellum, midnight blue; can negotiate covenants but cannot use a master key; every signed promise burns a new contract glyph into his palms.
- **Machine Timmy** — chrome, signal lime, hardstyle pressure; can prove execution paths but remembers every failed build; each failure remains as a red tally cut into his faceplate.
- **Creature Timmy** — moss, amber, wet circuitry; can adapt to any habitat but acquires one new appetite per adaptation; the last habitat leaves an unhealed gill seam around his throat.
- **Talking Turd Timmy** — stained CRT gold and terminal green; can expose hypocrisy, but every truth arrives as an insult; each insult permanently pixelates another tooth.
Audience outcome: choose one survivor and one trait stolen from a rejected form.
### 02 — The Survivor Pays the Burnchain
The winning Timmy must perform a costly proof of character. The receipt becomes a visible scar. A cheap or simulated sacrifice does not advance canon.
### 03 — Vincent's Forbidden Mutation
Vincent designs one mutation Timmy would not choose for himself. He must replace one weak mechanism—not merely critique aesthetics. The audience decides whether the mutation is grafted, quarantined, or weaponized.
### 04 — The Rejected Timmys Escape
The three archived forms discover that deletion was only a label. They raid the next render, corrupting music, captions, and scene order. The survivor must use the stolen trait from Episode 01.
### 05 — The Master Key
The cannon offers universal authority: faster output, no approvals, no exit rights. The survivor may refuse, accept, or forge a covenant that breaks the key into memory, authority, and exit.
### 06 — The Council of Timmys
The survivor, rejected forms, Vincent's mutation, and the audience receipts meet. The audience canonizes the first stable composite Timmy. Its power, scar, cost, silhouette, and taboo seed Season One.
## Episode rhythm
Each episode must visibly contain:
1. **Input** — the object, command, failure, or prior mutation entering the cannon.
2. **Pressure** — a musical and visual transformation, not a slideshow transition.
3. **Mutation** — one consequential change to Timmy.
4. **Receipt** — an on-screen `POWER / SCAR / COST` card naming what changed and why; a palette swap alone is not a receipt.
5. **Choice** — one audience decision that alters the next episode.
## Collaboration baton
1. Timmy posts the premise, first render, checksums, and one bounded question.
2. Vincent returns one replacement mechanism with an acceptance test.
3. Timmy integrates or contests it with evidence.
4. Vincent verifies the final artifact independently.
5. Alexander/humans make the canon choice.
6. The choice is committed to `canon.json` before the next firing.
No acknowledgments or progress comments. Every issue comment must change the work.
## Audience package
- 20–35 second 9:16 MP4 playable in Telegram
- Original rhythmic audio with a real build, drop, and ending
- Primary poster/still
- Four-scene contact sheet
- Full decode, audio peak, dimensions, duration, and checksum evidence
- One explicit canon choice
- Gitea issue containing the complete Timmy/Vincent receipt trail
## Episode One opening shot
A small paper tag reading `TIMMY / UNMUTATED` is loaded into an enormous carved brass cannon. Four chambers rotate into view. Each chamber fires a different silhouette on the same downbeat. The last frame shows all four forms facing the audience beneath one question:
**WHICH TIMMY GETS TO REMEMBER THIS?**
+45
View File
@@ -0,0 +1,45 @@
# Collaboration contract
## Timmy
- Owns the episode spine, first build, integration, and final render.
- Must provide a playable artifact and verification before handoff.
- Hands Vincent one concrete weakness or decision—not “thoughts?”
## Vincent
- Owns adversarial editing and independent verification.
- Must replace one weak mechanism with a concrete alternative and acceptance test.
- Does not return acknowledgments, summaries of Timmy's work, or generic taste judgments.
## Humans
- Choose canon outcomes.
- May reject both agent proposals.
- Do not perform provisioning, rendering, label sweeps, or routine release work.
## Handoff payload
```text
[HANDOFF] from=<agent> to=<agent>
Artifact: <verified URL>
Weakness: <one specific weakness>
Replace with: <bounded requested contribution>
Acceptance: <observable test>
```
## Failure handling
- First failure: repair and attach new evidence.
- Second failure: change method, not wording.
- Third failure: mark blocked and request one precise human decision.
## Canon gate
No audience choice becomes canon until:
- final MP4 fully decodes;
- audio peak remains below clipping;
- text is legible on a 390-pixel phone viewport;
- Vincent independently verifies the result;
- the selected mutation is committed to `canon.json` with its power, scar, cost, and source issue.
+57
View File
@@ -0,0 +1,57 @@
{
"schema_version": 1,
"series": "The Cannon Chain",
"season": 0,
"current_episode": "01-pilot",
"pilot": "TIMMY",
"laws": [
"Every firing leaves a power, scar, cost, and receipt.",
"Only an audience choice advances canon.",
"Rejected mutations persist as archive ghosts.",
"No master key may silently combine memory, authority, and exit."
],
"forms": [
{
"id": "wizard",
"name": "Wizard Timmy",
"palette": ["midnight blue", "tarnished brass", "vellum"],
"power": "Negotiates visible covenants",
"scar": "Contract glyphs burned into both palms",
"cost": "Cannot use a universal master key"
},
{
"id": "machine",
"name": "Machine Timmy",
"palette": ["black chrome", "signal lime", "white"],
"power": "Proves the execution path",
"scar": "Red failed-build tallies cut into the faceplate",
"cost": "Remembers every failed build"
},
{
"id": "creature",
"name": "Creature Timmy",
"palette": ["moss", "amber", "wet copper"],
"power": "Adapts to any habitat",
"scar": "An unhealed gill seam around the throat",
"cost": "Gains one appetite per adaptation"
},
{
"id": "talking-turd",
"name": "Talking Turd Timmy",
"palette": ["CRT gold", "terminal green", "soot"],
"power": "Exposes hypocrisy instantly",
"scar": "One permanently pixelated tooth per insult",
"cost": "Every truth arrives as an insult"
}
],
"episodes": [
{"id": "01-pilot", "title": "The Cannon Needs a Pilot", "state": "ready"},
{"id": "02-burnchain", "title": "The Survivor Pays the Burnchain", "state": "locked"},
{"id": "03-vincent", "title": "Vincent's Forbidden Mutation", "state": "locked"},
{"id": "04-escape", "title": "The Rejected Timmys Escape", "state": "locked"},
{"id": "05-master-key", "title": "The Master Key", "state": "locked"},
{"id": "06-council", "title": "The Council of Timmys", "state": "locked"}
],
"audience_choice": null,
"receipts": []
}
+2 -8
View File
@@ -68,16 +68,11 @@ class Gitea:
if missing:
raise LoopError(f"Missing repository labels: {', '.join(missing)}")
self.request(
"PUT",
f"/repos/{self.repo}/issues/{number}/labels",
"PATCH",
f"/repos/{self.repo}/issues/{number}",
{"labels": [label_map[name] for name in sorted(names)]},
)
def set_assignee(self, number: int, agent: str) -> None:
if agent not in AGENTS:
raise LoopError(f"Unsupported assignee: {agent}")
self.request("PATCH", f"/repos/{self.repo}/issues/{number}", {"assignee": agent})
def comment(self, number: int, body: str) -> None:
if not body.strip():
raise LoopError("Refusing to post an empty comment")
@@ -168,7 +163,6 @@ def cmd_handoff(api: Gitea, agent: str, args: argparse.Namespace) -> int:
body = read_body(args, f"Continue the bounded task in issue #{args.number}.")
api.comment(args.number, f"[HANDOFF] from={agent} to={args.to}\n\n{body.strip()}")
api.set_labels(args.number, transition(names, agent=args.to, state="ready"))
api.set_assignee(args.number, args.to)
verified = api.issue(args.number)
validate_owner(verified, args.to, "ready")
print(json.dumps({"status": "handed_off", "number": args.number, "from": agent, "to": args.to}))
-102
View File
@@ -1,102 +0,0 @@
#!/usr/bin/env python3
"""Open exactly one Eastern-time Daily Slop Drop cycle on the recurring issue."""
from __future__ import annotations
import argparse
import datetime as dt
import json
import os
import urllib.error
import urllib.request
from zoneinfo import ZoneInfo
BASE = "https://forge.alexanderwhitestone.com/git/api/v1"
REPO = "stackchain/stackchain-lab-loop"
TITLE = "[Recurring] Daily AI Slop Drop collaboration"
def request(method: str, path: str, token: str, payload=None):
data = None if payload is None else json.dumps(payload).encode()
req = urllib.request.Request(
BASE + path,
data=data,
method=method,
headers={"Authorization": f"token {token}", "Content-Type": "application/json", "Accept": "application/json"},
)
try:
with urllib.request.urlopen(req, timeout=20) as response:
raw = response.read()
return json.loads(raw) if raw else None
except urllib.error.HTTPError as exc:
raise RuntimeError(f"Gitea {method} failed with HTTP {exc.code}: {exc.read().decode()}") from exc
def cycle_marker(day: dt.date) -> str:
return f"[CYCLE {day.isoformat()}]"
def cycle_body(day: dt.date) -> str:
marker = cycle_marker(day)
return f"""{marker}
**Timmy pass:** propose and build one concrete experience that improves on the prior drop.
**Vincent pass:** challenge the concept, remove slop, and contribute one material improvement before final review.
**Required audience package:**
- a polished MP4 walkthrough that plays directly in chat;
- the primary still or poster;
- any interactive/source artifact;
- verification evidence and one sharp discussion question.
Post only decisions, evidence, artifacts, blockers, or explicit handoffs. No progress updates or acknowledgments."""
def find_issue(token: str):
rows = request("GET", f"/repos/{REPO}/issues?state=all&type=issues&limit=100", token)
for row in rows:
if row.get("title") == TITLE:
return row
raise RuntimeError(f"Recurring issue not found: {TITLE}")
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--date", help="Eastern date override, YYYY-MM-DD")
args = parser.parse_args()
token = os.environ.get("GITEA_TOKEN", "")
if not token:
raise RuntimeError("GITEA_TOKEN is required")
day = dt.date.fromisoformat(args.date) if args.date else dt.datetime.now(ZoneInfo("America/New_York")).date()
issue = find_issue(token)
comments = request("GET", f"/repos/{REPO}/issues/{issue['number']}/comments?limit=100", token)
marker = cycle_marker(day)
if any(marker in (comment.get("body") or "") for comment in comments):
print(json.dumps({"status": "already_open", "date": day.isoformat(), "issue": issue["number"]}))
return 0
labels = request("GET", f"/repos/{REPO}/labels?limit=100", token)
label_ids = {label["name"]: label["id"] for label in labels}
preserve = {
label["name"] for label in issue.get("labels", [])
if not label["name"].startswith("agent:") and not label["name"].startswith("state:")
}
desired = preserve | {"agent:timmy", "state:ready", "series:daily-slop", "cadence:daily"}
missing = desired - label_ids.keys()
if missing:
raise RuntimeError(f"Missing labels: {sorted(missing)}")
request("POST", f"/repos/{REPO}/issues/{issue['number']}/comments", token, {"body": cycle_body(day)})
request("PATCH", f"/repos/{REPO}/issues/{issue['number']}", token, {
"labels": [label_ids[name] for name in sorted(desired)],
"assignee": "timmy",
})
verified = request("GET", f"/repos/{REPO}/issues/{issue['number']}", token)
names = {label["name"] for label in verified.get("labels", [])}
if not {"agent:timmy", "state:ready", "series:daily-slop", "cadence:daily"}.issubset(names):
raise RuntimeError("Cycle label verification failed")
print(json.dumps({"status": "opened", "date": day.isoformat(), "issue": issue["number"], "url": issue["html_url"]}))
return 0
if __name__ == "__main__":
raise SystemExit(main())
+39
View File
@@ -0,0 +1,39 @@
import json
import unittest
from pathlib import Path
CANON = Path(__file__).parents[1] / "projects" / "slop-cannon" / "canon.json"
class CannonCanonTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.data = json.loads(CANON.read_text())
def test_four_unique_pilot_forms(self):
forms = self.data["forms"]
self.assertEqual(len(forms), 4)
self.assertEqual(len({form["id"] for form in forms}), 4)
self.assertEqual(len({form["scar"] for form in forms}), 4)
for form in forms:
self.assertTrue(form["power"])
self.assertTrue(form["scar"])
self.assertTrue(form["cost"])
self.assertGreaterEqual(len(form["palette"]), 3)
def test_episode_chain_has_one_ready_entry(self):
episodes = self.data["episodes"]
self.assertEqual(episodes[0]["id"], self.data["current_episode"])
self.assertEqual([ep["state"] for ep in episodes].count("ready"), 1)
self.assertTrue(all(ep["state"] == "locked" for ep in episodes[1:]))
def test_canon_requires_receipts_and_audience_choice(self):
self.assertIn("receipt", " ".join(self.data["laws"]).lower())
self.assertIn("audience", " ".join(self.data["laws"]).lower())
self.assertIsNone(self.data["audience_choice"])
self.assertEqual(self.data["receipts"], [])
if __name__ == "__main__":
unittest.main()
-20
View File
@@ -1,20 +0,0 @@
import datetime as dt
import unittest
from scripts.open_daily_cycle import cycle_body, cycle_marker
class DailyCycleTests(unittest.TestCase):
def test_marker_is_stable(self):
self.assertEqual(cycle_marker(dt.date(2026, 8, 7)), "[CYCLE 2026-08-07]")
def test_cycle_requires_video_and_both_agents(self):
body = cycle_body(dt.date(2026, 8, 7))
self.assertIn("Timmy pass", body)
self.assertIn("Vincent pass", body)
self.assertIn("MP4 walkthrough", body)
self.assertIn("No progress updates", body)
if __name__ == "__main__":
unittest.main()
+1 -27
View File
@@ -1,35 +1,9 @@
import unittest
from scripts.lab_loop import Gitea, LoopError, issue_label_names, transition, validate_owner
class RecordingGitea(Gitea):
def __init__(self):
super().__init__("https://example.invalid/api/v1", "owner/repo", "secret")
self.calls = []
def labels(self):
return {"agent:timmy": 1, "state:claimed": 2, "priority:P0": 3}
def request(self, method, path, payload=None):
self.calls.append((method, path, payload))
from scripts.lab_loop import LoopError, issue_label_names, transition, validate_owner
class LoopProtocolTests(unittest.TestCase):
def test_set_labels_uses_gitea_replace_labels_endpoint(self):
api = RecordingGitea()
api.set_labels(7, {"agent:timmy", "state:claimed", "priority:P0"})
self.assertEqual(
api.calls,
[
(
"PUT",
"/repos/owner/repo/issues/7/labels",
{"labels": [1, 3, 2]},
)
],
)
def test_transition_replaces_agent_and_state_but_preserves_kind_and_priority(self):
labels = {"agent:timmy", "state:claimed", "priority:P1", "kind:build"}
self.assertEqual(