rspace-online/modules/rtime/skill-curve.ts

97 lines
2.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Per-skill bonding curve — demand/supply pricing for intent marketplace.
*
* Formula: price = BASE_SKILL_PRICE × (demandHours / supplyHours) ^ ELASTICITY
*
* High demand + low supply → higher price → incentivizes offers.
* All prices in tokens per hour.
*/
import type { Skill } from './schemas';
import type { SkillCurvesDoc, SkillCurveEntry } from './schemas-intent';
// ── Curve parameters ──
/** Base price in tokens per hour */
export const BASE_SKILL_PRICE = 100;
/** Elasticity exponent — 0.5 = square root (moderate responsiveness) */
export const ELASTICITY = 0.5;
/** Minimum supply to avoid division by zero / extreme prices */
const MIN_SUPPLY = 1;
/** Maximum price cap (10x base) to prevent runaway pricing */
const MAX_PRICE = BASE_SKILL_PRICE * 10;
// ── Price functions ──
/**
* Calculate the current price per hour for a skill.
* Returns tokens per hour.
*/
export function getSkillPrice(skill: Skill, curves: SkillCurvesDoc): number {
const curve = curves.curves[skill];
if (!curve || curve.supplyHours <= 0) return BASE_SKILL_PRICE;
const supply = Math.max(curve.supplyHours, MIN_SUPPLY);
const demand = Math.max(curve.demandHours, 0);
const ratio = demand / supply;
const price = Math.round(BASE_SKILL_PRICE * Math.pow(ratio, ELASTICITY));
return Math.min(price, MAX_PRICE);
}
/**
* Calculate the total cost for a need intent (hours × skill price).
*/
export function calculateIntentCost(skill: Skill, hours: number, curves: SkillCurvesDoc): number {
const price = getSkillPrice(skill, curves);
return price * hours;
}
/**
* Get all current skill prices.
*/
export function getAllSkillPrices(curves: SkillCurvesDoc): Record<Skill, number> {
const skills: Skill[] = ['facilitation', 'design', 'tech', 'outreach', 'logistics'];
const prices = {} as Record<Skill, number>;
for (const skill of skills) {
prices[skill] = getSkillPrice(skill, curves);
}
return prices;
}
/**
* Create or update a skill curve entry after an intent is created/withdrawn.
* Returns the updated curve entry.
*/
export function recalculateCurve(
skill: Skill,
supplyHours: number,
demandHours: number,
): Omit<SkillCurveEntry, 'history'> {
const supply = Math.max(supplyHours, MIN_SUPPLY);
const demand = Math.max(demandHours, 0);
const ratio = demand / supply;
const price = Math.min(Math.round(BASE_SKILL_PRICE * Math.pow(ratio, ELASTICITY)), MAX_PRICE);
return {
skill,
supplyHours,
demandHours,
currentPrice: price,
};
}
/**
* Get curve configuration for UI display.
*/
export function getSkillCurveConfig() {
return {
basePrice: BASE_SKILL_PRICE,
elasticity: ELASTICITY,
maxPrice: MAX_PRICE,
};
}