const MODE = Object.freeze({ // Hirt et al. time-dependent maximum-speed form: // v = a * M^b * (1 - exp(-h * M^i)), v in km/h, M in kg. // Flying, running and swimming coefficients are the published Supplementary // Table 4 fits for the time-dependent maximum-speed model. running:{a:25.5,b:.26,h:22,i:-.60,routineFraction:.34,calibration:'Hirt-2017'}, swimming:{a:11.2,b:.36,h:19.5,i:-.56,routineFraction:.30,calibration:'Hirt-2017'}, flying:{a:142.8,b:.24,h:2.4,i:-.72,routineFraction:.42,calibration:'Hirt-2017'}, }); export const locomotionModes=Object.freeze(Object.keys(MODE)); export const normalizeLocomotionMode=mode=>MODE[mode]?mode:'running'; export const locomotionCalibration=mode=>({...MODE[normalizeLocomotionMode(mode)]}); export function maximumSpeedMPerDay(massKg,mode='running'){ const c=MODE[normalizeLocomotionMode(mode)],m=Math.max(massKg,1e-6),kmh=c.a*Math.pow(m,c.b)*(1-Math.exp(-c.h*Math.pow(m,c.i))); return Math.max(1,kmh*24000); // km/h -> m/day } export const maximumSpeedMultiplier=massKg=>maximumSpeedMPerDay(massKg,'running')/maximumSpeedMPerDay(3,'running'); export function routineTravelSpeedMPerDay(massKg,dailyMovementBudgetM,mode='running'){ const c=MODE[normalizeLocomotionMode(mode)],budgetSpeed=Math.max(0,dailyMovementBudgetM)*c.routineFraction; return Math.min(maximumSpeedMPerDay(massKg,mode)*.22,Math.max(1,budgetSpeed)); } export function foragingSpeedMPerDay(massKg,dailyMovementBudgetM,mode='running'){ return Math.min(maximumSpeedMPerDay(massKg,mode)*.42,routineTravelSpeedMPerDay(massKg,dailyMovementBudgetM,mode)*1.35); } export function escapeSpeedMPerDay(massKg,dailyMovementBudgetM,mode='running'){ return Math.min(maximumSpeedMPerDay(massKg,mode),routineTravelSpeedMPerDay(massKg,dailyMovementBudgetM,mode)*2.4); } export function dailyMovementBudgetM(massKg,geneBudgetM){ // Daily travel capacity is an independent movement trait. return Math.max(25,geneBudgetM)*(0.75+0.25*Math.pow(Math.max(massKg,.01)/3,.08)); } export function sampleRoamingDistanceM(random,dailyBudgetM){ // Persistent free-roaming waypoint: typically ~0.35-1.5 days of travel, // capped to avoid asking a local navigator to cross the entire world at once. const budget=Math.max(80,dailyBudgetM),scale=Math.max(120,budget*.72),draw=-Math.log(Math.max(1e-9,1-random()))*scale; return Math.min(Math.max(90,draw),Math.min(1800,budget*1.8)); } export function limitSpeedForArrival(speedMPerDay,distanceM,arrivalRadiusM,dtDays){ const available=Math.max(0,Math.max(0,distanceM)-Math.max(0,arrivalRadiusM)); return Math.min(Math.max(0,speedMPerDay),dtDays>0?available/dtDays:0); }