A controlled memetic petri dish. Little agents live on a grid. They eat, move, share food, attack each other, copy their neighbors, and reproduce. They also carry ideas — small symbolic memes — that spread between them, mutate as they spread, and shape how their carriers behave. The whole point is to watch ideas behave like life and ask whether one strategy beats another.
- The idea
- The question we want to answer first
- What ships today
- Quick start
- Architecture at a glance
- Config reference
- Quick recipes
- Tech spec — the engineering deep-dive
Pretend culture is biology. An "idea" is not flavor text — it's an object in the simulation, a tiny rule like "when next to a hungry friend, share food." That rule sits in an agent's inventory the way a gene sits in a cell. When the agent interacts with a neighbor, the rule can copy itself over to the neighbor with some probability. Sometimes it copies with a small change — a mutation. Sometimes two rules in the same agent combine into a new one. Over hundreds of ticks, the simulator gives you a frame-by-frame view of which rules spread, which die out, and which mutate into something unrecognizable.
The interesting part is that the rules affect the host's behavior. Carrying "share with allies" makes you more likely to give energy away. Carrying "attack low-energy outsiders" makes you more likely to predate on weaker strangers. Conflicting rules can't quietly coexist in the same agent — when a contradictory new meme arrives, the carrier either rejects it, gets converted, or fuses the two into a hybrid child. So the world is a tug-of-war: cooperators bleed energy by sharing, predators pay attack costs and risk retaliation, and the question of which kind of agent is fittest is not settled in advance — the simulator settles it for us, deterministically, given a seed.
We deliberately avoided giving agents natural language or LLM-generated thoughts. Memes are bounded symbolic structures — finite enums plus a few numbers — so the simulation is fast, debuggable, and reproducible to the byte. That matters: if you want to take conclusions out of this, you need to be able to re-run an experiment and get the same answer.
Can a cooperative meme survive against a selfish meme under different levels of scarcity, mutation, and social copying?
The MVP answers it end-to-end. Three shipped presets — cooperation-vs-selfish-low, -mid, -high — run the same experiment under increasingly tight food supply (food at 100% / 50% / 20%).
Populations here are small (tens of agents) and meme prevalence drifts, so any single seed is noise. The milestone is therefore recorded as a win-rate across a 16-seed sweep (seeds 1–16, horizon 1000) — not one cherry-picked run. A "win" means that side's prevalence leads among the survivors at horizon:
| Scarcity | Cooperative wins | Aggressive wins | Total collapse | Mean population |
|---|---|---|---|---|
| low (food abundant) | 14 / 16 | 2 / 16 | 0 / 16 | ~71 |
| mid (food halved) | 11 / 16 | 5 / 16 | 0 / 16 | ~17 |
| high (food at 20%) | 3 / 16 | 3 / 16 | 10 / 16 | ~0 |
The answer: cooperation is broadly viable. Once predation carries real risk (an attacked agent fights back) and sharing is positive-sum (energy is worth more to a starving neighbour than to a full one), the cooperative meme usually wins under abundant and moderate food. Only under extreme scarcity does the outcome stop being about memes at all — the whole population starves before either side settles anything.
The full per-tick event stream for any run is in runs/<id>/events.jsonl for anyone who wants to ask the data different questions.
The simulator runs the full life-cycle described above and writes everything to disk:
- A grid world where agents move, eat, share, attack, imitate, transmit, reproduce, and die. Death has three causes: starvation, aging, combat.
- Bounded symbolic memes with a trigger, a target, an effect, a strength, and a few probabilities. Six starters ship in the box: share with allies, avoid strangers, copy high-energy agents, attack low-energy outsiders, punish non-sharers, prefer agents with the same meme.
- Mutation and recombination with full lineage tracking — every meme alive at any tick traces back through its ancestors to a founding starter.
- A live terminal UI (the GIF up top) with the world on the left and a chart of meme prevalence over time on the right.
- A headless mode that produces byte-identical output to the TUI for the same seed.
- A self-describing run artifact: every run writes its resolved config, a JSON-Lines event stream, and a flat CSV summary, all under
runs/<timestamp>-<name>/.
The whole thing has 51 tests, runs 1000 ticks in under a second in release mode, and is bit-identical across reruns with the same seed. The constitution that keeps it that way lives at .specify/memory/constitution.md.
# The milestone, one command:
cargo run --release -p meme-garden-cli -- headless \
--preset cooperation-vs-selfish-low --seed 42
# The same world, but you can watch:
cargo run -p meme-garden-cli -- run \
--preset cooperation-vs-selfish-mid --seed 42
# See what's in the box:
cargo run -p meme-garden-cli -- list-presets
# Talk to a finished run:
cargo run -p meme-garden-cli -- analyze runs/<some-run>/Inside the TUI: space to pause, s to single-step, + / - to speed up or down, q to quit. The right pane shows live prevalence per meme kind; the left pane shows agents (C = cooperative carrier, S = aggressive carrier, X = both, a = no relevant meme) and food (.).
The project is two Rust crates. Core is pure — no terminal, no network, no filesystem writes. The CLI wraps it, owns I/O, and presents the TUI or the headless runner.
flowchart LR
Cfg["configs/*.toml"]:::input
CLI["meme-garden-cli<br/><b>I/O + UI</b>"]:::impure
Core["meme-garden-core<br/><b>pure simulation</b>"]:::pure
Runs["runs/<id>/<br/>events · summary · config"]:::output
Term["Terminal UI<br/>(ratatui)"]:::output
Cfg --> CLI
CLI -->|"config + seed"| Core
Core -->|"Metrics + Events"| CLI
CLI --> Runs
CLI --> Term
classDef pure fill:#2563eb,stroke:#1e3a8a,color:#fff,stroke-width:2px;
classDef impure fill:#ea580c,stroke:#9a3412,color:#fff,stroke-width:2px;
classDef input fill:#0891b2,stroke:#155e75,color:#fff,stroke-width:2px;
classDef output fill:#475569,stroke:#1e293b,color:#fff,stroke-width:2px;
AI providers (MemeNamer, ExperimentDesigner, RunAnalyst) plug in behind traits in core::ai with a NoopProvider default. They're called only from CLI commands — never from the per-tick loop.
One tick of the simulation is a fixed sequence of phases. Reordering them is a breaking change — the determinism gate fires immediately:
flowchart TB
P1["1 · Perception<br/>look around"]:::life
P2["2 · Policy<br/>traits + memes → Action"]:::life
P3["3 · Action<br/>move · eat · share · attack"]:::life
P4["4 · Transmission<br/>spread · maybe mutate"]:::evo
P5["5 · Reproduction<br/>inherit · recombine"]:::evo
P6["6 · Death<br/>metabolism + aging"]:::life
P7["7 · World<br/>food regrowth"]:::life
P8["8 · Metrics<br/>emit Tick + Events"]:::out
P1 --> P2 --> P3 --> P4 --> P5 --> P6 --> P7 --> P8
P8 -->|"tick += 1"| P1
classDef life fill:#3b82f6,stroke:#1e3a8a,color:#fff,stroke-width:2px;
classDef evo fill:#7c3aed,stroke:#4c1d95,color:#fff,stroke-width:2px;
classDef out fill:#059669,stroke:#064e3b,color:#fff,stroke-width:2px;
Blue phases are the biological life-cycle. Purple phases are where memes evolve — spread, mutate, recombine. Green is the metrics gate, where one tick's outcome leaves the simulator.
And the data inside the simulator — what an Agent and a Meme actually look like — is bounded and small:
flowchart LR
Agent["<b>Agent</b><br/>position · energy · age<br/>traits · memory · trust<br/>—<br/>inventory: [Meme]"]:::core
Meme["<b>Meme</b><br/>kind (cooperative / aggressive / …)<br/>trigger · target · effect<br/>strength · transmissibility<br/>mutation_rate · cognitive_cost"]:::core
Lineage["<b>LineageGraph</b><br/>append-only nodes<br/>every Meme traces back<br/>to a founding starter"]:::evo
Agent -->|"carries 0..N"| Meme
Meme -->|"lineage_id ↦ node"| Lineage
classDef core fill:#2563eb,stroke:#1e3a8a,color:#fff,stroke-width:2px;
classDef evo fill:#7c3aed,stroke:#4c1d95,color:#fff,stroke-width:2px;
That's the whole mental model. Memes are inventory items. Inventory items bias behavior. Behavior changes who's adjacent to whom next tick, which changes who transmits what, which changes which memes survive.
Every run is a TOML file. Below is every section, every knob, and what turning it does.
| Param | Meaning |
|---|---|
width |
Grid columns. |
height |
Grid rows. Agents can stack on the same cell. |
Bigger grid → agents spread out, transmission slows. Smaller grid → forced contact, faster spread, more fights.
| Param | Meaning |
|---|---|
count |
Initial agent population. |
starting_energy |
Energy each agent spawns with. Also the baseline for the Hungry trigger (< 0.5 ×) and the LowEnergyAgent target. |
metabolism |
Energy lost per tick by every living agent, before action costs. |
max_energy |
Hard cap. The HighEnergyAgent threshold is 0.75 × this. |
max_age |
Ticks before death by aging. |
initial_traits_dist |
Probabilities summing to 1.0: [Generous, Cautious, Aggressive, Conformist]. Traits bias per-tick action weights. |
trait_mutation_rate |
Per-trait chance at reproduction that an inherited trait re-rolls. |
| Param | Meaning |
|---|---|
initial_density |
Fraction of cells seeded with food at tick 0. |
regrowth_rate |
Per-empty-cell, per-tick chance of food growing. |
energy_per_food |
Energy gained when eating one food unit. |
| Param | Meaning |
|---|---|
level |
One of low (1.0× food), mid (0.5×), high (0.2×), custom (leave food values untouched). Multiplied into food.initial_density and food.regrowth_rate at load time. |
This is why the three milestone presets are byte-identical except for this one line.
| Param | Meaning |
|---|---|
inventory_cap |
Max memes per agent. On overflow the oldest meme is dropped FIFO and a MemeForgotten event fires. |
| Param | Meaning |
|---|---|
base_rate |
Multiplied into every transmission roll. 0 disables transmission entirely. |
social_copying_bias_mean |
Mean of the per-agent "how willing am I to adopt new ideas" trait, sampled at birth. |
social_copying_bias_std |
Spread of that per-agent draw. |
prestige_boost |
Additive bonus when the transmitter is in the top-quartile of energy. |
The roll for "does meme M move from A to B this tick" is base_rate × meme.transmissibility × B.social_copying_bias, plus prestige_boost if A is high-energy, clamped to [0, 1].
| Param | Meaning |
|---|---|
strength_jitter_max |
Max ± delta on strength on a mutation event. Clamped to [0, 1]. |
enum_swap_probability |
Probability a mutation event swaps one of trigger, target, or effect to a different variant. |
The per-meme mutation_rate gates whether mutation fires on a transmission; these knobs control how big the change is. Mutation preserves the meme's kind.
Some memes can't sensibly coexist in the same agent — Cooperative and Aggressive are the first such pair. When transmission, imitation, or inheritance would deliver a conflicting meme to a carrier of its opposite, the simulator rolls one of three outcomes: reject (keep the old one), replace (drop the old, keep the new), or recombine (fuse both into a hybrid child meme, drop both originals).
| Param | Meaning |
|---|---|
recombine_share |
Fraction of contested acquires that fuse the two memes via recombination. The remaining share splits between reject and replace, weighted by relative strength — stronger new memes win replace rolls more often. |
Default is 0.20. Set to 0 to disable fusing entirely (everything becomes reject-or-replace). Set high to bias toward hybridization.
| Param | Meaning |
|---|---|
energy_threshold |
Both parents must be at or above this energy. |
offspring_energy_cost |
Energy each parent pays. |
inherit_meme_prob |
Per-parent-meme probability of inheritance. |
min_age |
Minimum age to reproduce. |
Recombination of two parental memes fires with a fixed 20% chance when both parents have non-empty inventories.
| Param | Meaning |
|---|---|
energy_cost_attacker |
Energy the attacker spends. |
energy_steal |
Energy transferred from victim to attacker (capped at victim's energy). |
retaliation_chance |
Probability that a victim who survives the hit strikes back, dealing energy_steal damage to the attacker (a deterrent — that energy is destroyed, not transferred). Makes unprovoked predation risky instead of free profit. |
Attacks also drop the victim's trust in the attacker, mark the victim as recently attacked (which feeds the AttackedRecently trigger for 10 ticks), and kill the victim if energy reaches zero.
| Param | Meaning |
|---|---|
share_threshold |
Donor only shares if its own energy is above this. |
share_amount |
Energy the donor spends per share. The donor's trust in the recipient bumps +0.10. |
recipient_multiplier |
The recipient gains share_amount × recipient_multiplier. Above 1.0, sharing is positive-sum — energy is worth more to a starving agent — so cooperative clusters can out-reproduce instead of merely bleeding. Defaults to 1.0 (zero-sum) for legacy configs. |
A repeated table — one entry per seeded meme.
| Param | Meaning |
|---|---|
name |
One of the six starters: share_with_allies, avoid_strangers, copy_high_energy, attack_low_energy_outsiders, punish_non_sharers, prefer_same_meme. |
carrier_fraction |
Per-agent probability of starting with this meme. |
Each agent receives at most one starter at tick 0 — the pool is treated as a categorical draw. So two entries at 0.5 each give ~50% of agents the first meme, ~50% the second, and 0% none. If the entries sum to less than 1.0, the remaining probability is the chance of starting empty (which leaves transmission room to do work). If they sum to more than 1.0, the weights are normalized.
| Param | Meaning |
|---|---|
seed |
The only randomness source. Same seed + same config = bit-identical metrics. |
horizon |
Max ticks. Overridable via --ticks. |
stop_on_extinction |
If true, terminate at the first population extinction. Default false keeps emitting metrics so the post-extinction tail is visible. |
cluster_snapshot_every |
Cadence (in ticks) for Jaccard-similarity cultural-cluster snapshots. 0 disables. |
metrics_emit_every |
Cadence for per-tick metric emission. Raise it for shorter events.jsonl. |
survival_threshold |
Prevalence a meme must clear at horizon to be reported as "survived." |
- Make cooperation fail. Set
sharing.recipient_multiplier = 1.0(sharing back to zero-sum, so cooperators only bleed) and lowerattack.retaliation_chancetoward0(predation becomes a free lunch). Raisingagents.metabolismand loweringfood.regrowth_rateon top of that starves donors faster than they can recover. - Maximize mutation drift. Raise
mutation.strength_jitter_maxandmutation.enum_swap_probabilitytoward 1.0. - Pure deterministic baseline (no mutation, no trait drift). Set
mutation.strength_jitter_max = 0,mutation.enum_swap_probability = 0,agents.trait_mutation_rate = 0. Memes still spread but never change. - Fast-forward a sweep. Set
metrics_emit_every = 10,cluster_snapshot_every = 0, raise--ticks. Same simulation, ~10× smallerevents.jsonl. - Bias conflict toward hybridization. Raise
conflict.recombine_sharetoward 1.0 — every contested acquire fuses the two memes into a recombined child instead of one displacing the other. - Force a single-meme world. Drop one
[[memes.seed]]entry and bump the remaining one to0.9+. Useful for "does this meme spread on its own merits" tests. - Test a meme's solo viability. Seed only one starter at
carrier_fraction = 0.05. Check whether it reaches≥ run.survival_thresholdby horizon. - Reproducibility sanity check. Run twice with the same seed. The hash of
tail -n +2 events.jsonl(i.e. everything after the run-id-bearing header) must match.
Everything below is for someone reading or editing the code. The narrative above is what the project is; this section is how it works.
crates/
meme-garden-core/ simulation engine — pure, deterministic, no I/O
src/
lib.rs public surface + CORE_VERSION
rng.rs SimRng — the ONLY randomness source (Pcg64Mcg)
config.rs SimConfig + sub-configs + validate + legacy adapter
world.rs Simulation + 8-phase tick loop + Grid
agent.rs Agent, AgentId, AgentTrait, AgentMemory, TrustMap
meme.rs Meme + Trigger/TargetSelector/Effect/MemeKind enums
action.rs Action enum (Move/Eat/Share/Attack/Imitate/Transmit/Reproduce/Idle)
policy.rs per-tick compute_action + Perception + NeighborInfo
mutation.rs mutate_in_place + recombine
lineage.rs LineageGraph (append-only, traces to starter)
starters.rs six starter meme constructors + STARTERS table
metrics.rs Metrics + Event enum + shannon/top1 helpers
ai.rs MemeNamer / ExperimentDesigner / RunAnalyst + NoopProvider
tests/ 9 integration test files
meme-garden-cli/ binary: TUI + headless runner
src/
main.rs clap subcommands (run | headless | list-presets | export | analyze | experiment)
runner.rs shared tick loop + default_run_id timestamp generator
export.rs RunWriter (JSONL + CSV + config.toml) + export helpers
app.rs TUI state (history ring + tps + pause)
tui.rs ratatui rendering (grid + sparkline)
tests/ 4 CLI integration test files (headless, sweep, export_roundtrip, list-presets)
configs/
default.toml baseline parameters
presets/ shipped milestone presets (low/mid/high scarcity)
docs/
design.md long-term vision (north star, not spec)
meme-grammar.md the symbolic grammar in detail
assets/ README assets (gif)
runs/ per-run artifacts (gitignored except .gitkeep)
specs/001-meme-garden-mvp/ executable spec: spec.md, plan.md, tasks.md, contracts/
.specify/memory/constitution.md project principles (binding rules)
Implemented in crates/meme-garden-core/src/world.rs::Simulation::step. Phase order is the determinism contract; reordering changes outputs and breaks the regression test.
-
perception_phase(world.rs) — for every agent, build aPerceptionstruct (policy.rs): adjacent food cells, 4-cell-radius neighbors with classifications (trust, kin, shares-meme, high/low-energy), thehungryflag (energy < 0.5 × starting_energy), theattacked_recentlyflag (last attack within 10 ticks). Read-only. -
policy_phase(world.rs) — for each living agent inAgentIdorder, callpolicy::compute_action. The algorithm: start from an 8-slot baseline weight array (one slot per action category); multiply by trait modifiers (Generous× 1.4 on Share,Cautious× 0.6 on Attack, etc.); bias by hunger and adjacent food; gate reproduction by energy + age + partner; for each meme whosetriggermatches the perception, multiply the weight ofeffect_to_category(meme.effect)by(1 + meme.strength); zero out categories with no valid target (e.g. no adjacent hungry ally → Share weight 0). Sample viaSimRng, then turn the category into a concreteActionby picking a target (e.g.pick_share_target,pick_attack_target). -
action_phase(world.rs) — apply each chosenActioninAgentIdorder. Decrementsenergybymetabolism + Σ cognitive_cost; emitsEvent::Death { cause: Starvation }onenergy ≤ 0.Share,Attack, andImitatemutate the relevant agents and the trust map.Sharegives the recipientshare_amount × recipient_multiplierwhile the donor paysshare_amount.Attackstealsenergy_steal; if the victim survives, it strikes back with probabilityretaliation_chanceforenergy_stealdamage to the attacker.Imitateinherits the target's first novel meme into the imitator's inventory (subject toinventory_cap; oldest gets evicted FIFO, emittingMemeForgotten). -
transmission_phase(world.rs) — independent fromAction::Transmit; runs over all (agent, meme) pairs for every adjacent neighbor. Rollp = base_rate × meme.transmissibility × recipient.social_copying_bias (+ prestige_boost if sender is top-quartile energy)viaSimRng::gen_bool. On success, allocate newMemeId+ lineage node (LineageOrigin::Inheritance), then rollmeme.mutation_rate; if it hits, callmutation::mutate_in_placeand (on a real mutation) allocate a second lineage node (LineageOrigin::Mutation). A mutation that swaps theeffectfield re-derives the meme'skindfrom its new behaviour (share → cooperative,attack → aggressive). Applyinventory_capand push. EmitEvent::Transmissionand (on mutation)Event::Mutation. -
reproduction_phase(world.rs) — iterate inAgentIdorder; for each agent meetingenergy ≥ reproduction.energy_threshold && age ≥ reproduction.min_age, find an adjacent partner with the same energy precondition (i < j to avoid double counting). Both parents payoffspring_energy_cost. Offspring takes traits inherited from union of parents withagents.trait_mutation_rateper-trait reroll, social-copying-bias = average of parents, and memes inherited per parent atreproduction.inherit_meme_prob(each gets a freshMemeId+LineageOrigin::Inheritancenode). With 20% probability and a free inventory slot, fuseparents[i].inventory[0]andparents[j].inventory[0]viamutation::recombine(the child'skindis derived from its resultingeffect, then routed through conflict resolution so it can't violate the no-two-conflicting-memes invariant) →Event::Recombination. EmitEvent::Birth. -
death_phase(world.rs) — agents withage ≥ max_agedie (cause: Aging). Trust map entries decay by 1% per tick; entries with|trust| < 0.05are dropped. -
world_maintenance_phase(world.rs) — for each empty cell,SimRng::gen_bool(food.regrowth_rate)to spawn food. O(W·H) per tick. -
emit_metrics_phase(world.rs) — walk all living agents to compute per-tick aggregates:population_by_traitcounts,meme_count,carriers_by_kind[7](carriers, not instances, perdata-model.md),meme_prevalence_by_kind = carriers / alive,mean_energy,mean_age. Thendiversity_shannon = shannon_diversity(prevalence)anddominance_top1_fraction = top1_fraction(prevalence). EmitEvent::Tick(Box<Metrics>). Detect first-time population/meme extinction and emitEvent::Extinctionexactly once each. Everyrun.cluster_snapshot_everyticks, runcompute_clusters(Jaccard on inventory-kind sets, threshold0.6) →Event::ClusterSnapshot.
After phase 8, tick += 1 and the next call to step() repeats.
crates/meme-garden-core MUST NOT touch std::time, rand::thread_rng, environment variables, process IDs, or any other ambient nondeterminism. Every stochastic decision goes through rng::SimRng, which is owned by Simulation and constructed from the (config, seed) pair in Simulation::new. The CLI's runner::default_run_id is the only place a wall-clock dependency lives — it generates the run directory name before Simulation::new is called, so the timestamp never enters the metrics stream.
The contract has two tripwires:
crates/meme-garden-core/src/world.rs::tests::same_seed_same_metrics— pairedSimulationinstances with the same seed produce identical event JSON for 100 ticks.crates/meme-garden-core/tests/milestone.rs::milestone_direction_is_recorded— the cooperative-vs-selfish experiment's direction of survival under low/mid/high scarcity is stable across re-runs. If the simulator changes shape, this fails and the milestone outcome has to be reconfirmed deliberately.
Each run writes three files under runs/<YYYYMMDD-HHMMSS>-<short-name>/, created by crates/meme-garden-cli/src/export.rs::RunWriter:
config.toml— byte-identical resolved configuration (afterscarcity.apply_scarcity()), so the artifact is self-describing.events.jsonl— line-delimited JSON. First record is{"kind":"header","schema_version":1,"run_id":"...","core_version":"..."}. Subsequent records are tagged-unionEvents:tick,birth,death,transmission,mutation,recombination,meme_forgotten,extinction,cluster_snapshot.summary.csv— flat per-tick summary, header first row. Same first columns as the POC's CSV for backward-compatible tooling.
RunWriter::finalize flushes and fsyncs both files so a kill -9'd run leaves recoverable artifacts. The contract for the JSONL stream lives at specs/001-meme-garden-mvp/contracts/metrics.schema.md.
From .specify/memory/constitution.md:
- I. Determinism Is Sacred (NON-NEGOTIABLE) — every RNG flows through
SimRng; same(seed, config)⇒ bit-identical metrics. - II. Pure Core, Impure Edges —
meme-garden-corereads config but writes nothing; the CLI owns file I/O and the TUI. - III. Stable Iteration Order — agents are processed in
AgentIdorder;HashMapiteration is banned in the hot path. - IV. Symbolic Memes, Not Black Boxes — memes are bounded
{trigger, target, effect, strength, transmissibility, mutation_rate, cognitive_cost, lineage}; no LLM or arbitrary-code execution insideSimulation::step. - V. Metrics-First Experimentation — every behavioral claim must be answerable from the metrics stream, not from eyeballing the TUI.
PRs that touch the simulator MUST address each principle in the description (even if only to assert "no constitutional impact").
cargo test --workspace47 tests across 13 files. The load-bearing ones:
world::tests::same_seed_same_metrics— Principle I gate.crates/meme-garden-core/tests/milestone.rs::milestone_direction_is_recorded— survival-direction regression.crates/meme-garden-core/tests/tui_headless_equivalence.rs::drain_cadence_does_not_affect_outputs— proves the TUI cannot influence the metrics stream.crates/meme-garden-core/tests/lineage.rs::every_live_meme_traces_to_a_starter— lineage closure invariant.crates/meme-garden-core/tests/mutation.rs::mutated_memes_stay_in_enum_ranges— bounded-mutation invariant.
Tracked in specs/001-meme-garden-mvp/tasks.md and ## Open follow-ups of research.md:
- LLM-backed implementations of the three AI seams (in a future
meme-garden-aicrate;meme-garden-corestays HTTP-free). - A lineage-tree visualization pane in the TUI.
- A
sweepsubcommand that runs a parameter grid in one invocation. - A
replaysubcommand that re-renders a finished run fromevents.jsonlwithout re-running the simulator. - Mutation of
transmissibilityandmutation_ratethemselves (currently fixed per-meme, by design — keeps the milestone interpretable). - Connected-component cultural clusters (currently Jaccard threshold-based).
- Property-based fuzzing of the mutation operator (
proptest). - Removing the legacy-config adapter in
config.rs(deferred from MVP polish; seetasks.md::T080).
