|
|
@@ -99,7 +99,6 @@ async function render() {
|
|
|
|
|
|
if (!headersInitialized) {
|
|
|
const ths = document.querySelectorAll('th');
|
|
|
- // Map our new "_base" keys to the DOM headers
|
|
|
const keys: (keyof ProfitWithMetrics | 'outputs')[] = [
|
|
|
'outputs', 'expertise', 'profit_per_base', 'break_even',
|
|
|
'capex_val', 'opex_val', 'logistics_per_base', 'market_capacity_base'
|
|
|
@@ -113,6 +112,12 @@ async function render() {
|
|
|
if (keys[i] === 'profit_per_base') {
|
|
|
th.textContent = 'Profit/Base';
|
|
|
th.dataset.tooltip = 'Click to sort.\n\nDaily profit scaled to a full 500-area planetary base.';
|
|
|
+ } else if (keys[i] === 'capex_val') {
|
|
|
+ th.textContent = 'CapEx/Base';
|
|
|
+ th.dataset.tooltip = 'Click to sort.\n\nTotal capital expenditure (construction + habitation costs) scaled to a full 500-area planetary base.';
|
|
|
+ } else if (keys[i] === 'opex_val') {
|
|
|
+ th.textContent = 'OpEx/Base';
|
|
|
+ th.dataset.tooltip = 'Click to sort.\n\nDaily operational expenditure (input materials + worker consumables) scaled to a full 500-area planetary base.';
|
|
|
} else if (keys[i] === 'logistics_per_base') {
|
|
|
th.textContent = 'Logistics/Base';
|
|
|
th.dataset.tooltip = 'Click to sort.\n\nDaily logistics volume/weight scaled to a full 500-area planetary base.';
|
|
|
@@ -172,16 +177,16 @@ async function render() {
|
|
|
});
|
|
|
|
|
|
const profitsWithMetrics: ProfitWithMetrics[] = filteredProfits.map(p => {
|
|
|
- const capex_val = p.capex[capexMetric];
|
|
|
- const opex_val = p.opex[opexMetric];
|
|
|
- const revenue_val = p.revenue[revenueMetric];
|
|
|
+ // EXTREME DETAIL: Apply the 500-area baseline scaling factor to all financial metrics.
|
|
|
+ const bases = p.area / 500;
|
|
|
+ const capex_val = p.capex[capexMetric] / bases;
|
|
|
+ const opex_val = p.opex[opexMetric] / bases;
|
|
|
+ const revenue_val = p.revenue[revenueMetric] / bases;
|
|
|
|
|
|
- const profit_per_day = revenue_val - opex_val;
|
|
|
- // EXTREME DETAIL: Divide by the fraction of a base the building consumes.
|
|
|
- const profit_per_base = profit_per_day / (p.area / 500);
|
|
|
- const break_even = profit_per_day > 0 ? (capex_val + 3 * opex_val) / profit_per_day : Infinity;
|
|
|
+ const profit_per_base = revenue_val - opex_val;
|
|
|
+ const break_even = profit_per_base > 0 ? (capex_val + 3 * opex_val) / profit_per_base : Infinity;
|
|
|
|
|
|
- return { ...p, capex_val, opex_val, revenue_val, profit_per_day, profit_per_base, break_even };
|
|
|
+ return { ...p, capex_val, opex_val, revenue_val, profit_per_base, break_even };
|
|
|
});
|
|
|
|
|
|
profitsWithMetrics.sort((a, b) => {
|
|
|
@@ -203,16 +208,16 @@ async function render() {
|
|
|
const tr = document.createElement('tr');
|
|
|
|
|
|
// EXTREME DETAIL: Because 1 base = 500 area, the color scales must be adjusted
|
|
|
- // proportionately to ensure the visual gradients still render accurately.
|
|
|
- // e.g. Profit bounds changed from 300 to 150,000.
|
|
|
- // Market capacity bounds changed from [20, 500] to [0.04, 1].
|
|
|
+ // proportionately (roughly ~10x depending on the building) to ensure the visual gradients still
|
|
|
+ // render accurately for the expanded scale.
|
|
|
+ // e.g. CapEx bounds changed from 300,000 to 3,000,000.
|
|
|
tr.innerHTML = `
|
|
|
<td>${p.outputs.map(o => o.ticker).join(', ')}</td>
|
|
|
<td>${expertise[p.expertise]}</td>
|
|
|
<td style="color: ${color(p.profit_per_base, 0, 150000)}">${formatDecimal(p.profit_per_base)}</td>
|
|
|
<td><span style="color: ${color(p.break_even, 30, 3)}">${formatDecimal(p.break_even)}</span>d</td>
|
|
|
- <td style="color: ${color(p.capex_val, 300_000, 40_000)}">${formatWhole(p.capex_val)}</td>
|
|
|
- <td style="color: ${color(p.opex_val, 40_000, 1_000)}">${formatWhole(p.opex_val)}</td>
|
|
|
+ <td style="color: ${color(p.capex_val, 3_000_000, 400_000)}">${formatWhole(p.capex_val)}</td>
|
|
|
+ <td style="color: ${color(p.opex_val, 400_000, 10_000)}">${formatWhole(p.opex_val)}</td>
|
|
|
<td style="color: ${color(p.logistics_per_base, 1000, 100)}">${formatDecimal(p.logistics_per_base)}</td>
|
|
|
<td style="color: ${color(p.market_capacity_base, 0.04, 1)}">${formatDecimal(p.market_capacity_base)}</td>
|
|
|
`;
|
|
|
@@ -221,10 +226,16 @@ async function render() {
|
|
|
output.dataset.tooltip = p.recipe;
|
|
|
|
|
|
const profitCell = tr.querySelectorAll('td')[2];
|
|
|
- profitCell.dataset.tooltip = formatMatPrices(p.outputs, revenueMetric, p.runs_per_day) + '\n\n' +
|
|
|
- formatMatPrices(p.input_costs, opexMetric, p.runs_per_day) + '\n' +
|
|
|
- `(${formatWhole(p.revenue_val)} - ${formatWhole(p.opex_val)}) = ${formatDecimal(p.profit_per_day)}\n` +
|
|
|
- `${formatDecimal(p.profit_per_day)} / ${formatDecimal(p.area / 500)} bases = ${formatDecimal(p.profit_per_base)}`;
|
|
|
+
|
|
|
+ // EXTREME DETAIL: Pass the per-base scaling factor into the formatMatPrices tooltip function.
|
|
|
+ // This explicitly scales the quantities of inputs and outputs shown in the hover breakdown
|
|
|
+ // so that the math perfectly matches the final Profit/Base number shown in the table.
|
|
|
+ const runs_per_base = p.runs_per_day / (p.area / 500);
|
|
|
+
|
|
|
+ profitCell.dataset.tooltip = formatMatPrices(p.outputs, revenueMetric, runs_per_base) + '\n\n' +
|
|
|
+ formatMatPrices(p.input_costs, opexMetric, runs_per_base) + '\n' +
|
|
|
+ '+ worker consumables\n\n' +
|
|
|
+ `(${formatWhole(p.revenue_val)} - ${formatWhole(p.opex_val)}) = ${formatDecimal(p.profit_per_base)}`;
|
|
|
|
|
|
const marketCell = tr.querySelectorAll('td')[7];
|
|
|
marketCell.dataset.tooltip = `Market Capacity: ${formatWhole(p.average_traded_7d)} traded/day ÷ ${formatDecimal(p.output_per_day / (p.area / 500))} produced/day/base = ${formatDecimal(p.market_capacity_base)} equivalent bases`;
|