ASSEMBLY-WP-0007-T03: endogenous selection weights (capture slow; robust capture/de-capture; no material VFAC effect); T04 opened as two-good demand split

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 14913@bnt-lap001
Assistant-Session: add7ef69-b901-4756-9a67-34cf1125d96f
This commit is contained in:
tegwick 2026-10-13 23:15:04 +02:00
parent 8dd9e97d33
commit 80055bc700
7 changed files with 261 additions and 2 deletions

View file

@ -41,6 +41,7 @@ cd simulations/gadm02 && python3 -B world_scenarios.py # contexts X1–X4, hyp
cd simulations/gadm02 && python3 -B exports.py # export channel and the "Argentina may thrive" hypothesis
cd simulations/gadm02 && python3 -B nonrival.py # GADM-0.5-dev rival vs. non-rival output across the band
cd simulations/gadm02 && python3 -B eco_classes.py # GADM-0.5-dev ecological resource classes
cd simulations/gadm02 && python3 -B selection.py # GADM-0.5-dev endogenous selection weights
cd simulations/gadm02 && python3 -B matching.py && python3 -B matching.py --imm # model matching; IMM flip-detection acceptance test (~6 min)
```

View file

@ -117,3 +117,51 @@ years.
it within the horizon. The effect is conservative: sink regeneration is
fitted so that each share starts at rest, so the lever acts only on the
growth of the load.
## 3. Endogenous selection weights (T03)
`gadm05.simulate_sel`: the five selection weights (market, science,
community, public, organism) relax at 0.05 per year toward targets pushed by
domination and civic capacity:
- **Domination:** market concentration Pm raises the market weight, and
discovery concentration Pd raises the organism weight (strength 2).
- **Civic capacity:** epistemic capacity Q raises science, and commons X
raise community (strength 1).
**Capture** is the share of selection weight held by the market and organism
channels. Results are in `selection.py` and `results/selection.md`.
| Reading (endogenous − fixed weights, t 100) | B1 | B2 | B3 | B4 | class |
|---|---|---|---|---|---|
| capture, attention oligopoly | +0.114 | +0.131 | +0.138 | +0.141 | robust |
| capture, baseline | +0.023 | +0.109 | +0.115 | +0.115 | quantitative shift |
| capture, generative regime | −0.240 | −0.211 | −0.189 | −0.161 | robust |
| ΔVFAC, any scenario | ≤ 0.002 in absolute value | | | | no effect |
Largest change in the weights themselves: 0.0044 per year.
Readings:
1. **Domination captures selection, and the generative regime de-captures
it.** Both are robust across the band. Capture is slow and incremental, so
the grounding test passes: no flip within a few years.
2. **Capture of selection is not a material channel for VFAC in GADM.** The
five quality signals are of similar size, so moving weight between them
hardly changes selection quality σ. A plural selection mix also does not
make the generative regime more stable against a domination shock.
3. **What would make it material:** captured channels that deliver
systematically worse quality. An example is organism selection under
discovery concentration, `O = Q (1 − disc)`, which falls only through the
lever `disc`, not through the discovery channel Pd. Linking O to Pd is the
candidate change. It is recorded here and not implemented, because no
reading depends on it yet.
## 4. Open: demand composition (T04, refined)
T01 found no genuine decoupling, because non-rival output only adds to rival
consumption (BR-010). T02 locates the generative regime's ecological cost in
the sinks. Both point to the same missing structure: a **two-good demand
split**, rival and non-rival consumption, with substitution between them
(dematerialization). This is narrower than the four-sector option in the scope
note, and it is the question that opens T04.

View file

@ -115,6 +115,11 @@ least on materials (−0.018). New priors to ground: the class starting levels,
the load shares, and material replenishment (reserve growth). Which class
binds next depends on the replenishment.
**T03 result (2026-10-13, `models/GADM-0.5.md` § 3):** test passed. The
largest change in the selection weights is 0.0044 per year, so capture is slow
and incremental. New priors to ground: the adjustment speed 0.05 per year and
the strengths k_cap 2 and k_civ 1.
## 4. Remaining open grounding needs
1. Commons investment `IX`: needs a mapping concept, not just data.

View file

@ -114,3 +114,58 @@ def simulate_eco(params: dict | None = None, init: dict | None = None, eco: dict
else min(1.0, max(0.0, s[k] + dt * d[k]))) for k in STOCKS_ECO}
s["E"] = eco_E(s)
return rows
# --- ASSEMBLY-WP-0007-T03: endogenous selection weights ----------------------------------------
# Selection quality sigma mixes five channels (market M, science Q, community N(1-P), public S,
# organism O) with weights wm, ws, wc, wp, wo. Here the weights are a slow state: each relaxes at
# rate sel_lam toward a target pushed by domination (market concentration Pm raises the market
# weight, discovery concentration Pd raises the organism weight) and by civic capacity (epistemic
# capacity Q raises science, commons X raise community). Scope: GADM-Sectors-Scope.md § 4.
SEL_PARAMS = {"sel_lam": 0.05, "k_cap": 2.0, "k_civ": 1.0}
WEIGHTS = ("wm", "ws", "wc", "wp", "wo")
def sel_targets(s: dict, w0: dict, s0: dict, q: dict) -> dict:
return {"wm": w0["wm"] * max(0.0, 1 + q["k_cap"] * (s["Pm"] - s0["Pm"])),
"wo": w0["wo"] * max(0.0, 1 + q["k_cap"] * (s["Pd"] - s0["Pd"])),
"ws": w0["ws"] * max(0.0, 1 + q["k_civ"] * (s["Q"] - s0["Q"])),
"wc": w0["wc"] * max(0.0, 1 + q["k_civ"] * (s["X"] - s0["X"])),
"wp": w0["wp"]}
def capture(w: dict, Ko: float) -> float:
"""Share of selection weight held by market and organism channels."""
wo = w["wo"] * Ko
return (w["wm"] + wo) / (w["wm"] + w["ws"] + w["wc"] + w["wp"] + wo)
def simulate_sel(params: dict | None = None, init: dict | None = None, sel: dict | None = None,
endogenous: bool = True, T: float = 100.0, dt: float = 0.1, record_every: int = 10) -> list[dict]:
"""GADM-0.5 with endogenous selection weights (fixed weights if endogenous=False)."""
q = {**SEL_PARAMS, **(sel or {})}
p = {**PARAMS, **(params or {})}
s = {**g4.INIT, **(init or {})}
if p["sent"] and p["sig_pb0"] is None:
_d, a0 = g4.derivatives(s, p)
p = {**p, "sig_pb0": g4.perceived_benefit(s, a0, g4.legislated(s, p))}
w0 = {k: p[k] for k in WEIGHTS}
w = dict(w0)
s0 = dict(s)
rows = []
steps = int(round(T / dt))
for i in range(steps + 1):
d, a = g4.derivatives(s, {**p, **w})
if i % record_every == 0:
row = {"t": round(i * dt, 6), **s, "P": g4.g3.total_p(s), "G": a["Gagg"],
**{k: a[k] for k in ("H", "A", "Ystar", "dem", "B", "V", "Vsel", "sigma")},
**w, "capture": capture(w, s["Ko"])}
row["VFAC"] = g4.g2.vfac(row)
rows.append(row)
if i < steps:
if endogenous:
tg = sel_targets(s, w0, s0, q)
w = {k: max(0.0, w[k] + dt * q["sel_lam"] * (tg[k] - w[k])) for k in WEIGHTS}
s = {k: (max(0.0, s[k] + dt * d[k]) if k.startswith("N")
else min(1.0, max(0.0, s[k] + dt * d[k]))) for k in g4.STOCKS}
return rows

View file

@ -0,0 +1,41 @@
# GADM-0.5-dev: endogenous selection weights across the band
Generated by `simulations/gadm02/selection.py` (ASSEMBLY-WP-0007-T03). Priors: weights relax at
0.05/year; domination strength 2.0, civic strength 1.0. Capture = share of selection weight held by market + organism channels.
Shock = {'Pd': 0.25, 'Pm': 0.15} added to the starting state. t = 100.
| Scenario | Anchor | VFAC fixed → endogenous | σ fixed → endogenous | capture t0 → t100 (fixed weights at t100) | max endogenous capture change/yr | shock VFAC loss fixed / endogenous |
|---|---|---|---|---|---|---|
| Baseline (current trends) | B1 | 0.411 → 0.411 | 0.418 → 0.426 | 0.42 → 0.45 (0.43) | 0.0021 | +0.003 / +0.003 |
| Baseline (current trends) | B2 | 0.353 → 0.353 | 0.366 → 0.389 | 0.42 → 0.55 (0.44) | 0.0020 | -0.000 / -0.000 |
| Baseline (current trends) | B3 | 0.337 → 0.337 | 0.326 → 0.341 | 0.42 → 0.60 (0.48) | 0.0023 | +0.000 / +0.000 |
| Baseline (current trends) | B4 | 0.311 → 0.311 | 0.284 → 0.290 | 0.42 → 0.64 (0.53) | 0.0027 | +0.000 / +0.000 |
| Attention oligopoly | B1 | 0.338 → 0.339 | 0.357 → 0.380 | 0.42 → 0.54 (0.42) | 0.0017 | +0.000 / +0.000 |
| Attention oligopoly | B2 | 0.302 → 0.302 | 0.311 → 0.326 | 0.42 → 0.57 (0.44) | 0.0020 | +0.000 / +0.000 |
| Attention oligopoly | B3 | 0.289 → 0.289 | 0.264 → 0.264 | 0.42 → 0.61 (0.47) | 0.0026 | +0.000 / -0.000 |
| Attention oligopoly | B4 | 0.265 → 0.266 | 0.212 → 0.201 | 0.42 → 0.66 (0.52) | 0.0035 | +0.000 / -0.000 |
| Generative regime, concentrated lineages | B1 | 0.528 → 0.529 | 0.530 → 0.465 | 0.42 → 0.17 (0.44) | 0.0041 | -0.001 / -0.000 |
| Generative regime, concentrated lineages | B2 | 0.474 → 0.475 | 0.479 → 0.474 | 0.42 → 0.38 (0.47) | 0.0036 | -0.004 / -0.003 |
| Generative regime, concentrated lineages | B3 | 0.426 → 0.426 | 0.439 → 0.455 | 0.42 → 0.48 (0.51) | 0.0031 | -0.000 / -0.000 |
| Generative regime, concentrated lineages | B4 | 0.389 → 0.390 | 0.414 → 0.424 | 0.42 → 0.51 (0.55) | 0.0027 | -0.000 / -0.000 |
| Generative regime | B1 | 0.685 → 0.685 | 0.660 → 0.690 | 0.42 → 0.23 (0.47) | 0.0044 | -0.000 / -0.000 |
| Generative regime | B2 | 0.661 → 0.659 | 0.612 → 0.652 | 0.42 → 0.29 (0.50) | 0.0043 | -0.000 / -0.001 |
| Generative regime | B3 | 0.641 → 0.639 | 0.579 → 0.618 | 0.42 → 0.34 (0.53) | 0.0040 | -0.000 / -0.001 |
| Generative regime | B4 | 0.577 → 0.577 | 0.507 → 0.521 | 0.42 → 0.39 (0.55) | 0.0038 | -0.000 / -0.000 |
## Readings and robustness class
| Reading | B1 | B2 | B3 | B4 | class |
|---|---|---|---|---|---|
| endogenous capture at t100 (endogenous − fixed weights): Baseline (current trends) | +0.023 | +0.109 | +0.115 | +0.115 | quantitative shift |
| ΔVFAC endogenous − fixed: Baseline (current trends) | +0.000 | +0.000 | -0.000 | -0.000 | QUALITATIVE BREAK |
| endogenous capture at t100 (endogenous − fixed weights): Attention oligopoly | +0.114 | +0.131 | +0.138 | +0.141 | robust |
| ΔVFAC endogenous − fixed: Attention oligopoly | +0.000 | +0.000 | +0.000 | +0.000 | quantitative shift |
| endogenous capture at t100 (endogenous − fixed weights): Generative regime, concentrated lineages | -0.265 | -0.092 | -0.028 | -0.034 | quantitative shift |
| ΔVFAC endogenous − fixed: Generative regime, concentrated lineages | +0.001 | +0.000 | -0.000 | +0.000 | QUALITATIVE BREAK |
| endogenous capture at t100 (endogenous − fixed weights): Generative regime | -0.240 | -0.211 | -0.189 | -0.161 | robust |
| ΔVFAC endogenous − fixed: Generative regime | -0.001 | -0.001 | -0.002 | -0.000 | quantitative shift |
| shock loss difference (endogenous − fixed), generative regime | -0.000 | -0.000 | -0.000 | -0.000 | quantitative shift |
Largest change of the endogenous part of capture in any run: 0.0044 per year (slow and incremental; grounding test: no flip within a few years).
Rows with ΔVFAC of ±0.000 are classified by sign noise; read them as 'no effect'.

View file

@ -0,0 +1,89 @@
"""Endogenous selection weights across the adoption band (ASSEMBLY-WP-0007-T03).
Tests (GADM-Sectors-Scope.md § 4; ParameterGrounding.md § 3):
1. Does domination capture selection itself (market + organism weight share rising with P)?
2. Is capture slow and incremental (no flip of the weights within a few years)?
3. Does a plural selection mix make the generative regime more stable against a
domination shock?
Usage: python3 -B simulations/gadm02/selection.py
"""
from __future__ import annotations
import sys
from pathlib import Path
sys.dont_write_bytecode = True
import band_probe as bp # noqa: E402
import gadm05 as g5 # noqa: E402
import grounding as gr # noqa: E402
from scenarios import load_all # noqa: E402
OUT = Path(__file__).resolve().parent / "results"
ANCHORS = list(gr.BAND)
SCEN = ("baseline", "attention_oligopoly", "generative_concentrated_lineages", "generative_regime")
SHOCK = {"Pd": 0.25, "Pm": 0.15} # domination shock added to the starting state
def max_rate(en: list[dict], fx: list[dict]) -> float:
"""Fastest change of the *endogenous part* of capture (endogenous minus fixed weights) per year.
Capture itself also moves with K_Omega (organism weight x K_Omega), even with fixed weights."""
diff = [(e["t"], e["capture"] - f["capture"]) for e, f in zip(en, fx)]
return max(abs(b - a) / (tb - ta) for (ta, a), (tb, b) in zip(diff, diff[1:]))
def main() -> None:
OUT.mkdir(exist_ok=True)
scs = load_all()
var = {a: gr.variant(gr.BAND[a]) for a in ANCHORS}
res = {}
for a in ANCHORS:
for sc in SCEN:
p = {**scs[sc]["params"], **var[a][0]}
i0 = {**scs[sc]["init"], **var[a][1]}
fx = g5.simulate_sel(p, i0, endogenous=False)
en = g5.simulate_sel(p, i0, endogenous=True)
shock = {k: min(1.0, i0.get(k, g5.g4.INIT[k]) + v) for k, v in SHOCK.items()}
fxs = g5.simulate_sel(p, {**i0, **shock}, endogenous=False)
ens = g5.simulate_sel(p, {**i0, **shock}, endogenous=True)
res[(a, sc)] = {"Vfx": fx[-1]["VFAC"], "Ven": en[-1]["VFAC"], "c0": en[0]["capture"],
"c100": en[-1]["capture"], "rate": max_rate(en, fx),
"cfx100": fx[-1]["capture"],
"loss_fx": fxs[-1]["VFAC"] - fx[-1]["VFAC"], "loss_en": ens[-1]["VFAC"] - en[-1]["VFAC"],
"sig_fx": fx[-1]["sigma"], "sig_en": en[-1]["sigma"]}
lines = ["# GADM-0.5-dev: endogenous selection weights across the band", "",
"Generated by `simulations/gadm02/selection.py` (ASSEMBLY-WP-0007-T03). Priors: weights relax at",
f"{g5.SEL_PARAMS['sel_lam']}/year; domination strength {g5.SEL_PARAMS['k_cap']}, civic strength "
f"{g5.SEL_PARAMS['k_civ']}. Capture = share of selection weight held by market + organism channels.",
f"Shock = {SHOCK} added to the starting state. t = 100.", "",
"| Scenario | Anchor | VFAC fixed → endogenous | σ fixed → endogenous | capture t0 → t100 (fixed weights at t100) | max endogenous capture change/yr | shock VFAC loss fixed / endogenous |",
"|---|---|---|---|---|---|---|"]
for sc in SCEN:
for a in ANCHORS:
r = res[(a, sc)]
lines.append(f"| {scs[sc]['title']} | {a} | {r['Vfx']:.3f} → {r['Ven']:.3f} | {r['sig_fx']:.3f} → "
f"{r['sig_en']:.3f} | {r['c0']:.2f} → {r['c100']:.2f} ({r['cfx100']:.2f}) | {r['rate']:.4f} | "
f"{r['loss_fx']:+.3f} / {r['loss_en']:+.3f} |")
readings = []
for sc in SCEN:
readings.append((f"endogenous capture at t100 (endogenous − fixed weights): {scs[sc]['title']}",
[res[(a, sc)]["c100"] - res[(a, sc)]["cfx100"] for a in ANCHORS]))
readings.append((f"ΔVFAC endogenous − fixed: {scs[sc]['title']}", [res[(a, sc)]["Ven"] - res[(a, sc)]["Vfx"] for a in ANCHORS]))
readings.append(("shock loss difference (endogenous − fixed), generative regime",
[res[(a, 'generative_regime')]["loss_en"] - res[(a, 'generative_regime')]["loss_fx"] for a in ANCHORS]))
lines += ["", "## Readings and robustness class", "", "| Reading | " + " | ".join(ANCHORS) + " | class |",
"|---|" + "---|" * (len(ANCHORS) + 1)]
for name, vals in readings:
lines.append(f"| {name} | " + " | ".join(f"{v:+.3f}" for v in vals) + f" | {bp.classify(vals, 'sign')} |")
rate = max(r["rate"] for r in res.values())
lines += ["", f"Largest change of the endogenous part of capture in any run: {rate:.4f} per year "
f"({'slow and incremental' if rate < 0.02 else 'FAST'}; grounding test: no flip within a few years).",
"Rows with ΔVFAC of ±0.000 are classified by sign noise; read them as 'no effect'.", ""]
(OUT / "selection.md").write_text("\n".join(lines))
print("\n".join(lines))
if __name__ == "__main__":
main()

View file

@ -67,11 +67,16 @@ floor. Find which class binds first per scenario (scoping note § 2).
```task
id: ASSEMBLY-WP-0007-T03
status: todo
status: done
priority: medium
state_hub_task_id: "e58601b5-ca84-59c5-8022-8dcfd084728a"
```
**Done 2026-10-13:** `gadm05.simulate_sel`, `selection.py`, `models/GADM-0.5.md`
§ 3. Domination captures selection (attention oligopoly +0.11 to +0.14), and
the generative regime de-captures it (−0.16 to −0.24); both robust. Capture is
slow (grounding test passed) and has no material effect on VFAC.
Make the selection-channel weights a slow state pushed by domination channels
and governance levers. Test capture of selection (scoping note § 4).
@ -79,10 +84,25 @@ and governance levers. Test capture of selection (scoping note § 4).
```task
id: ASSEMBLY-WP-0007-T04
status: wait
status: todo
priority: low
state_hub_task_id: "c3dbbe24-4378-55cf-91c1-9e2788646728"
```
Only if T01–T03 raise a question that needs sector composition (scoping
note § 3).
**Opened 2026-10-13, refined:** T01 and T02 raise one such question. Genuine
decoupling needs non-rival consumption to *replace* rival consumption (BR-010),
and the generative regime's ecological cost sits in the sinks. Implement a
**two-good demand split**: rival and non-rival consumption with a substitution
elasticity and a preference shift driven by non-rival quality (X, Q). Do not
implement the full four-sector model. Tests:
1. Can VFAC rise while physical throughput falls, for reasons other than
organism decline?
2. Does the generative regime's sink breach move out of the 100-year
horizon?
Ground the elasticity in dematerialization evidence, for example the material
footprint per unit of GDP over time (Eurostat env_ac_rme, UNEP IRP).