diff --git a/CMakeLists.txt b/CMakeLists.txt
index 45dc97d..3e1cde3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -72,6 +72,14 @@ target_link_libraries(flux_sweep PRIVATE adsc_core adsc_flags)
add_executable(decay_trade src/main_decay_trade.cpp)
target_link_libraries(decay_trade PRIVATE adsc_core adsc_flags)
+# WP13 kit-class trade CSV emitter: regenerates generated/wp13_kit_trade.{csv,md}
+# (sail/EDT/hybrid per-class A/B/C trade) and generated/wp13_classC.{csv,md}
+# (controlled-reentry mission-class comparison). Reuses src/decay.cpp's
+# existing sail model plus the new EDT-v1 physics core; no other physics
+# change.
+add_executable(kit_trade src/main_kit_trade.cpp)
+target_link_libraries(kit_trade PRIVATE adsc_core adsc_flags)
+
# WP7 reference-metrics emitter: re-computes the pinned WP1/F1/WP2/WP3/WP4
# demo numbers as generated/reference_metrics.csv so the evidence pack quotes
# committed data only (R6). Reuses existing code paths; no physics change.
diff --git a/README.md b/README.md
index 20c86fa..acd03a1 100644
--- a/README.md
+++ b/README.md
@@ -488,13 +488,20 @@ adsc-specification-v4.md retired v4.2 spec (superseded by v5; kept because v5
keep-out violations re-verified at L1/L2 [ds-v1/ds-v2]; the
CW-safe-but-higher-level-unsafe case does NOT materialize for this
dispersion set — measured, see `generated/wp12_ladder.md`).
-- **WP13–WP15 (v5)** — not started; `adsc-specification-v5.md` defines the
- kit-class trade + EDT physics, cost ranges + FoM, and the proposal package.
- WP9 remains the only path to TRL 5, unchanged by v5.
+- **WP13 — Kit-class trade + EDT physics** ✅ implemented (aligned-dipole
+ EDT-v1 band model with mandatory η(i) = |cos i|..cos²i — derivation:
+ `wp13-edt-derivation.md`, citations: `wp13-literature.md`; per-class
+ recommended-kit table `generated/wp13_kit_trade.csv`; class-C
+ controlled-reentry comparison `generated/wp13_classC.md`; libration T7
+ explicitly **unresolved**; SL-16 EDT is a *candidate with open risks*,
+ not a closed recommendation).
+- **WP14–WP15 (v5)** — not started; `adsc-specification-v5.md` defines the
+ cost ranges + FoM upgrade and the proposal package. WP9 remains the only
+ path to TRL 5, unchanged by v5.
**Roadmap end state: WP1–WP8 complete; Phase 0 (WP10), WP11 safety
-hardening, and WP12 fidelity ladder complete.** The package is regenerable
-end-to-end with one command
+hardening, WP12 fidelity ladder, and WP13 kit-class trade + EDT physics
+complete.** The package is regenerable end-to-end with one command
(`bash tools/regenerate_all.sh build`) and CI enforces byte-identity of every
committed artifact on every push.
@@ -528,8 +535,13 @@ handoff (range = solar max .. solar min) makes the honest structure concrete:
Sail-only closes for the lighter/lower class B (tens of m²) but **not** for the
heavy, high stage A (hundreds-to-thousands of m² — impractical): the honest
negative that brackets open trade T1 (drag sail vs electrodynamic tether). The
-EDT parametric study spans, e.g., 36.1 yr at 50 m/day down to 1.2 yr at
-1500 m/day of along-track decay. (The 25-year column is the IADC guideline;
+legacy EDT *parametric* study spans, e.g., 36.1 yr at 50 m/day down to 1.2 yr at
+1500 m/day of along-track decay; since WP13 the EDT branch also has a *physics*
+band (aligned-dipole, capped-current, mandatory η(i) = |cos i|..cos²i): the SL-16
+class works out at **2.98 .. 9.17 yr** with a 3 km / 2 A (PLACEHOLDER) tether —
+an **EDT candidate with open risks** (libration T7 unresolved, plasma Ne pair
+PLACEHOLDER), *not* a closed recommendation — see `generated/wp13_kit_trade.csv`
+and `wp13-edt-derivation.md`. (The 25-year column is the IADC guideline;
under a US FCC license the current rule is **5 years** — FCC 22-74, 2022 — which
makes the class-A sail-only negative strictly harder. See the WP8 rulepacks.)
diff --git a/evidence/adsc_evidence_pack.md b/evidence/adsc_evidence_pack.md
index 4ccc778..3e32b15 100644
--- a/evidence/adsc_evidence_pack.md
+++ b/evidence/adsc_evidence_pack.md
@@ -263,6 +263,60 @@ per-orbit table for both geometries is in `generated/wp12_ladder.md`.
with a deliberately coarse altitude-independent solar factor marked
PLACEHOLDER.
+### Kit-class trade + EDT physics (WP13)
+
+Data source: `generated/wp13_kit_trade.csv` (schema 1.0; column
+definitions in `generated/wp13_kit_trade_schema.md`), regenerated by
+`kit_trade` (src/main_kit_trade.cpp) by combining the EXISTING WP3
+sail-decay model with the NEW WP13 EDT-v1 aligned-dipole physics core
+(src/decay.cpp: edt_deorbit_years) -- no hand-written numbers.
+
+| catalog | recommended kit | sail area, 25-yr [m^2] | EDT deorbit time [yr] | EDT eta(i) band |
+|---|---|---:|---:|---:|
+| SL-16 / Zenit-2 second stage | edt-candidate-open-risks | 135..2155 | 3.0..9.2 | 0.106..0.326 |
+| SL-8 / Kosmos-3M second stage | sail | 7..115 | 0.6..3.0 | 0.043..0.208 |
+| Envisat-class massive SSO payload | controlled-reentry-mission-class | 61..983 | 5.5..37.5 | 0.021..0.146 |
+
+**EDT-v1 model scope (R14 fidelity tag), quoted verbatim from the
+committed CSV -- never hand-transcribed, so the generator and the data
+cannot drift:**
+
+> [model: EDT-v1 aligned-dipole, capped-current; eta band |cos i|..cos^2 i; libration T7 OPEN - PLACEHOLDER duty factor]
+
+**Honesty check (T7, D12/D13 - never claim closed).** Libration /
+dynamic tether stability is explicitly UNRESOLVED (T7; Pelaez, J., et
+al., 2000, cited in `wp13-literature.md`); the edt_years band above
+folds it in only as a flat PLACEHOLDER duty-cycle knob
+(`EdtConfig::eta_libration`), never claimed solved. Plasma electron
+density is a cited PLACEHOLDER solar-min/max pair, not an IRI
+implementation (spec non-goal; `wp13-literature.md` Topic 3 citation
+gap). Every band above is an aligned-dipole idealization (SPENVIS
+centred dipole, IGRF epoch 2000), not a tilted-dipole or full-IGRF
+field. Deploy risk is reported, not applied: deploy_risk = 0.0500 [-]
+(PLACEHOLDER `EdtConfig::deploy_failure_prob`) is quoted for the
+record and is NOT folded into the edt_years band above. Bottom line,
+EDT is a candidate for the SL-16 class, not a closed recommendation --
+quoted verbatim from the committed CSV's own recommended-kit
+rationale: "sail infeasible (135..2155 m2 for the 25-yr guideline, solar max..min); EDT (band 3.0..9.2 yr) subject to T7; libration (T7, Pelaez et al. 2000) and plasma Ne (PLACEHOLDER solar-min/max pair, wp13-literature.md Topic 3) remain open -- an honest EDT candidate, not a closed recommendation"
+
+**Class C (controlled reentry, a separate mission class - not a
+sail/EDT kit choice).** Massive/high-risk derelicts where the
+deliverable is a controlled reentry, evaluated on casualty risk,
+ground footprint, delta-v, consent and cost in
+`generated/wp13_classC.md` (data: `generated/wp13_classC.csv`, schema
+1.0), not on a sail/EDT kit trade. Computed controlled-deorbit delta-v
+(single impulsive perigee-lowering burn from the catalog's circular
+orbit to a target perigee of 6371+40 km):
+
+| candidate | v_c [m/s] | delta-v [m/s] |
+|---|---:|---:|
+| Zenit-2 stage (catalog_A) | 7431.2 | 223.4 |
+| Envisat-class (catalog_C) | 7462.2 | 208.3 |
+
+Cross-reference: the eta(i) closed-form derivation is
+`wp13-edt-derivation.md`; the citation pack is `wp13-literature.md`
+(both committed at the repo root).
+
## 5. Campaign statistics and cost/FoM
WP5 Monte Carlo: 500 dispersed missions per catalog at fixed master seed
@@ -422,7 +476,7 @@ in `include/`, `src/` and `tools/` carrying the uppercase PLACEHOLDER mark
only in order to collect and audit it). If a value is listed here, treat
it as unvalidated until a cited source replaces it.
-Total marks: **113**
+Total marks: **130**
| location | line |
|---|---|
@@ -476,8 +530,11 @@ Total marks: **113**
| `include/adsc/cost.hpp:86` | double cu_to_musd_high = 0.0; // PLACEHOLDER (cited range, WP7) |
| `include/adsc/cost.hpp:88` | // Normalized congestion-weight table (PLACEHOLDER; cite on fill). Peaks |
| `include/adsc/cost.hpp:101` | // Launch band factor for a target band (PLACEHOLDER model). |
-| `include/adsc/decay.hpp:33` | DebrisCatalog catalog_C(); // CZ upper-stage class (PLACEHOLDER) |
| `include/adsc/decay.hpp:34` | DebrisCatalog catalog_D(); // US Delta-class stage (PLACEHOLDER) |
+| `include/adsc/decay.hpp:111` | double kit_mass_kg = 20.0; // PLACEHOLDER EDT kit mass (tether + deployer + electron... |
+| `include/adsc/decay.hpp:112` | double avg_current_a = 2.0; // PLACEHOLDER average bare-tether collected current, BET... |
+| `include/adsc/decay.hpp:113` | double eta_libration = 0.75; // PLACEHOLDER libration/duty-cycle efficiency penalty [-... |
+| `include/adsc/decay.hpp:114` | double deploy_failure_prob = 0.05; // PLACEHOLDER deployment-failure probability [-] (report... |
| `include/adsc/flux.hpp:21` | // them. All debris-population figures are PLACEHOLDER, marked below and to be |
| `include/adsc/flux.hpp:26` | // PLACEHOLDER-marked flux parameters (R10); the physical constants (aluminium |
| `include/adsc/flux.hpp:34` | // Spatial number density of >=1 cm debris [objects / km^3]. PLACEHOLDER -- |
@@ -521,12 +578,26 @@ Total marks: **113**
| `src/cost.cpp:448` | "/ notes / provenance / PLACEHOLDER caveats /\n" |
| `src/cost.cpp:460` | " sum m_i*w(h_i)/C_campaign (spec ?4). The two weightings are PLACEHOLDER and\n" |
| `src/cost.cpp:467` | "- **currency_anchor** (global): `cu_to_musd_range` -- a PLACEHOLDER cited\n" |
-| `src/decay.cpp:63` | return {"CZ upper stage (PLACEHOLDER)", 0.0, 0.0, 0.0, true}; |
-| `src/decay.cpp:66` | return {"US Delta-class stage (PLACEHOLDER)", 0.0, 0.0, 0.0, true}; |
+| `src/decay.cpp:83` | return {"US Delta-class stage (PLACEHOLDER)", 0.0, 0.0, 0.0, true}; |
| `src/flux.cpp:81` | "figures are PLACEHOLDER (MASTER-8 / ESA spatial-density class; cite at " |
| `src/flux.cpp:94` | std::fprintf(f, "PLACEHOLDER spatial densities: average %.1e /km^3, peak " |
| `src/main.cpp:137` | std::printf(" target inertia diag: %.2f / %.2f / %.2f kg m^2 (PLACEHOLDER ratios)\n", |
| `src/main_flux.cpp:33` | std::printf("\n[T6] collector exposure (>= 1 cm; PLACEHOLDER densities, cite MASTER-8)\n"); |
+| `src/main_kit_trade.cpp:38` | "/cos i/..cos^2 i; libration T7 OPEN - PLACEHOLDER duty factor]"; |
+| `src/main_kit_trade.cpp:117` | // kit_mass (edt): NEW EdtConfig::kit_mass_kg, PLACEHOLDER. |
+| `src/main_kit_trade.cpp:120` | "\"installed EDT kit mass, PLACEHOLDER (EdtConfig::kit_mass_kg)\"\n", |
+| `src/main_kit_trade.cpp:124` | // deploy_risk: EdtConfig::deploy_failure_prob, PLACEHOLDER. Reported |
+| `src/main_kit_trade.cpp:128` | "\"PLACEHOLDER EDT deployment-failure probability " |
+| `src/main_kit_trade.cpp:203` | " `EdtConfig::kit_mass_kg` (PLACEHOLDER). value = value_lo = value_hi (a\n" |
+| `src/main_kit_trade.cpp:205` | "- `deploy_risk`: `EdtConfig::deploy_failure_prob`, PLACEHOLDER. Reported\n" |
+| `src/main_kit_trade.cpp:219` | "libration T7 OPEN - PLACEHOLDER duty factor]`. This states the model scope\n" |
+| `src/main_kit_trade.cpp:228` | "PLACEHOLDER duty-cycle knob (`EdtConfig::eta_libration`) -- never claimed\n" |
+| `src/main_kit_trade.cpp:229` | "solved. Plasma electron density is a cited PLACEHOLDER parameter, not an\n" |
+| `src/main_kit_trade.cpp:366` | "**[CITATION NEEDED - PLACEHOLDER: per-object Ec analysis]**. Both " |
+| `src/main_kit_trade.cpp:427` | std::fprintf(f, "/ Zenit-2 / SL-16 (catalog_A) / PLACEHOLDER until WP14 / WP14 cost-range ite... |
+| `src/main_kit_trade.cpp:428` | std::fprintf(f, "/ Envisat-class (catalog_C) / PLACEHOLDER until WP14 / WP14 cost-range itemi... |
+| `src/main_kit_trade.cpp:466` | // open risks (T7 libration, plasma Ne PLACEHOLDER). ------------------- |
+| `src/main_kit_trade.cpp:479` | "(T7, Pelaez et al. 2000) and plasma Ne (PLACEHOLDER solar-min/" |
| `src/main_ladder.cpp:75` | // PLACEHOLDER bare-stage drag cross-sections (WP12 L2). Kept local to this |
| `src/main_ladder.cpp:78` | constexpr double kSL16AreaM2 = 33.0; // PLACEHOLDER SL-16 / Zenit-2 bare-stage cross-section... |
| `src/main_ladder.cpp:79` | constexpr double kSL8AreaM2 = 7.5; // PLACEHOLDER SL-8 / Kosmos-3M bare-stage cross-sectio... |
diff --git a/generated/viz/wp3_decay_trade.svg b/generated/viz/wp3_decay_trade.svg
index 4043fb4..43f34a5 100644
--- a/generated/viz/wp3_decay_trade.svg
+++ b/generated/viz/wp3_decay_trade.svg
@@ -1 +1 @@
-
+
diff --git a/generated/wp13_classC.csv b/generated/wp13_classC.csv
new file mode 100644
index 0000000..a293fee
--- /dev/null
+++ b/generated/wp13_classC.csv
@@ -0,0 +1,7 @@
+schema_version,candidate,mass_kg,altitude_km,inclination_deg,metric,value,units,notes
+1.0,Zenit-2 stage (catalog_A),9000.0,840.0,71.0,v_circular_initial,7431.1551,m/s,"circular-orbit speed at the catalog altitude, v_c = sqrt(mu/r_a)"
+1.0,Zenit-2 stage (catalog_A),9000.0,840.0,71.0,target_perigee_radius,6411.0,km,"controlled-deorbit target perigee, mean-Earth-radius convention 6371+40 km (distinct from the WGS-84 kEarthRadius used for r_a)"
+1.0,Zenit-2 stage (catalog_A),9000.0,840.0,71.0,controlled_deorbit_dv,223.3998,m/s,"impulsive perigee-lowering burn from circular (RE+alt) to perigee 6371+40 km: dv = v_c*(1-sqrt(2*r_p/(r_a+r_p)))"
+1.0,Envisat-class (catalog_C),8211.0,780.0,98.4,v_circular_initial,7462.2344,m/s,"circular-orbit speed at the catalog altitude, v_c = sqrt(mu/r_a)"
+1.0,Envisat-class (catalog_C),8211.0,780.0,98.4,target_perigee_radius,6411.0,km,"controlled-deorbit target perigee, mean-Earth-radius convention 6371+40 km (distinct from the WGS-84 kEarthRadius used for r_a)"
+1.0,Envisat-class (catalog_C),8211.0,780.0,98.4,controlled_deorbit_dv,208.3495,m/s,"impulsive perigee-lowering burn from circular (RE+alt) to perigee 6371+40 km: dv = v_c*(1-sqrt(2*r_p/(r_a+r_p)))"
diff --git a/generated/wp13_classC.md b/generated/wp13_classC.md
new file mode 100644
index 0000000..7cf0769
--- /dev/null
+++ b/generated/wp13_classC.md
@@ -0,0 +1,50 @@
+# WP13 Class-C controlled-reentry mission-class comparison
+
+Class C (spec WP13) is a **separate mission class**, not a sail/EDT kit choice: massive/high-risk derelicts where the deliverable is a controlled reentry, evaluated on casualty risk, ground footprint, delta-v, consent, and cost. The comparison against tug architectures is allowed **here only**, and only on the **same target set** (spec WP13/WP14, instr Sec 0.4). Data source: `generated/wp13_classC.csv` (schema 1.0), regenerated by `kit_trade` -- no hand-written numbers.
+
+## Candidates (same target set)
+
+| candidate | mass [kg] | altitude [km] | inclination [deg] | source |
+|---|---:|---:|---:|---|
+| Zenit-2 / SL-16 second stage (`catalog_A`) | 9000 | 840 | 71.0 | web-verified ~8300(-9000) kg, ~845x857 km, 71.01 deg (Tselina-2 family), `wp13-literature.md` Topic 5a; also class A's sail/EDT anchor -- massive by the same McKnight et al. 2021 top-50 ranking that motivates class C |
+| Envisat-class massive SSO payload (`catalog_C`) | 8211 | 780 | 98.4 | web-verified 8211 kg, ~765-800 km sun-synchronous, ~98.4-98.55 deg, eoPortal / ESA Earth Online, `wp13-literature.md` Topic 6 item 1 |
+
+## Uncontrolled-reentry casualty framing (cited, not computed)
+
+NASA-STD-8719.14A (with Change 1), Requirement 4.7-1: the expected worldwide human-casualty risk from reentering debris shall not exceed **Ec < 1e-4** (1 in 10,000); casualty is assumed for any surviving fragment with impact kinetic energy **> 15 J**; a controlled reentry must additionally ensure no surviving >15 J fragment lands within **370 km of foreign landmasses** (soma.larc.nasa.gov/SIMPLEx/pdf_files/871914.pdf; ESA applies the same 1e-4 limit via DRAMA/SARA, technology.esa.int/page/re-entry-safety -- both web-verified, `wp13-literature.md` Topic 7). This repo does **not** compute a per-object Ec for either candidate: that requires a survivability / ground-footprint tool (NASA DAS/ORSAT or ESA SARA class), out of scope here: **[CITATION NEEDED - PLACEHOLDER: per-object Ec analysis]**. Both candidates are multi-tonne, so an UNCONTROLLED reentry is the presumptive risk driver the 370 km / Ec<1e-4 rule exists for; that presumption is not itself a computed Ec value.
+
+## Controlled-deorbit delta-v (COMPUTED)
+
+Single impulsive perigee-lowering burn from the catalog's circular orbit (radius r_a = R_E + altitude) to a target perigee r_p = 6371 + 40 km (mean-Earth-radius convention for the reentry perigee, distinct from the WGS-84 kEarthRadius used for r_a elsewhere in this repo -- a deliberate, documented choice, not a silent inconsistency): dv = v_c * (1 - sqrt(2 r_p / (r_a + r_p))), v_c = sqrt(mu / r_a). This is the minimum single-burn delta-v to commit the stage to reentry; it excludes targeting/footprint-control burns and any margin. Full precision in `generated/wp13_classC.csv`.
+
+| candidate | v_c [m/s] | dv [m/s] |
+|---|---:|---:|
+| Zenit-2 / SL-16 (catalog_A) | 7431.2 | **223.4** |
+| Envisat-class (catalog_C) | 7462.2 | **208.3** |
+
+## Consent gate
+
+Legal accessibility enters as a **gate and metadata flags, never a multiplier** (D12): the WP8 compliance engine BLOCKs unconsented active debris removal, and target prioritization consumes its PASS/BLOCK output and flags rather than folding a subjective legal weight into any score (`tools/compliance/check_compliance.py`, policy `ADSC-POL-01`, consent-missing -> BLOCK). Any controlled-reentry mission on either candidate above is subject to that same gate: no consent, no operation, regardless of the casualty-risk or delta-v numbers on this page.
+
+## Kit-installer vs tug (same target set)
+
+Steelmanned: a tug architecture also amortizes fixed costs (launch, ground segment, ops) across a batch of targets visited in one plane, exactly like the kit-installer's own batch amortization (WP6 amortization curve). The comparison is fair only because it is the SAME target set (spec WP14 rule) -- comparing a tug against a different, easier target set would be meaningless. What does NOT amortize for a tug, and is the installer's structural edge (D1: installer-not-tug): (1) a tug must carry propellant to change EACH target's orbit itself, so its per-target delta-v cost scales with target count, while an installer's kit-carrying stages do their own deorbit after release -- the installer pays no per-target deorbit delta-v; (2) a tug must detumble, dock/grapple, and physically tow each multi-tonne stage through its own controlled-reentry burn, repeating the highest-risk contact event per target, while an installer's own contact event is bounded by the WP3 low-energy capture approach (0.333 J contact-energy budget at 0.15 m/s, README/evidence pack Section on WP3) and does not itself carry a target through reentry. Both architectures still need SOME per-target consent/compliance gate (D12) and SOME reentry delta-v paid by someone (either pre-installed as a kit, or delivered by the tug) -- the installer's edge is where that delta-v and that repeated high-risk contact event are paid, not that they vanish.
+
+## Cost
+
+| candidate | cost | source |
+|---|---|---|
+| Zenit-2 / SL-16 (catalog_A) | PLACEHOLDER until WP14 | WP14 cost-range itemization |
+| Envisat-class (catalog_C) | PLACEHOLDER until WP14 | WP14 cost-range itemization |
+
+## `wp13_classC.csv` columns
+
+| column | meaning |
+|---|---|
+| schema_version | class-C schema id (`1.0`) |
+| candidate | candidate label (Zenit-2 stage / Envisat-class) |
+| mass_kg, altitude_km, inclination_deg | catalog parameters (constant per candidate) |
+| metric | v_circular_initial / target_perigee_radius / controlled_deorbit_dv |
+| value | metric value at full precision (the md table above rounds to 0.1) |
+| units | m/s or km |
+| notes | formula / provenance |
diff --git a/generated/wp13_kit_trade.csv b/generated/wp13_kit_trade.csv
new file mode 100644
index 0000000..8347a17
--- /dev/null
+++ b/generated/wp13_kit_trade.csv
@@ -0,0 +1,25 @@
+schema_version,catalog,mass_kg,altitude_km,inclination_deg,record_type,kit_option,value,value_lo,value_hi,units,notes
+1.0,SL-16 / Zenit-2 second stage,9000.0,840.0,71.0,sail_area_25yr,sail,0.0,134.6904,2155.0459,m2,"sail area for the 25-yr IADC guideline (solar max .. solar min); existing area_for_target_years model, see wp3_decay_trade.csv area_for_25yr"
+1.0,SL-16 / Zenit-2 second stage,9000.0,840.0,71.0,edt_years,edt,0.0,2.9844,9.1668,years,"[model: EDT-v1 aligned-dipole, capped-current; eta band |cos i|..cos^2 i; libration T7 OPEN - PLACEHOLDER duty factor]"
+1.0,SL-16 / Zenit-2 second stage,9000.0,840.0,71.0,edt_eta,edt,0.0,0.105995,0.325568,[-],"eta_lo = cos^2(i) (EMF/collection-limited current), eta_hi = |cos i| (power/current-capped current); wp13-edt-derivation.md Section 5"
+1.0,SL-16 / Zenit-2 second stage,9000.0,840.0,71.0,edt_emf_power,edt,150.3125,0.0,300.6250,"V (value); W (value_hi)","value = open-circuit motional EMF at the catalog altitude [V]; value_hi = diagnostic power = emf_v * EdtConfig::avg_current_a [W]; value_lo unused (0.0); not a deorbit-time metric"
+1.0,SL-16 / Zenit-2 second stage,9000.0,840.0,71.0,kit_mass,sail,2.4000,2.4000,2.4000,kg,"installed drag-sail kit mass (Config::kit_mass_kg)"
+1.0,SL-16 / Zenit-2 second stage,9000.0,840.0,71.0,kit_mass,edt,20.0000,20.0000,20.0000,kg,"installed EDT kit mass, PLACEHOLDER (EdtConfig::kit_mass_kg)"
+1.0,SL-16 / Zenit-2 second stage,9000.0,840.0,71.0,deploy_risk,edt,0.0500,0.0500,0.0500,[-],"PLACEHOLDER EDT deployment-failure probability (EdtConfig::deploy_failure_prob); reported only, not applied to the edt_years band above"
+1.0,SL-16 / Zenit-2 second stage,9000.0,840.0,71.0,recommended_kit,edt-candidate-open-risks,0.0,0.0,0.0,[-],"sail infeasible (135..2155 m2 for the 25-yr guideline, solar max..min); EDT (band 3.0..9.2 yr) subject to T7; libration (T7, Pelaez et al. 2000) and plasma Ne (PLACEHOLDER solar-min/max pair, wp13-literature.md Topic 3) remain open -- an honest EDT candidate, not a closed recommendation"
+1.0,SL-8 / Kosmos-3M second stage,1400.0,750.0,78.0,sail_area_25yr,sail,0.0,7.2046,115.2728,m2,"sail area for the 25-yr IADC guideline (solar max .. solar min); existing area_for_target_years model, see wp3_decay_trade.csv area_for_25yr"
+1.0,SL-8 / Kosmos-3M second stage,1400.0,750.0,78.0,edt_years,edt,0.0,0.6291,3.0259,years,"[model: EDT-v1 aligned-dipole, capped-current; eta band |cos i|..cos^2 i; libration T7 OPEN - PLACEHOLDER duty factor]"
+1.0,SL-8 / Kosmos-3M second stage,1400.0,750.0,78.0,edt_eta,edt,0.0,0.043227,0.207912,[-],"eta_lo = cos^2(i) (EMF/collection-limited current), eta_hi = |cos i| (power/current-capped current); wp13-edt-derivation.md Section 5"
+1.0,SL-8 / Kosmos-3M second stage,1400.0,750.0,78.0,edt_emf_power,edt,100.3007,0.0,200.6013,"V (value); W (value_hi)","value = open-circuit motional EMF at the catalog altitude [V]; value_hi = diagnostic power = emf_v * EdtConfig::avg_current_a [W]; value_lo unused (0.0); not a deorbit-time metric"
+1.0,SL-8 / Kosmos-3M second stage,1400.0,750.0,78.0,kit_mass,sail,2.4000,2.4000,2.4000,kg,"installed drag-sail kit mass (Config::kit_mass_kg)"
+1.0,SL-8 / Kosmos-3M second stage,1400.0,750.0,78.0,kit_mass,edt,20.0000,20.0000,20.0000,kg,"installed EDT kit mass, PLACEHOLDER (EdtConfig::kit_mass_kg)"
+1.0,SL-8 / Kosmos-3M second stage,1400.0,750.0,78.0,deploy_risk,edt,0.0500,0.0500,0.0500,[-],"PLACEHOLDER EDT deployment-failure probability (EdtConfig::deploy_failure_prob); reported only, not applied to the edt_years band above"
+1.0,SL-8 / Kosmos-3M second stage,1400.0,750.0,78.0,recommended_kit,sail,0.0,0.0,0.0,[-],"sail closes (7..115 m2 for the 25-yr guideline, solar max..min); no EDT kit needed for this class"
+1.0,Envisat-class massive SSO payload,8211.0,780.0,98.4,sail_area_25yr,sail,0.0,61.4390,983.0247,m2,"sail area for the 25-yr IADC guideline (solar max .. solar min); existing area_for_target_years model, see wp3_decay_trade.csv area_for_25yr"
+1.0,Envisat-class massive SSO payload,8211.0,780.0,98.4,edt_years,edt,0.0,5.4814,37.5223,years,"[model: EDT-v1 aligned-dipole, capped-current; eta band |cos i|..cos^2 i; libration T7 OPEN - PLACEHOLDER duty factor]"
+1.0,Envisat-class massive SSO payload,8211.0,780.0,98.4,edt_eta,edt,0.0,0.021340,0.146083,[-],"eta_lo = cos^2(i) (EMF/collection-limited current), eta_hi = |cos i| (power/current-capped current); wp13-edt-derivation.md Section 5; retrograde (i > 90 deg): the EMF polarity reverses but the induced current always drags (Lenz), so the magnitude |cos i| is used"
+1.0,Envisat-class massive SSO payload,8211.0,780.0,98.4,edt_emf_power,edt,69.4450,0.0,138.8899,"V (value); W (value_hi)","value = open-circuit motional EMF at the catalog altitude [V]; value_hi = diagnostic power = emf_v * EdtConfig::avg_current_a [W]; value_lo unused (0.0); not a deorbit-time metric"
+1.0,Envisat-class massive SSO payload,8211.0,780.0,98.4,kit_mass,sail,2.4000,2.4000,2.4000,kg,"installed drag-sail kit mass (Config::kit_mass_kg)"
+1.0,Envisat-class massive SSO payload,8211.0,780.0,98.4,kit_mass,edt,20.0000,20.0000,20.0000,kg,"installed EDT kit mass, PLACEHOLDER (EdtConfig::kit_mass_kg)"
+1.0,Envisat-class massive SSO payload,8211.0,780.0,98.4,deploy_risk,edt,0.0500,0.0500,0.0500,[-],"PLACEHOLDER EDT deployment-failure probability (EdtConfig::deploy_failure_prob); reported only, not applied to the edt_years band above"
+1.0,Envisat-class massive SSO payload,8211.0,780.0,98.4,recommended_kit,controlled-reentry-mission-class,0.0,0.0,0.0,[-],"massive (8211 kg) high-inclination payload -- sail/EDT kit-only deorbit is not the recommended mission class; see generated/wp13_classC.md for the controlled-reentry comparison"
diff --git a/generated/wp13_kit_trade_schema.md b/generated/wp13_kit_trade_schema.md
new file mode 100644
index 0000000..18fbec6
--- /dev/null
+++ b/generated/wp13_kit_trade_schema.md
@@ -0,0 +1,97 @@
+# WP13 kit-class trade CSV schema (version 1.0)
+
+`generated/wp13_kit_trade.csv` is the committed per-class (A/B/C) kit-trade
+table emitted by `kit_trade` (src/main_kit_trade.cpp). It combines the
+EXISTING WP3 sail-decay model (src/decay.cpp: sail_decay_years,
+area_for_target_years) with the NEW WP13 EDT-v1 aligned-dipole physics core
+(src/decay.cpp: edt_deorbit_years) into one deterministic, timestamp-free
+table (R6): sail area, EDT deorbit-time band, EDT diagnostics, kit mass
+(sail and EDT), EDT deployment risk, and a generated (never hand-written)
+recommended-kit row with a one-line rationale per class. Class-C
+controlled-reentry mission-class detail lives in the separate
+`wp13_classC.{csv,md}` pair, not here (see that file's own column notes).
+
+## Columns
+
+| column | meaning |
+|---|---|
+| schema_version | WP13 kit-trade schema id (`1.0`) |
+| catalog | class-level preset name (`DebrisCatalog::name`) |
+| mass_kg | catalog stage mass [kg] |
+| altitude_km | catalog altitude [km] |
+| inclination_deg | catalog inclination [deg] (drives the mandatory EDT eta(i) band) |
+| record_type | see record_type rows below |
+| kit_option | sail / edt, or the recommendation label (recommended_kit only) |
+| value | single-value metric, or 0.0 for band-only / not-applicable rows |
+| value_lo | band lower edge (see per-row meaning below) |
+| value_hi | band upper edge (see per-row meaning below) |
+| units | value / value_lo / value_hi units |
+| notes | provenance, model-scope tag, and caveats |
+
+## record_type rows
+
+- `sail_area_25yr`: sail area for the 25-yr IADC guideline from the
+ EXISTING WP3 model (`area_for_target_years`, unchanged). value_lo/hi are
+ the solar-max/solar-min band (mirrors `wp3_decay_trade.csv`'s
+ `area_for_25yr` row; this does not repeat the full area sweep -- see
+ that file for the sweep, now including the 1000 m^2 row).
+- `edt_years`: EDT-v1 deorbit-time band from `edt_deorbit_years`.
+ value_lo = years_optimistic (eta_hi = |cos i|, power/current-capped
+ edge); value_hi = years_conservative (eta_lo = cos(i)^2,
+ EMF/collection-limited edge) -- NEVER a point value. notes carries the
+ EDT-v1 model-scope tag (below). Exactly-polar catalogs (|cos i| ~= 0:
+ the aligned-dipole orbit-normal field vanishes) print the literal
+ text `n/a (polar: aligned-dipole avg force -> 0)` in value_lo/value_hi
+ instead of a number: this is the honest, physically-correct null result
+ for that idealization (see the `edt_deorbit_years` doc comment in
+ decay.hpp), not a missing-data placeholder. Retrograde catalogs
+ (i > 90 deg) get FINITE bands: the passive tether's induced current
+ always drags (Lenz), so the force magnitude scales with |cos i|.
+- `edt_eta`: the mandatory inclination-dependent v x B efficiency band
+ (spec:240-243, never omitted). value_lo = eta_lo = cos(i)^2, value_hi =
+ eta_hi = |cos i| (see `wp13-edt-derivation.md` Section 5 for
+ both current-limit regimes; for i > 90 deg the EMF polarity reverses
+ and the magnitude is reported, with a note in the row).
+- `edt_emf_power`: value = open-circuit motional EMF at the catalog
+ altitude [V]; value_hi = diagnostic power = emf_v * avg_current_a [W];
+ value_lo is unused (0.0). Diagnostic only, not a deorbit-time metric.
+- `kit_mass`: installed kit mass. kit_option = sail uses the EXISTING
+ `Config::kit_mass_kg`; kit_option = edt uses the NEW
+ `EdtConfig::kit_mass_kg` (PLACEHOLDER). value = value_lo = value_hi (a
+ point value, not a band).
+- `deploy_risk`: `EdtConfig::deploy_failure_prob`, PLACEHOLDER. Reported
+ only -- NOT applied to the edt_years band above (see the
+ `edt_deorbit_years` doc comment in decay.hpp).
+- `recommended_kit`: value = 0.0 (a label row, not a numeric metric);
+ kit_option carries the recommendation label; notes carries the
+ one-line, generated (never hand-written) rationale, quoting the actual
+ computed sail-area and/or EDT-years numbers for that catalog. Class C's
+ recommendation is `controlled-reentry-mission-class`, pointing at
+ `wp13_classC.md` rather than a sail/EDT kit choice.
+
+## EDT-v1 model scope (R14 fidelity tag)
+
+Every `edt_years` row's notes column carries the literal tag
+`[model: EDT-v1 aligned-dipole, capped-current; eta band cos(i)..cos^2(i);
+libration T7 OPEN - PLACEHOLDER duty factor]`. This states the model scope
+in-line with the number it qualifies (R14): aligned (untilted) dipole
+geomagnetic field (SPENVIS centred dipole, IGRF epoch 2000); force capped
+by either a fixed/power-limited current (optimistic edge) or an
+EMF/collection-limited current (conservative edge); the along-track
+efficiency factor is the orbit-averaged eta(i) in [cos(i)^2, cos(i)],
+MANDATORY per spec WP13 (never a point value, never omitted for a
+high-inclination catalog). Libration/dynamic stability (T7, Pelaez et al.
+2000) is explicitly UNRESOLVED and is folded in only as a flat
+PLACEHOLDER duty-cycle knob (`EdtConfig::eta_libration`) -- never claimed
+solved. Plasma electron density is a cited PLACEHOLDER parameter, not an
+IRI implementation (spec non-goal); see
+`wp13-literature.md` Topic 3 for the citation and the open
+solar-min/max pair gap.
+
+## Schema changes (R15)
+
+Additive columns to this file bump the minor version (1.0 -> 1.1) with
+this file and all consumers updated in the same PR, the same rule as
+`wp3_decay_trade_schema.md`'s own closing paragraph. Additive rows (a new
+catalog, a new record_type) do NOT need a version bump under this
+project's own rule.
diff --git a/generated/wp3_decay_trade.csv b/generated/wp3_decay_trade.csv
index de32042..89824bb 100644
--- a/generated/wp3_decay_trade.csv
+++ b/generated/wp3_decay_trade.csv
@@ -7,6 +7,7 @@ schema_version,catalog,mass_kg,altitude_km,record_type,sail_area_m2,value_solar_
1.0,SL-16 / Zenit-2 second stage,9000.0,840.0,decay_years,200.0000,16.8363,269.3807,years,""
1.0,SL-16 / Zenit-2 second stage,9000.0,840.0,decay_years,400.0000,8.4181,134.6904,years,""
1.0,SL-16 / Zenit-2 second stage,9000.0,840.0,decay_years,800.0000,4.2091,67.3452,years,""
+1.0,SL-16 / Zenit-2 second stage,9000.0,840.0,decay_years,1000.0000,3.3673,53.8761,years,""
1.0,SL-16 / Zenit-2 second stage,9000.0,840.0,decay_years,1600.0000,2.1045,33.6726,years,""
1.0,SL-16 / Zenit-2 second stage,9000.0,840.0,decay_years,3200.0000,1.0523,16.8363,years,""
1.0,SL-16 / Zenit-2 second stage,9000.0,840.0,area_for_25yr,0.0,134.6904,2155.0459,m2,"sail area for the 25-yr guideline (solar max .. min)"
@@ -18,6 +19,7 @@ schema_version,catalog,mass_kg,altitude_km,record_type,sail_area_m2,value_solar_
1.0,SL-8 / Kosmos-3M second stage,1400.0,750.0,decay_years,200.0000,0.9006,14.4091,years,""
1.0,SL-8 / Kosmos-3M second stage,1400.0,750.0,decay_years,400.0000,0.4503,7.2046,years,""
1.0,SL-8 / Kosmos-3M second stage,1400.0,750.0,decay_years,800.0000,0.2251,3.6023,years,""
+1.0,SL-8 / Kosmos-3M second stage,1400.0,750.0,decay_years,1000.0000,0.1801,2.8818,years,""
1.0,SL-8 / Kosmos-3M second stage,1400.0,750.0,decay_years,1600.0000,0.1126,1.8011,years,""
1.0,SL-8 / Kosmos-3M second stage,1400.0,750.0,decay_years,3200.0000,0.0563,0.9006,years,""
1.0,SL-8 / Kosmos-3M second stage,1400.0,750.0,area_for_25yr,0.0,7.2046,115.2728,m2,"sail area for the 25-yr guideline (solar max .. min)"
diff --git a/include/adsc/decay.hpp b/include/adsc/decay.hpp
index 9b721c8..22fe2be 100644
--- a/include/adsc/decay.hpp
+++ b/include/adsc/decay.hpp
@@ -30,7 +30,7 @@ struct DebrisCatalog {
DebrisCatalog catalog_A(); // SL-16 / Zenit-2 second-stage class
DebrisCatalog catalog_B(); // SL-8 / Kosmos-3M second-stage class
-DebrisCatalog catalog_C(); // CZ upper-stage class (PLACEHOLDER)
+DebrisCatalog catalog_C(); // Envisat-class massive SSO payload (WP13, cited)
DebrisCatalog catalog_D(); // US Delta-class stage (PLACEHOLDER)
// Piecewise-exponential atmospheric density [kg/m^3] at geodetic altitude [m].
@@ -91,4 +91,91 @@ double area_for_target_years(const DebrisCatalog& target, double target_years,
// specific tether.
double edt_deorbit_days(double a0_m, double a_stop_m, double delta_a_per_day_m);
+// ============================================================================
+// Electrodynamic-tether (EDT) physics core (WP13)
+// ----------------------------------------------------------------------------
+// Aligned-dipole v x B model for a radial (nadir-aligned) bare tether. This is
+// a NEW model alongside edt_deorbit_days above (kept byte-for-byte, R15 pin) --
+// it does not replace or edit that legacy parametric knob or any sail/
+// atmosphere function in this file. See wp13-edt-derivation.md
+// for the full eta(i) derivation and wp13-literature.md for
+// citations (Sanmartin, Martinez-Sanchez & Ahedo 1993 OML bare-tether
+// baseline; BETs FP7 GA 262972 tape-tether reference geometry; SPENVIS
+// centred-dipole field; Pelaez et al. 2000 EDT libration/dynamic-instability
+// finding). Every output is reported as a MANDATORY [conservative,
+// optimistic] band -- never a point value (spec:247-249).
+// ============================================================================
+
+struct EdtConfig {
+ double tether_length_m = 3000.0; // BETs FP7 tape-tether reference length [m] (2 km bare + 1 km inert; thebetsproject.com / oa.upm.es/39287, cited wp13-literature.md Topic 1b)
+ double kit_mass_kg = 20.0; // PLACEHOLDER EDT kit mass (tether + deployer + electronics) [kg]
+ double avg_current_a = 2.0; // PLACEHOLDER average bare-tether collected current, BETs-scale [A]
+ double eta_libration = 0.75; // PLACEHOLDER libration/duty-cycle efficiency penalty [-] (T7 open; Pelaez et al. 2000 dynamic-instability finding, never claimed solved)
+ double deploy_failure_prob = 0.05; // PLACEHOLDER deployment-failure probability [-] (reported only, not applied to years_* below)
+};
+
+// Deorbit-time band and diagnostics returned by edt_deorbit_years(). Report
+// the [years_optimistic, years_conservative] pair together -- never
+// years_optimistic alone (spec:247-249).
+struct EdtResult {
+ double years_optimistic; // deorbit time [yr], eta_hi = |cos i| edge (fixed / power-capped current)
+ double years_conservative; // deorbit time [yr], eta_lo = cos(i)^2 edge (EMF / collection-limited current)
+ double eta_hi; // |cos i|: optimistic orbit-averaged inclination efficiency [-]
+ double eta_lo; // cos(i)^2: conservative orbit-averaged inclination efficiency [-]
+ double emf_v; // open-circuit motional EMF at the initial altitude [V]
+ double power_w; // emf_v * avg_current_a, diagnostic only [W]
+};
+
+// General EDT quasi-circular spiral integrator: an along-track Lorentz drag
+// force F(a) [N] (any function of semi-major axis a, not necessarily a power
+// law) drives, for a circular orbit (Gauss planetary equation),
+// da/dt = -2 * F(a) * a^1.5 / (m * sqrt(mu))
+// Integrates from a0_m down to a_stop_m and returns elapsed time [s]. Same
+// trapezoidal-in-a quadrature style as integrate_decay_seconds above. For the
+// aligned-dipole force law F(a) = C / a^3 this cross-checks the elementary
+// closed form used internally by edt_deorbit_years (WP13 task-3 requirement).
+template
+double integrate_edt_seconds(double a0_m, double a_stop_m, double m,
+ ForceFn force_at_a, int steps = 6000) {
+ if (a0_m <= a_stop_m || steps < 1 || m <= 0.0) {
+ return 0.0;
+ }
+ const double da = (a0_m - a_stop_m) / steps;
+ const auto integrand = [&](double a) {
+ return m * std::sqrt(kEarthMu) / (2.0 * force_at_a(a) * std::pow(a, 1.5));
+ };
+ double sum = 0.5 * (integrand(a_stop_m) + integrand(a0_m));
+ for (int i = 1; i < steps; ++i) {
+ sum += integrand(a_stop_m + static_cast(i) * da);
+ }
+ return sum * da;
+}
+
+// Aligned-dipole EDT deorbit-time band for a debris target fitted with an EDT
+// kit (see EdtConfig). Orbit-normal field magnitude B_n(a) = kB0 *
+// (kDipoleRefRadius_m / a)^3 * |cos i| (SPENVIS centred dipole, IGRF epoch
+// 2000; i = target's catalog inclination -- this is the MANDATORY
+// inclination-dependent v x B efficiency, spec:240-243). |cos i|, not signed
+// cos i: the passive tether's induced-current direction follows the
+// motional-EMF sign (Lenz), so the along-track Lorentz force is always a
+// drag; a retrograde orbit (i > 90 deg, e.g. the class-C sun-synchronous
+// candidate) reverses the EMF polarity but keeps a finite deorbit drag
+// scaling with |cos i|. Along-track force at semi-major axis a: F(a) =
+// avg_current_a * tether_length_m * B_n(a) * eta_libration is the optimistic
+// edge (eta_hi = |cos i|, fixed/power-capped current); the conservative edge
+// is F(a) * |cos i| (eta_lo = cos(i)^2, EMF/collection-limited current) --
+// see wp13-edt-derivation.md Section 5 for both current-limit regimes.
+// Solved in closed form (elementary power-law integral, exact);
+// integrate_edt_seconds above cross-checks the algebra. Exactly-polar orbits
+// (|cos i| <= 1e-6): the aligned-dipole orbit-normal field is zero
+// everywhere on the orbit (derivation note Section 6, limit L3) --
+// years_optimistic/years_conservative are +infinity, a physically correct
+// null result for this idealization (not a numerical artifact); emitters
+// should print "n/a (polar: aligned-dipole avg force -> 0)" for it.
+// Libration (T7) is folded in only as the flat eta_libration duty-cycle knob
+// above -- dynamic stability itself is explicitly unresolved (never claimed
+// solved).
+EdtResult edt_deorbit_years(const DebrisCatalog& target, const EdtConfig& cfg,
+ double stop_altitude_km);
+
} // namespace adsc
diff --git a/src/decay.cpp b/src/decay.cpp
index 043b355..573685a 100644
--- a/src/decay.cpp
+++ b/src/decay.cpp
@@ -1,6 +1,7 @@
#include "adsc/decay.hpp"
#include
+#include
namespace adsc {
@@ -34,6 +35,16 @@ constexpr Band kBands[] = {
constexpr int kNumBands = static_cast(sizeof(kBands) / sizeof(kBands[0]));
constexpr double kSecondsPerYear = 365.25 * 86400.0;
+// SPENVIS (ESA/BIRA-IASB Space Environment Information System), "Dipole
+// approximations of the geomagnetic field", centred-dipole IGRF epoch-2000
+// values: equatorial surface field B0 and the reference (mean) Earth radius
+// used in the dipole formula (https://www.spenvis.oma.be/help/background/
+// magfield/cd.html, web-verified 2026-07-11, wp13-literature.md Topic 4).
+// Deliberately NOT kEarthRadius (WGS-84 equatorial, relmotion.hpp): the cited
+// dipole formula is defined against this specific mean-radius reference.
+constexpr double kB0 = 3.01153e-5; // T
+constexpr double kDipoleRefRadius_m = 6371200.0; // m
+
} // namespace
double atmospheric_density(double altitude_m, double solar_factor) {
@@ -60,7 +71,13 @@ DebrisCatalog catalog_B() {
return {"SL-8 / Kosmos-3M second stage", 1400.0, 750.0, 78.0, false};
}
DebrisCatalog catalog_C() {
- return {"CZ upper stage (PLACEHOLDER)", 0.0, 0.0, 0.0, true};
+ // Envisat (ESA): mass 8211 kg (Service Module 2673 + PEB 1021 + Payload
+ // Carrier 2078 + fuel 319 + instruments 2118 kg); sun-synchronous orbit
+ // ~765-800 km, inclination ~98.4-98.55 deg; defunct since April 2012 --
+ // the archetypal massive / controlled-reentry-class Class-C target.
+ // eoPortal (https://www.eoportal.org/satellite-missions/envisat),
+ // web-verified 2026-07-11 (wp13-literature.md Topic 6, item 1).
+ return {"Envisat-class massive SSO payload", 8211.0, 780.0, 98.4, false};
}
DebrisCatalog catalog_D() {
return {"US Delta-class stage (PLACEHOLDER)", 0.0, 0.0, 0.0, true};
@@ -96,4 +113,67 @@ double edt_deorbit_days(double a0_m, double a_stop_m, double delta_a_per_day_m)
return (a0_m - a_stop_m) / delta_a_per_day_m;
}
+EdtResult edt_deorbit_years(const DebrisCatalog& target, const EdtConfig& cfg,
+ double stop_altitude_km) {
+ EdtResult result{};
+
+ // Magnitude |cos i|: the passive tether's induced-current direction is set
+ // by the motional-EMF sign (Lenz), so the along-track Lorentz force is
+ // ALWAYS a drag. For a retrograde orbit (i > 90 deg, e.g. the class-C
+ // sun-synchronous candidate) the EMF polarity reverses but the deorbit
+ // force magnitude scales with |cos i|, not signed cos i.
+ const double cos_i = std::cos(target.inclination_deg * kPi / 180.0);
+ const double abs_cos_i = std::fabs(cos_i);
+ result.eta_hi = abs_cos_i;
+ result.eta_lo = cos_i * cos_i;
+
+ const double a0 = kEarthRadius + target.altitude_km * 1000.0;
+ const double Bn0 = kB0 * std::pow(kDipoleRefRadius_m / a0, 3.0) * abs_cos_i;
+ const double v0 = std::sqrt(kEarthMu / a0);
+ // EMF/power reported as magnitudes; polarity reverses for retrograde
+ // orbits (diagnostic only -- the drag sign is unaffected, see above).
+ result.emf_v = v0 * Bn0 * cfg.tether_length_m;
+ result.power_w = result.emf_v * cfg.avg_current_a;
+
+ if (abs_cos_i <= 1e-6) {
+ // Exactly-polar orbit: the aligned-dipole orbit-normal field is zero
+ // everywhere on the orbit (wp13-edt-derivation.md Section 6, limit
+ // L3), so the along-track EDT force -> 0 identically, not just on
+ // average. This is the correct physics for the aligned-dipole
+ // idealization, not a numerical artifact. (Retrograde orbits with
+ // |cos i| > 0, e.g. i = 98.4 deg, are handled normally above.)
+ result.years_optimistic = std::numeric_limits::infinity();
+ result.years_conservative = std::numeric_limits::infinity();
+ return result;
+ }
+
+ const double a_stop = kEarthRadius + stop_altitude_km * 1000.0;
+ const double m = target.mass_kg + cfg.kit_mass_kg;
+ if (a0 <= a_stop || m <= 0.0) {
+ result.years_optimistic = 0.0;
+ result.years_conservative = 0.0;
+ return result;
+ }
+
+ // Optimistic edge: F_hi(a) = I * L * B_n(a) * eta_lib
+ // = (I * L * kB0 * kDipoleRefRadius_m^3 * |cos i| * eta_lib) / a^3
+ // = C_hi / a^3.
+ // Conservative edge: F_lo(a) = F_hi(a) * |cos i| = C_lo / a^3
+ // (wp13-edt-derivation.md Section 5: the two current-limit regimes).
+ const double C_hi = cfg.avg_current_a * cfg.tether_length_m * kB0 *
+ std::pow(kDipoleRefRadius_m, 3.0) * abs_cos_i * cfg.eta_libration;
+ const double C_lo = C_hi * abs_cos_i;
+
+ // Quasi-circular spiral under F(a) = C / a^3:
+ // da/dt = -2*F(a)*a^1.5/(m*sqrt(mu)) = -(2*C/(m*sqrt(mu))) * a^-1.5
+ // => a^1.5 da = -(2*C/(m*sqrt(mu))) dt
+ // => (2/5)(a_stop^2.5 - a0^2.5) = -(2*C/(m*sqrt(mu))) * t
+ // => t = m*sqrt(mu)*(a0^2.5 - a_stop^2.5) / (5*C)
+ const double sqrt_mu = std::sqrt(kEarthMu);
+ const double delta_a25 = std::pow(a0, 2.5) - std::pow(a_stop, 2.5);
+ result.years_optimistic = (m * sqrt_mu * delta_a25 / (5.0 * C_hi)) / kSecondsPerYear;
+ result.years_conservative = (m * sqrt_mu * delta_a25 / (5.0 * C_lo)) / kSecondsPerYear;
+ return result;
+}
+
} // namespace adsc
diff --git a/src/main_decay_trade.cpp b/src/main_decay_trade.cpp
index b95029c..fdbfd63 100644
--- a/src/main_decay_trade.cpp
+++ b/src/main_decay_trade.cpp
@@ -25,9 +25,12 @@ namespace {
const char* kSchema = "1.0";
// Sail areas swept for the trade curve [m^2]. Spans B's ~7 m^2 and A's
-// ~135-2155 m^2 25-year crossings so both are visible.
+// ~135-2155 m^2 25-year crossings so both are visible. 1000.0 added (WP13
+// review minor) so it lines up with main.cpp scenario-6's areas[] and the
+// README's 1000 m^2 table column is byte-checkable against this CSV -- an
+// additive row, not a column, so no schema bump (R15 clause below).
const double kAreas[] = {5.0, 10.0, 25.0, 50.0, 100.0, 200.0,
- 400.0, 800.0, 1600.0, 3200.0};
+ 400.0, 800.0, 1000.0, 1600.0, 3200.0};
// Writes generated/wp3_decay_trade_schema.md documenting the wp3_decay_trade.csv
// columns. A single fputs of one string literal -- no printf format specifiers,
diff --git a/src/main_kit_trade.cpp b/src/main_kit_trade.cpp
new file mode 100644
index 0000000..4f929be
--- /dev/null
+++ b/src/main_kit_trade.cpp
@@ -0,0 +1,538 @@
+// WP13 kit-class trade CSV emitter: writes generated/wp13_kit_trade.{csv,md}
+// combining the EXISTING WP3 sail-decay model (src/decay.cpp:
+// sail_decay_years, area_for_target_years) with the NEW WP13 EDT-v1
+// aligned-dipole physics core (src/decay.cpp: edt_deorbit_years) into one
+// per-class (A/B/C) recommended-kit trade table. Also writes the WP13
+// class-C controlled-reentry mission-class comparison, generated/
+// wp13_classC.{csv,md} (Zenit-2 stage / catalog_A vs Envisat-class /
+// catalog_C -- same-target-set rule, spec:237/244-246). This changes no
+// existing physics and no existing pinned number: it is a pure consumer of
+// catalog_A/B/C and the pre-existing decay.cpp functions. Deterministic and
+// timestamp-free (R6).
+//
+// kit_trade [out_dir]
+//
+// Default out_dir = "generated".
+#include
+#include
+#include
+#include
+#include
+
+#include "adsc/decay.hpp"
+#include "adsc/mission.hpp" // Config
+
+using namespace adsc;
+
+namespace {
+
+// Stable schema id for wp13_kit_trade.csv and wp13_classC.csv (independent
+// of wp3's schema id; both new files start at 1.0).
+const char* kSchema = "1.0";
+
+// R14 model-scope tag, carried verbatim in every edt_years row's notes: the
+// aligned-dipole force model, the mandatory eta(i) band, and the T7
+// libration caveat, stated in-line with the number it qualifies.
+const char* kEdtModelTag =
+ "[model: EDT-v1 aligned-dipole, capped-current; eta band "
+ "|cos i|..cos^2 i; libration T7 OPEN - PLACEHOLDER duty factor]";
+
+// decay.hpp's own documented print for the exactly-polar (|cos i| ~= 0)
+// null-coupling case: years_optimistic/years_conservative are +infinity, a
+// physically correct null result, not a numerical artifact (see the
+// edt_deorbit_years doc comment). Retrograde catalogs (i > 90 deg) get
+// FINITE numbers: the drag magnitude scales with |cos i| (Lenz).
+const char* kEdtPolarNote = "n/a (polar: aligned-dipole avg force -> 0)";
+
+// Writes the 7 record_type rows shared by every catalog (sail_area_25yr,
+// edt_years, edt_eta, edt_emf_power, kit_mass x2, deploy_risk). The 8th row
+// (recommended_kit) is catalog-specific prose and is written by the caller.
+void write_common_rows(std::FILE* f, const DebrisCatalog& c, const Config& cfg,
+ const EdtConfig& ecfg, double a25_max, double a25_min,
+ const EdtResult& edt) {
+ // sail_area_25yr: EXISTING WP3 model, unchanged. value_lo/value_hi =
+ // solar-max/solar-min band (mirrors wp3_decay_trade.csv's area_for_25yr).
+ std::fprintf(f,
+ "%s,%s,%.1f,%.1f,%.1f,sail_area_25yr,sail,0.0,%.4f,%.4f,m2,"
+ "\"sail area for the 25-yr IADC guideline (solar max .. solar min); "
+ "existing area_for_target_years model, see wp3_decay_trade.csv "
+ "area_for_25yr\"\n",
+ kSchema, c.name, c.mass_kg, c.altitude_km, c.inclination_deg,
+ a25_max, a25_min);
+
+ // edt_years: WP13 EDT-v1 band. value_lo = years_optimistic (eta_hi =
+ // |cos i|), value_hi = years_conservative (eta_lo = cos(i)^2) -- never a
+ // point value. The exactly-polar null case prints text, not a number.
+ if (std::isinf(edt.years_optimistic)) {
+ std::fprintf(f,
+ "%s,%s,%.1f,%.1f,%.1f,edt_years,edt,0.0,%s,%s,years,"
+ "\"%s inclination %.1f deg gives |cos i| ~= 0 (exactly polar), "
+ "so the aligned-dipole model reports null orbit-normal "
+ "coupling %s\"\n",
+ kSchema, c.name, c.mass_kg, c.altitude_km, c.inclination_deg,
+ kEdtPolarNote, kEdtPolarNote, kEdtModelTag, c.inclination_deg,
+ kEdtPolarNote);
+ } else {
+ std::fprintf(f,
+ "%s,%s,%.1f,%.1f,%.1f,edt_years,edt,0.0,%.4f,%.4f,years,\"%s\"\n",
+ kSchema, c.name, c.mass_kg, c.altitude_km, c.inclination_deg,
+ edt.years_optimistic, edt.years_conservative, kEdtModelTag);
+ }
+
+ // edt_eta: the mandatory inclination-dependent v x B efficiency band.
+ // value_lo = eta_lo = cos(i)^2, value_hi = eta_hi = |cos i|.
+ const char* sign_note =
+ (c.inclination_deg > 90.0)
+ ? "; retrograde (i > 90 deg): the EMF polarity reverses but the "
+ "induced current always drags (Lenz), so the magnitude "
+ "|cos i| is used"
+ : "";
+ std::fprintf(f,
+ "%s,%s,%.1f,%.1f,%.1f,edt_eta,edt,0.0,%.6f,%.6f,[-],"
+ "\"eta_lo = cos^2(i) (EMF/collection-limited current), eta_hi = "
+ "|cos i| (power/current-capped current); "
+ "wp13-edt-derivation.md Section 5%s\"\n",
+ kSchema, c.name, c.mass_kg, c.altitude_km, c.inclination_deg,
+ edt.eta_lo, edt.eta_hi, sign_note);
+
+ // edt_emf_power: value = open-circuit motional EMF [V]; value_hi =
+ // diagnostic power = emf_v * avg_current_a [W]; value_lo unused (0.0).
+ std::fprintf(f,
+ "%s,%s,%.1f,%.1f,%.1f,edt_emf_power,edt,%.4f,0.0,%.4f,"
+ "\"V (value); W (value_hi)\","
+ "\"value = open-circuit motional EMF at the catalog altitude [V]; "
+ "value_hi = diagnostic power = emf_v * EdtConfig::avg_current_a "
+ "[W]; value_lo unused (0.0); not a deorbit-time metric\"\n",
+ kSchema, c.name, c.mass_kg, c.altitude_km, c.inclination_deg,
+ edt.emf_v, edt.power_w);
+
+ // kit_mass (sail): EXISTING Config::kit_mass_kg. Point value: value =
+ // value_lo = value_hi.
+ std::fprintf(f,
+ "%s,%s,%.1f,%.1f,%.1f,kit_mass,sail,%.4f,%.4f,%.4f,kg,"
+ "\"installed drag-sail kit mass (Config::kit_mass_kg)\"\n",
+ kSchema, c.name, c.mass_kg, c.altitude_km, c.inclination_deg,
+ cfg.kit_mass_kg, cfg.kit_mass_kg, cfg.kit_mass_kg);
+
+ // kit_mass (edt): NEW EdtConfig::kit_mass_kg, PLACEHOLDER.
+ std::fprintf(f,
+ "%s,%s,%.1f,%.1f,%.1f,kit_mass,edt,%.4f,%.4f,%.4f,kg,"
+ "\"installed EDT kit mass, PLACEHOLDER (EdtConfig::kit_mass_kg)\"\n",
+ kSchema, c.name, c.mass_kg, c.altitude_km, c.inclination_deg,
+ ecfg.kit_mass_kg, ecfg.kit_mass_kg, ecfg.kit_mass_kg);
+
+ // deploy_risk: EdtConfig::deploy_failure_prob, PLACEHOLDER. Reported
+ // only -- NOT applied to the edt_years band above.
+ std::fprintf(f,
+ "%s,%s,%.1f,%.1f,%.1f,deploy_risk,edt,%.4f,%.4f,%.4f,[-],"
+ "\"PLACEHOLDER EDT deployment-failure probability "
+ "(EdtConfig::deploy_failure_prob); reported only, not applied to "
+ "the edt_years band above\"\n",
+ kSchema, c.name, c.mass_kg, c.altitude_km, c.inclination_deg,
+ ecfg.deploy_failure_prob, ecfg.deploy_failure_prob,
+ ecfg.deploy_failure_prob);
+}
+
+// Writes generated/wp13_kit_trade_schema.md documenting the wp13_kit_trade.csv
+// columns. A single fputs of one string literal -- no printf format
+// specifiers, so there is zero formatting risk (mirrors main_decay_trade.cpp's
+// write_schema_md).
+void write_kit_trade_schema_md(const std::string& out_dir) {
+ std::FILE* f = std::fopen((out_dir + "/wp13_kit_trade_schema.md").c_str(), "w");
+ if (!f) return;
+ std::fputs(
+"# WP13 kit-class trade CSV schema (version 1.0)\n"
+"\n"
+"`generated/wp13_kit_trade.csv` is the committed per-class (A/B/C) kit-trade\n"
+"table emitted by `kit_trade` (src/main_kit_trade.cpp). It combines the\n"
+"EXISTING WP3 sail-decay model (src/decay.cpp: sail_decay_years,\n"
+"area_for_target_years) with the NEW WP13 EDT-v1 aligned-dipole physics core\n"
+"(src/decay.cpp: edt_deorbit_years) into one deterministic, timestamp-free\n"
+"table (R6): sail area, EDT deorbit-time band, EDT diagnostics, kit mass\n"
+"(sail and EDT), EDT deployment risk, and a generated (never hand-written)\n"
+"recommended-kit row with a one-line rationale per class. Class-C\n"
+"controlled-reentry mission-class detail lives in the separate\n"
+"`wp13_classC.{csv,md}` pair, not here (see that file's own column notes).\n"
+"\n"
+"## Columns\n"
+"\n"
+"| column | meaning |\n"
+"|---|---|\n"
+"| schema_version | WP13 kit-trade schema id (`1.0`) |\n"
+"| catalog | class-level preset name (`DebrisCatalog::name`) |\n"
+"| mass_kg | catalog stage mass [kg] |\n"
+"| altitude_km | catalog altitude [km] |\n"
+"| inclination_deg | catalog inclination [deg] (drives the mandatory EDT eta(i) band) |\n"
+"| record_type | see record_type rows below |\n"
+"| kit_option | sail / edt, or the recommendation label (recommended_kit only) |\n"
+"| value | single-value metric, or 0.0 for band-only / not-applicable rows |\n"
+"| value_lo | band lower edge (see per-row meaning below) |\n"
+"| value_hi | band upper edge (see per-row meaning below) |\n"
+"| units | value / value_lo / value_hi units |\n"
+"| notes | provenance, model-scope tag, and caveats |\n"
+"\n"
+"## record_type rows\n"
+"\n"
+"- `sail_area_25yr`: sail area for the 25-yr IADC guideline from the\n"
+" EXISTING WP3 model (`area_for_target_years`, unchanged). value_lo/hi are\n"
+" the solar-max/solar-min band (mirrors `wp3_decay_trade.csv`'s\n"
+" `area_for_25yr` row; this does not repeat the full area sweep -- see\n"
+" that file for the sweep, now including the 1000 m^2 row).\n"
+"- `edt_years`: EDT-v1 deorbit-time band from `edt_deorbit_years`.\n"
+" value_lo = years_optimistic (eta_hi = |cos i|, power/current-capped\n"
+" edge); value_hi = years_conservative (eta_lo = cos(i)^2,\n"
+" EMF/collection-limited edge) -- NEVER a point value. notes carries the\n"
+" EDT-v1 model-scope tag (below). Exactly-polar catalogs (|cos i| ~= 0:\n"
+" the aligned-dipole orbit-normal field vanishes) print the literal\n"
+" text `n/a (polar: aligned-dipole avg force -> 0)` in value_lo/value_hi\n"
+" instead of a number: this is the honest, physically-correct null result\n"
+" for that idealization (see the `edt_deorbit_years` doc comment in\n"
+" decay.hpp), not a missing-data placeholder. Retrograde catalogs\n"
+" (i > 90 deg) get FINITE bands: the passive tether's induced current\n"
+" always drags (Lenz), so the force magnitude scales with |cos i|.\n"
+"- `edt_eta`: the mandatory inclination-dependent v x B efficiency band\n"
+" (spec:240-243, never omitted). value_lo = eta_lo = cos(i)^2, value_hi =\n"
+" eta_hi = |cos i| (see `wp13-edt-derivation.md` Section 5 for\n"
+" both current-limit regimes; for i > 90 deg the EMF polarity reverses\n"
+" and the magnitude is reported, with a note in the row).\n"
+"- `edt_emf_power`: value = open-circuit motional EMF at the catalog\n"
+" altitude [V]; value_hi = diagnostic power = emf_v * avg_current_a [W];\n"
+" value_lo is unused (0.0). Diagnostic only, not a deorbit-time metric.\n"
+"- `kit_mass`: installed kit mass. kit_option = sail uses the EXISTING\n"
+" `Config::kit_mass_kg`; kit_option = edt uses the NEW\n"
+" `EdtConfig::kit_mass_kg` (PLACEHOLDER). value = value_lo = value_hi (a\n"
+" point value, not a band).\n"
+"- `deploy_risk`: `EdtConfig::deploy_failure_prob`, PLACEHOLDER. Reported\n"
+" only -- NOT applied to the edt_years band above (see the\n"
+" `edt_deorbit_years` doc comment in decay.hpp).\n"
+"- `recommended_kit`: value = 0.0 (a label row, not a numeric metric);\n"
+" kit_option carries the recommendation label; notes carries the\n"
+" one-line, generated (never hand-written) rationale, quoting the actual\n"
+" computed sail-area and/or EDT-years numbers for that catalog. Class C's\n"
+" recommendation is `controlled-reentry-mission-class`, pointing at\n"
+" `wp13_classC.md` rather than a sail/EDT kit choice.\n"
+"\n"
+"## EDT-v1 model scope (R14 fidelity tag)\n"
+"\n"
+"Every `edt_years` row's notes column carries the literal tag\n"
+"`[model: EDT-v1 aligned-dipole, capped-current; eta band cos(i)..cos^2(i);\n"
+"libration T7 OPEN - PLACEHOLDER duty factor]`. This states the model scope\n"
+"in-line with the number it qualifies (R14): aligned (untilted) dipole\n"
+"geomagnetic field (SPENVIS centred dipole, IGRF epoch 2000); force capped\n"
+"by either a fixed/power-limited current (optimistic edge) or an\n"
+"EMF/collection-limited current (conservative edge); the along-track\n"
+"efficiency factor is the orbit-averaged eta(i) in [cos(i)^2, cos(i)],\n"
+"MANDATORY per spec WP13 (never a point value, never omitted for a\n"
+"high-inclination catalog). Libration/dynamic stability (T7, Pelaez et al.\n"
+"2000) is explicitly UNRESOLVED and is folded in only as a flat\n"
+"PLACEHOLDER duty-cycle knob (`EdtConfig::eta_libration`) -- never claimed\n"
+"solved. Plasma electron density is a cited PLACEHOLDER parameter, not an\n"
+"IRI implementation (spec non-goal); see\n"
+"`wp13-literature.md` Topic 3 for the citation and the open\n"
+"solar-min/max pair gap.\n"
+"\n"
+"## Schema changes (R15)\n"
+"\n"
+"Additive columns to this file bump the minor version (1.0 -> 1.1) with\n"
+"this file and all consumers updated in the same PR, the same rule as\n"
+"`wp3_decay_trade_schema.md`'s own closing paragraph. Additive rows (a new\n"
+"catalog, a new record_type) do NOT need a version bump under this\n"
+"project's own rule.\n"
+ , f);
+ std::fclose(f);
+}
+
+// ----------------------------------------------------------------------------
+// WP13 class-C controlled-reentry mission-class comparison.
+// ----------------------------------------------------------------------------
+
+// Controlled-deorbit target perigee: 6371 + 40 km, a mean-Earth-radius
+// reentry-perigee convention (per WP13 task spec) -- deliberately distinct
+// from the WGS-84 kEarthRadius used below for the starting circular orbit
+// radius r_a. This is a stated choice, not a silent inconsistency: r_a
+// matches every other altitude-to-radius conversion in this codebase
+// (kEarthRadius + altitude_km*1000), while r_p uses the literal 6371+40 km
+// perigee target exactly as specified.
+constexpr double kReentryPerigeeRadius_m = (6371.0 + 40.0) * 1000.0;
+
+struct ClassCCandidate {
+ const char* label;
+ DebrisCatalog cat;
+};
+
+// Single impulsive perigee-lowering burn from the catalog's circular orbit
+// (radius r_a) to the target perigee r_p: dv = v_c*(1 - sqrt(2*r_p/(r_a+r_p))),
+// v_c = sqrt(mu/r_a). Writes the circular speed to *v_c_out for reuse by the
+// caller (both the CSV and the md table report v_c alongside dv).
+double controlled_deorbit_dv_ms(const DebrisCatalog& cat, double* v_c_out) {
+ const double r_a = kEarthRadius + cat.altitude_km * 1000.0;
+ const double r_p = kReentryPerigeeRadius_m;
+ const double v_c = std::sqrt(kEarthMu / r_a);
+ const double dv = v_c * (1.0 - std::sqrt(2.0 * r_p / (r_a + r_p)));
+ if (v_c_out) *v_c_out = v_c;
+ return dv;
+}
+
+void write_classc_csv(const std::string& out_dir, const DebrisCatalog& zenit,
+ const DebrisCatalog& envisat) {
+ std::FILE* f = std::fopen((out_dir + "/wp13_classC.csv").c_str(), "w");
+ if (!f) return;
+ std::fprintf(f,
+ "schema_version,candidate,mass_kg,altitude_km,inclination_deg,"
+ "metric,value,units,notes\n");
+
+ const ClassCCandidate cands[] = {
+ {"Zenit-2 stage (catalog_A)", zenit},
+ {"Envisat-class (catalog_C)", envisat},
+ };
+ for (const ClassCCandidate& cd : cands) {
+ double v_c = 0.0;
+ const double dv = controlled_deorbit_dv_ms(cd.cat, &v_c);
+ std::fprintf(f,
+ "%s,%s,%.1f,%.1f,%.1f,v_circular_initial,%.4f,m/s,"
+ "\"circular-orbit speed at the catalog altitude, "
+ "v_c = sqrt(mu/r_a)\"\n",
+ kSchema, cd.label, cd.cat.mass_kg, cd.cat.altitude_km,
+ cd.cat.inclination_deg, v_c);
+ std::fprintf(f,
+ "%s,%s,%.1f,%.1f,%.1f,target_perigee_radius,%.1f,km,"
+ "\"controlled-deorbit target perigee, mean-Earth-radius "
+ "convention 6371+40 km (distinct from the WGS-84 kEarthRadius "
+ "used for r_a)\"\n",
+ kSchema, cd.label, cd.cat.mass_kg, cd.cat.altitude_km,
+ cd.cat.inclination_deg, kReentryPerigeeRadius_m / 1000.0);
+ std::fprintf(f,
+ "%s,%s,%.1f,%.1f,%.1f,controlled_deorbit_dv,%.4f,m/s,"
+ "\"impulsive perigee-lowering burn from circular (RE+alt) to "
+ "perigee 6371+40 km: dv = v_c*(1-sqrt(2*r_p/(r_a+r_p)))\"\n",
+ kSchema, cd.label, cd.cat.mass_kg, cd.cat.altitude_km,
+ cd.cat.inclination_deg, dv);
+ }
+ std::fclose(f);
+}
+
+void write_classc_md(const std::string& out_dir, const DebrisCatalog& zenit,
+ const DebrisCatalog& envisat) {
+ std::FILE* f = std::fopen((out_dir + "/wp13_classC.md").c_str(), "w");
+ if (!f) return;
+
+ double v_c_z = 0.0, v_c_e = 0.0;
+ const double dv_z = controlled_deorbit_dv_ms(zenit, &v_c_z);
+ const double dv_e = controlled_deorbit_dv_ms(envisat, &v_c_e);
+
+ std::fprintf(f, "# WP13 Class-C controlled-reentry mission-class comparison\n\n");
+ std::fprintf(f,
+ "Class C (spec WP13) is a **separate mission class**, not a sail/EDT "
+ "kit choice: massive/high-risk derelicts where the deliverable is a "
+ "controlled reentry, evaluated on casualty risk, ground footprint, "
+ "delta-v, consent, and cost. The comparison against tug architectures "
+ "is allowed **here only**, and only on the **same target set** (spec "
+ "WP13/WP14, instr Sec 0.4). Data source: `generated/wp13_classC.csv` "
+ "(schema %s), regenerated by `kit_trade` -- no hand-written numbers.\n\n",
+ kSchema);
+
+ std::fprintf(f, "## Candidates (same target set)\n\n");
+ std::fprintf(f, "| candidate | mass [kg] | altitude [km] | inclination [deg] | source |\n");
+ std::fprintf(f, "|---|---:|---:|---:|---|\n");
+ std::fprintf(f,
+ "| Zenit-2 / SL-16 second stage (`catalog_A`) | %.0f | %.0f | %.1f | "
+ "web-verified ~8300(-9000) kg, ~845x857 km, 71.01 deg (Tselina-2 "
+ "family), `wp13-literature.md` Topic 5a; also class A's "
+ "sail/EDT anchor -- massive by the same McKnight et al. 2021 top-50 "
+ "ranking that motivates class C |\n",
+ zenit.mass_kg, zenit.altitude_km, zenit.inclination_deg);
+ std::fprintf(f,
+ "| Envisat-class massive SSO payload (`catalog_C`) | %.0f | %.0f | %.1f | "
+ "web-verified 8211 kg, ~765-800 km sun-synchronous, ~98.4-98.55 deg, "
+ "eoPortal / ESA Earth Online, `wp13-literature.md` "
+ "Topic 6 item 1 |\n\n",
+ envisat.mass_kg, envisat.altitude_km, envisat.inclination_deg);
+
+ std::fprintf(f, "## Uncontrolled-reentry casualty framing (cited, not computed)\n\n");
+ std::fprintf(f,
+ "NASA-STD-8719.14A (with Change 1), Requirement 4.7-1: the expected "
+ "worldwide human-casualty risk from reentering debris shall not "
+ "exceed **Ec < 1e-4** (1 in 10,000); casualty is assumed for any "
+ "surviving fragment with impact kinetic energy **> 15 J**; a "
+ "controlled reentry must additionally ensure no surviving >15 J "
+ "fragment lands within **370 km of foreign landmasses** "
+ "(soma.larc.nasa.gov/SIMPLEx/pdf_files/871914.pdf; ESA applies the "
+ "same 1e-4 limit via DRAMA/SARA, technology.esa.int/page/"
+ "re-entry-safety -- both web-verified, "
+ "`wp13-literature.md` Topic 7). This repo does **not** "
+ "compute a per-object Ec for either candidate: that requires a "
+ "survivability / ground-footprint tool (NASA DAS/ORSAT or ESA "
+ "SARA class), out of scope here: "
+ "**[CITATION NEEDED - PLACEHOLDER: per-object Ec analysis]**. Both "
+ "candidates are multi-tonne, so an UNCONTROLLED reentry is the "
+ "presumptive risk driver the 370 km / Ec<1e-4 rule exists for; that "
+ "presumption is not itself a computed Ec value.\n\n");
+
+ std::fprintf(f, "## Controlled-deorbit delta-v (COMPUTED)\n\n");
+ std::fprintf(f,
+ "Single impulsive perigee-lowering burn from the catalog's circular "
+ "orbit (radius r_a = R_E + altitude) to a target perigee r_p = "
+ "6371 + 40 km (mean-Earth-radius convention for the reentry perigee, "
+ "distinct from the WGS-84 kEarthRadius used for r_a elsewhere in "
+ "this repo -- a deliberate, documented choice, not a silent "
+ "inconsistency): dv = v_c * (1 - sqrt(2 r_p / (r_a + r_p))), v_c = "
+ "sqrt(mu / r_a). This is the minimum single-burn delta-v to commit "
+ "the stage to reentry; it excludes targeting/footprint-control burns "
+ "and any margin. Full precision in `generated/wp13_classC.csv`.\n\n");
+ std::fprintf(f, "| candidate | v_c [m/s] | dv [m/s] |\n");
+ std::fprintf(f, "|---|---:|---:|\n");
+ std::fprintf(f, "| Zenit-2 / SL-16 (catalog_A) | %.1f | **%.1f** |\n", v_c_z, dv_z);
+ std::fprintf(f, "| Envisat-class (catalog_C) | %.1f | **%.1f** |\n\n", v_c_e, dv_e);
+
+ std::fprintf(f, "## Consent gate\n\n");
+ std::fprintf(f,
+ "Legal accessibility enters as a **gate and metadata flags, never a "
+ "multiplier** (D12): the WP8 compliance engine BLOCKs unconsented "
+ "active debris removal, and target prioritization consumes its "
+ "PASS/BLOCK output and flags rather than folding a subjective legal "
+ "weight into any score (`tools/compliance/check_compliance.py`, "
+ "policy `ADSC-POL-01`, consent-missing -> BLOCK). Any controlled-"
+ "reentry mission on either candidate above is subject to that same "
+ "gate: no consent, no operation, regardless of the casualty-risk or "
+ "delta-v numbers on this page.\n\n");
+
+ std::fprintf(f, "## Kit-installer vs tug (same target set)\n\n");
+ std::fprintf(f,
+ "Steelmanned: a tug architecture also amortizes fixed costs (launch, "
+ "ground segment, ops) across a batch of targets visited in one plane, "
+ "exactly like the kit-installer's own batch amortization (WP6 "
+ "amortization curve). The comparison is fair only because it is the "
+ "SAME target set (spec WP14 rule) -- comparing a tug against a "
+ "different, easier target set would be meaningless. What does NOT "
+ "amortize for a tug, and is the installer's structural edge (D1: "
+ "installer-not-tug): (1) a tug must carry propellant to change EACH "
+ "target's orbit itself, so its per-target delta-v cost scales with "
+ "target count, while an installer's kit-carrying stages do their own "
+ "deorbit after release -- the installer pays no per-target deorbit "
+ "delta-v; (2) a tug must detumble, dock/grapple, and physically tow "
+ "each multi-tonne stage through its own controlled-reentry burn, "
+ "repeating the highest-risk contact event per target, while an "
+ "installer's own contact event is bounded by the WP3 low-energy "
+ "capture approach (0.333 J contact-energy budget at 0.15 m/s, "
+ "README/evidence pack Section on WP3) and does not itself carry a "
+ "target through reentry. Both architectures still need SOME per-"
+ "target consent/compliance gate (D12) and SOME reentry delta-v paid "
+ "by someone (either pre-installed as a kit, or delivered by the tug) "
+ "-- the installer's edge is where that delta-v and that repeated "
+ "high-risk contact event are paid, not that they vanish.\n\n");
+
+ std::fprintf(f, "## Cost\n\n");
+ std::fprintf(f, "| candidate | cost | source |\n");
+ std::fprintf(f, "|---|---|---|\n");
+ std::fprintf(f, "| Zenit-2 / SL-16 (catalog_A) | PLACEHOLDER until WP14 | WP14 cost-range itemization |\n");
+ std::fprintf(f, "| Envisat-class (catalog_C) | PLACEHOLDER until WP14 | WP14 cost-range itemization |\n\n");
+
+ std::fprintf(f, "## `wp13_classC.csv` columns\n\n");
+ std::fprintf(f, "| column | meaning |\n");
+ std::fprintf(f, "|---|---|\n");
+ std::fprintf(f, "| schema_version | class-C schema id (`%s`) |\n", kSchema);
+ std::fprintf(f, "| candidate | candidate label (Zenit-2 stage / Envisat-class) |\n");
+ std::fprintf(f, "| mass_kg, altitude_km, inclination_deg | catalog parameters (constant per candidate) |\n");
+ std::fprintf(f, "| metric | v_circular_initial / target_perigee_radius / controlled_deorbit_dv |\n");
+ std::fprintf(f, "| value | metric value at full precision (the md table above rounds to 0.1) |\n");
+ std::fprintf(f, "| units | m/s or km |\n");
+ std::fprintf(f, "| notes | formula / provenance |\n");
+
+ std::fclose(f);
+}
+
+} // namespace
+
+int main(int argc, char** argv) {
+ const std::string out_dir = (argc > 1) ? argv[1] : "generated";
+ std::error_code ec;
+ std::filesystem::create_directories(out_dir, ec);
+
+ const Config cfg;
+ const EdtConfig ecfg;
+ const double stop_km = cfg.reentry_handoff_altitude_km;
+
+ const DebrisCatalog A = catalog_A();
+ const DebrisCatalog B = catalog_B();
+ const DebrisCatalog C = catalog_C();
+
+ std::FILE* f = std::fopen((out_dir + "/wp13_kit_trade.csv").c_str(), "w");
+ if (!f) return 1;
+ std::fprintf(f,
+ "schema_version,catalog,mass_kg,altitude_km,inclination_deg,record_type,"
+ "kit_option,value,value_lo,value_hi,units,notes\n");
+
+ // --- Catalog A: SL-16/Zenit-2 -- sail infeasible, EDT candidate with
+ // open risks (T7 libration, plasma Ne PLACEHOLDER). -------------------
+ {
+ const double a25_max = area_for_target_years(A, 25.0, cfg.kit_mass_kg,
+ cfg.drag_cd, stop_km, cfg.solar_max_density_factor);
+ const double a25_min = area_for_target_years(A, 25.0, cfg.kit_mass_kg,
+ cfg.drag_cd, stop_km, cfg.solar_min_density_factor);
+ const EdtResult edt = edt_deorbit_years(A, ecfg, stop_km);
+ write_common_rows(f, A, cfg, ecfg, a25_max, a25_min, edt);
+
+ char notes[512];
+ std::snprintf(notes, sizeof(notes),
+ "sail infeasible (%.0f..%.0f m2 for the 25-yr guideline, solar "
+ "max..min); EDT (band %.1f..%.1f yr) subject to T7; libration "
+ "(T7, Pelaez et al. 2000) and plasma Ne (PLACEHOLDER solar-min/"
+ "max pair, wp13-literature.md Topic 3) remain open -- an honest "
+ "EDT candidate, not a closed recommendation",
+ a25_max, a25_min, edt.years_optimistic, edt.years_conservative);
+ std::fprintf(f,
+ "%s,%s,%.1f,%.1f,%.1f,recommended_kit,edt-candidate-open-risks,"
+ "0.0,0.0,0.0,[-],\"%s\"\n",
+ kSchema, A.name, A.mass_kg, A.altitude_km, A.inclination_deg, notes);
+ }
+
+ // --- Catalog B: SL-8/Kosmos-3M -- sail closes, no EDT needed. -----------
+ {
+ const double a25_max = area_for_target_years(B, 25.0, cfg.kit_mass_kg,
+ cfg.drag_cd, stop_km, cfg.solar_max_density_factor);
+ const double a25_min = area_for_target_years(B, 25.0, cfg.kit_mass_kg,
+ cfg.drag_cd, stop_km, cfg.solar_min_density_factor);
+ const EdtResult edt = edt_deorbit_years(B, ecfg, stop_km);
+ write_common_rows(f, B, cfg, ecfg, a25_max, a25_min, edt);
+
+ char notes[256];
+ std::snprintf(notes, sizeof(notes),
+ "sail closes (%.0f..%.0f m2 for the 25-yr guideline, solar "
+ "max..min); no EDT kit needed for this class",
+ a25_max, a25_min);
+ std::fprintf(f,
+ "%s,%s,%.1f,%.1f,%.1f,recommended_kit,sail,0.0,0.0,0.0,[-],\"%s\"\n",
+ kSchema, B.name, B.mass_kg, B.altitude_km, B.inclination_deg, notes);
+ }
+
+ // --- Catalog C: Envisat-class -- controlled-reentry mission class,
+ // not a sail/EDT kit recommendation (see wp13_classC.md). -----------
+ {
+ const double a25_max = area_for_target_years(C, 25.0, cfg.kit_mass_kg,
+ cfg.drag_cd, stop_km, cfg.solar_max_density_factor);
+ const double a25_min = area_for_target_years(C, 25.0, cfg.kit_mass_kg,
+ cfg.drag_cd, stop_km, cfg.solar_min_density_factor);
+ const EdtResult edt = edt_deorbit_years(C, ecfg, stop_km);
+ write_common_rows(f, C, cfg, ecfg, a25_max, a25_min, edt);
+
+ std::fprintf(f,
+ "%s,%s,%.1f,%.1f,%.1f,recommended_kit,controlled-reentry-mission-class,"
+ "0.0,0.0,0.0,[-],\"massive (%.0f kg) high-inclination payload -- "
+ "sail/EDT kit-only deorbit is not the recommended mission class; "
+ "see generated/wp13_classC.md for the controlled-reentry "
+ "comparison\"\n",
+ kSchema, C.name, C.mass_kg, C.altitude_km, C.inclination_deg,
+ C.mass_kg);
+ }
+
+ std::fclose(f);
+
+ write_kit_trade_schema_md(out_dir);
+ write_classc_csv(out_dir, A, C);
+ write_classc_md(out_dir, A, C);
+
+ std::printf("[WP13] wrote %s/wp13_kit_trade.csv, wp13_kit_trade_schema.md, "
+ "wp13_classC.csv and wp13_classC.md (3 catalogs, stop %.0f km)\n",
+ out_dir.c_str(), stop_km);
+ return 0;
+}
diff --git a/tests/test_decay.cpp b/tests/test_decay.cpp
index 7cc5209..feb2778 100644
--- a/tests/test_decay.cpp
+++ b/tests/test_decay.cpp
@@ -89,6 +89,100 @@ int main() {
CHECK(d2 < d1);
}
+ // 7. EDT physics core (WP13): eta(i) = |cos i| (optimistic) / cos(i)^2
+ // (conservative), eta(0) = 1, matching the derivation-note table
+ // (wp13-edt-derivation.md Section 8.3).
+ {
+ const DebrisCatalog equatorial{"eta-test equatorial", 9000.0, 840.0, 0.0, false};
+ const EdtConfig cfg{};
+ const EdtResult eq = edt_deorbit_years(equatorial, cfg, 180.0);
+ CHECK(std::abs(eq.eta_hi - 1.0) < 1e-12);
+ CHECK(std::abs(eq.eta_lo - 1.0) < 1e-12);
+
+ const EdtResult a71 = edt_deorbit_years(catalog_A(), cfg, 180.0);
+ CHECK(std::abs(a71.eta_hi - 0.3255681545) < 1e-6);
+ CHECK(std::abs(a71.eta_lo - 0.1059946232) < 1e-6);
+ }
+
+ // 8. EDT deorbit-time band: the conservative (EMF-limited, cos^2 i) edge
+ // always takes longer than the optimistic (power-capped, cos i) edge
+ // for a non-equatorial catalog -- never collapse to a point value.
+ {
+ const EdtResult a = edt_deorbit_years(catalog_A(), EdtConfig{}, 180.0);
+ CHECK(std::isfinite(a.years_optimistic));
+ CHECK(std::isfinite(a.years_conservative));
+ CHECK(a.years_conservative > a.years_optimistic);
+ }
+
+ // 9. Cross-check: the public power-law EDT integrator against the
+ // elementary closed form t = m*sqrt(mu)*(a0^2.5 - as^2.5) / (5*C) for
+ // F(a) = C / a^3 (aligned-dipole B_n ~ a^-3, wp13-edt-derivation.md
+ // Section 8.1), and against edt_deorbit_years' own optimistic-edge
+ // output for the SAME physical constants (verifies the algebra AND the
+ // wiring, mirroring CHECK 1's integrator-vs-closed-form pattern).
+ {
+ const double kB0_T = 3.01153e-5; // SPENVIS centred dipole, IGRF 2000
+ const double kDipoleRefRadius = 6371200.0; // m, SPENVIS centred-dipole radius
+ const double kSecPerYear = 365.25 * 86400.0;
+
+ const DebrisCatalog A = catalog_A();
+ const EdtConfig cfg{};
+ const double cos_i = std::cos(A.inclination_deg * kPi / 180.0);
+ const double a0 = kEarthRadius + A.altitude_km * 1000.0;
+ const double a_stop = kEarthRadius + 180.0 * 1000.0;
+ const double m = A.mass_kg + cfg.kit_mass_kg;
+ const double C_hi = cfg.avg_current_a * cfg.tether_length_m * kB0_T *
+ std::pow(kDipoleRefRadius, 3.0) * cos_i * cfg.eta_libration;
+
+ const double t_num = integrate_edt_seconds(
+ a0, a_stop, m, [C_hi](double a) { return C_hi / (a * a * a); });
+ const double t_closed = m * std::sqrt(kEarthMu) *
+ (std::pow(a0, 2.5) - std::pow(a_stop, 2.5)) / (5.0 * C_hi);
+ CHECK(std::abs(t_num - t_closed) / t_closed < 1e-6);
+
+ const EdtResult r = edt_deorbit_years(A, cfg, 180.0);
+ const double closed_years = t_closed / kSecPerYear;
+ CHECK(std::abs(r.years_optimistic - closed_years) / closed_years < 1e-6);
+ }
+
+ // 10. Monotonicity: years increase (deorbit is slower) as inclination
+ // departs from equatorial, matching eta(i)'s monotonic decrease.
+ {
+ const DebrisCatalog A = catalog_A();
+ DebrisCatalog A_equatorial = A;
+ A_equatorial.inclination_deg = 0.0;
+ const EdtConfig cfg{};
+ const EdtResult incl = edt_deorbit_years(A, cfg, 180.0);
+ const EdtResult equ = edt_deorbit_years(A_equatorial, cfg, 180.0);
+ CHECK(incl.years_optimistic > equ.years_optimistic);
+ CHECK(incl.years_conservative > equ.years_conservative);
+ }
+
+ // 11. Polar orbit: the aligned-dipole orbit-normal field is exactly zero
+ // (wp13-edt-derivation.md Section 6, limit L3) -> infinite deorbit
+ // time, reported honestly rather than as a divide-by-zero artifact.
+ {
+ const DebrisCatalog polar{"eta-test polar", 9000.0, 840.0, 90.0, false};
+ const EdtResult p = edt_deorbit_years(polar, EdtConfig{}, 180.0);
+ CHECK(std::isinf(p.years_optimistic));
+ CHECK(std::isinf(p.years_conservative));
+ }
+
+ // 12. Retrograde orbit (catalog_C, Envisat-class at 98.4 deg): the passive
+ // tether's induced current always drags (Lenz), so the force magnitude
+ // scales with |cos i| and the deorbit time is FINITE -- only an
+ // exactly-polar orbit nulls the aligned-dipole coupling. eta_hi is the
+ // magnitude |cos 98.4 deg|, eta_lo its square.
+ {
+ const EdtResult r = edt_deorbit_years(catalog_C(), EdtConfig{}, 180.0);
+ CHECK(std::abs(r.eta_hi - 0.1460830286) < 1e-6);
+ CHECK(std::abs(r.eta_lo - 0.0213402512) < 1e-6);
+ CHECK(!std::isinf(r.years_optimistic));
+ CHECK(!std::isinf(r.years_conservative));
+ CHECK(r.years_conservative > r.years_optimistic);
+ CHECK(r.emf_v > 0.0); // reported as a magnitude (polarity note in docs)
+ }
+
std::printf("decay: all tests passed\n");
return 0;
}
diff --git a/tools/evidence/make_evidence.py b/tools/evidence/make_evidence.py
index 2d4c67c..51159a1 100644
--- a/tools/evidence/make_evidence.py
+++ b/tools/evidence/make_evidence.py
@@ -54,6 +54,8 @@ def __init__(self, root):
self.wp6 = load_csv(os.path.join(g, "wp6_cost_summary.csv"))
self.wp3 = load_csv(os.path.join(g, "wp3_decay_trade.csv"))
self.wp12 = load_csv(os.path.join(g, "wp12_ladder.csv"))
+ self.wp13 = load_csv(os.path.join(g, "wp13_kit_trade.csv"))
+ self.wp13c = load_csv(os.path.join(g, "wp13_classC.csv"))
with open(os.path.join(g, "t6_flux_sweep.md"), encoding="utf-8") as f:
self.t6 = {}
for line in f:
@@ -66,6 +68,14 @@ def __init__(self, root):
for r in self.wp5:
if r["catalog"] not in self.catalogs:
self.catalogs.append(r["catalog"])
+ self.wp13_catalogs = []
+ for r in self.wp13:
+ if r["catalog"] not in self.wp13_catalogs:
+ self.wp13_catalogs.append(r["catalog"])
+ self.wp13c_candidates = []
+ for r in self.wp13c:
+ if r["candidate"] not in self.wp13c_candidates:
+ self.wp13c_candidates.append(r["candidate"])
def wp5m(self, catalog, metric):
for r in self.wp5:
@@ -97,6 +107,25 @@ def wp3_area25(self, catalog):
return float(r["value_solar_max"]), float(r["value_solar_min"])
raise KeyError(catalog)
+ def wp13v(self, catalog, record_type, kit_option=None):
+ """One row from generated/wp13_kit_trade.csv (WP13 kit-class trade +
+ EDT-v1 physics). kit_option disambiguates only where a (catalog,
+ record_type) pair has more than one row (e.g. kit_mass: sail vs
+ edt); every other record_type has exactly one row per catalog."""
+ for r in self.wp13:
+ if (r["catalog"] == catalog and r["record_type"] == record_type
+ and (kit_option is None or r["kit_option"] == kit_option)):
+ return r
+ raise KeyError((catalog, record_type, kit_option))
+
+ def wp13cv(self, candidate, metric):
+ """One row from generated/wp13_classC.csv (WP13 class-C controlled-
+ reentry mission comparison)."""
+ for r in self.wp13c:
+ if r["candidate"] == candidate and r["metric"] == metric:
+ return r
+ raise KeyError((candidate, metric))
+
def rate_str(row):
"""'0.556 [0.512, 0.599] (Wilson 95%)' from a WP5 rate row."""
@@ -124,6 +153,19 @@ def pctl_str(row, dp=2):
fmt(row["p05"]), fmt(row["p50"]), fmt(row["p95"]))
+def edt_band_str(row, fmt):
+ """'lo..hi' from a wp13_kit_trade.csv value_lo/value_hi band, formatted
+ with fmt -- or the literal text verbatim (e.g. an exactly-polar catalog's
+ 'n/a (polar: aligned-dipole avg force -> 0)') when the field is not a
+ number: that text is the honest physical null result, not missing data
+ (see wp13_kit_trade_schema.md's edt_years row)."""
+ lo, hi = row["value_lo"], row["value_hi"]
+ try:
+ return "%s..%s" % (fmt(float(lo)), fmt(float(hi)))
+ except ValueError:
+ return "%s .. %s" % (lo, hi)
+
+
def collect_placeholders(root):
"""Mechanically collect every uppercase PLACEHOLDER mark from the source
tree (include/, src/, tools/ minus tools/evidence which only names the
@@ -530,6 +572,74 @@ def build(d):
w(" with a deliberately coarse altitude-independent solar factor marked")
w(" PLACEHOLDER.")
w("")
+ w("### Kit-class trade + EDT physics (WP13)")
+ w("")
+ w("Data source: `generated/wp13_kit_trade.csv` (schema %s; column"
+ % d.wp13[0]["schema_version"])
+ w("definitions in `generated/wp13_kit_trade_schema.md`), regenerated by")
+ w("`kit_trade` (src/main_kit_trade.cpp) by combining the EXISTING WP3")
+ w("sail-decay model with the NEW WP13 EDT-v1 aligned-dipole physics core")
+ w("(src/decay.cpp: edt_deorbit_years) -- no hand-written numbers.")
+ w("")
+ w("| catalog | recommended kit | sail area, 25-yr [m^2] | EDT deorbit time [yr] | EDT eta(i) band |")
+ w("|---|---|---:|---:|---:|")
+ for cat in d.wp13_catalogs:
+ rk = d.wp13v(cat, "recommended_kit")
+ sail = d.wp13v(cat, "sail_area_25yr")
+ edt = d.wp13v(cat, "edt_years")
+ eta = d.wp13v(cat, "edt_eta")
+ w("| %s | %s | %s | %s | %s |" % (
+ cat, rk["kit_option"], edt_band_str(sail, f0),
+ edt_band_str(edt, f1), edt_band_str(eta, f3)))
+ w("")
+ edt_tag = d.wp13v(A, "edt_years")["notes"]
+ w("**EDT-v1 model scope (R14 fidelity tag), quoted verbatim from the")
+ w("committed CSV -- never hand-transcribed, so the generator and the data")
+ w("cannot drift:**")
+ w("")
+ w("> %s" % edt_tag)
+ w("")
+ deploy = d.wp13v(A, "deploy_risk")
+ rk_A = d.wp13v(A, "recommended_kit")
+ w("**Honesty check (T7, D12/D13 - never claim closed).** Libration /")
+ w("dynamic tether stability is explicitly UNRESOLVED (T7; Pelaez, J., et")
+ w("al., 2000, cited in `wp13-literature.md`); the edt_years band above")
+ w("folds it in only as a flat PLACEHOLDER duty-cycle knob")
+ w("(`EdtConfig::eta_libration`), never claimed solved. Plasma electron")
+ w("density is a cited PLACEHOLDER solar-min/max pair, not an IRI")
+ w("implementation (spec non-goal; `wp13-literature.md` Topic 3 citation")
+ w("gap). Every band above is an aligned-dipole idealization (SPENVIS")
+ w("centred dipole, IGRF epoch 2000), not a tilted-dipole or full-IGRF")
+ w("field. Deploy risk is reported, not applied: deploy_risk = %s [-]"
+ % f4(deploy["value"]))
+ w("(PLACEHOLDER `EdtConfig::deploy_failure_prob`) is quoted for the")
+ w("record and is NOT folded into the edt_years band above. Bottom line,")
+ w("EDT is a candidate for the %s class, not a closed recommendation --"
+ % Ashort)
+ w("quoted verbatim from the committed CSV's own recommended-kit")
+ w("rationale: \"%s\"" % rk_A["notes"])
+ w("")
+ w("**Class C (controlled reentry, a separate mission class - not a")
+ w("sail/EDT kit choice).** Massive/high-risk derelicts where the")
+ w("deliverable is a controlled reentry, evaluated on casualty risk,")
+ w("ground footprint, delta-v, consent and cost in")
+ w("`generated/wp13_classC.md` (data: `generated/wp13_classC.csv`, schema")
+ w("%s), not on a sail/EDT kit trade. Computed controlled-deorbit delta-v"
+ % d.wp13c[0]["schema_version"])
+ w("(single impulsive perigee-lowering burn from the catalog's circular")
+ w("orbit to a target perigee of 6371+40 km):")
+ w("")
+ w("| candidate | v_c [m/s] | delta-v [m/s] |")
+ w("|---|---:|---:|")
+ for cand in d.wp13c_candidates:
+ vc = d.wp13cv(cand, "v_circular_initial")
+ dv = d.wp13cv(cand, "controlled_deorbit_dv")
+ w("| %s | %s | %s |" % (cand, f1(vc["value"]), f1(dv["value"])))
+ w("")
+ w("Cross-reference: the eta(i) closed-form derivation is")
+ w("`wp13-edt-derivation.md`; the citation pack is `wp13-literature.md`")
+ w("(both committed at the repo root).")
+ w("")
w("## 5. Campaign statistics and cost/FoM")
w("")
w("WP5 Monte Carlo: %s dispersed missions per catalog at fixed master seed"
diff --git a/tools/evidence/test_evidence.py b/tools/evidence/test_evidence.py
index 83e0228..3aa3be2 100644
--- a/tools/evidence/test_evidence.py
+++ b/tools/evidence/test_evidence.py
@@ -107,11 +107,21 @@ def main():
(r"ds-v2", "L2 ladder re-verification (ds-v2 stream)"),
(r"\[L4: L0 dynamics \+ dropout",
"L4 estimate-driven guidance"),
- (r"\[L5: MIB/delay/fault", "L5 actuator realization")):
+ (r"\[L5: MIB/delay/fault", "L5 actuator realization"),
+ (r"\[model: EDT-v1 aligned-dipole",
+ "EDT-v1 model-scope tag")):
check(re.search(tag, pack) is not None,
"pack lacks R14-tagged headline claim: %s" % why)
check("element" in pack[pack.find("TRL 4"):pack.find("TRL 4") + 200],
"TRL 4 statement is not element-scoped")
+ # T7 honesty check (WP13): libration/dynamic tether stability must be
+ # flagged as an open, unresolved risk -- never claimed solved.
+ check("libration" in low, "pack lacks 'libration' (T7 honesty check)")
+ check(re.search(r"\bT7\b", pack) is not None,
+ "pack lacks a 'T7' reference (T7 honesty check)")
+ check(re.search(r"UNRESOLVED", pack) is not None or
+ re.search(r"\bOPEN\b", pack) is not None,
+ "pack lacks 'UNRESOLVED' or 'OPEN' for the T7 libration caveat")
# citation discipline: the pack either cites fully or marks the gap -
# the marker must exist for the knowingly-unconfirmed external sources.
check("[CITATION NEEDED - PLACEHOLDER" in pack,
@@ -167,6 +177,18 @@ def must_contain(s, why):
must_contain("%.0f..%.0f m^2" % (float(r["value_solar_max"]),
float(r["value_solar_min"])),
"25-yr sail area range for %s" % r["catalog"])
+ # WP13 EDT-v1 deorbit-time band for the SL-16 / Zenit-2 catalog, formatted
+ # exactly as make_evidence.py's edt_band_str(row, f1) formats it.
+ wp13 = load_csv(os.path.join(g, "wp13_kit_trade.csv"))
+ for r in wp13:
+ if (r["catalog"] == "SL-16 / Zenit-2 second stage"
+ and r["record_type"] == "edt_years"):
+ must_contain("%.1f..%.1f" % (float(r["value_lo"]),
+ float(r["value_hi"])),
+ "SL-16 EDT-v1 deorbit-time band (WP13)")
+ break
+ else:
+ check(False, "SL-16 edt_years row not found in wp13_kit_trade.csv")
# rates with Wilson interval formatting
for metric in ("success_rate", "keep_out_violation_rate",
"gate_abort_run_rate"):
diff --git a/tools/regenerate_all.sh b/tools/regenerate_all.sh
index 63cb3c4..357367e 100644
--- a/tools/regenerate_all.sh
+++ b/tools/regenerate_all.sh
@@ -13,25 +13,27 @@ set -euo pipefail
cd "$(dirname "$0")/.." # repo root
BUILD="${1:-build}"
-echo "[1/10] WP5 campaign Monte Carlo (N=500 x 2 catalogs)"
+echo "[1/11] WP5 campaign Monte Carlo (N=500 x 2 catalogs)"
"$BUILD/adsc_campaign" 500 generated
-echo "[2/10] WP12 fidelity ladder (L0/L1/L2 re-verification of the WP5 abort events)"
+echo "[2/11] WP12 fidelity ladder (L0/L1/L2 re-verification of the WP5 abort events)"
"$BUILD/adsc_ladder" 500 generated
-echo "[3/10] WP6 cost model + FoM (kit sweep, consumes WP5)"
+echo "[3/11] WP6 cost model + FoM (kit sweep, consumes WP5)"
"$BUILD/adsc_cost" 500 generated
-echo "[4/10] WP3 decay-trade CSV"
+echo "[4/11] WP3 decay-trade CSV"
"$BUILD/decay_trade" generated
-echo "[5/10] T6 small-debris flux sweep"
+echo "[5/11] WP13 kit-class trade + class-C controlled-reentry comparison"
+"$BUILD/kit_trade" generated
+echo "[6/11] T6 small-debris flux sweep"
"$BUILD/flux_sweep" generated
-echo "[6/10] WP7 reference metrics (WP1/F1/WP2/WP3/WP4 pinned numbers)"
+echo "[7/11] WP7 reference metrics (WP1/F1/WP2/WP3/WP4 pinned numbers)"
"$BUILD/sim_metrics" generated
-echo "[7/10] WP10c keep-out violation forensics (read-only replay of WP5)"
+echo "[8/11] WP10c keep-out violation forensics (read-only replay of WP5)"
python3 tools/forensics/make_forensics.py
-echo "[8/10] WP7a visualization pack"
+echo "[9/11] WP7a visualization pack"
python3 tools/viz/make_viz.py . generated/viz
-echo "[9/10] WP8 compliance precheck + matrix (not legal advice)"
+echo "[10/11] WP8 compliance precheck + matrix (not legal advice)"
python3 tools/compliance/check_compliance.py
python3 tools/compliance/generate_matrix.py
-echo "[10/10] WP7 evidence pack"
+echo "[11/11] WP7 evidence pack"
python3 tools/evidence/make_evidence.py
echo "regenerate_all: complete"
diff --git a/wp13-edt-derivation.md b/wp13-edt-derivation.md
new file mode 100644
index 0000000..a948344
--- /dev/null
+++ b/wp13-edt-derivation.md
@@ -0,0 +1,345 @@
+# WP13 Annex — Orbit-Averaged Inclination Efficiency eta(i) for a Nadir-Aligned Electrodynamic Tether
+
+Status: derivation for review (Fable5 reviews before coding, WP13 task-2).
+Scope: closed-form derivation + deterministic quadrature cross-check of the
+mandatory inclination-dependent v x B efficiency (spec:247-251, instr 0.1).
+All math ASCII; every step reproducible. Numerics cross-checked by a 64-node
+Gauss-Legendre quadrature (script at end; results pinned in Section 8).
+
+--------------------------------------------------------------------------
+## 0. Result up front (for the impatient reviewer)
+
+For a tether deployed along the local vertical (radial / nadir-zenith) in an
+Earth-centered ALIGNED-dipole field, on a circular orbit of radius r and
+inclination i, the orbit-averaged along-track (drag) efficiency is CLOSED FORM:
+
+ Fixed-current model (I held at a power/limit-capped value):
+ eta_F(i) = cos(i)
+
+ Self-consistent model (current scales with the motional EMF, I ~ EMF):
+ eta_SC(i) = cos(i)^2
+
+both normalized so eta(0) = 1. No quadrature is actually required for the
+aligned dipole; the 64-node Gauss-Legendre integration in Section 8 reproduces
+these to 2.2e-16 and exists only as a cross-check and as the general-field
+harness (a future tilted dipole reintroduces u-dependence).
+
+Why so clean: for a radial tether the ONLY field component that produces an
+along-track Lorentz force -- and the only one that appears in the radial
+tether's motional EMF -- is the orbit-normal component B_n, and for an aligned
+dipole B_n = B0 (RE/r)^3 cos(i) is CONSTANT around the orbit. The u-varying
+components B_r and B_t cancel out of the along-track projection.
+
+--------------------------------------------------------------------------
+## 1. Assumptions and caveats (stated, not hidden)
+
+A1. Circular orbit, radius r, inclination i, argument of latitude u measured
+ from the ascending node. Orbital speed v = sqrt(GM/r), inertial.
+A2. Tether along the local vertical: t_hat = e_r (radial, zenith-nadir).
+ Rigid, straight; libration IGNORED here (T7 open -- separate penalty knob,
+ instr 0.2; never claimed solved).
+A3. Geomagnetic field = Earth-centered ALIGNED dipole: moment along the Earth
+ rotation / geographic axis z_hat.
+ CAVEAT (tilt): the real dipole is tilted ~11.5 deg (and offset). The tilt
+ rotates B_n by a slowly varying phase and, critically, makes eta(90 deg)
+ small-but-nonzero instead of exactly zero (Section 6). Treat the aligned
+ result as the leading-order geometry; carry tilt as an uncertainty, not a
+ correction baked into v1.
+A4. Non-rotating plasma / inertial frame for the motional EMF: the corotation
+ electric field E_corot = -(Omega_E x r) x B is IGNORED.
+ CAVEAT (corotation): at LEO v_orbit ~ 7.6 km/s while the corotation speed
+ ~0.4 km/s, so the neglected term is ~5% of the EMF and is largest near the
+ equator; it does not change the cos(i) geometry, only the scalar prefactor.
+ Fold into the current-efficiency band, do not model explicitly in v1.
+A5. B0 = mu0 m /(4 pi RE^3) ~ 3.12e-5 T is the equatorial surface field.
+ Define the shorthand beta(r) = B0 (RE/r)^3.
+
+--------------------------------------------------------------------------
+## 2. Aligned-dipole field in the orbital frame
+
+### 2.1 Field vector form
+A magnetic dipole of moment m_vec = m * m_hat produces, at position R,
+
+ B = (mu0 m / 4 pi R^3) [ 3 (m_hat . r_hat) r_hat - m_hat ].
+
+For Earth the external field points NORTH at the equator, which requires the
+moment to point SOUTH: m_hat = -z_hat. With beta = B0 (RE/r)^3,
+
+ B = beta [ 3 (-z_hat . r_hat) r_hat - (-z_hat) ]
+ = beta [ z_hat - 3 (z_hat . r_hat) r_hat ]. (2.1)
+
+Sanity: at the equator z_hat . r_hat = 0 => B = beta z_hat (northward). OK.
+
+### 2.2 Orbital frame unit vectors
+Place the ascending node on the x-axis (RAAN = 0; RAAN only rotates the whole
+picture about z and drops out of orbit averages). At argument of latitude u:
+
+ r_hat = ( cos u , sin u cos i , sin u sin i ) (radial out)
+ e_t = d r_hat/du = ( -sin u , cos u cos i , cos u sin i ) (along-track)
+ e_n = r_hat x e_t = ( 0 , -sin i , cos i ) (orbit normal)
+
+e_n is constant (fixed orbit plane), as it must be. (e_r, e_t, e_n) is a
+right-handed triad: e_r x e_t = e_n, e_t x e_n = e_r, e_n x e_r = e_t.
+
+The z-projections we will need:
+ z_hat . r_hat = sin i sin u (= cos(colatitude) = sin(mag. latitude))
+ z_hat . e_t = sin i cos u
+ z_hat . e_n = cos i
+
+Note the magnetic latitude relation sin(lambda) = sin i sin u (spherical
+triangle: a great circle inclined at i has latitude lambda at angle u from the
+node with sin lambda = sin i sin u).
+
+### 2.3 Components (project 2.1 onto the triad)
+Using B . a = beta [ (z_hat.a) - 3 (z_hat.r_hat)(r_hat.a) ] and r_hat.e_t =
+r_hat.e_n = 0, r_hat.r_hat = 1:
+
+ B_r = B . e_r = beta [ sin i sin u - 3 sin i sin u ] = -2 beta sin i sin u
+ B_t = B . e_t = beta [ sin i cos u - 0 ] = beta sin i cos u
+ B_n = B . e_n = beta [ cos i - 0 ] = beta cos i (2.3)
+
+ B_r = -2 B0 (RE/r)^3 sin i sin u (radial)
+ B_t = B0 (RE/r)^3 sin i cos u (along-track)
+ B_n = B0 (RE/r)^3 cos i (orbit-normal, CONSTANT in u)
+
+The B_r form matches the value given in the task statement.
+
+### 2.4 Consistency check vs textbook spherical components
+Geomagnetic spherical components (theta = colatitude): B_r = -2 beta cos theta,
+B_theta = -beta sin theta, B_phi = 0. With cos theta = sin i sin u this gives
+B_r = -2 beta sin i sin u (matches 2.3). The tangential-plane magnitude must be
+frame-independent:
+
+ B_theta^2 + B_phi^2 = beta^2 sin^2 theta = beta^2 (1 - sin^2 i sin^2 u)
+ B_t^2 + B_n^2 = beta^2 (sin^2 i cos^2 u + cos^2 i) = beta^2 (1 - sin^2 i sin^2 u)
+
+They agree identically. Full magnitude: |B| = beta sqrt(1 + 3 sin^2 i sin^2 u)
+= beta sqrt(1 + 3 sin^2 lambda). OK.
+
+--------------------------------------------------------------------------
+## 3. (a) Motional EMF along the radial tether
+
+Per unit length the motional field is (v x B); projected on the tether
+direction t_hat = e_r it drives the current:
+
+ E_m(u) = (v x B) . e_r , v = v e_t .
+
+Compute v x B with v = v e_t and B = B_r e_r + B_t e_t + B_n e_n:
+
+ v x B = v [ B_r (e_t x e_r) + B_n (e_t x e_n) ]
+ = v [ -B_r e_n + B_n e_r ] (2.2 triad rules)
+
+Projection on e_r:
+
+ E_m(u) = v B_n = v beta cos i = v B0 (RE/r)^3 cos i. (3.1)
+
+KEY: the radial-tether EMF sees ONLY B_n. It is constant around the orbit and
+scales as cos i. The open-circuit tether voltage is V_emf = E_m * L =
+v B0 (RE/r)^3 cos i * L.
+
+--------------------------------------------------------------------------
+## 4. (b) Lorentz drag force for radial current
+
+Current I flows along the tether, so the current element is I L e_r. The force
+
+ F = I L (e_r x B)
+ = I L [ B_t (e_r x e_t) + B_n (e_r x e_n) ]
+ = I L [ B_t e_n - B_n e_t ]. (4.1)
+
+Two components:
+ - Along-track (drag / energy): F_t = F . e_t = -I L B_n = -I L beta cos i
+ - Orbit-normal (out-of-plane): F_n = F . e_t? no: F_n = F . e_n = I L B_t
+ = I L beta sin i cos u
+
+The sign of I is fixed by the passive EMF-driven current so that F_t opposes v
+(drag). The DRAG magnitude that removes orbital energy is
+
+ |F_t| = I L B_n = I L B0 (RE/r)^3 cos i. (4.2)
+
+The out-of-plane F_n = I L beta sin i cos u averages to zero over an orbit and,
+being perpendicular to v, does ZERO work: it perturbs node/inclination at
+second order but does NOT drive secular semimajor-axis decay. Only F_t enters
+the deorbit rate (Gauss planetary eq.: da/dt = (2/n) * (F_t / m) for a
+circular orbit, i.e. da/dt is proportional to the along-track force).
+
+--------------------------------------------------------------------------
+## 5. (c) Same geometry factor; definition of eta(i)
+
+From (3.1) and (4.2), BOTH the EMF projection and the along-track force
+projection carry the identical field-geometry factor
+
+ B_n = beta cos i (the orbit-normal field, constant over u).
+
+This is the whole content of the "inclination-dependent v x B efficiency": a
+radial tether couples to the orbit-normal field only, and for an aligned dipole
+that projection is cos i.
+
+Define eta(i) = (orbit-averaged secular drag at i) / (same at i = 0), so
+eta(0) = 1. Let < . > = (1/2pi) integral_0^{2pi} ( . ) du.
+
+### 5.1 Model (i): FIXED current (force-averaged)
+I is set by the power budget / hardware current cap and held constant vs i.
+Secular drag ~ < |F_t| > = I L beta < |cos i| >. Since B_n is constant and
+single-signed over the orbit (no cancellation),
+
+ eta_F(i) = < |B_n(u,i)| > / < |B_n(u,0)| > = |cos i| / 1 = cos i. (5.1)
+
+### 5.2 Model (ii): SELF-CONSISTENT current (I ~ EMF)
+When the current is EMF/collection-limited rather than power-capped, I(u) is
+proportional to the local EMF. In the linear (resistively-limited or
+short-circuit) regime I ~ E_m ~ v B_n. The secular drag is then the orbit
+average of the PRODUCT (EMF factor) x (force factor):
+
+ F_t ~ I * B_n ~ B_n^2 , so
+ eta_SC(i) = < B_n(u,i)^2 > / < B_n(u,0)^2 > = cos^2 i. (5.2)
+
+General statement (holds for a future u-dependent B_n, e.g. tilted dipole):
+
+ eta_F(i) = < |B_n(u,i)| > / < |B_n(u,0)| >
+ eta_SC(i) = < EMF(u,i) |B_n(u,i)| > / < EMF(u,0) |B_n(u,0)| >
+ = < B_n(u,i)^2 > / < B_n(u,0)^2 > (EMF ~ B_n).
+
+For the aligned dipole B_n is u-independent and these collapse to cos i and
+cos^2 i respectively. A nonlinear OML law I ~ EMF^(3/2) would give an
+intermediate power ~ cos^(1+3/2)=cos^2.5 i locally, but bracketed by the two
+models above; cos i and cos^2 i are the honest optimistic/conservative edges.
+
+--------------------------------------------------------------------------
+## 6. Limit checks
+
+L1. eta(0) = 1 for both (equatorial orbit: orbit normal = z_hat = north, the
+ full dipole equatorial field is orbit-normal => maximal coupling). PASS.
+
+L2. Monotone decreasing on [0, 90 deg]: cos i and cos^2 i are both strictly
+ decreasing there. PASS (verified numerically, Section 8).
+
+L3. eta(90 deg): EXACTLY ZERO for the aligned dipole (both models):
+ eta_F(90) = cos 90 = 0, eta_SC(90) = 0.
+ WHY (physical, not a bug): the aligned-dipole field always lies in the
+ meridian plane spanned by z_hat and r_hat, i.e. B . e_n = beta[ z_hat.e_n
+ - 3(z_hat.r_hat)(r_hat.e_n) ]. For a polar orbit the orbit plane CONTAINS
+ z_hat, so its normal e_n is equatorial (z_hat.e_n = cos 90 = 0) and r_hat
+ lies in a plane orthogonal to e_n (r_hat.e_n = 0). Hence B_n = 0 everywhere
+ on a polar orbit, so a radial current has zero along-track force at every
+ point -- not just on average. A polar radial tether does no secular drag in
+ an aligned dipole. The out-of-plane F_n = I L beta sin i cos u is nonzero at
+ 90 deg but does no work.
+ HONESTY NOTE: this exact zero is an ARTIFACT of the aligned-dipole + radial-
+ tether idealization. The ~11.5 deg dipole tilt tips e_n out of the field's
+ null, restoring a small nonzero drag of order (sin 11.5 deg)^2 ~ 0.04 of the
+ equatorial value (order-of-magnitude; a tilted-dipole quadrature would pin
+ it). So report eta(90) = 0 for the aligned model and flag "true value is
+ small-but-nonzero, dominated by dipole tilt" as the caveat. Neither catalog
+ is at 90 deg (A ~71, B ~74), so this does not touch the headline numbers.
+
+--------------------------------------------------------------------------
+## 7. Recommendation for ADSC v1
+
+Expectation under test (from the task): "fixed-average-current with a power-
+budget cap is the simplest defensible model" => eta = cos i. Verdict: JUSTIFIED
+as the OPTIMISTIC edge, but it must NOT be shipped as a point value.
+
+Reasoning:
+ - eta_F = cos i is exactly correct WHEN the binding limit is the power supply /
+ hardware max current, so I really is inclination-independent. It is the
+ simplest, and it is the honest UPPER efficiency.
+ - eta_SC = cos^2 i is exactly correct WHEN the current is EMF / OML-collection
+ limited (I falls with the EMF, which itself falls as cos i). It is the honest
+ LOWER efficiency. At 71 deg it is 0.106 vs 0.326 -- a ~3x heavier penalty.
+ - A real bare tether at ~800 km, 71-74 deg with realistic Ne sits BETWEEN
+ these regimes and can switch between them over solar min/max. Committing to
+ one point value would either overstate EDT at high i (cos i) -- exactly the
+ trap the WP13 claims-audit exists to catch (instr 3) -- or understate it.
+
+RECOMMENDATION: implement BOTH as the inclination contribution to the MANDATORY
+uncertainty band (spec: "never a point value"):
+
+ eta_hi(i) = cos i (optimistic edge; power/limit-capped current)
+ eta_lo(i) = cos^2 i (conservative edge; EMF/collection-limited current)
+
+and report the deorbit-time band with eta in [eta_lo, eta_hi]. The band widens
+at high inclination precisely where honesty matters most, and the conservative
+edge structurally prevents EDT from silently "fixing" class A at 71 deg.
+
+Cleanest single-code realization (avoids double counting): apply the geometry
+factor cos i ONCE to the EMF and ONCE to the force, and let the current model
+decide the regime -- if I is power-capped the net inclination scaling is cos i;
+if I is computed from the EMF the net scaling is cos^2 i automatically. Then the
+band above is just the two current-limit regimes, not an ad hoc fudge.
+
+--------------------------------------------------------------------------
+## 8. C++-implementable formula + deterministic quadrature cross-check
+
+### 8.1 What v1 actually computes (O(1), closed form)
+```
+// inputs: i [rad], r [m]; constants B0=3.12e-5 T, RE=6.371e6 m, GM=3.986004418e14
+double beta = B0 * pow(RE/r, 3.0); // T
+double v = sqrt(GM / r); // m/s
+double Bn = beta * cos(i); // orbit-normal field (constant in u)
+// motional EMF per unit length and open-circuit voltage:
+double Em = v * Bn; // V/m
+double Vemf = Em * L; // V (L = tether length)
+// inclination efficiency knobs (eta(0)=1):
+double eta_hi = cos(i); // fixed / power-capped current
+double eta_lo = cos(i)*cos(i); // self-consistent EMF-limited current
+// along-track drag magnitude: |F_t| = I * L * Bn (I from the current model)
+```
+No branch, no iteration, pure double, no external deps. cos i / cos^2 i are the
+efficiency multipliers on the force.
+
+### 8.2 Fixed 64-node Gauss-Legendre harness (general field, deterministic)
+Needed only if B_n acquires u-dependence (tilted dipole extension). Nodes are
+generated by Newton-Raphson on the Legendre polynomial P_64 at init -- fully
+deterministic, SEEDLESS, no RNG, no external table. Map GL nodes x_k in [-1,1]
+to u_k = pi (x_k + 1) in [0, 2pi); orbit average = 0.5 * sum_k w_k f(u_k).
+
+ eta_F(i) = avg_u |Bn(u,i)| / avg_u |Bn(u,0)|
+ eta_SC(i) = avg_u Bn(u,i)^2 / avg_u Bn(u,0)^2
+
+with Bn(u,i) = beta cos i for the aligned dipole (u-independent => the average
+is exact for any node count; N=64 chosen for the general case). Pseudocode for
+node generation:
+```
+for k in 0..N-1:
+ z = cos(pi*(k+0.75)/(N+0.5)); // initial guess
+ repeat:
+ p0=1; p1=0;
+ for j in 0..N-1: p2=p1; p1=p0; p0=((2j+1)*z*p1 - j*p2)/(j+1); // P_N(z)
+ dp = N*(z*p0 - p1)/(z*z - 1); // P_N'(z)
+ dz = p0/dp; z -= dz;
+ until |dz| < 1e-15;
+ x[k]=z; w[k]=2/((1-z*z)*dp*dp);
+```
+
+### 8.3 Verified numeric table (GL-64 quadrature == closed form to 2.2e-16)
+Catalog inclinations: A 71 deg, B 78 deg (the code presets; note the real
+SL-8 population clusters at 74/83 deg -- wp13-literature.md Topic 5b -- so
+78 is a PLACEHOLDER compromise between the clusters).
+
+ i (deg) | eta_F = cos i | eta_SC = cos^2 i
+ --------+---------------+-----------------
+ 0.0 | 1.0000000000 | 1.0000000000
+ 30.0 | 0.8660254038 | 0.7500000000
+ 51.6 | 0.6211477803 | 0.3858245649
+ 71.0 | 0.3255681545 | 0.1059946232
+ 74.0 | 0.2756373558 | 0.0759759519
+ 90.0 | 0.0000000000 | 0.0000000000
+
+Limit checks (numerically confirmed):
+ - eta_F(0)=1, eta_SC(0)=1 PASS (L1)
+ - strictly monotone decreasing 0->90 deg PASS (L2)
+ - eta_F(90)=6.1e-17, eta_SC(90)=3.7e-33 (== 0) PASS (L3, exact-zero limit)
+ - max | GL-64 - closed form | over 10 sample i = 2.2e-16
+
+### 8.4 Reproduce
+Python cross-check script (`/tmp/wp13_eta.py` at authoring time; portable):
+implements the GL-64 harness above and the closed forms, printing the table and
+all four limit checks. Deterministic, no seeds, no external deps.
+
+--------------------------------------------------------------------------
+## 9. One-line summary for the evidence pack
+Radial EDT drag couples only to the orbit-normal field B_n = B0 (RE/r)^3 cos i;
+orbit-averaged inclination efficiency is eta = cos i (fixed/power-capped current)
+to cos^2 i (EMF/collection-limited current), = 0.33..0.11 at 71 deg -- ship the
+[cos^2 i, cos i] band, never a point value. eta(90 deg)=0 in the aligned dipole
+(tilt makes the true value small-but-nonzero). Libration (T7) remains open.
diff --git a/wp13-literature.md b/wp13-literature.md
new file mode 100644
index 0000000..6dd224a
--- /dev/null
+++ b/wp13-literature.md
@@ -0,0 +1,392 @@
+# WP13 Literature / Citation Pack
+
+Task: WP13 Task 1 (Opus, effort high) — literature/citation candidates for the
+kit-class trade study + EDT physics model. Spec: `adsc-specification-v5.md`
+§WP13 (lines 240-258), T7, D12/D13.
+
+Rules honored: EVERY citation below was checked by live WebSearch/WebFetch on the
+retrieval date. Each carries a verified-status label. **Nothing is fabricated.**
+Where a specific number could not be pulled from a freely accessible source, that
+is stated explicitly and the item is flagged for PLACEHOLDER treatment
+(spec:167-174 pattern) rather than invented.
+
+- Retrieval date (all items): **2026-07-11**
+- Verified-status legend:
+ - `web-verified` — citation and the key number(s) were confirmed against a
+ fetched page or an explicit search-result snippet on 2026-07-11.
+ - `web-verified (citation); number-paywalled` — bibliographic record confirmed
+ live; the specific numeric value sits behind a paywall/binary PDF and is
+ reported from a secondary snippet or left for PLACEHOLDER.
+ - `training-data / derived` — value computed or recalled, not directly fetched;
+ use only as cross-check, not as the primary cited number.
+ - `not-found` — could not be verified; do NOT insert into the repo.
+
+---
+
+## Topic 1 — Bare electrodynamic tether deorbit physics (citable baseline)
+
+### 1a. Sanmartín OML bare-tether foundation paper — PRIMARY BASELINE
+
+**Sanmartín, J. R., Martínez-Sánchez, M., & Ahedo, E. (1993). "Bare Wire Anodes
+for Electrodynamic Tethers." *Journal of Propulsion and Power*, 9(3), 353-360.**
+- DOI: 10.2514/3.23629
+- URL: https://arc.aiaa.org/doi/10.2514/3.23629
+- Verified-status: **web-verified** (title, all three authors, journal, vol 9,
+ no 3, pp 353-360, year 1993, and DOI confirmed via search 2026-07-11).
+- Key content extracted: introduces the *bare tether* concept — leave a fraction
+ of the tether near the anodic end uninsulated so it collects ambient electrons
+ in the **orbital-motion-limited (OML)** regime once locally biased positive
+ relative to the plasma. Validity condition: tether transverse radius small
+ compared with both the electron Debye length and thermal gyroradius; large
+ currents at moderate voltage drop. This is the citable OML current-collection
+ law the EdtConfig force/current model must reference (task §0.1, decay model).
+
+Supporting review (for the OML law written out + system context):
+- **Sanmartín, J. R. "A Review of Electrodynamic Tethers for Space Applications."**
+ UPM open archive PDF: https://oa.upm.es/30953/1/UA2.pdf
+ - Verified-status: **web-found** (URL live). PDF is image/scan-based — machine
+ text extraction failed, so specific formula lines were not pulled. Use the
+ 1993 paper above as the citable OML source; this review is a secondary pointer.
+
+### 1b. EU FP7 BETs project (Bare Electrodynamic Tethers) — deorbit performance
+
+**BETs — "Propellantless deorbiting of space debris by bare electrodynamic
+tethers", FP7-SPACE, Grant Agreement No. 262972.** Coordinator: Prof. Juan R.
+Sanmartín, Universidad Politécnica de Madrid (UPM). Duration: 2010-11-01 to
+2014-01-31.
+- CORDIS record: https://cordis.europa.eu/project/id/262972
+- CORDIS reporting: https://cordis.europa.eu/project/id/262972/reporting
+- Final Report PDF (UPM archive): https://oa.upm.es/39287/1/Final%20BETs%20Report%202.pdf
+- Project site: http://www.thebetsproject.com/ (resources page:
+ http://www.thebetsproject.com/resources)
+- Verified-status: **web-verified** (GA number, coordinator, institution, dates,
+ and project scope confirmed via CORDIS/search 2026-07-11). Final Report PDF URL
+ is live but FlateDecode-compressed — headline numbers below come from the ESA
+ outreach summary and BETs design papers, not the raw report binary.
+
+**Deorbit performance numbers (citable, web-verified via search 2026-07-11):**
+- Order-of-magnitude result: *a 1000-kg spacecraft can be deorbited from a
+ 1000-km-altitude orbit by a **10-kg** tether in about **one month**; a **1-kg**
+ tether deorbits the same 1000-kg spacecraft in **less than a year**.* (ESA /
+ phys.org BETs outreach, 2014: https://phys.org/news/2014-05-tether-solution-satellite-de-orbiting-reentry.html)
+- Comparative claim: for state-of-the-art values with a few-weeks deorbit time,
+ bare tethers are ~1-2 orders of magnitude lighter than active
+ (propulsive) technologies and than drag-augmentation (sail) devices respectively.
+- BETs design reference scenarios (used across BETs papers):
+ (i) Earth-observation satellites, **700-1000 kg, initial orbit ~800 km, 98°
+ inclination**; (ii) mega-constellation spacecraft, ~200 kg, 1200 km, 90°.
+ Example tape-tether geometry: L = 3 km, width w = 2.5 cm, thickness h = 40 µm,
+ with 2 km bare + 1 km inert segments.
+- Software: BETsMA v1.0 mission-analysis tool developed under the project.
+- Inclination note (directly supports spec's mandatory η(i), 240-243): the BETs
+ performance scenarios are all high-inclination (98°, 90°), and the along-track
+ component of v×B — the drag-producing component — degrades away from the
+ equatorial/low-inclination case. See Topic 2/derivation task for the physics.
+
+> Retrieval caveat: the exact BETs deorbit-time-vs-altitude-vs-inclination
+> tables live in the compressed Final Report PDF and the paywalled Acta
+> Astronautica BETs papers (e.g. "Comparison of technologies for deorbiting
+> spacecraft from LEO at end of mission", S0094576516306555; "A code for the
+> analysis of missions with electrodynamic tethers", S0094576522003113). The
+> qualitative + order-of-magnitude numbers above ARE web-verified; any finer
+> table pulled into the repo must cite the specific paper and be re-fetched.
+
+---
+
+## Topic 2 — EDT libration / dynamic instability (T7 open-risk pointer)
+
+### PRIMARY libration-instability citation
+
+**Peláez, J., Lorenzini, E. C., López-Rebollal, O., & Ruiz, M. (2000). "A New
+Kind of Dynamic Instability in Electrodynamic Tethers." *The Journal of the
+Astronautical Sciences*, 48(4), 449-476.**
+- DOI: 10.1007/BF03546266
+- URL: https://link.springer.com/article/10.1007/BF03546266
+- Verified-status: **web-verified** (authors, title, journal, vol 48, no 4, pp
+ 449-476, year 2000, DOI confirmed via search 2026-07-11; Springer page itself
+ redirects to an auth gate, so the citation is confirmed from the search
+ record, not a full-text fetch).
+- Key finding (for the T7 pointer): a conductive tether on a circular *inclined*
+ orbit exhibits a genuine **dynamic instability** — non-periodic trajectories
+ carry a positive net energy flux per orbit, pumping in-plane libration until it
+ grows into rotation. The instability is present for flexible OR rigid tethers,
+ in generator (deorbit) OR thruster mode, and its strength **depends on orbital
+ inclination**. This is exactly the "libration/dynamic stability is explicitly
+ unresolved" risk WP13 must cite and must NOT claim solved (spec:244-245,
+ task §0.2).
+
+### Supporting / corroborating libration references (all web-verified citations)
+
+- **Zhong, R., & Zhu, Z. H. (2013). "Libration dynamics and stability of
+ electrodynamic tethers in satellite deorbit." *Celestial Mechanics and
+ Dynamical Astronomy*, 116(3), 279-298.** DOI: 10.1007/s10569-013-9489-4 —
+ long-term libration/stability analysis in deorbit.
+ URL: https://link.springer.com/article/10.1007/s10569-013-9489-4
+- **"The dynamic instability analysis of electrodynamic tether system." *Nonlinear
+ Dynamics* (2024).** DOI: 10.1007/s11071-024-09771-w —
+ URL: https://link.springer.com/article/10.1007/s11071-024-09771-w — recent
+ confirmation the instability remains an active, unresolved research topic.
+- Verified-status: **web-verified** (both surfaced with full journal/vol/DOI in
+ search 2026-07-11). Use Peláez 2000 as the primary T7 pointer; these two as
+ "still open / actively studied" corroboration.
+
+---
+
+## Topic 3 — Ionospheric electron density Ne at ~700-900 km (cited parameter, no IRI impl.)
+
+Spec:246 forbids an IRI implementation; WP13 only needs cited Ne numbers with a
+source. Best web-verified anchors found:
+
+### 3a. Representative engineering value at 800 km (PRIMARY citable number)
+
+- **Ne ≈ 1.0 × 10¹¹ m⁻³ at 800 km altitude (daytime, representative value)** —
+ assumed in a plasma-deorbit particle-simulation study.
+ Source: "Particle Simulation of Plasma Drag Force Generation in the Magnetic
+ Plasma Deorbit", arXiv:1805.06123.
+ URL: https://arxiv.org/abs/1805.06123
+ - Verified-status: **web-verified** (the "ion number density of 1.0×10¹¹ m⁻³ …
+ typical daytime value at 800 km" statement confirmed via search snippet
+ 2026-07-11; the PDF binary itself did not text-extract cleanly).
+
+### 3b. Altitude-appropriate in-situ measurement basis (DMSP at ~840-860 km)
+
+- **Cai, X., et al. (2019). "Long-Term Trend of Topside Ionospheric Electron
+ Density Derived From DMSP Data During 1995-2017." *Journal of Geophysical
+ Research: Space Physics*, 124(12), 10708-10727.** DOI: 10.1029/2019JA027522
+ URL: https://agupubs.onlinelibrary.wiley.com/doi/full/10.1029/2019JA027522
+ - DMSP SSIES makes **in-situ** thermal ion/electron density measurements at
+ **~840-860 km** — i.e. directly in the ~800 km regime of both ADSC catalogs.
+ - Verified-status: **web-verified (citation); number-paywalled** (journal/vol/
+ pages/DOI confirmed; the article body returned HTTP 402, so discrete solar-
+ min/max Ne values were not fetched).
+- DMSP altitude confirmed independently: SSIES instruments measure at ~840 km;
+ at solar maximum the 848-km ionosphere is >50% O⁺ at all local times, while at
+ solar minimum O⁺ falls to ≲ H⁺/He⁺ (DMSP/SSIES mid-latitude page,
+ https://dmsp.bc.edu/html2/ssiesmidlatitude.html — page live but TLS-cert
+ mismatch blocked full fetch; content from search snippet).
+
+### 3c. Solar min vs solar max range (order-of-magnitude, to be pinned)
+
+Well-established topside behavior at ~800 km: Ne rises ~an order of magnitude from
+solar minimum to solar maximum. Working range for the EdtConfig uncertainty band:
+- **Solar min (~night/low F10.7): Ne ~ 10¹⁰ m⁻³ (order 10⁹-10¹⁰ m⁻³)**
+- **Solar max (~day/high F10.7): Ne ~ 10¹¹ m⁻³ (up to ~10¹² m⁻³ in daytime EIA)**
+
+> Honest gap: a single freely-accessible table giving a clean solar-min **and**
+> solar-max Ne pair at exactly 800 km was NOT extracted. The two best sources that
+> tabulate it are paywalled: Cai et al. 2019 (above) and "Validation of the
+> IRI-2020 topside ionosphere options through in-situ electron density
+> observations by LEO satellites", *Adv. Space Res.* (2024),
+> https://www.sciencedirect.com/science/article/pii/S0273117724005222 . For the
+> repo: cite 3a (1.0×10¹¹ m⁻³ @ 800 km) as the mid/solar-active anchor, use the
+> 3c range for the solar-min/max band, and keep the exact pair PLACEHOLDER until
+> one of the paywalled tables is obtained. Do NOT invent a solar-min number.
+
+Model-paper cross-refs (web-verified citations, for context, NOT to implement):
+- Hoque, M. M., Jakowski, N., & Prol, F. S. (2022). "A new climatological electron
+ density model…" *J. Space Weather Space Clim.*, 12, 1. DOI: 10.1051/swsc/2021044
+ (validated against DMSP at ~800 km).
+- Prol, F. S., Smirnov, A. G., Hoque, M. M., & Shprits, Y. Y. (2022). "Combined
+ model of topside ionosphere and plasmasphere…" *Scientific Reports*, 12, 9732.
+ DOI: 10.1038/s41598-022-13302-1 (topside model good to ~800 km).
+
+---
+
+## Topic 4 — Geomagnetic dipole moment (for the dipole B-field in the EDT model)
+
+### PRIMARY citable dipole values
+
+- **Centred-dipole magnetic moment M = 7.788 × 10²² A·m²; equatorial surface
+ field B₀ = 3.01153 × 10⁴ nT (IGRF epoch 2000).**
+ Source: SPENVIS (ESA/BIRA-IASB Space Environment Information System),
+ "Dipole approximations of the geomagnetic field."
+ URL: https://www.spenvis.oma.be/help/background/magfield/cd.html
+ - Verified-status: **web-verified** (both M and B₀ pulled directly from the
+ fetched page 2026-07-11). B₀ = √(g₁₀² + g₁₁² + h₁₁²); M = (4π R³/µ₀)·B₀.
+
+### Current (IGRF-13/14 era) model reference + present value
+
+- **Alken, P., Thébault, E., Beggan, C. D., et al. (2021). "International
+ Geomagnetic Reference Field: the thirteenth generation." *Earth, Planets and
+ Space*, 73, 49.** DOI: 10.1186/s40623-020-01288-x
+ URL: https://link.springer.com/article/10.1186/s40623-020-01288-x
+ - Verified-status: **web-verified (citation)** (authors, journal, article,
+ year, DOI, epoch-2020.0 main-field model confirmed via search 2026-07-11).
+- **Present value (epoch 2020.0): dipole moment ≈ 7.71 × 10²² A·m²** (slowly
+ declining, ~5-6% over 1980→2020). Axial coefficient g₁₀(2020.0) = -29404.8 nT;
+ with g₁₁ = -1450.9 nT, h₁₁ = 4652.5 nT ⇒ B₀ = √(·) ≈ 2.98 × 10⁴ nT ⇒
+ M = (4π R³/µ₀)B₀ ≈ 7.71 × 10²² A·m² (R = 6371.2 km).
+ - Verified-status: **training-data / derived** (IGRF-13 2020.0 g/h coefficients
+ are standard; the 7.71×10²² value is computed from them, not fetched as a
+ printed figure — the NOAA/IGRF-13 PDFs returned 403). Consistent with the
+ web-verified SPENVIS 2000 value of 7.788×10²² and the documented declining
+ trend. **For the repo, cite the SPENVIS value (7.788×10²²) as the primary
+ web-verified dipole moment and note IGRF-13 (Alken 2021) as the current
+ model; the 7.71×10²² is a derived cross-check.**
+
+Design note: for an *aligned* dipole the model overstates high-latitude field
+structure; spec/task allow "aligned dipole + stated tilt caveat" — the ~11° tilt
+(geomagnetic vs geographic axis) and the eccentric-dipole offset are the caveats
+to state (SPENVIS same page documents both).
+
+---
+
+## Topic 5 — Target parameters (catalog stage bodies)
+
+### 5a. Zenit-2 second stage (SL-16 class) — Catalog A anchor / also massive
+
+- **Empty (dry) mass ≈ 8,300 kg** (one source lists ~9,000 kg empty);
+ **length 11.50 m, diameter 3.90 m**; engine 1× RD-120 (+ RD-8 vernier).
+ Sources: braeunig space specs (http://www.braeunig.us/space/specs/zenit.htm),
+ Astronautix Zenit-2 (http://www.astronautix.com/z/zenit-2.html),
+ Wikipedia Zenit-2 (https://en.wikipedia.org/wiki/Zenit-2).
+- **Typical orbit (Tselina-2 payloads): ~845 × 857 km, inclination 71.01°.**
+ (Also a 65° family; SL-16 R/B clusters span ~750-850 km, incl. 71° and 81°.)
+- Verified-status: **web-verified** (mass 8,300 kg, dims, RD-120, and the
+ 845×857 km / 71.01° Tselina-2 orbit all confirmed via search 2026-07-11; the
+ 8,300-vs-9,000 kg spread is a real source discrepancy — carry both, flag the
+ 8,300 kg as the more common dry-mass figure).
+- Cross-check: McKnight 2021 (Topic 6) — the top 20 most-concerning LEO objects
+ are all SL-16 (Zenit-2) second stages, ~9 t class, ~850 km.
+
+### 5b. Kosmos-3M second stage (SL-8 class, 11K65M)
+
+- **Empty (dry) mass ≈ 1,435 kg**; gross mass ≈ 20,135 kg; engine 1× 11D49
+ (RD-219 family verniers); length ~6 m, diameter 2.4 m.
+ Sources: Astronautix Kosmos 11K65M (http://www.astronautix.com/k/kosmos11k65m.html),
+ Gunter's Space Page (https://space.skyrocket.de/doc_lau_det/kosmos-3m.htm).
+- **Typical orbits — SL-8 R/B clusters (as of 2014 survey): Box 3 ≈ 700-800 km
+ (50 R/Bs), Box 2 ≈ 900-1000 km (143 R/Bs), Box 1 ≈ 1500-1600 km (44 R/Bs);
+ inclinations ~74° and ~83°.** The ~740-780 km / ~74° preset the task cites
+ matches the 700-800 km, 74° sub-population.
+- Verified-status: **web-verified** (1,435 kg empty mass, 20,135 kg gross, 11D49,
+ and the three-box cluster altitudes/inclinations confirmed via search
+ 2026-07-11; per-object page fetches were blocked by host cert/connection
+ issues, so mass is snippet-level, not a fetched spec sheet — solid but tag it
+ as such).
+
+---
+
+## Topic 6 — Class-C candidates (massive / high-risk, controlled-reentry class)
+
+### PRIMARY ranking citation (the criticality basis, spec §4 amendment)
+
+- **McKnight, D., Witner, R., Letizia, F., Lemmens, S., Anselmo, L., Pardini, C.,
+ Rossi, A., Kunstadter, C., et al. (2021). "Identifying the 50
+ statistically-most-concerning derelict objects in LEO." *Acta Astronautica*,
+ 181, 282-291.** DOI: 10.1016/j.actaastro.2021.01.021
+ URL: https://www.sciencedirect.com/science/article/abs/pii/S0094576521000217
+ ADS: https://ui.adsabs.harvard.edu/abs/2021AcAau.181..282M/abstract
+ - Verified-status: **web-verified (citation + headline findings)** (journal,
+ vol 181, pp 282-291, 2021 confirmed; the abstract page and open PDF fetches
+ partially blocked, key findings from search 2026-07-11).
+ - Key findings: 11 expert teams produced ranked lists → consensus top-50.
+ **Rocket bodies dominate; the top ~20 are all SL-16 (Zenit-2) second stages.**
+ **Average mass of the top-50 objects ≈ 5,295 kg.** Removing the top-50 would
+ roughly halve LEO collision-risk; top-10 ≈ 30% reduction.
+
+### Class-C candidate targets (2-3, with cited mass/orbit/inclination)
+
+1. **Envisat (ESA) — the canonical controlled-reentry-class derelict.**
+ - **Mass 8,211 kg** (Service Module 2,673 + PEB 1,021 + Payload Carrier 2,078
+ + Fuel 319 + Instruments 2,118 kg); **sun-synchronous orbit ~765-800 km,
+ inclination ~98.4-98.55°**; defunct since April 2012.
+ - Sources: eoPortal (https://www.eoportal.org/satellite-missions/envisat),
+ ESA Earth Online (https://earth.esa.int/eogateway/missions/envisat/description),
+ Wikipedia (https://en.wikipedia.org/wiki/Envisat).
+ - Verified-status: **web-verified** (8,211 kg mass breakdown, ~800 km sun-sync,
+ ~98.5° inclination, 2012 failure all confirmed 2026-07-11). Envisat is the
+ most-cited single "too massive to safely leave / high casualty-risk on
+ uncontrolled reentry" object — the archetypal Class-C target.
+
+2. **SL-16 (Zenit-2) second stage — top of the McKnight ranking, massive.**
+ - **~8,300-9,000 kg, ~845-857 km, 71.0° (also an 81° cluster).** (Topic 5a.)
+ - Verified-status: **web-verified.** Note: the ADSC task uses Zenit-2 as the
+ Class-A *anchor*; by mass it is equally a Class-C controlled-reentry
+ candidate. Whether an 8.9-t stage needs controlled reentry (Ec>1e-4) is the
+ Topic-7 casualty-area question — flag for the Class-C vs Class-A split.
+
+3. **SL-8 (Kosmos-3M) second stage — high-count, lighter concern object.**
+ - **~1,435 kg, 700-1000 km, 74°/83°.** (Topic 5b.) Appears prominently in
+ SL-8-specific ADR mission studies (e.g. ADReS-A, Springer
+ 10.1007/978-3-319-15982-9_3). Lighter than Envisat/SL-16 → likely NOT
+ controlled-reentry class, useful as the mass contrast in the Class-C table.
+ - Verified-status: **web-verified.**
+
+> For a third *distinct massive* Class-C payload (not a rocket body), Envisat is
+> the strongest and only fully-verified large defunct **satellite** in the
+> concern literature at ~800 km. If a second large payload is wanted, candidates
+> to verify later: ADEOS-II/Midori-II (~3.7 t, ~800 km, 98.6°) and ERS-2 (~2.5 t
+> — but ERS-2 already reentered Feb 2024, so it is historical). These were NOT
+> re-verified in this pass — mark PLACEHOLDER until fetched.
+
+---
+
+## Topic 7 — Casualty-risk framework (controlled vs uncontrolled reentry)
+
+### PRIMARY standard
+
+- **NASA-STD-8719.14A (with Change 1), "Process for Limiting Orbital Debris."**
+ URL: https://soma.larc.nasa.gov/SIMPLEx/pdf_files/871914.pdf (also
+ https://explorers.larc.nasa.gov/HPMIDEX/pdf_files/10_nasa-std-8719.14a_with_change_1.pdf)
+ - **Requirement 4.7-1 — Limit the risk of human casualty: the expected
+ worldwide human-casualty risk from reentering debris shall not exceed
+ 0.0001 (1 in 10,000), i.e. Ec < 1 × 10⁻⁴.** Casualty is assumed for any
+ surviving fragment impacting with kinetic energy **> 15 J**.
+ - **Controlled reentry:** the reentry must still satisfy 4.7-1, AND the targeted
+ trajectory shall ensure no surviving debris of >15 J lands closer than
+ **370 km from foreign landmasses** (with defined stand-off from U.S.
+ territories); the deorbit-burn success probability must be high enough not to
+ violate 4.7-1.
+ - Compliance tools: **DAS (Debris Assessment Software)** and **ORSAT (Object
+ Reentry Survival Analysis Tool)**.
+ - Verified-status: **web-verified** (the 1×10⁻⁴ / 1-in-10,000 limit, the 15 J
+ casualty threshold, the 370 km controlled-reentry rule, and DAS/ORSAT all
+ confirmed via fetch + search 2026-07-11). Note: the requirement is numbered
+ **4.7-1** in the standard; a first WebFetch guessed "4.7.2" — disregard that,
+ 4.7-1 is correct per the follow-up search of the standard's own text.
+
+### ESA / European equivalent (same 1e-4 threshold, DRAMA/SARA tool)
+
+- **ESA re-entry safety: the 1 × 10⁻⁴ casualty-risk limit is likewise applied
+ (harmonised with IADC).** Uncontrolled reentries exceeding it require design-
+ for-demise or controlled/targeted reentry.
+ URL: https://technology.esa.int/page/re-entry-safety
+- **DRAMA / SARA** — ESA's Debris Risk Assessment and Mitigation Analysis suite;
+ **SARA (Survival And Risk Analysis)** is its reentry-survival + on-ground-
+ casualty module (the ESA analogue of NASA DAS/ORSAT).
+ ESA DRAMA: https://sdup.esoc.esa.int/drama/ (SARA is the reentry module).
+- Verified-status: **web-verified (framework + threshold)** (ESA's use of the
+ same 1e-4 casualty limit and the DRAMA/SARA tooling confirmed via search
+ 2026-07-11; SARA's exact algorithm details not fetched — cite the ESA
+ re-entry-safety page + DRAMA suite, not a specific SARA number).
+
+Cross-refs (web-verified citations): NASA ORSAT page
+(https://orbitaldebris.jsc.nasa.gov/reentry/orsat.html); "Debris Assessment
+Software (DAS) Reentry Risk Analysis" (ResearchGate 320808841).
+
+---
+
+## Summary table — verification status by topic
+
+| # | Item | Citation status | Key number status |
+|---|------|-----------------|-------------------|
+| 1a | Sanmartín/Martínez-Sánchez/Ahedo 1993 OML bare-tether | web-verified | OML law (qualitative) verified |
+| 1b | BETs FP7 GA 262972 + deorbit performance | web-verified | 10 kg→1000 kg from 1000 km in ~1 mo: verified; fine tables paywalled |
+| 2 | Peláez et al. 2000 libration instability (T7) | web-verified | inclination-dependent instability: verified |
+| 3 | Ne @ ~800 km | 1.0e11 m⁻³ @800 km verified; solar-min/max pair paywalled | mid anchor verified; solar-min PLACEHOLDER |
+| 4 | Geomagnetic dipole moment | SPENVIS M=7.788e22 web-verified; IGRF-13 model verified | 2020 value 7.71e22 derived |
+| 5a | Zenit-2/SL-16 2nd stage | web-verified | 8,300(-9,000) kg, 11.5×3.9 m, 845×857 km/71.0° |
+| 5b | Kosmos-3M/SL-8 2nd stage | web-verified | 1,435 kg dry, 700-1000 km, 74°/83° |
+| 6 | McKnight 2021 top-50 + Envisat | web-verified | Envisat 8,211 kg/~800 km/98.5°; top-50 avg 5,295 kg |
+| 7 | NASA-STD-8719.14A + ESA/DRAMA-SARA | web-verified | Ec<1e-4, 15 J, 370 km controlled rule |
+
+## Items to keep PLACEHOLDER (spec:167-174) — NOT fabricated
+
+- Exact solar-min **and** solar-max Ne pair at exactly 800 km (only paywalled
+ tables found; mid/solar-active anchor 1.0×10¹¹ m⁻³ is usable now).
+- Precise BETs deorbit-time-vs-(altitude, inclination) table (compressed Final
+ Report PDF + paywalled Acta Astronautica papers).
+- A second large non-rocket-body Class-C payload beyond Envisat (ADEOS-II / ERS-2
+ candidates not re-verified this pass).
+- IGRF-13 2020.0 printed dipole-moment figure (value derived, not fetched).