Corrected the catalogue descriptions and quoted every rate per day

This commit is contained in:
2026-09-27 10:38:37 +02:00
parent 26e5141ca2
commit 4d3591c48f
19 changed files with 108 additions and 70 deletions
+12
View File
@@ -78,6 +78,18 @@ There is no linter, formatter, or CI. Do not invent commands beyond these.
fragmented and large frames (`server/websocket.js`). Do not lower the buffer
limits without checking the snapshot path.
- Indent `.js` files with 2 spaces. `.editorconfig` only enforces UTF-8.
- The simulation ticks hourly, but the player reads rates per day: money income
and upkeep, research and culture effects, campaign spending and the opinion
drift are all scaled by `HOURS_PER_DAY` where they are shown
(`client/js/modals/format.js` `signedMoney`/`effectNodes`, `budget.js`,
`nation.js`, and the approval tooltip in `game_screen/panels.js`). Write new
rate copy per day, and leave durations (build time, cooldowns, endurance) in
hours.
- `description` fields under `shared/data/` are player-facing and must match the
simulation: they surface in tooltips — city building cards and resource
producers (`client/js/ui/card.js` `tooltipSections`), spy operations and
minority policies (`game_screen/panels.js`, `modals/nation.js`) — so correct
them when the behaviour they describe changes.
- Keep aircraft out of the melee: `_battleTiles`/`_combatTarget` skip them and
their strikes are routed explicitly. Mirror the air rules (airport-only
rebasing, strike radius, ferry fuel) on both client and server, or prediction
+2
View File
@@ -1106,6 +1106,7 @@ input:focus, select:focus { border-color: var(--accent); }
.card-tooltip .tip-consume,
.card-tooltip .tip-cost { color: #e2c08a; }
.card-tooltip .tip-none { color: var(--muted); }
.card-tooltip .tip-desc { color: var(--text); line-height: 1.35; }
.card-tooltip .res { display: inline; white-space: nowrap; }
.card-tooltip .res img { width: 13px; height: 13px; vertical-align: -2px; margin-right: 3px; }
@@ -2107,6 +2108,7 @@ input:focus, select:focus { border-color: var(--accent); }
}
.politics-estimate { margin-top: 8px; font-size: 13px; }
.politics-cost { color: #f0b060; }
.politics-desc { color: var(--muted); font-size: 12px; }
.politics-wizard .politics-controls { margin-top: 0; }
.ethnic-trend { font-weight: 700; margin-left: 2px; }
.trend-up { color: #6ee787; }
+6 -6
View File
@@ -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": "26e5141",
"date": "2026-09-25",
"subject": "Split the resource tooltip into aligned production and consumption lines",
"body": ""
},
{
"hash": "0a1c2d0",
"date": "2026-09-25",
@@ -54,11 +60,5 @@ export const DEVLOG = [
"date": "2026-09-24",
"subject": "Gave the world spies, treaties and nuclear weapons",
"body": ""
},
{
"hash": "54efd66",
"date": "2026-09-24",
"subject": "Grouped the unit stack by type and let ranged units pick a target",
"body": ""
}
];
+1 -1
View File
@@ -448,7 +448,7 @@ export class GameScreen {
!this._isFriendlyAirport(coords)) {
this.confirmModal.show(
"No runway",
"Aircraft may only fly to one of your cities with an airport, or attack an enemy unit.",
"Aircraft may only fly to one of your cities with an airport, or strike a hostile unit or city.",
null
);
return;
+16 -15
View File
@@ -25,6 +25,7 @@ import {
ECONOMY,
EFFECT_PRODUCTION,
APPROVAL,
POPULARITY,
effectValue,
pillageSteps,
ROADS,
@@ -47,7 +48,7 @@ import {
import { defenseFactors, unitDefense, productionFactors, productionMultiplier, popularityDisplay } from "../../../shared/rules.js";
import { formatResourceAmount, foodNeedPerDay, protoUpkeep, tileImprovementResourceCost } from "../../../shared/resources.js";
import { dateString, hoursInNextMonth } from "../../../shared/game_clock.js";
import { DEFAULT_UNIT_SPEED } from "../../../shared/game_state/constants.js";
import { DEFAULT_UNIT_SPEED, HOURS_PER_DAY } from "../../../shared/game_state/constants.js";
import { flightDistanceTiles } from "../../../shared/hex.js";
import { ethnicityChart } from "../ui/ethnicity.js";
import { isEconomicMode } from "../map_view/economic.js";
@@ -518,19 +519,13 @@ export const panelMethods = {
let hint = "Not yet available";
if (operation.id === SPY_SABOTAGE) {
enabled = this._canSabotage(units);
hint = enabled
? "Cripple this tile's production for a week (spends the spies)"
: "Move the spy onto foreign land";
hint = enabled ? operation.description : "Move the spy onto foreign land";
} else if (operation.id === SPY_CITY) {
enabled = this._spyActionTarget(SPY_CITY, units);
hint = enabled
? "Read a neighbouring foreign city (spends the spies)"
: "Move the spy beside a foreign city";
hint = enabled ? operation.description : "Move the spy beside a foreign city";
} else if (operation.id === SPY_COMMS) {
enabled = this._spyActionTarget(SPY_COMMS, units);
hint = enabled
? "Intercept a neighbouring capital's messages (spends the spies)"
: "Move the spy beside a foreign capital";
hint = enabled ? operation.description : "Move the spy beside a foreign capital";
}
const active = armed === operation.id;
$button
@@ -1001,15 +996,17 @@ export const panelMethods = {
const popularityPoints = stats.popularity || 0;
const popularity = popularityDisplay(popularityPoints);
this._updateApprovalStat(popularity);
// The people drift one point an hour back to neutral, so the estimate is
// simply how many points away they are; zero is already there.
// The people drift `POPULARITY.driftPerHour` points back to neutral every
// hour, so the estimate is how many points away they are spread over that
// daily rate; zero is already there.
const driftPerDay = POPULARITY.driftPerHour * HOURS_PER_DAY;
const pointsAway = Math.abs(popularityPoints);
this._setStatTooltip(
"stat-approval",
pointsAway <= 0
? "Popularity is exactly neutral"
: `Neutrality in about ${pointsAway} h (~${Math.ceil(pointsAway / 24)} d) ` +
"at the current drift of one point an hour"
: `Neutrality in about ${Math.ceil(pointsAway / driftPerDay)} d ` +
`at the current drift of ${driftPerDay} points a day`
);
// Popularity doubles its arrow past ten percentage points.
this._updateStatArrow(
@@ -1677,7 +1674,7 @@ export const panelMethods = {
$el.append(
$("<div class='site-material-label stalled'></div>").text(
"The market is above the budgeted prices. " +
"The budget rises 1% a day, doubling each day, until the materials can be bought."
"The budget is raised to meet them, then the materials are bought."
)
);
} else {
@@ -1829,7 +1826,11 @@ export const panelMethods = {
const materials = tileImprovementResourceCost(improvement);
const bill = this._resourceItems(materials);
const price = quotedBuildCost(improvement.buildCost, materials, prices);
// A resource producer's reference text sits above its output; the military
// works carry theirs as the produce line.
const desc = improvement.resource ? improvement.description || "" : "";
return tooltipSections({
desc,
produce,
produceSuffix: improvement.resource ? "/day" : "",
consume: this._improvementUpkeepItems(improvement),
+4 -3
View File
@@ -1,5 +1,5 @@
// The budget table shared by the nation and city panels. Every row is an item
// and its hourly amount, income in green and every cost in red, with the
// and its daily amount, income in green and every cost in red, with the
// projected total on the last row. Building types are rows that expand to their
// per-city cost. `breakdown` mirrors the shape both the national and the
// regional economy use, so one renderer serves them both.
@@ -88,7 +88,8 @@ export function renderBudget($list, breakdown = {}, { expanded = new Set(), onTo
}
// What the nation spent buying each resource, and earned selling it to other
// countries, over the last day, shown as an hourly average.
// countries, over the last day, shown as a daily figure. The record holds the
// day's total, so it is divided back to an hourly rate for `signedMoney`.
if (resourceMoney.length > 0) {
$body.append(sectionRow("Resource trade"));
for (const entry of resourceMoney) {
@@ -103,7 +104,7 @@ export function renderBudget($list, breakdown = {}, { expanded = new Set(), onTo
}
}
// The real resource bills of the last day, as hourly averages, in their own
// The real resource bills of the last day, as daily figures, in their own
// section so they are not confused with the forecast upkeep rows above.
const resourceCosts = breakdown.resourceCosts || [];
if (resourceCosts.length > 0) {
+2 -1
View File
@@ -138,7 +138,7 @@ export class CityModal {
);
for (const row of this.buildingRows) this.$buildingGrid.append(row.card.$card);
this.refreshBuildings(view.budget || 0, view.levels || {}, this.gdp, {});
this.$budgetSub.text(`Region net +${currencySymbol()}0/h`);
this.$budgetSub.text(`Region net +${currencySymbol()}0/day`);
this.renderBudget(null);
this.renderPeople(
view.ethnicMakeup || [],
@@ -804,6 +804,7 @@ export class CityModal {
const bill = this._resourceItems(materials);
const price = this._quotedCost(cost, row.proto, row.level + row.pending);
return tooltipSections({
desc: row.proto.description || "",
produce: effect && effect !== "No effect" ? [{ node: buildingEffectNodes(row.proto, shownLevel) }] : [],
consume: this._resourceItems(upkeep),
consumeSuffix: "/day",
+8 -6
View File
@@ -1,6 +1,7 @@
// Shared formatting helpers for the modal panels.
import { groupDigits } from "../../../shared/text_format.js";
import { HOURS_PER_DAY } from "../../../shared/game_clock.js";
import { policyById, resourceById, treatyById } from "../../../shared/data.js";
import { marketPriceIndex, resourceMarketValue } from "../../../shared/resources.js";
import { formatMoneySigned, currencySymbol } from "../currency.js";
@@ -34,9 +35,9 @@ export function formatEffect(effect, level) {
case "production":
return `${value >= 0 ? "+" : ""}${(value * 100).toFixed(1)}% production`;
case "research":
return `${value >= 0 ? "+" : ""}${value.toFixed(2)} research/h`;
return `${value >= 0 ? "+" : ""}${(value * HOURS_PER_DAY).toFixed(2)} research/day`;
case "culture":
return `${value >= 0 ? "+" : ""}${value.toFixed(2)} culture/h`;
return `${value >= 0 ? "+" : ""}${(value * HOURS_PER_DAY).toFixed(2)} culture/day`;
case "infantry_attack":
return `${value >= 0 ? "+" : ""}${(value * 100).toFixed(1)}% infantry attack`;
case "infantry_defense":
@@ -81,7 +82,7 @@ export function buildingEffectText(proto, level) {
return `−${saved}% civilian bombing losses · very hard to destroy`;
}
case "air_defense":
return "Fires one volley at hostile aircraft over the city";
return "Fires on hostile aircraft and missiles within two tiles";
case "port_production": {
const per = (proto.productionPerSeaTilePerLevel || 0) * level;
const food = (proto.foodPerSeaTilePerLevel || 0) * level * 100;
@@ -104,8 +105,8 @@ export function effectNodes(effects, level) {
if (index > 0) $frag.append(document.createTextNode(" · "));
if (effect.stat === "research") {
const value = level <= 0 ? 0 : effect.baseValue * Math.pow(level, effect.exponent);
$frag.append(researchNode(value, { sign: value >= 0 ? "+" : "", decimals: 2 }));
$frag.append(document.createTextNode("/h"));
$frag.append(researchNode(value * HOURS_PER_DAY, { sign: value >= 0 ? "+" : "", decimals: 2 }));
$frag.append(document.createTextNode("/day"));
} else {
$frag.append(document.createTextNode(formatEffect(effect, level)));
}
@@ -122,8 +123,9 @@ export function buildingEffectNodes(proto, level) {
export const SOURCE_LABELS = { building: "building", government: "government", technology: "research" };
// A signed money rate, given per hour by the model and shown per day.
export function signedMoney(value) {
return `${formatMoneySigned(value)}/h`;
return `${formatMoneySigned(value * HOURS_PER_DAY)}/day`;
}
// A signed money amount with no period suffix, for the monthly budget report.
+15 -8
View File
@@ -1600,8 +1600,8 @@ export class NationModal {
this.$technologySub
.empty()
.append(document.createTextNode("Research: "))
.append(researchNode(researchRate || 0, { sign: "+", decimals: 1 }))
.append(document.createTextNode("/h"));
.append(researchNode((researchRate || 0) * HOURS_PER_DAY, { sign: "+", decimals: 1 }))
.append(document.createTextNode("/day"));
const fin = finances || {};
this._treasury = budget;
@@ -2482,9 +2482,9 @@ export class NationModal {
const hourly = campaign.hourlyCost || 0;
const activeHours = Math.max(0, (info.totalHours || 0) - (campaign.startedHours || 0));
const spent = hourly * activeHours;
const days = Math.floor(activeHours / 24);
const days = Math.floor(activeHours / HOURS_PER_DAY);
return (
`${currencySymbol()}${groupDigits(this._quoted(hourly))}/h · ${currencySymbol()}${groupDigits(Math.round(spent))} spent over ` +
`${currencySymbol()}${groupDigits(Math.round(this._quoted(hourly) * HOURS_PER_DAY))}/day · ${currencySymbol()}${groupDigits(Math.round(spent))} spent over ` +
`${days} day${days === 1 ? "" : "s"}`
);
}
@@ -2512,10 +2512,12 @@ export class NationModal {
value: campaign.hourlyCost,
});
const $spend = $("<span class='politics-readout'></span>").text(
`${currencySymbol()}${groupDigits(this._quoted(campaign.hourlyCost))}/h`
`${currencySymbol()}${groupDigits(Math.round(this._quoted(campaign.hourlyCost) * HOURS_PER_DAY))}/day`
);
$slider.on("input", () =>
$spend.text(`${currencySymbol()}${groupDigits(this._quoted(Number($slider.val())))}/h`)
$spend.text(
`${currencySymbol()}${groupDigits(Math.round(this._quoted(Number($slider.val())) * HOURS_PER_DAY))}/day`
)
);
$slider.on("change", () => {
const amount = Number($slider.val());
@@ -2523,7 +2525,9 @@ export class NationModal {
// Reverting to the last known spend keeps the control honest until
// the player confirms the stop.
$slider.val(campaign.hourlyCost);
$spend.text(`${currencySymbol()}${groupDigits(this._quoted(campaign.hourlyCost))}/h`);
$spend.text(
`${currencySymbol()}${groupDigits(Math.round(this._quoted(campaign.hourlyCost) * HOURS_PER_DAY))}/day`
);
this.onConfirm(
"Stop this campaign?",
`Cutting the spending to ${currencySymbol()}0 will stop the campaign.`,
@@ -2563,7 +2567,7 @@ export class NationModal {
);
$card.append(
$("<div class='desc politics-cost'></div>").text(
`Costs ${currencySymbol()}${groupDigits(this._quoted(PROPAGANDA.defaultHourlyCost))}/h to begin; ` +
`Costs ${currencySymbol()}${groupDigits(Math.round(this._quoted(PROPAGANDA.defaultHourlyCost) * HOURS_PER_DAY))}/day to begin; ` +
"adjust the spending on an active campaign to make it land harder or softer."
)
);
@@ -2677,6 +2681,7 @@ export class NationModal {
const $eth = this._civSelect(civs, policy.ethnicity);
this.$policyType = $type;
this.$policyEth = $eth;
this.$policyDesc = $("<div class='politics-estimate politics-desc'></div>");
this.$policyTarget = $("<div class='politics-estimate'></div>");
this.$policyCost = $("<div class='politics-estimate politics-cost'></div>");
this.$policyDuplicate = $("<div class='politics-estimate politics-penalty'></div>");
@@ -2701,6 +2706,7 @@ export class NationModal {
$controls.append($("<div class='politics-field'></div>").append(this.$policyEnact));
this.$politicsWizard.append(
$controls,
this.$policyDesc,
this.$policyTarget,
this.$policyCost,
this.$policyDuplicate
@@ -2719,6 +2725,7 @@ export class NationModal {
const type = String(this.$policyType.val());
const ethnicity = Number(this.$policyEth.val());
const proto = policyById(type);
this.$policyDesc.text(proto ? proto.description : "");
this.$policyTarget.text(proto ? `Target: ${proto.targetEffect}` : "");
this.$policyCost.text(proto ? `Political cost: ${proto.politicalCost}` : "");
const duplicate = active.some(
+9 -1
View File
@@ -60,18 +60,22 @@ export function resourceBill(items, { label = "", sign = "−", suffix = "" } =
// The three tooltip sections from a work's flows. Each entry is a resource
// { id, amount } or free text { text }; free text is shown exactly as written.
// `desc` is an optional prose line shown first, with no section heading.
export function tooltipSections({
desc = "",
produce = [],
produceSuffix = "",
consume = [],
consumeSuffix = "",
cost = [],
} = {}) {
return [
const sections = [
{ label: "Produces", cls: "tip-produce", sign: "+", suffix: produceSuffix, items: produce },
{ label: "Consumes", cls: "tip-consume", sign: "−", suffix: consumeSuffix, items: consume },
{ label: "Costs", cls: "tip-cost", sign: "−", suffix: "", items: cost },
];
if (desc) sections.unshift({ desc });
return sections;
}
let $tooltip = null;
@@ -93,6 +97,10 @@ export function showCardTooltip($anchor, sections) {
if (!$anchor || !$anchor.length) return;
$tooltip = $("<div class='card-tooltip'></div>");
for (const section of sections) {
if (section.desc) {
$tooltip.append($("<div class='tip-section tip-desc'></div>").text(section.desc));
continue;
}
const $section = $("<div class='tip-section'></div>");
$section.append($("<div class='tip-head'></div>").text(section.label));
if (!section.items || section.items.length === 0) {
+3 -3
View File
@@ -13,7 +13,7 @@ export const BUILDING_MECHANIC = {
PARALLEL_BUILDING: "parallel_building",
// Cuts civilian deaths from bombing, and is very hard to knock down.
CIVILIAN_SHELTER: "civilian_shelter",
// Fires one volley at hostile aircraft passing over the city.
// Fires on hostile aircraft and missiles that enter a two-tile corridor.
AIR_DEFENSE: "air_defense",
// Flat production per head for the city's region, per adjacent sea tile.
PORT_PRODUCTION: "port_production",
@@ -40,7 +40,7 @@ export const BUILDINGS = [
{
id: "shelters",
name: "Wartime shelters",
description: "Each level reduces civilian life loss from bombing by 10%. Extremely hard to destroy. Does not protect other buildings.",
description: "Each level shelters another 10% of the city's people from bombing, up to all of them. Extremely hard to destroy. Does not protect other buildings.",
baseCost: 100_000_000,
costAlpha: 2,
refundFraction: 0.5,
@@ -58,7 +58,7 @@ export const BUILDINGS = [
id: "aa_building",
advanced: true,
name: "Air defense",
description: "Damages hostile aircraft that fly above this city.",
description: "Fires on hostile aircraft and missiles that come within two tiles of this city.",
baseCost: 1_000_000_000,
costAlpha: 2,
refundFraction: 0.5,
+1 -1
View File
@@ -7,7 +7,7 @@ export const GOVERNMENTS = [
{
id: "republic",
name: "Republic",
description: "Rule by elected representatives. A balanced government that spreads its support across the economy, trade and the sciences.",
description: "Rule by elected representatives. A balanced government that spreads its support across the economy and the sciences.",
baseCost: 0,
costGdpFraction: 0.02,
effects: [effect(EFFECT_PRODUCTION, 0.05, 1.0), effect(EFFECT_RESEARCH, 1.0, 1.0)],
+1 -1
View File
@@ -77,7 +77,7 @@ export const TILE_IMPROVEMENTS = [
advanced: true,
name: "Coastal cannon",
description:
"Automatically fires on hostile warships within two tiles. Only a coastal tile can carry one.",
"Automatically fires on hostile naval units within two tiles. Only a coastal tile can carry one.",
buildCost: 120_000_000,
maintenance: 2000,
range: 2,
+12 -12
View File
@@ -311,7 +311,7 @@ export const RESOURCE_BUILDINGS = [
id: "coal_power_plant",
name: "Coal power plant",
description:
"Burns coal to make 250 GWh of electricity a day. Cheap to raise, but the fuel bill never stops.",
"Burns coal to make 2,500 GWh of electricity a day. Cheap to raise, but the fuel bill never stops.",
resource: "energy",
outputPerDay: 250 * KWH_PER_GWH,
fuelEnergyPerDay: 250 * KWH_PER_GWH * 0.25,
@@ -324,7 +324,7 @@ export const RESOURCE_BUILDINGS = [
id: "natural_gas_power_plant",
name: "Natural gas power plant",
description:
"The cleanest fossil plant: 300 GWh a day for a higher fuel bill.",
"The cleanest fossil plant: 3,000 GWh a day for a higher fuel bill.",
resource: "energy",
outputPerDay: 300 * KWH_PER_GWH,
fuelEnergyPerDay: 300 * KWH_PER_GWH * 0.25,
@@ -336,7 +336,7 @@ export const RESOURCE_BUILDINGS = [
{
id: "oil_power_plant",
name: "Oil power plant",
description: "Burns oil for 270 GWh of electricity a day.",
description: "Burns oil for 2,700 GWh of electricity a day.",
resource: "energy",
outputPerDay: 270 * KWH_PER_GWH,
fuelEnergyPerDay: 270 * KWH_PER_GWH * 0.25,
@@ -349,7 +349,7 @@ export const RESOURCE_BUILDINGS = [
id: "solar_power_plant",
name: "Solar power plant",
description:
"A field of panels producing a steady 30 GWh a day with no fuel and no upkeep.",
"A field of panels producing a steady 300 GWh a day with no fuel and no upkeep.",
resource: "energy",
outputPerDay: 30 * KWH_PER_GWH,
energyPerDay: 0,
@@ -361,7 +361,7 @@ export const RESOURCE_BUILDINGS = [
{
id: "geothermal_plant",
name: "Geothermal plant",
description: "Taps the earth's heat for 60 GWh a day of free electricity.",
description: "Taps the earth's heat for 600 GWh a day of free electricity.",
resource: "energy",
outputPerDay: 60 * KWH_PER_GWH,
energyPerDay: 0,
@@ -373,7 +373,7 @@ export const RESOURCE_BUILDINGS = [
{
id: "wind_turbines",
name: "Wind turbines",
description: "A hilltop wind farm making 25 GWh a day for free.",
description: "A hilltop wind farm making 250 GWh a day for free.",
resource: "energy",
outputPerDay: 25 * KWH_PER_GWH,
energyPerDay: 0,
@@ -385,7 +385,7 @@ export const RESOURCE_BUILDINGS = [
{
id: "offshore_wind_turbines",
name: "Offshore wind turbines",
description: "Turbines out at sea making 50 GWh a day. Needs a coastal tile.",
description: "Turbines out at sea making 500 GWh a day. Needs a coastal tile.",
resource: "energy",
outputPerDay: 50 * KWH_PER_GWH,
energyPerDay: 0,
@@ -399,7 +399,7 @@ export const RESOURCE_BUILDINGS = [
id: "fusion_power_plant",
advanced: true,
name: "Fusion power plant",
description: "A late-game reactor producing 4 TWh of electricity a day.",
description: "A late-game reactor producing 40 TWh of electricity a day.",
resource: "energy",
outputPerDay: 4_000 * KWH_PER_GWH,
energyPerDay: 0,
@@ -417,7 +417,7 @@ export const RESOURCE_BUILDINGS = [
id: "steel_mill",
name: "Steel mill",
description:
"Smelts iron into 20,000 t of steel a day, spending 20 MWh of energy per tonne.",
"Smelts iron into 200,000 t of steel a day, spending 20 MWh of energy per tonne.",
resource: "steel",
outputPerDay: 20_000,
energyPerDay: 2_000 * RESOURCE_RULES.energyPerSteelTonne,
@@ -428,7 +428,7 @@ export const RESOURCE_BUILDINGS = [
{
id: "gold_mine",
name: "Gold mine",
description: "Produces 20,000 carats of luxury a day, at 10 MWh a carat.",
description: "Produces 200,000 carats of luxury a day, at 10 MWh a carat.",
resource: "luxury",
outputPerDay: 20_000,
energyPerDay: 20_000 * RESOURCE_RULES.energyPerLuxuryCarat.gold_mine,
@@ -439,7 +439,7 @@ export const RESOURCE_BUILDINGS = [
{
id: "diamond_mine",
name: "Diamond mine",
description: "Produces 20,000 carats of luxury a day, at 5 MWh a carat.",
description: "Produces 200,000 carats of luxury a day, at 5 MWh a carat.",
resource: "luxury",
outputPerDay: 20_000,
energyPerDay: 20_000 * RESOURCE_RULES.energyPerLuxuryCarat.diamond_mine,
@@ -452,7 +452,7 @@ export const RESOURCE_BUILDINGS = [
advanced: true,
name: "Chip foundry",
description:
"Fabricates 5000 units of high-tech a day, spending 100 MWh of energy per unit.",
"Fabricates 50,000 units of high-tech a day, spending 10 MWh of energy per unit.",
resource: "hightech",
outputPerDay: 5_000,
energyPerDay: 5_000 * RESOURCE_RULES.energyPerHighTechUnit,
+2 -2
View File
@@ -16,7 +16,7 @@ export const STATUSES = [
id: STATUS_ENCIRCLED,
label: "Encircled",
icon: "encirclement.svg",
description: "Cut off from every allied city while a hostile unit is adjacent. Loses 10% of its maximum hit points each day, or 2% inside a city.",
description: "Cut off: every adjacent land tile is held by a hostile unit and no friendly port supplies it by sea. Loses 10% of its maximum hit points each day, or 2% inside a city.",
hpLossPerDay: 0.10,
cityHpLossPerDay: 0.02,
},
@@ -24,7 +24,7 @@ export const STATUSES = [
id: STATUS_BESIEGED,
label: "Besieged",
icon: "encirclement.svg",
description: "Cut off from every other allied city while a hostile unit is adjacent.",
description: "Cut off: every adjacent land tile is held by a hostile unit and no friendly port supplies it by sea.",
},
{
id: STATUS_BATTLE,
+1 -1
View File
@@ -74,7 +74,7 @@ export const TECHNOLOGIES = [
{
id: "space_program",
name: "Space Program",
description: "Reaching orbit inspires the nation and spins off technology across the economy.",
description: "Reaching orbit inspires the nation, lifting its culture and its industry.",
theme: "science",
cost: 300,
prerequisites: ["industrial_automation", "quantum_computing"],
+2 -2
View File
@@ -26,7 +26,7 @@ export const TREATIES = [
id: TREATY_ALLIANCE,
name: "Alliance",
description:
"The signatories cannot go to war with each other; attacking one declares war on all.",
"The signatories spare each other's forces; attacking one breaks the pact and draws its allies into the war.",
},
{
id: TREATY_FREE_TRADE,
@@ -48,7 +48,7 @@ export const TREATIES = [
{
id: TREATY_GIVE_RESOURCES,
name: "Give resources",
description: "Transfers stores of steel, high-tech, luxury, food or energy.",
description: "Transfers stores of steel, high-tech, luxury or food.",
payload: "resources",
},
{
+1 -1
View File
@@ -2291,7 +2291,7 @@ export class GameScreenTest extends TestCase {
.map((_, el) => env.$(el).text())
.get()
.join("|");
this.assertTrue(labels.includes("doubling"), `the automatic rise is explained: ${labels}`);
this.assertTrue(labels.includes("raised to meet them"), `the automatic rise is explained: ${labels}`);
this.assertFalse(labels.includes("Increase budget"), `no raise is offered: ${labels}`);
this.assertEqual(
env.$("#tile-works .site-warning").length,
+10 -6
View File
@@ -34,7 +34,10 @@ export class CityModalTest extends TestCase {
1,
"the lab's science output carries the erlenmeyer icon"
);
this.assertFalse($tip.text().includes("research"), "the unit word is replaced by the icon");
this.assertFalse(
$tip.find(".tip-produce").text().includes("research"),
"the unit word is replaced by the icon"
);
modal.close();
} finally {
teardownDom(env);
@@ -287,13 +290,13 @@ export class CityModalTest extends TestCase {
};
modal.show({ id: 1, name: "Paris" }, { budget: 1.0e15, buildings: BUILDINGS, levels: {} });
modal.renderBudget(breakdown);
this.assertTrue(env.$("#city-budget-sub").text().includes("+¤635/h"), "the region net is shown");
this.assertTrue(env.$("#city-budget-sub").text().includes("+¤15 240/day"), "the region net is shown");
const text = env.$("#city-budget-list").text();
this.assertTrue(text.includes("+¤1 035/h"), "region tax income is green");
this.assertTrue(text.includes("+¤24 840/day"), "region tax income is green");
this.assertTrue(text.includes("Bank in Paris (building) +5.0%"), "a building bonus is listed");
this.assertTrue(text.includes("−¤400/h"), "the building upkeep is listed");
this.assertTrue(text.includes("−¤9 600/day"), "the building upkeep is listed");
this.assertTrue(text.includes("TOTAL"), "a total row is present");
this.assertTrue(text.includes("+¤635/h"), "the total carries the region net");
this.assertTrue(text.includes("+¤15 240/day"), "the total carries the region net");
this.assertFalse(text.includes("No units"), "unit upkeep is national, not regional");
env.$("#city-budget-list tr.budget-group .budget-name").click();
@@ -664,7 +667,8 @@ export class NationModalTest extends TestCase {
1,
"the rate carries the erlenmeyer icon"
);
this.assertTrue($sub.text().includes("2.5"), "the rate is shown");
this.assertTrue($sub.text().includes("60.0"), "the per-day rate is shown");
this.assertTrue($sub.text().includes("/day"), "the rate is stated per day");
this.assertFalse($sub.text().includes("RP"), "no text 'RP' unit is left");
modal.close();
} finally {