fix: Changing way to sell with demand

This commit is contained in:
gauvainboiche
2026-10-08 12:34:33 +02:00
parent 1734487f1e
commit c98995f310
7 changed files with 103 additions and 38 deletions
+9 -9
View File
@@ -3,9 +3,9 @@
"click": { "yield": 1 },
"unit": { "materialPerUnit": 1 },
"market": {
"fairPrice": 0.25,
"referencePrice": 0.25,
"baseDemand": 2.5,
"elasticity": 2,
"elasticity": 3,
"minPrice": 0.01,
"maxPrice": 50,
"priceStep": 0.01
@@ -17,13 +17,13 @@
"startUnitPrice": 0.08,
"floorUnitPrice": 0.01,
"scale": 3000,
"minFactor": 0.4,
"maxFactor": 3,
"volatility": 0.3,
"reversion": 0.08,
"spikeChance": 0.06,
"spikeSize": 1.9,
"updateEverySeconds": 1.5,
"minFactor": 0.3,
"maxFactor": 4,
"volatility": 0.5,
"reversion": 0.12,
"spikeChance": 0.2,
"spikeSize": 2,
"updateEverySeconds": 10,
"autoBuyBelowFactor": 1.2
},
"marketing": { "baseCost": 40, "costGrowth": 1.45, "demandPerLevel": 1.4 },
+17 -8
View File
@@ -45,17 +45,26 @@ export function materialBatch(mm, totalProduced, rate, factor, discount) {
const basis = Math.max(totalProduced * mm.batchShareOfTotal, rate * mm.batchSecondsOfProduction);
const doublings = Math.max(0, Math.floor(Math.log2(Math.max(1, basis / mm.minBatch))));
const size = mm.minBatch * 2 ** doublings;
const cost = Math.max(1, Math.ceil(size * materialUnitPrice(mm, totalProduced) * factor * discount));
return { size, cost };
const cents = size * materialUnitPrice(mm, totalProduced) * factor * discount;
return { size, cost: Math.max(0.01, Math.round(cents * 100) / 100) };
}
/**
* Units customers want to buy per second at `price`.
* Demand is maxed at or below the fair price and drops steeply above it, so
* extra production is only worth selling at a higher price while supply is
* short; to sell more at the fair price, the player needs more demand.
* Units customers want to buy per second at `price`: a smooth curve, with more
* demand the lower the price and less the higher it is (`referencePrice` is
* just where demand equals baseDemand x multiplier).
*/
export function demandPerSecond(market, price, multiplier) {
const overpriced = Math.max(1, price / market.fairPrice);
return (market.baseDemand * multiplier) / overpriced ** market.elasticity;
return market.baseDemand * multiplier * (market.referencePrice / price) ** market.elasticity;
}
/**
* The price at which customers buy exactly what is produced. Selling for less
* gains no volume (everything is already sold); selling for more leaves stock
* unsold. It moves with output and every demand bonus, and the UI deliberately
* does not reveal it: the player finds it from the market feedback.
*/
export function balancedPrice(market, supplyPerSecond, multiplier) {
const demandAtReference = market.baseDemand * multiplier;
return market.referencePrice * (demandAtReference / supplyPerSecond) ** (1 / market.elasticity);
}
+19 -4
View File
@@ -6,7 +6,8 @@ import { t } from '../services/i18n.js';
import { $, h, icon, refreshStaticIcons } from './dom.js';
const SLIDER_STEPS = 1000;
const TREND = { '-1': '▼', 1: '▲' }; // last move of the material price
const MARKET_TOLERANCE = 1.15; // demand within ±15 % of output counts as balanced
const TREND ={ '-1': '▼', 1: '▲' }; // last move of the material price
function setText(node, text) {
if (node.textContent !== text) node.textContent = text;
@@ -184,14 +185,28 @@ export function createView(actions) {
);
}
/**
* Feedback on the price: what customers want versus what is produced, and
* which side of the balance the player is on. It never gives the balanced
* price itself: it depends on output and every bonus, so it must be found.
*/
function marketHint(d) {
const demand = fmt.number(d.demand);
if (d.rate <= 0) return t('ui.hintDemandOnly', { demand });
const ratio = d.demand / d.rate;
let verdict = 'ui.marketBalanced';
if (ratio > MARKET_TOLERANCE) verdict = 'ui.marketShort';
else if (ratio < 1 / MARKET_TOLERANCE) verdict = 'ui.marketLong';
return `${t('ui.hintDemand', { demand, production: fmt.number(d.rate) })} ${t(verdict)}`;
}
function updateMarket(state, d) {
const { fairPrice, minPrice } = ctx.rules.market;
const { minPrice } = ctx.rules.market;
if (document.activeElement !== el.priceInput) el.priceInput.value = state.price.toFixed(2);
if (document.activeElement !== el.priceSlider) {
el.priceSlider.value = Math.round(sliderFromPrice(Math.max(state.price, minPrice)));
}
const hintKey = state.price > fairPrice ? 'ui.hintHigh' : 'ui.hintFair';
setText(el.priceHint, t(hintKey, { fair: money(fairPrice), demand: fmt.number(d.demand) }));
setText(el.priceHint, marketHint(d));
const nextLevelMultiplier = ctx.rules.marketing.demandPerLevel ** (state.marketing + 1);
setText(
+9 -4
View File
@@ -1,5 +1,7 @@
{
"meta": { "name": "English" },
"meta": {
"name": "English"
},
"ui": {
"theme": "Theme",
"language": "Language",
@@ -25,12 +27,15 @@
"makeYield": "+{amount} per click",
"batchInfo": "+{amount} {@theme.res.material} · {trend} {cost}",
"marketingInfo": "Level {level} → demand ×{multiplier} · {cost}",
"hintFair": "At or below the fair price ({fair}) demand is at its maximum: lowering the price sells nothing more.",
"hintHigh": "Customers want {demand}/s at this price. Above the fair price ({fair}) demand drops fast.",
"owned": "Owned: {count}",
"rateEach": "{rate}/s each",
"buyN": "Buy ×{count}",
"upgradeCount": "{owned} / {total}"
"upgradeCount": "{owned} / {total}",
"hintDemand": "At this price customers want {demand}/s; you make {production}/s.",
"hintDemandOnly": "At this price customers want {demand}/s.",
"marketShort": "Demand outpaces your output: you are selling too cheap.",
"marketLong": "You make more than customers want: stock piles up. Try a lower price.",
"marketBalanced": "Supply and demand are about even."
},
"effect": {
"click_mult": "Manual work ×{value}",
+9 -4
View File
@@ -1,5 +1,7 @@
{
"meta": { "name": "Français" },
"meta": {
"name": "Français"
},
"ui": {
"theme": "Thème",
"language": "Langue",
@@ -25,12 +27,15 @@
"makeYield": "+{amount} par clic",
"batchInfo": "+{amount} {@theme.res.material} · {trend} {cost}",
"marketingInfo": "Niveau {level} → demande ×{multiplier} · {cost}",
"hintFair": "Au prix juste ({fair}) ou en dessous, la demande est maximale : baisser le prix ne vend rien de plus.",
"hintHigh": "Les clients veulent {demand}/s à ce prix. Au-dessus du prix juste ({fair}), la demande chute vite.",
"owned": "Possédé : {count}",
"rateEach": "{rate}/s chacun",
"buyN": "Acheter ×{count}",
"upgradeCount": "{owned} / {total}"
"upgradeCount": "{owned} / {total}",
"hintDemand": "À ce prix, les clients veulent {demand}/s ; vous produisez {production}/s.",
"hintDemandOnly": "À ce prix, les clients veulent {demand}/s.",
"marketShort": "La demande dépasse votre production : vous vendez trop bon marché.",
"marketLong": "Vous produisez plus que la demande : le stock s'accumule. Essayez un prix plus bas.",
"marketBalanced": "L'offre et la demande sont à peu près égales."
},
"effect": {
"click_mult": "Travail manuel ×{value}",