diff --git a/devTools/types/FC/gameState.d.ts b/devTools/types/FC/gameState.d.ts
index b28d13f53d15424cab78f1c43c2367ceefad9a4b..1615f94cbd8d124d2a7c5d7fe240fff567e91d51 100644
--- a/devTools/types/FC/gameState.d.ts
+++ b/devTools/types/FC/gameState.d.ts
@@ -118,7 +118,7 @@ declare namespace FC {
 		activeArcologyIdx?: number;
 
 		passageSwitchHandler?: () => void;
-		showAllEntries?: {
+		showAllEntries: {
 			costsBudget: number;
 			repBudget: number;
 		};
@@ -132,7 +132,7 @@ declare namespace FC {
 		clubSlavesGettingHelp?: number;
 
 		lastWeeksRepErrors?: string;
-		lastWeeksCashErrors?: string;
+		lastWeeksCashErrors: Array<string>;
 
 		arcadeDemandDegResult?: 1 | 2 | 3 | 4 | 5;
 
diff --git a/js/002-config/fc-js-init.js b/js/002-config/fc-js-init.js
index d48f8a99ba733e149fcc35a517c6b36e940f7ebe..f96e54356f9fbc43d15971456928fdeec28a18f2 100644
--- a/js/002-config/fc-js-init.js
+++ b/js/002-config/fc-js-init.js
@@ -15,6 +15,7 @@ var App = { }; // eslint-disable-line no-redeclare
 App.Arcology = {};
 App.Arcology.Cell = {};
 App.Art = {};
+App.Budget = {};
 App.Corporate = {};
 App.Data = {};
 App.Data.clothes = new Map();
diff --git a/js/003-data/gameVariableData.js b/js/003-data/gameVariableData.js
index 4f2b71a016380147a83ec30740745baa92bcd36d..4a3bb1a7c1e111de02284b30010034ee4bf76aaa 100644
--- a/js/003-data/gameVariableData.js
+++ b/js/003-data/gameVariableData.js
@@ -413,10 +413,6 @@ App.Data.resetOnNGPlus = {
 
 	reminderEntry: "",
 	reminderWeek: "",
-	lastWeeksCashIncome: {},
-	lastWeeksCashExpenses: {},
-	lastWeeksRepIncome: {},
-	lastWeeksRepExpenses: {},
 	currentRule: {},
 	costs: 0,
 	seeBuilding: 0,
@@ -433,6 +429,14 @@ App.Data.resetOnNGPlus = {
 	DJignoresFlaws: 0,
 	DJnoSex: 0,
 
+	// Budget
+	lastWeeksCashIncome: {},
+	lastWeeksCashExpenses: {},
+	lastWeeksRepIncome: {},
+	lastWeeksRepExpenses: {},
+	showAllEntries: {costsBudget: 0, repBudget: 0},
+	lastWeeksCashErrors: [],
+
 	localEcon: 0,
 	econRate: 0,
 	drugsCost: 0,
diff --git a/src/005-passages/budgetPassages.js b/src/005-passages/budgetPassages.js
new file mode 100644
index 0000000000000000000000000000000000000000..09ea968153bcecc581274d826e77e7ea39cf8ce7
--- /dev/null
+++ b/src/005-passages/budgetPassages.js
@@ -0,0 +1,7 @@
+new App.DomPassage("Costs Budget",
+	() => {
+		V.nextButton = "Back";
+		V.nextLink = "Main";
+		return App.Budget.costs();
+	}, ["jump-to-safe", "jump-from-safe"]
+);
diff --git a/src/uncategorized/budget.js b/src/budget/budget.js
similarity index 99%
rename from src/uncategorized/budget.js
rename to src/budget/budget.js
index 4e5bfcfc018094ecbe2d38532810e20e763cfcaa..abd3ede0152557652f7dbbe325ab1c1d159d8735 100644
--- a/src/uncategorized/budget.js
+++ b/src/budget/budget.js
@@ -3,7 +3,7 @@
  * @param {"cash"|"rep"} budgetType
  * @returns {HTMLTableElement}
  */
-App.UI.budget = function(budgetType) {
+App.Budget.table = function(budgetType) {
 	let coloredRow = true;
 
 	// Set up object to track calculated displays
@@ -395,10 +395,13 @@ App.UI.budget = function(budgetType) {
 		cell.append("Tracked totals");
 
 		cell = row.insertCell();
+		// Make the total 0 first, otherwise it gets counted as part of the new total
+		V[income].Total = 0;
 		V[income].Total = hashSum(V[income]);
 		cell.append(formatColorDOM(Math.trunc(V[income].Total)));
 
 		cell = row.insertCell();
+		V[expenses].Total = 0;
 		V[expenses].Total = hashSum(V[expenses]);
 		cell.append(formatColorDOM(Math.trunc(V[expenses].Total)));
 
diff --git a/src/budget/costsBudget.js b/src/budget/costsBudget.js
new file mode 100644
index 0000000000000000000000000000000000000000..f8a7b4a6720fbe9c236b9a1991ecebb643c12f7b
--- /dev/null
+++ b/src/budget/costsBudget.js
@@ -0,0 +1,114 @@
+/**
+ * Costs Budget Passage
+ * @returns {DocumentFragment}
+ */
+App.Budget.costs = function() {
+	const f = new DocumentFragment();
+
+	App.UI.DOM.appendNewElement("h1", f, "Costs Budget");
+	f.append(intro());
+	if (V.difficultySwitch === 1) {
+		f.append(economy());
+	}
+	f.append(settings());
+
+	// Table of Totals
+	if (!V.lastWeeksCashIncome) {
+		App.UI.DOM.appendNewElement("p", f, "Financial data currently unavailable.");
+	} else {
+		App.UI.DOM.appendNewElement("p", f, App.Budget.table("cash"));
+	}
+
+	errors(f);
+	return f;
+
+	/**
+	 * @returns {HTMLParagraphElement}
+	 */
+	function intro() {
+		return App.UI.DOM.makeElement("p", `Here you can view many of the financial details of your arcology, ${properTitle()}. The detailed list of slaves and their costs (food, hormones) that you may remember can now be found at slave maintenance. Other links will allow you to directly control areas of your arcology to adjust spending to suit your tastes.`, "scene-intro");
+	}
+
+	/**
+	 * @returns {HTMLParagraphElement}
+	 */
+	function economy() {
+		const p = document.createElement("p");
+		App.UI.DOM.appendNewElement("div", p, `The Local Economy score effects some prices in your ecology. The lower the score, the higher the prices. The base score is 100.`, "scene-intro");
+
+		const grid = document.createElement("div");
+		grid.className = "grid-2columns-auto";
+
+		App.UI.DOM.appendNewElement("div", grid, "Global Economy", "cash");
+		if ((V.cheatMode) && (V.cheatModeM)) {
+			const div = document.createElement("div");
+			div.append(App.UI.DOM.makeTextBox(V.economy, v => {
+				V.economy = v;
+				V.cheater = 1;
+			}, true));
+			grid.append(div);
+		} else {
+			App.UI.DOM.appendNewElement("div", grid, String(V.economy));
+		}
+
+		App.UI.DOM.appendNewElement("div", grid, "Local Economy", "cash");
+		if ((V.cheatMode) && (V.cheatModeM)) {
+			const div = document.createElement("div");
+			div.append(App.UI.DOM.makeTextBox(V.localEcon, v => {
+				V.localEcon = v;
+				V.cheater = 1;
+			}, true));
+			grid.append(div);
+		} else {
+			App.UI.DOM.appendNewElement("div", grid, String(V.localEcon));
+		}
+
+		p.append(grid);
+
+		const r = [];
+		r.push("The current score is");
+		if (V.localEcon > 100) {
+			let _econPercent = Math.trunc(1000 - 100000 / V.localEcon) / 10;
+			r.push(`reducing prices by <span class="cash inc">${_econPercent}%.</span>`);
+		} else if (V.localEcon === 100) {
+			r.push("equal to the base score. There are no price modifications.");
+		} else {
+			let _econPercent = Math.trunc(100000 / V.localEcon - 1000) / 10;
+			r.push(`increasing prices by <span class="cash dec">${_econPercent}%.</span>`);
+		}
+		$(p).append(...App.Events.spaceSentences(r));
+
+		return p;
+	}
+
+	/**
+	 * @returns {HTMLParagraphElement}
+	 */
+	function settings() {
+		const p = document.createElement("p");
+		App.UI.DOM.appendNewElement("div", p, "Your weekly costs are as follows:", "detail");
+
+		let _options = new App.UI.OptionsGroup();
+		_options.addOption("", "costsBudget", V.showAllEntries)
+			.addValue("Normal", 0).on().addValue("Show Empty Entries", 1);
+		p.append(_options.render());
+
+		return p;
+	}
+
+	/**
+	 * @param {DocumentFragment} container
+	 */
+	function errors(container) {
+		if (V.lastWeeksCashErrors.length > 0) {
+			const p = document.createElement("p");
+			p.append(App.UI.DOM.passageLink("Reset", "Costs Budget",
+				() => { V.lastWeeksCashErrors = []; }));
+			App.UI.DOM.appendNewElement("div", p, "Errors:", "error");
+			for (const error of V.lastWeeksCashErrors) {
+				App.UI.DOM.appendNewElement("div", p, error, "error");
+			}
+			container.append(p);
+		}
+	}
+};
diff --git a/src/data/backwardsCompatibility/backwardsCompatibility.js b/src/data/backwardsCompatibility/backwardsCompatibility.js
index 5973ea3894dc67824d18940dc36885124c14bb57..0eb51c3bffdd8d64c88c130617e138a466338c9f 100644
--- a/src/data/backwardsCompatibility/backwardsCompatibility.js
+++ b/src/data/backwardsCompatibility/backwardsCompatibility.js
@@ -1308,6 +1308,17 @@ App.Update.globalVariables = function(node) {
 	V.experimental.raGrowthExpr = V.experimental.raGrowthExpr || 0;
 	V.experimental.reportMissingClothing = V.experimental.reportMissingClothing || 0;
 
+	// Budget
+	V.showAllEntries.costsBudget = V.showAllEntries.costsBudget || 0;
+	V.showAllEntries.repBudget = V.showAllEntries.repBudget || 0;
+	if (typeof V.lastWeeksCashErrors === "string") {
+		if (V.lastWeeksCashErrors === "Errors: ") {
+			V.lastWeeksCashErrors = [];
+		} else {
+			V.lastWeeksCashErrors = [V.lastWeeksCashErrors];
+		}
+	}
+
 	node.append(`Done!`);
 };
 
diff --git a/src/js/economyJS.js b/src/js/economyJS.js
index 85e2b2de7de308a0efcfa839a2584220d13de456..70ad99e44a1fb176baa9fe52bb2ff33bb290a6cf 100644
--- a/src/js/economyJS.js
+++ b/src/js/economyJS.js
@@ -2224,7 +2224,7 @@ The third category, the "slave slot" is completely optional. Sometimes you just
  */
 globalThis.cashX = function(cost, what, who) {
 	if (!Number.isFinite(cost)) {
-		V.lastWeeksCashErrors += `Expected a finite number for ${what}, but got ${cost}<br>`;
+		V.lastWeeksCashErrors.push(`Expected a finite number for ${what}, but got ${cost}`);
 		return 0;
 	}
 
@@ -2240,7 +2240,7 @@ globalThis.cashX = function(cost, what, who) {
 		if (typeof V.lastWeeksCashIncome[what] !== 'undefined') {
 			V.lastWeeksCashIncome[what] += cost;
 		} else {
-			V.lastWeeksCashErrors += `Unknown place "${what}" gained you ${cost}<br>`;
+			V.lastWeeksCashErrors.push(`Unknown place "${what}" gained you ${cost}`);
 		}
 
 		// record the slave, if available
@@ -2256,7 +2256,7 @@ globalThis.cashX = function(cost, what, who) {
 		if (typeof V.lastWeeksCashExpenses[what] !== 'undefined') {
 			V.lastWeeksCashExpenses[what] += cost;
 		} else {
-			V.lastWeeksCashErrors += `Unknown place "${what}" charged you ${cost}<br>`;
+			V.lastWeeksCashErrors.push(`Unknown place "${what}" charged you ${cost}`);
 		}
 
 		// record the slave, if available
@@ -2551,7 +2551,7 @@ globalThis.ownershipReport = function(short) {
 globalThis.setupLastWeeksCash = function() {
 	V.lastWeeksCashIncome = new App.Data.Records.LastWeeksCash();
 	V.lastWeeksCashExpenses = new App.Data.Records.LastWeeksCash();
-	V.lastWeeksCashErrors = "Errors: ";
+	V.lastWeeksCashErrors = [];
 };
 
 globalThis.setupLastWeeksRep = function() {
diff --git a/src/uncategorized/costsBudget.tw b/src/uncategorized/costsBudget.tw
deleted file mode 100644
index 79ea4f02fc5858fe45fc3817f43cfd74dbfc7c12..0000000000000000000000000000000000000000
--- a/src/uncategorized/costsBudget.tw
+++ /dev/null
@@ -1,102 +0,0 @@
-:: Costs Budget [nobr jump-to-safe jump-from-safe]
-
-<<set $nextButton = "Back to Main", $nextLink = "Main", _arcologyCosts = 0>>
-
-<<if def $lastWeeksCashIncome>>
-	<<set $lastWeeksCashIncome.Total = 0>>
-	<<set $lastWeeksCashExpenses.Total = 0>>
-<</if>>
-
-<<if ndef $showAllEntries>>
-	<<set $showAllEntries = {costsBudget: 0, repBudget: 0}>>
-<</if>>
-
-<div class="scene-intro">
-	Here you can view many of the financial details of your arcology, <<= properTitle()>>. The detailed list of slaves and their costs (food, hormones) that you may remember can now be found in the "Slave maintenance" link. Other links will allow you to directly control areas of your arcology to adjust spending to suit your tastes.
-</div>
-
-<br style="clear:both"><<if $lineSeparations == 0>><br><<else>><hr style="margin:0"><</if>>
-
-<<if $difficultySwitch == 1>>
-	<div class="scene-intro">
-		The Local Economy score effects some prices in your ecology. The lower the score, the higher the prices. The base score is ''100''.
-	</div>
-	<span id="economy">
-		<span class="yellowgreen">
-			Global Economy
-		</span>
-	| <<print $economy>>
-	</span>
-	<<if ($cheatMode) && ($cheatModeM)>>
-		<<set _tEconomy = $economy>>
-		<<textbox "_tEconomy" _tEconomy>>
-		<<link "Apply">>
-		<<set $economy = Math.trunc(Number(_tEconomy)) || $economy, $cheater = 1>>
-		<<replace "#economy">>
-		<span class="yellowgreen">
-			Global Economy
-		</span>
-		| <<print $economy>>
-		<</replace>>
-		<</link>>
-	<</if>>
-	<br>
-
-	<span id="localEcon">
-		<span class="yellowgreen">
-			Local Economy
-		</span>
-	| <<print $localEcon>>
-	</span>
-	<<if ($cheatMode) && ($cheatModeM)>>
-		<<set _tLocalEcon = $localEcon>>
-		<<textbox "_tLocalEcon" _tLocalEcon>>
-		<<link "Apply">>
-		<<set $localEcon = Math.trunc(Number(_tLocalEcon)) || $localEcon, $cheater = 1>>
-		<<replace "#localEcon">>
-		<span class="yellowgreen">
-			Local Economy
-		</span>
-		| <<print $localEcon>>
-		<</replace>>
-		<</link>>
-	<</if>>
-
-	<br> The current score is
-	<<if $localEcon > 100>>
-		<<set _econPercent = Math.trunc(1000-100000/$localEcon)/10>>
-		reducing prices by <span class="cash inc">''<<print _econPercent>>%.''</span>
-	<<elseif $localEcon == 100>>
-		equal to the base score. There are no price modifications.
-	<<else>>
-		<<set _econPercent = Math.trunc(100000/$localEcon-1000)/10>>
-		increasing prices by <span class="cash dec">''<<print _econPercent>>%.''</span>
-	<</if>>
-	<br style="clear:both"><<if $lineSeparations == 0>><br><br><<else>><hr style="margin:0"><</if>>
-<</if>>
-
-<span class="detail">Your weekly costs are as follows:</span>
-<<set _options = new App.UI.OptionsGroup()>>
-<<run _options.addOption("", "costsBudget", $showAllEntries)
-.addValue("Normal", 0).on().addValue("Show Empty Entries", 1)>>
-<<includeDOM _options.render()>>
-
-/* Table of Totals */
-<p>
-	<<if ndef $lastWeeksCashIncome>>
-		Financial data currently unavailable.
-	<<else>>
-		<<includeDOM App.UI.budget("cash")>>
-	<</if>>
-</p>
-
-<<if ndef $lastWeeksCashErrors>>
-	<<set $lastWeeksCashErrors = "Errors: ">>
-<</if>>
-
-<<if $lastWeeksCashErrors !== "Errors: ">>
-	<<link "Reset">>
-		<<set $lastWeeksCashErrors = "Errors: ">>
-	<</link>><br>
-	@@.red;<<print $lastWeeksCashErrors>>@@
-<</if>>
diff --git a/src/uncategorized/repBudget.tw b/src/uncategorized/repBudget.tw
index 263a7b79e988d331c5e24fbe2bd02df4463851db..40aa3cde368cd4d14085840c2cae647bec0a6ebe 100644
--- a/src/uncategorized/repBudget.tw
+++ b/src/uncategorized/repBudget.tw
@@ -26,7 +26,7 @@
 <<if ndef $lastWeeksRepIncome>>
 	Financial data currently unavailable.
 <<else>>
-	<<includeDOM App.UI.budget("rep")>>
+	<<includeDOM App.Budget.table("rep")>>
 <</if>>
 
 <<if ndef $lastWeeksRepErrors>>