commons.js
74 lines · in natura-src-1.0.0.zip as natura/commons.js · a module · its entry in the module reference · its test: commons-test.js
// commons.js -- Natura THE COMMONS (society / resource): a renewable common-pool resource and the tragedy that
// takes it, or the governance that saves it. A shared stock (a fishery, a pasture, an aquifer) GROWS logistically
// but collapses through a depensation floor if drawn too low; harvesters each expand effort while it pays, so under
// OPEN ACCESS the stock is drawn past the floor and crashes -- the tragedy of the commons, no one's fault and
// everyone's. Add Ostrom's governance -- a quota, monitoring, and graduated sanctions that build COMPLIANCE and
// curb effort -- and the same commons settles instead onto a sustained high yield. Distinct from economy.js (price)
// and speculation.js (bubbles): this is the shared-resource feedback. Deterministic mean-field (no rng). UMD.
(function (root, factory) {
if (typeof module !== 'undefined' && module.exports) module.exports = factory();
else root.Commons = factory();
})(typeof self !== 'undefined' ? self : this, function () {
'use strict';
const clamp = (v, a, b) => v < a ? a : v > b ? b : v;
// config: { mode('open'|'managed'), r, q, floor, mort, quota, monitor, sanction, cost, effortGain, E0, S0, maxDtDays, name }
function makeCommons(config) {
config = config || {};
const mode = config.mode || 'open';
const r = config.r != null ? config.r : 0.9; // intrinsic regrowth rate
const q = config.q != null ? config.q : 1.1; // catchability (harvest per unit effort per unit stock)
const floor = config.floor != null ? config.floor : 0.14; // depensation threshold -- below it recruitment fails
const mort = config.mort != null ? config.mort : 0.10; // background mortality (makes the floor a real trap)
const quota = config.quota != null ? config.quota : 0.16; // managed: the sustainable-ish cap on harvest
const monitor = config.monitor != null ? config.monitor : 0.6;
const sanction = config.sanction != null ? config.sanction : 0.8;
const cost = config.cost != null ? config.cost : 0.30; // cost per unit effort (below q*S it pays to expand)
const effortGain = config.effortGain != null ? config.effortGain : 0.5;
const maxDd = config.maxDtDays != null ? config.maxDtDays : 2;
return {
name: config.name || 'commons',
domain: 'society',
scale: 'regional',
params: config,
init() { return { S: config.S0 != null ? config.S0 : 0.9, E: config.E0 != null ? config.E0 : 0.15, c: 0, harvest: 0 }; },
step(st, ctx, dt) {
const dd = clamp(dt / 86400, 0, maxDd); if (dd <= 0) return st;
const S = st.S;
const hill = (S * S) / (S * S + floor * floor); // recruitment choke below the depensation floor
const growth = r * S * (1 - S) * hill - mort * S * (1 - hill); // net regrowth (negative in the trap)
// desired harvest from current effort; governance caps + curbs it
const desired = q * st.E * S;
let harvest;
if (mode === 'managed') {
const ct = (monitor * sanction) / (monitor * sanction + 0.08), crate = monitor * sanction + 0.08; // equilibrium compliance + approach rate
st.c = clamp(st.c + (ct - st.c) * (1 - Math.exp(-crate * dd)), 0, 1); // stable exponential approach (dd-robust) -- compliance builds under monitoring+sanctions
harvest = st.c * Math.min(desired, quota) + (1 - st.c) * desired; // compliers respect the quota; the rest overharvest
} else { harvest = desired; } // open access: no cap
st.S = clamp(S + (growth - harvest) * dd, 0, 1);
// effort chases profit (q*S - cost); managed sanctions add an expected penalty that curbs expansion
let profit = q * st.S - cost;
if (mode === 'managed') profit -= monitor * sanction * 0.55; // the deterrent
st.E = Math.max(0.02, st.E + effortGain * profit * st.E * dd);
st.harvest = harvest;
return st;
},
// OUT: stock (0..1 of carrying capacity), effort, compliance, harvest/yield, collapsed flag
fields(st) {
return { stock: +st.S.toFixed(4), effort: +st.E.toFixed(4), compliance: +st.c.toFixed(3),
harvest: +st.harvest.toFixed(4), yield: +st.harvest.toFixed(4), collapsed: st.S < floor * 0.6 };
},
events(st) {
const out = [];
if (st.S < floor * 0.6) out.push({ type: 'collapse', stock: +st.S.toFixed(3) });
else if (st.S > 0.4 && st.harvest > 0.02) out.push({ type: 'sustained', stock: +st.S.toFixed(3), yield: +st.harvest.toFixed(3) });
return out;
}
};
}
return { makeCommons };
});