Made producers pay their own way and settled the world to steady prices
- The monthly budget breaks each expense into the resource bought, its quantity and the average price paid: tile improvements already did, and now every upkeep row (buildings, units, works, transport) does too. - Private producers settle their daily wear from their own cash and borrow the shortfall from the central bank, so their cash never goes negative. - Producers fund their own construction and upgrades on the same credit; the treasury pays only for public works, roads and railways, and no private purchase is filed as a public tile-improvement expense. - The server settles the world until every commodity holds within 10% over a trailing seven days instead of a fixed 150, capped by --warmup (0 skips it). - The shipped month report carries its label again, so the budget header and pager name the month.
This commit is contained in:
@@ -1707,6 +1707,8 @@ input:focus, select:focus { border-color: var(--accent); }
|
||||
.budget-caret { display: inline-block; width: 12px; color: var(--muted); }
|
||||
.budget-note { margin-top: 2px; color: var(--muted); font-size: 11px; }
|
||||
.budget-table tr.budget-buy .budget-name { padding-left: 26px; }
|
||||
.budget-table .res { display: inline; white-space: nowrap; }
|
||||
.budget-table .res img { width: 13px; height: 13px; vertical-align: -2px; margin-right: 3px; }
|
||||
.budget-table tr.budget-source .budget-name { padding-left: 44px; color: var(--muted); }
|
||||
.budget-table tr.budget-source .budget-value { color: var(--muted); }
|
||||
.budget-table tfoot .budget-total td {
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
// Generated by scripts/generate-devlog.js from `git log`; do not edit.
|
||||
// The pre-commit hook refreshes it so the main menu shows the latest commits.
|
||||
export const DEVLOG = [
|
||||
{
|
||||
"hash": "31683be",
|
||||
"date": "2026-09-23",
|
||||
"subject": "Rolled the commodity index over ninety days and gave the market an equilibrium",
|
||||
"body": "The central bank now charts one point a day for a rolling ninety days, shipped rebased to 100, with alternating month columns named once along the bottom so no two dates collide; short games leave future days blank and the axis floor is pinned at 0 with the 100 baseline labelled. The market no longer clamps a price to 0.5-1.8x base: the day's supply and demand set an equilibrium (base * demand/supply) and the price eases toward it, so a sustained shortage lifts a good well past its old band while a glut cheapens it, and the price settles instead of compounding to infinity. The nation modal holds each scroll position across a snapshot rebuild so the foreign reserves below the fold stay put."
|
||||
},
|
||||
{
|
||||
"hash": "7dbbe86",
|
||||
"date": "2026-09-23",
|
||||
@@ -54,11 +60,5 @@ export const DEVLOG = [
|
||||
"date": "2026-09-22",
|
||||
"subject": "Moved the economy onto resources and surfaced each building's material bill",
|
||||
"body": "Rebalanced the resource rules so nothing is priced primarily in money: steel, electricity and high-tech lead every construction bill, chip foundries are seeded everywhere instead of being hand-placed, and cities open with stores of steel and high-tech. City building rows now lead with the resource bill and keep the money as an aside, and the region's resources expand into the buildings that demand them. Pulled the graph and cost maths into a framework-free shared/economy_graph.js, quoted currency through one helper, and gave every data table the same striping and row highlight."
|
||||
},
|
||||
{
|
||||
"hash": "1324128",
|
||||
"date": "2026-09-22",
|
||||
"subject": "Showed idle trade routes and flagged migrant flows on the economic maps",
|
||||
"body": "The delivery graph now ships every route the viewer's own storage nodes could trade over, not just the legs that ran, and keeps exports as well as imports so goods leaving the nation appear. The map draws the idle routes grey beneath the active white legs, with a key entry for them.\n\nThe population map replaces its bare flow arrows with the arrows plus a disc textured with the origin nation's flag drifting from city to city, mirroring the merchandise that rides the trade graph."
|
||||
}
|
||||
];
|
||||
|
||||
@@ -1078,7 +1078,9 @@ export const panelMethods = {
|
||||
}
|
||||
$el.append(
|
||||
$("<div class='site-material-label'></div>").text(
|
||||
`Budget ${currencySymbol()}${compact(site.budget || 0)}`
|
||||
site.private
|
||||
? "Private work — the owner's cash and credit buy the materials"
|
||||
: `Budget ${currencySymbol()}${compact(site.budget || 0)}`
|
||||
)
|
||||
);
|
||||
$el.append(
|
||||
|
||||
@@ -4,9 +4,10 @@
|
||||
// per-city cost. `breakdown` mirrors the shape both the national and the
|
||||
// regional economy use, so one renderer serves them both.
|
||||
|
||||
import { percent, signedMoney, money, SOURCE_LABELS } from "./format.js";
|
||||
import { percent, signedMoney, money, unitMoney, SOURCE_LABELS } from "./format.js";
|
||||
import { BUDGET_EXPENSE_CATEGORIES, resourceById } from "../../../shared/data.js";
|
||||
import { groupDigits } from "../../../shared/text_format.js";
|
||||
import { resourceIcon } from "../ui/card.js";
|
||||
import { currencySymbol } from "../currency.js";
|
||||
|
||||
// Draws the table into `$list`. `expanded` is the set of open building types,
|
||||
@@ -172,10 +173,23 @@ export function renderMonthlyBudget($list, report, { expanded = new Set(), onTog
|
||||
$body.append($tr);
|
||||
if (isOpen) {
|
||||
for (const buy of row.buys) {
|
||||
const [resource, amount, sources] = buy;
|
||||
const [resource, amount, sources, quantity = 0] = buy;
|
||||
const proto = resourceById(resource);
|
||||
const name = proto ? proto.name : resource;
|
||||
$body.append(monthRow(name, -amount, "expense-detail budget-buy", null));
|
||||
const unit = proto ? proto.unit : "";
|
||||
// The money is the total bill; the quantity and the average delivered
|
||||
// price the money bought it at is what shows the cost of the work.
|
||||
const $label = $("<span class='res'></span>");
|
||||
$label.append(resourceIcon(resource));
|
||||
if (quantity > 0) {
|
||||
$label.append(document.createTextNode(
|
||||
`${groupDigits(Math.round(quantity))}${unit ? ` ${unit}` : ""}` +
|
||||
` @ ${unitMoney(amount / quantity)}${unit ? `/${unit}` : ""} avg`
|
||||
));
|
||||
} else {
|
||||
$label.append(document.createTextNode(name));
|
||||
}
|
||||
$body.append(monthRow($label, -amount, "expense-detail budget-buy", null));
|
||||
for (const [source, sourceAmount] of sources || []) {
|
||||
$body.append(
|
||||
monthRow(source, -sourceAmount, "expense-detail budget-source", null)
|
||||
@@ -208,7 +222,9 @@ export function renderMonthlyBudget($list, report, { expanded = new Set(), onTog
|
||||
|
||||
function monthRow(label, value, cls, note) {
|
||||
const $tr = $("<tr></tr>").addClass(cls);
|
||||
const $name = $("<td class='budget-name'></td>").text(label);
|
||||
const $name = $("<td class='budget-name'></td>");
|
||||
if (typeof label === "string") $name.text(label);
|
||||
else $name.append(label);
|
||||
if (note) $name.append($("<div class='budget-note'></div>").text(note));
|
||||
$tr.append($name, $("<td class='budget-value'></td>").text(money(value)));
|
||||
return $tr;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { groupDigits } from "../../../shared/text_format.js";
|
||||
import { policyById, resourceById } from "../../../shared/data.js";
|
||||
import { marketPriceIndex, resourceMarketValue } from "../../../shared/resources.js";
|
||||
import { formatMoneySigned } from "../currency.js";
|
||||
import { formatMoneySigned, currencySymbol } from "../currency.js";
|
||||
import { researchNode } from "../ui/card.js";
|
||||
|
||||
// The world market's current prices, from the snapshot's market listing.
|
||||
@@ -131,6 +131,16 @@ export function money(value) {
|
||||
return formatMoneySigned(value);
|
||||
}
|
||||
|
||||
// A per-unit price. Ordinary figures round to the nearest unit of currency;
|
||||
// sub-unit prices (a kilowatt-hour, say) keep significant digits so the row
|
||||
// does not read as free.
|
||||
export function unitMoney(value) {
|
||||
const amount = Number(value) || 0;
|
||||
if (amount >= 100) return `${currencySymbol()}${groupDigits(Math.round(amount))}`;
|
||||
if (amount >= 1) return `${currencySymbol()}${amount.toFixed(1)}`;
|
||||
return `${currencySymbol()}${amount.toPrecision(3)}`;
|
||||
}
|
||||
|
||||
export function percent(value) {
|
||||
return `${value < 0 ? "−" : "+"}${(Math.abs(value) * 100).toFixed(1)}%`;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,15 @@ import { formatMoney } from "../currency.js";
|
||||
// The materials a construction bills in, in reading order.
|
||||
export const CARD_RESOURCES = ["steel", "energy", "hightech"];
|
||||
|
||||
// A commodity's icon as an image node, for a row that names a resource with a
|
||||
// picture rather than the word.
|
||||
export function resourceIcon(id) {
|
||||
const proto = resourceById(id);
|
||||
const $img = $("<img>");
|
||||
if (proto) $img.attr("src", `assets/${proto.icon}`).attr("alt", proto.name);
|
||||
return $img;
|
||||
}
|
||||
|
||||
// A resource amount with its commodity icon. `sign` marks the direction of the
|
||||
// flow: "+" for output, "−" for a cost.
|
||||
function resourceLine(id, amount, sign) {
|
||||
|
||||
Reference in New Issue
Block a user