diff --git a/src/005-passages/interactPassages.js b/src/005-passages/interactPassages.js
index df3b388188cb2ea6703e49f7a657638143464077..f462029f6084158d7eb11d8cc6d7630bd706b926 100644
--- a/src/005-passages/interactPassages.js
+++ b/src/005-passages/interactPassages.js
@@ -30,3 +30,27 @@ new App.DomPassage("Fat Grafting",
 		return App.UI.SlaveInteract.fatGraft(getSlave(V.AS));
 	}
 );
+
+new App.DomPassage(
+	"Slave Slave Swap Workaround",
+	() => {
+		V.nextButton = "Abort Operation";
+		V.nextLink = "Main";
+		return bodySwapSelection(getSlave(V.AS));
+	}
+);
+
+new App.DomPassage(
+	"Husk Slave Swap Workaround",
+	() => {
+		V.nextButton = "Abort Operation";
+		if (V.activeSlave.tankBaby !== 3) {
+			V.nextLink = "Scheduled Event";
+			V.returnTo = "Scheduled Event";
+		} else {
+			V.nextLink = "Main";
+			V.returnTo = "Incubator";
+		}
+		return bodySwapSelection(getSlave(V.AS));
+	}
+);
diff --git a/src/endWeek/reports/clinicReport.js b/src/endWeek/reports/clinicReport.js
index 9366fb523ede2bf98ee03d5ea15d2e63ca9956b7..65dec26f6805a49a127d461c23cdbdbf1f95fc32 100644
--- a/src/endWeek/reports/clinicReport.js
+++ b/src/endWeek/reports/clinicReport.js
@@ -46,7 +46,7 @@ App.EndWeek.clinicReport = function() {
 					S.Nurse.fetishStrength += 4;
 				}
 			}
-			const {He, he, His, his, him, wife} = getPronouns(S.Nurse);
+			const {He, he, His, his, him, himself, wife} = getPronouns(S.Nurse);
 			r.push(`${SlaveFullName(S.Nurse)} is serving as the clinical nurse.`);
 			if (S.Nurse.relationship === -3 && S.Nurse.devotion > 50) {
 				r.push(`${He} does ${his} best to be a caring and nurturing ${wife}.`);
@@ -123,32 +123,43 @@ App.EndWeek.clinicReport = function() {
 						improveCondition(slave, 2);
 						break;
 					case "$He is remembered for winning best in show as a cockmilker.":
-						if ((slave.balls > 4) && (slave.dick !== 0)) {
+						if (slave.balls > 4 && slave.dick !== 0) {
 							r.push(`${He} does ${his} best to aid the dribbling ${slave.slaveName}, paying special attention to ${his2} huge cock and balls as well as making sure to milk ${his2}`);
 							if (canAchieveErection(slave)) {
 								r.push(`erect`);
 							}
 							r.push(`dick hourly.`);
 							improveCondition(slave, 4);
-							if (jsRandom(1, 100) > 65) {
+							if (jsRandom(1, 100) > 65 && canTaste(S.Nurse)) {
 								if (S.Nurse.fetish === "none") {
-									S.Nurse.fetish = "cumslut"; // FIXME: no player notification on fetish acquisition?
+									r.push(`It's not uncommon for ${him} to receive a load to the face; before long, <span class="fetish gain">${he} starts to enjoy the taste.</span>`);
+									S.Nurse.fetish = "cumslut";
 								} else if (S.Nurse.fetish === "cumslut") {
-									S.Nurse.fetishStrength += 4;
+									r.push(`${canSmell(S.Nurse) ? `The  smell of ${his2}` : `${His2}`} semen is irresistable to ${him} and sometimes ${he} can't help but`);
+									if (slave.chastityPenis === 0 && slave.rules.release.facilityLeader === 1) {
+										r.push(`wrap ${his} lips around ${him2} and sample the stuff directly,`);
+										seX(S.Nurse, "oral", slave, "penetrative");
+									} else {
+										r.push(`catch a little of it in a cup for further "analysis",`);
+									}
+									r.push(`<span class="fetish inc">further feeding ${his} cum fetishism.</span>`);
+									Math.clamp(S.Nurse.fetishStrength += 4, 0, 100);
 								}
 							}
 						}
 						break;
 					case "$He is remembered for winning best in show as a dairy cow.":
-						if ((slave.lactation > 0) && ((slave.boobs-slave.boobsImplant) > 6000)) {
+						if (slave.lactation > 0 && (slave.boobs - slave.boobsImplant - slave.boobsMilk) > 6000) {
 							r.push(`${He} does ${his} best to aid the leaking ${slave.slaveName}, paying special attention to ${his2} huge breasts as well as making sure to milk ${him2} hourly.`);
 							improveCondition(slave, 4);
 							slave.lactationDuration = 2;
-							if (jsRandom(1, 100) > 65) {
+							if (jsRandom(1, 100) > 65 && canTaste(S.Nurse)) {
 								if (S.Nurse.fetish === "none") {
-									S.Nurse.fetish = "boobs"; // FIXME: no player notification on fetish acquisition?
+									r.push(`Those lovely globes are just too tantilizing for ${him} to resist, <span class="fetish gain">${he} can't help but take a drink ${himself}.</span>`);
+									S.Nurse.fetish = "boobs";
 								} else if (S.Nurse.fetish === "boobs") {
-									S.Nurse.fetishStrength += 4;
+									r.push(`${He} takes the opportunity to <span class="fetish gain">feed ${his} breast fetishism</span> by getting up close and personal with ${his2} prized udders.`);
+									Math.clamp(S.Nurse.fetishStrength += 4, 0, 100);
 								}
 							}
 						}
@@ -172,7 +183,7 @@ App.EndWeek.clinicReport = function() {
 					if (slave.devotion > 50) {
 						slave.devotion += 4;
 						slave.trust += 3;
-					} else if ((slave.devotion >= -20)) {
+					} else if (slave.devotion >= -20) {
 						slave.trust -= 5;
 					} else {
 						slave.devotion -= 5;
diff --git a/src/endWeek/saLongTermEffects.js b/src/endWeek/saLongTermEffects.js
index 02643bb3432fcdaada267a12e3bd00c276ed8488..5994a4517e07ced2ed699c2a1b0f575efd65c5b2 100644
--- a/src/endWeek/saLongTermEffects.js
+++ b/src/endWeek/saLongTermEffects.js
@@ -2547,7 +2547,7 @@ App.SlaveAssignment.longTermEffects = (function() {
 				}
 			}
 			if (V.seeAge === 1) {
-				deathSeed = ((slave.health.health * 2) - (slave.physicalAge * 2) - (slave.chem * 4) - (slave.addict * 3));
+				deathSeed = ((slave.health.health * 2) - (slave.physicalAge * 2) - (slave.assignment === Job.CLINIC && S.Nurse && V.clinicUpgradeFilters >= 1 ? slave.chem : (slave.chem * 4)) - (slave.addict * 3));
 				if (slave.physicalAge >= Math.max((70 + (slave.health.health / 5) - (slave.addict) - (slave.chem / 20)), 50) && random(1, 1000) > 800 + deathSeed) {
 					planDeath(slave, "oldAge");
 				}
diff --git a/src/facilities/incubator/incubatorRetrievalWorkaround.tw b/src/facilities/incubator/incubatorRetrievalWorkaround.tw
index ba3b56c0f2596d1547b6206c6e4da61a9e55bfc2..b5b886d376c5e9ca7529f534384f90917f24cb9a 100644
--- a/src/facilities/incubator/incubatorRetrievalWorkaround.tw
+++ b/src/facilities/incubator/incubatorRetrievalWorkaround.tw
@@ -25,14 +25,14 @@
 		<</if>>
 		<<includeDOM App.UI.newChildIntro($readySlave)>>
 	<<else>>
-		<<set $activeSlave = $readySlave>> /* $activeSlave is used by husk Slave Swap Workaround */
+		<<set $activeSlave = $readySlave>> /* $activeSlave is used by Husk Slave Swap Workaround */
 		A husk is ready to be used.
 		<br>
 		//As expected, $he is a complete vegetable, but that is what you wanted after all. You lack the facilities to care for $him in this state, so you should do what you are planning quickly. Or you could sell $him to the Flesh Heap.//
 		<<set _price = Math.trunc(slaveCost($readySlave)/3)>>
 		<span id="result">
 		<<if $cash >= $surgeryCost>>
-			<br>[[Contact the bodyswap surgeon.|husk Slave Swap Workaround]] //Will significantly increase the selected slave's upkeep.//
+			<br>[[Contact the bodyswap surgeon.|Husk Slave Swap Workaround]] //Will significantly increase the selected slave's upkeep.//
 			<br>[[Sell the husk to Flesh Heap.|Main][cashX(_price, "slaveTransfer")]]
 			//This body can be bought by the Flesh Heap for <<print cashFormat(_price)>>//.
 		<<else>>
diff --git a/src/gui/userButton.js b/src/gui/sideBar.js
similarity index 66%
rename from src/gui/userButton.js
rename to src/gui/sideBar.js
index 0c8cf11d416de2f83b950d4cc7650cd3bb5bcfad..9c1e8e3508a06265f8f80745c8a64a4805fae3cf 100644
--- a/src/gui/userButton.js
+++ b/src/gui/sideBar.js
@@ -1,3 +1,26 @@
+/** Notify the game that the sidebar needs to be refreshed as soon as possible, but do not do it immediately.
+ *  This allows us to call this function repeatedly without impacting performance (for example, from repX() and cashX()).
+ *  The game will redraw the sidebar exactly once, as soon as all the scripts have finished executing.
+ */
+App.Utils.scheduleSidebarRefresh = (function() {
+	let refresh = false;
+
+	function updateSidebar() {
+		refresh = false;
+		UIBar.update();
+		App.UI.updateSidebarTooltips();
+	}
+
+	function schedule() {
+		if (!refresh) {
+			refresh = true;
+			setTimeout(updateSidebar, 0);
+		}
+	}
+
+	return schedule;
+})();
+
 App.Utils.userButton = function(nextButton = V.nextButton, nextLink = V.nextLink) {
 	const el = document.createElement("span");
 	el.id = "next-button-wrapper"; // We must always have a named element so we have something to refresh even if the button is hidden for a scene
diff --git a/src/interaction/prostheticLab.js b/src/interaction/prostheticLab.js
new file mode 100644
index 0000000000000000000000000000000000000000..5a34abceb4f036a6730c7dd06fbdf59bcf0ace65
--- /dev/null
+++ b/src/interaction/prostheticLab.js
@@ -0,0 +1,26 @@
+/**
+ * @returns {string}
+ */
+
+globalThis.getProstheticsStockpile = function() {
+	return `<div>Prosthetics interfaces: ${num(V.prosthetics.interfaceP1.amount + V.prosthetics.interfaceP2.amount)}</div>` +
+		`<div class="choices">Basic: ${V.prosthetics.interfaceP1.amount}</div>` +
+		`<div class="choices">Advanced: ${V.prosthetics.interfaceP2.amount}</div>` +
+		`<div>Limbs: ${num(V.prosthetics.basicL.amount + V.prosthetics.sexL.amount + V.prosthetics.beautyL.amount +
+			V.prosthetics.combatL.amount + V.prosthetics.cyberneticL.amount)}</div>` +
+		`<div class="choices">Basic: ${V.prosthetics.basicL.amount}</div>` +
+		`<div class="choices">Sex: ${V.prosthetics.sexL.amount}</div>` +
+		`<div class="choices">Beauty: ${V.prosthetics.beautyL.amount}</div>` +
+		`<div class="choices">Combat: ${V.prosthetics.combatL.amount}</div>` +
+		`<div class="choices">Cybernetic: ${V.prosthetics.cyberneticL.amount}</div>` +
+		`<div>Implants: ${num(V.prosthetics.ocular.amount + V.prosthetics.cochlear.amount + V.prosthetics.electrolarynx.amount)}</div>` +
+		`<div class="choices">Ocular: ${V.prosthetics.ocular.amount}</div>` +
+		`<div class="choices">Cochlear: ${V.prosthetics.cochlear.amount}</div>` +
+		/* `<div class="choices">Erectile: ${V.prosthetics.erectile.amount}</div>` + */
+		`<div class="choices">Electrolarynx: ${V.prosthetics.electrolarynx.amount}</div>` +
+		`<div>Tail interface: ${V.prosthetics.interfaceTail.amount}</div>` +
+		`<div>Tails: ${num(V.prosthetics.modT.amount + V.prosthetics.sexT.amount + V.prosthetics.combatT.amount)}</div>` +
+		`<div class="choices">Modular: ${V.prosthetics.modT.amount}</div>` +
+		`<div class="choices">Pleasure: ${V.prosthetics.sexT.amount}</div>` +
+		`<div class="choices">Combat: ${V.prosthetics.combatT.amount}</div>`;
+};
diff --git a/src/js/assayJS.js b/src/js/assayJS.js
deleted file mode 100644
index 7329abef92b6e99373cb3ae23185683f10181ff0..0000000000000000000000000000000000000000
--- a/src/js/assayJS.js
+++ /dev/null
@@ -1,1706 +0,0 @@
-/**
- * @param {App.Entity.SlaveState} A
- * @param {App.Entity.SlaveState} B
- * @returns {boolean}
- */
-globalThis.sameAssignmentP = function(A, B) {
-	return A.assignment === B.assignment;
-};
-
-/**
- * @param {App.Entity.SlaveState} slave
- * @returns {boolean}
- */
-globalThis.canImproveIntelligence = function(slave) {
-	let origIntel = V.genePool.find(function(s) { return s.ID === slave.ID; }).intelligence;
-	return (slave.intelligence < origIntel + 15) && (slave.intelligence < 100);
-};
-
-/**
- * @param {App.Entity.SlaveState} slave
- * @returns {number}
- */
-globalThis.maxHeight = function(slave) {
-	let max = Math.trunc(Math.clamp((Height.mean(slave) * 1.25), 0, 274)); /* max achievable height is expected height plus 25% */
-
-	if (slave.geneticQuirks.dwarfism === 2 && slave.geneticQuirks.gigantism !== 2) {
-		max = Math.min(max, 160);
-	}
-
-	return max;
-};
-
-/**
- * @param {App.Entity.SlaveState} slave
- * @returns {boolean}
- */
-globalThis.canImproveHeight = function(slave) {
-	return slave.height < maxHeight(slave);
-};
-
-/**
- * @param {App.Entity.SlaveState} slave
- * @param {FC.HumanState} target
- * @returns {boolean}
- */
-globalThis.haveRelationshipP = function(slave, target) {
-	return slave.relationshipTarget === target.ID;
-};
-
-/**
- * @param {App.Entity.SlaveState} slave
- * @param {App.Entity.SlaveState} target
- * @returns {boolean}
- */
-globalThis.isRivalP = function(slave, target) {
-	return slave.rivalryTarget === target.ID;
-};
-
-/**
- * @param {FC.HumanState} slave
- * @returns {boolean}
- */
-globalThis.supremeRaceP = function(slave) {
-	return V.arcologies[0].FSSupremacistRace === slave.race;
-};
-
-/**
- * @param {FC.HumanState} slave
- * @returns {boolean}
- */
-globalThis.inferiorRaceP = function(slave) {
-	return V.arcologies[0].FSSubjugationistRace === slave.race;
-};
-
-/**
- * @param {App.Entity.SlaveState} slave
- * @returns {boolean}
- */
-globalThis.isLeaderP = function(slave) {
-	const leaders = [S.HeadGirl, S.Bodyguard, S.Recruiter, S.Concubine, S.Nurse, S.Attendant, S.Matron, S.Madam, S.DJ, S.Milkmaid, S.Farmer, S.Stewardess, S.Schoolteacher, S.Wardeness];
-
-	return leaders.some(leader => leader && leader.ID === slave.ID);
-};
-
-/**
- * colors skin, eyes and hair based on genetic Color.
- * Takes .override_*_Color into account.
- *
- * @param {App.Entity.SlaveState} slave
- */
-globalThis.applyGeneticColor = function(slave) {
-	if (slave.override_Eye_Color !== 1) {
-		resetEyeColor(slave, "both");
-	}
-	if (slave.override_H_Color !== 1) {
-		slave.hColor = getGeneticHairColor(slave);
-	}
-	if (slave.override_Arm_H_Color !== 1) {
-		slave.underArmHColor = getGeneticHairColor(slave);
-	}
-	if (slave.override_Pubic_H_Color !== 1) {
-		slave.pubicHColor = getGeneticHairColor(slave);
-	}
-	if (slave.override_Brow_H_Color !== 1) {
-		slave.eyebrowHColor = getGeneticHairColor(slave);
-	}
-	if (slave.override_Skin !== 1) {
-		if (!(slave.skin === "sun tanned" || slave.skin === "spray tanned")) {
-			slave.skin = getGeneticSkinColor(slave);
-		}
-	}
-};
-
-/**
- * @param {FC.GingeredSlave} slave
- */
-globalThis.newSlave = function(slave) {
-	if (getSlave(slave.ID)) {
-		throw "Slave already exists";
-	}
-
-	// if the slave is gingered, remove the gingering proxy
-	if (slave.beforeGingering) {
-		slave = slave.beforeGingering;
-	}
-
-	if (slave.override_Race !== 1) {
-		slave.origRace = slave.race;
-	}
-
-	applyGeneticColor(slave);
-
-	/* eslint-disable camelcase */
-	slave.override_Race = 0;
-	slave.override_H_Color = 0;
-	slave.override_Arm_H_Color = 0;
-	slave.override_Pubic_H_Color = 0;
-	slave.override_Brow_H_Color = 0;
-	slave.override_Skin = 0;
-	slave.override_Eye_Color = 0;
-	/* eslint-enable camelcase */
-
-	// too tall to be a dwarf catch for event slaves
-	if (slave.geneticQuirks.dwarfism === 2 && slave.geneticQuirks.gigantism !== 2 && slave.height > 165) {
-		slave.geneticQuirks.dwarfism = 1;
-	}
-
-	if (V.surnamesForbidden === 1) {
-		slave.slaveSurname = 0;
-	}
-
-	if (slave.preg > 0) {
-		slave.pregWeek = slave.preg;
-	} else {
-		slave.pregWeek = 0;
-	}
-
-	if (slave.clone !== 0) {
-		slave.canRecruit = 0;
-	}
-
-	slave.sisters = 0;
-	slave.daughters = 0;
-	if (slave.mother === -1 || slave.father === -1) {
-		V.PC.daughters += 1;
-	}
-	if (areSisters(V.PC, slave) > 0) {
-		V.PC.sisters += 1;
-	}
-	for (let k = 0; k < V.slaves.length; k++) {
-		if (V.slaves[k].mother === slave.ID || V.slaves[k].father === slave.ID) {
-			slave.daughters++;
-		}
-		if (slave.mother === V.slaves[k].ID || slave.father === V.slaves[k].ID) {
-			V.slaves[k].daughters++;
-		}
-		if (areSisters(V.slaves[k], slave) > 0) {
-			slave.sisters++;
-			V.slaves[k].sisters++;
-		}
-	}
-
-	if (slave.genes === "XX") {
-		if (slave.pubertyXX === 1) {
-			if (slave.pubertyXY === 1) {
-				slave.hormoneBalance = 20;
-			} else {
-				slave.hormoneBalance = 60;
-			}
-		} else {
-			if (slave.pubertyXY === 1) {
-				slave.hormoneBalance = -20;
-			} else {
-				slave.hormoneBalance = 20;
-			}
-		}
-	} else if (slave.genes === "XY") {
-		if (slave.pubertyXX === 1) {
-			if (slave.pubertyXY === 1) {
-				slave.hormoneBalance = 20;
-			} else {
-				slave.hormoneBalance = 40;
-			}
-		} else {
-			if (slave.pubertyXY === 1) {
-				slave.hormoneBalance = -40;
-			} else {
-				slave.hormoneBalance = 20;
-			}
-		}
-	}
-
-	if (slave.dick > 0 &&
-		slave.balls > 0 &&
-		slave.vagina < 0 &&
-		slave.anus === 0 &&
-		slave.genes === "XY" &&
-		slave.faceShape === "masculine" &&
-		slave.attrXY <= 35 &&
-		slave.boobs < 400 &&
-		slave.hormoneBalance < 0) {
-		V.REFeminizationCheckinIDs.push(slave.ID);
-	}
-	if (slave.actualAge > 35 && slave.face <= 10 && slave.faceImplant === 0 && slave.energy <= 60) {
-		V.REMILFCheckinIDs.push(slave.ID);
-	}
-	if (slave.attrXY <= 35 && slave.attrXX > 65) {
-		V.REOrientationCheckinIDs.push(slave.ID);
-	}
-	if (slave.face < -10) {
-		V.REUglyCheckinIDs.push(slave.ID);
-	}
-	if (slave.anus < 2) {
-		V.REButtholeCheckinIDs.push(slave.ID);
-	}
-	if (slave.boobs < 800) {
-		V.REReductionCheckinIDs.push(slave.ID);
-	}
-
-	generatePronouns(slave);
-	SetBellySize(slave);
-	V.slaveIndices[slave.ID] = V.slaves.push(slave) - 1;
-
-	if (slave.origin !== "$He was your slave, but you freed $him, which $he repaid by participating in a coup attempt against you. It failed, and $he is again your chattel." && slave.ID !== V.boomerangSlave.ID) {
-		V.genePool.push(clone(slave));
-	} else {
-		if (!V.genePool.some(s => s.ID === slave.ID)) {
-			V.genePool.push(slave);
-		}
-	}
-
-	/* special case for dulling intelligence via drugs in slave acquisition */
-	if (slave.dullIntelligence) {
-		slave.intelligence = -100;
-		delete slave.dullIntelligence;
-	}
-
-	if (slave.assignment) {
-		assignJob(slave, slave.assignment);
-	} else {
-		slave.assignment = Job.CHOICE;
-	}
-
-	/** do not run the Rules Assistant before adding the new slave to the slaves list! **/
-	if (V.ui !== "start" && V.universalRulesNewSlavesRA === 1 && V.rulesAssistantAuto !== 0) {
-		DefaultRules(slave);
-	}
-};
-
-/**
- * @param {App.Entity.SlaveState[]} [slaves]
- * @returns {Object.<number, number>}
- */
-globalThis.slaves2indices = function(slaves = V.slaves) {
-	return slaves.reduce((acc, slave, i) => { acc[slave.ID] = i; return acc; }, {});
-};
-
-/**
- * @param {number} ID
- * @returns {App.Entity.SlaveState}
- */
-globalThis.getSlave = function(ID) {
-	const index = V.slaveIndices[ID];
-	return index === undefined ? undefined : V.slaves[index];
-};
-
-/**
- * @param {number} ID
- * @returns {App.Entity.SlaveState}
- */
-globalThis.slaveStateById = function(ID) {
-	const index = V.slaveIndices[ID];
-	return index === undefined ? null : V.slaves[index];
-};
-
-globalThis.getChild = function(ID) {
-	return V.cribs.find(s => s.ID === ID);
-};
-
-/** Get the written title for a given slave, without messing with global state.
- * @param {App.Entity.SlaveState} [slave]
- * @returns {string}
- */
-globalThis.getWrittenTitle = function(slave) {
-	if (slave && slave.custom.title !== undefined && slave.custom.title !== "" && slave.rudeTitle === 0) {
-		return slave.custom.title;
-	}
-	if (V.PC.customTitle !== undefined) {
-		return V.PC.customTitle;
-	} else if (V.PC.title !== 0) {
-		return "Master";
-	} else {
-		return "Mistress";
-	}
-};
-
-/**
- * @param {App.Entity.SlaveState} slave
- * @returns {number}
- */
-globalThis.fetishChangeChance = function(slave) {
-	let chance = 0,
-		fetish = (slave.fetishStrength / 4),
-		sex = 0;
-
-	if (slave.clitSetting !== slave.fetish) {
-		// fetish should be more uncertain leading towards puberty and then steadily become more set in stone afterwards
-		if (slave.balls) {
-			if (V.potencyAge >= slave.actualAge) {
-				sex = (50 - ((V.potencyAge - slave.actualAge) * 10));
-				fetish = (slave.fetishStrength / 2);
-			} else {
-				sex = ((slave.actualAge - V.potencyAge) / 4);
-			}
-		} else if (slave.ovaries || slave.mpreg) {
-			if (V.fertilityAge >= slave.actualAge) {
-				sex = (50 - ((V.fertilityAge - slave.actualAge) * 10));
-				fetish = (slave.fetishStrength / 2);
-			} else {
-				sex = ((slave.actualAge - V.fertilityAge) / 4);
-			}
-		}
-		chance = Math.trunc(Math.clamp((slave.devotion / 4) - (fetish) - (sex), 0, 100));
-	}
-
-	return chance;
-};
-
-/**
- * @param {App.Entity.SlaveState} slave
- * @returns {string}
- */
-globalThis.SlaveFullName = function(slave) {
-	const pair = slave.slaveSurname ? [slave.slaveName, slave.slaveSurname] : [slave.slaveName];
-	if ((V.surnameOrder !== 1 && ["Cambodian", "Chinese", "Hungarian", "Japanese", "Korean", "Mongolian", "Taiwanese", "Vietnamese"].includes(slave.nationality)) || (V.surnameOrder === 2)) {
-		pair.reverse();
-	}
-	return pair.join(" ");
-};
-
-/**
- * @param {App.Entity.SlaveState} slave
- * @returns {string}
- */
-globalThis.SlaveFullBirthName = function(slave) {
-	const pair = slave.birthSurname ? [slave.birthName, slave.birthSurname] : [slave.birthName];
-	if ((V.surnameOrder !== 1 && ["Cambodian", "Chinese", "Hungarian", "Japanese", "Korean", "Mongolian", "Taiwanese", "Vietnamese"].includes(slave.nationality)) || (V.surnameOrder === 2)) {
-		pair.reverse();
-	}
-	return pair.join(" ");
-};
-
-/**
- * @param {App.Entity.SlaveState} slave
- * @returns {string}
- */
-globalThis.PoliteRudeTitle = function(slave) {
-	const PC = V.PC;
-	const {s, ss, title} = getEnunciation(slave);
-
-	let r = "";
-	if (slave.nationality === "Japanese") {
-		if (slave.trust > 0) {
-			r += `${PC.slaveName}${PC.title > 0 ? "kun" : "chan"}`;
-		} else {
-			r += (PC.slaveSurname ? PC.slaveSurname : `${PC.slaveName}${s}an`);
-		}
-	} else {
-		if (slave.intelligence + slave.intelligenceImplant < -95) {
-			r += title;
-		} else if (slave.intelligence + slave.intelligenceImplant > 50) {
-			r += (PC.title > 0 ? `Ma${s}ter` : `Mi${s}tre${ss}`);
-		} else if (slave.trust > 0) {
-			r += PC.slaveName;
-		} else {
-			r += (PC.slaveSurname ? PC.slaveSurname : PC.slaveName);
-		}
-	}
-	return r;
-};
-
-/**
- * @param {App.Entity.SlaveState} slave
- * @returns {string}
- */
-globalThis.SlaveTitle = function(slave) {
-	let r;
-	if (V.newDescriptions === 1) {
-		if (slave.dick > 0 && slave.balls > 0 && slave.boobs > 300 && slave.vagina > -1 && slave.ovaries === 1) {
-			if (jsRandom(1, 100) > 50) {
-				r = "futanari";
-			} else {
-				r = "herm";
-			}
-		} else if (slave.dick > 0 && slave.balls === 0 && slave.boobs > 300 && slave.vagina > -1 && slave.ovaries === 1) {
-			r = "dickgirl";
-		} else if (slave.dick > 0 && slave.vagina > -1 && slave.ovaries === 0) {
-			r = "shemale";
-		} else if (slave.dick > 0 && slave.balls === 0 && slave.vagina === -1 && slave.ovaries === 0) {
-			r = "eunuch";
-		} else if (slave.dick > 0 && slave.balls > 0 && slave.vagina === -1 && slave.ovaries === 0) {
-			if (slave.face > 10 && slave.hips > -1 && slave.shoulders < 1 && slave.faceShape !== "masculine") {
-				r = "trap";
-			} else if (slave.boobs > 800) {
-				r = "tittyboy";
-			} else if (slave.dick === 1 && slave.balls === 1) {
-				r = "sissy";
-			} else if (slave.dick > 1 && slave.balls > 1 && slave.height < 165 && slave.muscles < 5 && slave.visualAge < 19 && slave.faceShape !== "masculine") {
-				r = "twink";
-			} else if (slave.dick > 1 && slave.balls > 1 && slave.height < 160 && slave.muscles < 5 && slave.visualAge < 19) {
-				r = "boytoy";
-			} else if (slave.muscles > 95 && slave.height >= 185) {
-				r = "titan";
-			} else if (slave.muscles > 30) {
-				r = "muscleboy";
-			} else {
-				r = "slaveboy";
-			}
-		} else if (slave.dick === 0 && slave.balls === 0 && slave.vagina > -1) {
-			if ((slave.shoulders > slave.hips || slave.faceShape === "masculine") && slave.boobs < 400 && slave.genes === "XY") {
-				r = "cuntboy";
-			} else if (slave.ovaries === 0 && slave.genes === "XY") {
-				r = "tranny";
-			} else if (slave.weight > 10 && slave.boobs > 800 && slave.counter.birthsTotal > 0 && slave.physicalAge > 59) {
-				r = "GMILF";
-			} else if (slave.weight > 10 && slave.boobs > 800 && slave.counter.birthsTotal > 0 && slave.physicalAge > 35) {
-				r = "MILF";
-			} else if (slave.lips > 70 && slave.boobs > 2000 && slave.butt > 3) {
-				r = "bimbo";
-			} else if (slave.hips > 1 && slave.boobs > 2000 && slave.butt > 3 && slave.waist < 50) {
-				r = "hourglass";
-			} else if (slave.muscles > 95 && slave.height >= 185) {
-				r = "amazon";
-			} else if (slave.muscles > 30) {
-				r = "musclegirl";
-			} else {
-				r = "slavegirl";
-			}
-		} else if (slave.dick === 0 && slave.balls === 0 && slave.vagina === -1) {
-			r = "neuter";
-		} else if (slave.dick === 0 && slave.vagina === -1) {
-			r = "ballslave";
-		} else {
-			r = "slave";
-		}
-
-		if (slave.visualAge < 13) {
-			if (slave.actualAge < 3) {
-				if (slave.actualAge < 1) {
-					r = "baby " + r;
-				} else {
-					r = "toddler " + r;
-				}
-			} else {
-				if (slave.genes === "XY" && slave.vagina === -1) {
-					r = "shota " + r;
-				} else {
-					r = "loli " + r;
-				}
-			}
-		}
-
-		if (slave.geneticQuirks.albinism === 2) {
-			r = `albino ${r}`;
-		}
-
-		if (slave.dick > 9 && slave.balls > 9 && slave.boobs > 12000) {
-			r = `hyper ${r}`;
-		}
-
-		if (slave.boobs > 4000 && slave.lactation > 0) {
-			if (slave.physicalAge < 13) {
-				r = `${r} calf`;
-			} else {
-				r = `${r} cow`;
-			}
-		} else if (slave.lactation > 0) {
-			r = `milky ${r}`;
-		}
-
-		if (slave.boobs > 20000) {
-			r = `supermassive titted ${r}`;
-		} else if (slave.boobs > 10000) {
-			r = `giant titted ${r}`;
-		} else if (slave.boobs > 4000) {
-			r = `huge titted ${r}`;
-		} else if (slave.boobs > 1000) {
-			r = `busty ${r}`;
-		}
-
-		if (slave.dick > 5 && slave.balls > 5) {
-			r = `womb filling ${r}`;
-		} else if (slave.dick > 5) {
-			r = `well hung ${r}`;
-		}
-
-		if (slave.butt >= 12) {
-			r = `colossal assed ${r}`;
-		} else if (slave.butt >= 10) {
-			r = `massive assed ${r}`;
-		} else if (slave.butt >= 8) {
-			r = `fat assed ${r}`;
-		} else if (slave.butt >= 6) {
-			r = `bottom heavy ${r}`;
-		} else if (slave.butt >= 4) {
-			r = `big bottomed ${r}`;
-		}
-
-		if (slave.weight > 10 && slave.weight < 100 && slave.boobs > 5000 && slave.butt > 5 && slave.hips >= 2 && slave.bellyPreg >= 30000 && slave.counter.births >= 10) {
-			r = `${r} fertility goddess`;
-		} else if (slave.counter.births >= 6) {
-			r = `${r} broodmother`;
-		} else if (slave.counter.births >= 3) {
-			r = `${r} breeder`;
-		}
-
-		if (slave.indenture > -1) {
-			r = `indentured ${r}`;
-		}
-
-		if (slave.preg > slave.pregData.normalBirth / 4 && slave.pregKnown === 1) {
-			r = `pregnant ${r}`;
-		} else if (slave.bellyFluid >= 5000) {
-			r = `bloated ${r}`;
-		} else if (slave.belly >= 5000) {
-			r = `gravid ${r}`;
-		}
-
-		if (slave.fuckdoll > 0) {
-			r = `${r} fuckdoll`;
-		}
-	} else {
-		r = "slave"; /* I don't think there is an 'else'? */
-		if ((slave.dick === 0) && (slave.vagina === -1)) {
-			/* NULLS */
-			r = "null";
-			if ((slave.lactation > 0) && (slave.boobs > 2000)) {
-				r = `${r} cow`;
-			} else if ((slave.boobsImplant > 0) && (slave.buttImplant > 0)) {
-				r = `${r} bimbo `;
-			} else if (slave.boobs > 6000) {
-				r = `${r} boob`;
-			} else if (slave.butt > 6) {
-				r = `${r} ass`;
-			} else if ((slave.muscles > 30) && (slave.height < 185)) {
-				r = `${r} muscle`;
-			}
-			if (slave.visualAge > 55) {
-				r = `${r}GILF`;
-			} else if (slave.visualAge > 35) {
-				r = `${r}MILF`;
-			} else if (slave.visualAge >= 25) {
-				r = `${r}slave`;
-			} else {
-				r = `${r}girl`;
-			}
-		}
-
-		if ((slave.dick === 0) && (slave.vagina !== -1)) {
-			/* FEMALES */
-			if (slave.visualAge > 55) {
-				r = "GILF";
-			} else if (slave.visualAge > 35) {
-				r = "MILF";
-			} else if (slave.visualAge >= 25) {
-				r = "slave";
-			} else {
-				r = "slavegirl";
-			}
-			if ((slave.muscles > 30) && (slave.height < 185)) {
-				r = `muscle ${r}`;
-			} else if ((slave.lactation > 0) && (slave.boobs > 2000)) {
-				r = `${r} cow`;
-			} else if ((slave.boobsImplant > 0) && (slave.buttImplant > 0)) {
-				r = `${r} bimbo`;
-			} else if (slave.boobs > 6000) {
-				r = `boob${r}`;
-			} else if (slave.butt > 6) {
-				r = `ass${r}`;
-			}
-		}
-
-		if ((slave.dick !== 0) && (slave.vagina !== -1)) {
-			if (slave.balls > 0) {
-				/* FUTANARI: cock & balls & vagina */
-				r = "futanari ";
-			} else {
-				/* FUTANARI: cock & vagina */
-				r = "futa ";
-			}
-			if ((slave.lactation > 0) && (slave.boobs > 2000)) {
-				r = `${r}cow`;
-			} else if ((slave.boobsImplant > 0) && (slave.buttImplant > 0)) {
-				r = `${r}bimbo `;
-			} else if (slave.boobs > 6000) {
-				r = `${r}boob`;
-			} else if (slave.butt > 6) {
-				r = `${r}ass`;
-			} else if ((slave.muscles > 30) && (slave.height < 185)) {
-				r = `${r}muscle`;
-			}
-			if (slave.visualAge > 55) {
-				r = `${r}GILF`;
-			} else if (slave.visualAge > 35) {
-				r = `${r}MILF`;
-			} else if (slave.visualAge >= 25) {
-				r = `${r}slave`;
-			} else {
-				r = `${r}girl`;
-			}
-			if (slave.dick > 5 && slave.balls > 5 && slave.boobs > 5000) {
-				r = `hyper ${r}`;
-			}
-		}
-
-		if ((slave.dick !== 0) && (slave.vagina === -1) && (slave.balls > 0) && (slave.boobs > 300) && (slave.butt > 2)) {
-			/* SHEMALES: cock & balls, T&A above minimum */
-			if (slave.visualAge > 55) {
-				r = "sheGILF";
-			} else if (slave.visualAge > 35) {
-				r = "sheMILF";
-			} else if (slave.visualAge >= 25) {
-				r = "shemale";
-			} else {
-				r = "tgirl";
-			}
-			if ((slave.muscles > 30) && (slave.height < 185)) {
-				r = `muscle${r}`;
-			} else if ((slave.lactation > 0) && (slave.boobs > 2000)) {
-				r = `${r} cow`;
-			} else if ((slave.boobsImplant > 0) && (slave.buttImplant > 0)) {
-				r = `${r} bimbo`;
-			} else if (slave.boobs > 6000) {
-				r = `topheavy ${r}`;
-			} else if (slave.butt > 6) {
-				r = `bottomheavy ${r}`;
-			}
-		}
-
-		if ((slave.boobs < 300) || (slave.butt < 2)) {
-			if ((slave.dick !== 0) && (slave.vagina === -1) && (slave.balls > 0)) {
-				if ((slave.shoulders < 1) || (slave.muscles <= 30)) {
-					if ((slave.faceShape === "masculine") || (slave.faceShape === "androgynous")) {
-						/* SISSIES: feminine shoulders or muscles, masculine faces */
-						if (slave.visualAge > 55) {
-							r = "sissyGILF";
-						} else if (slave.visualAge > 35) {
-							r = "sissyMILF";
-						} else {
-							r = "sissy";
-						}
-					} else {
-						/* TRAPS: feminine shoulders or muscles, feminine faces */
-						if (slave.visualAge > 55) {
-							r = "trapGILF";
-						} else if (slave.visualAge > 35) {
-							r = "trapMILF";
-						} else if (slave.visualAge >= 25) {
-							r = "trap";
-						} else {
-							r = "trapgirl";
-						}
-					}
-					if (slave.lactation > 0) {
-						r = `${r} cow`;
-					} else if ((slave.boobsImplant > 0) && (slave.buttImplant > 0)) {
-						r = `${r} bimbo`;
-					}
-				}
-			}
-		}
-
-		if ((slave.boobs < 300) || (slave.butt < 2)) {
-			if ((slave.dick !== 0) && (slave.vagina === -1) && (slave.balls > 0)) {
-				if ((slave.shoulders > 1) || (slave.muscles >= 30)) {
-					/* BITCHES: masculine shoulders or muscles */
-					r = "bitch";
-					if ((slave.muscles > 30) && (slave.height < 185)) {
-						r = `muscle${r}`;
-					} else if (slave.lactation > 0) {
-						r = `${r}cow`;
-					} else if ((slave.boobsImplant > 0) && (slave.buttImplant > 0)) {
-						r = `bimbo ${r}`;
-					}
-					if (slave.visualAge > 55) {
-						r = `aged ${r}`;
-					} else if (slave.visualAge > 35) {
-						r = `mature ${r}`;
-					} else if (slave.visualAge < 25) {
-						r = `young ${r}`;
-					}
-				}
-			}
-		}
-
-		if ((slave.dick !== 0) && (slave.vagina === -1) && (slave.balls === 0)) {
-			r = "dick";
-			if (slave.visualAge > 55) {
-				r = `${r}GILF`;
-			} else if (slave.visualAge > 35) {
-				r = `${r}MILF`;
-			} else if (slave.visualAge >= 25) {
-				r = `${r}slave`;
-			} else {
-				r = `${r}girl`;
-			}
-			if ((slave.muscles > 30) && (slave.height < 185)) {
-				r = `muscle${r}`;
-			} else if ((slave.lactation > 0) && (slave.boobs > 2000)) {
-				r = `${r} cow`;
-			} else if ((slave.boobsImplant > 0) && (slave.buttImplant > 0)) {
-				r = `${r} bimbo`;
-			} else if (slave.boobs > 6000) {
-				r = `boob ${r}`;
-			} else if (slave.butt > 6) {
-				r = `ass ${r}`;
-			}
-		}
-
-		if ((slave.muscles > 30) && (slave.height > 185)) {
-			r = `amazon ${r}`;
-		} else if ((slave.muscles < 30) && (slave.height > 185)) {
-			r = `statuesque ${r}`;
-		} else if ((slave.boobs < 800) && (slave.height < 150)) {
-			r = `petite ${r}`;
-		} else if ((slave.boobs > 800) && (slave.height < 150)) {
-			r = `shortstack ${r}`;
-		}
-
-		if (slave.counter.births >= 5) {
-			r = `${r} broodmother`;
-		} else if (slave.counter.births >= 2) {
-			r = `${r} breeder`;
-		}
-
-		if (slave.geneticQuirks.albinism === 2) {
-			r = `albino ${r}`;
-		}
-
-		if (slave.indenture > -1) {
-			r = `indentured ${r}`;
-		}
-
-		if (slave.preg > slave.pregData.normalBirth / 4 && slave.pregKnown === 1) {
-			r = `pregnant ${r}`;
-		} else if (slave.bellyFluid >= 5000) {
-			r = `bloated ${r}`;
-		} else if (slave.belly >= 5000) {
-			r = `gravid ${r}`;
-		}
-
-		if (slave.fuckdoll > 0) {
-			r = `${r} fuckdoll`;
-		}
-	}
-	return r;
-};
-
-/**
- * @param {App.Entity.SlaveState} slave
- */
-globalThis.DegradingName = function(slave) {
-	const leadershipPosition = [
-		Job.ATTENDANT,
-		Job.MATRON,
-		Job.STEWARD,
-		Job.MILKMAID,
-		Job.FARMER,
-		Job.DJ,
-		Job.CONCUBINE,
-		Job.MADAM,
-		Job.TEACHER,
-		Job.WARDEN,
-		Job.NURSE,
-		Job.HEADGIRL,
-		Job.BODYGUARD,
-		Job.RECRUITER
-	];
-	const names = [];
-	const suffixes = [];
-
-	if (slave.fuckdoll > 0) {
-		slave.slaveName = `Fuckdoll No. ${slave.ID}`;
-		slave.slaveSurname = 0;
-	} else if (slave.assignment === Job.DAIRY && V.dairyRestraintsSetting >= 2) {
-		slave.slaveName = `Bioreactor No. ${slave.ID}`;
-		slave.slaveSurname = 0;
-	} else {
-		if (V.seeRace === 1) {
-			switch (slave.race) {
-				case "white":
-					names.push("Pale", "White");
-					break;
-				case "asian":
-					names.push("Asian", "Yellow");
-					break;
-				case "latina":
-					names.push("Brown", "Latina");
-					break;
-				case "black":
-					names.push("Black", "Dark");
-					break;
-				case "pacific islander":
-					names.push("Islander", "Pacific", "Sea");
-					break;
-				case "malay":
-					names.push("Cinnamon", "Pinoy", "Spice");
-					break;
-				case "southern european":
-					names.push("Mediterranean", "Olive");
-					break;
-				case "amerindian":
-					names.push("Indian", "Reservation");
-					break;
-				case "semitic":
-					names.push("Semite", "Semitic");
-					break;
-				case "middle eastern":
-					names.push("Arab", "Sand");
-					break;
-				case "indo-aryan":
-					names.push("Brown", "Indian");
-					break;
-				case "mixed race":
-					names.push("Mixed", "Mulatto", "Mutt");
-					break;
-			}
-		}
-		names.push(slave.hColor);
-		if (!hasAnyEyes(slave)) {
-			names.push("Blind", "Eyeless", "Sightless");
-		}
-		if (slave.hears === -2) {
-			names.push("Deaf", "Earless", "Unhearing");
-		}
-		if (slave.boobs >= 2000) {
-			suffixes.push("Boob", "Boobs", "Titty");
-		}
-		if (slave.boobs < 500 && slave.butt < 3) {
-			names.push("Girly", "Slim", "Thin");
-		}
-		if (slave.boobs < 300) {
-			names.push("Flat");
-		}
-		if (slave.anus > 2 || slave.vagina > 2) {
-			names.push("Gaping", "Hallway", "Slit", "Wideopen");
-		}
-		if (slave.weight > 160) {
-			names.push("Blimp", "Cow", "Fat", "Fatass", "Whale");
-		} else if (slave.weight > 30) {
-			names.push("Chubby", "Fat", "Whale");
-		} else if (slave.weight <= -30) {
-			names.push("Bony", "Rail", "Skinny");
-		}
-		if (slave.muscles > 30) {
-			names.push("Huge", "Muscles", "Ripped", "Strong");
-		}
-		if (slave.fetishKnown === 1) {
-			if (slave.fetish === "buttslut") {
-				names.push("Anal", "Sodomy");
-			}
-			if (slave.fetish === "cumslut") {
-				names.push("Cum", "Dicksuck", "Sucker");
-			}
-			if (slave.fetish === "humiliation") {
-				names.push("Rape");
-			}
-			if (slave.fetish === "masochist") {
-				names.push("Pain", "Rape", "Struggle");
-			}
-			if (slave.fetish === "pregnancy") {
-				names.push("Fertile");
-			}
-		}
-		if (slave.boobs * slave.lactation > 1000) {
-			names.push("Creamy", "Milky");
-			suffixes.push("Cow");
-		}
-		if (slave.skill.oral <= 30 && slave.skill.anal <= 30) {
-			names.push("Cheap", "Fail", "Gutter");
-		}
-		if (slave.nipples === "fuckable") {
-			names.push("Nipplefuck", "Nipplecunt");
-		} else if (slave.nipples !== "tiny" && slave.nipples !== "cute") {
-			names.push("Pointy", "Titclit");
-			suffixes.push("Nipples");
-		}
-		if (slave.visualAge > 35) {
-			names.push("Mature");
-			suffixes.push("Cougar", "MILF");
-		} else if (slave.visualAge < 25) {
-			names.push("Girly", "Thin", "Young");
-		}
-		if (isAmputee(slave)) {
-			names.push("Stumpy");
-			suffixes.push("Stumpy");
-		}
-		if (slave.boobsImplant > 1000 || slave.buttImplant > 3) {
-			names.push("Fake", "Plastic", "Silicone");
-		}
-		if (slave.dick > 5 && slave.balls > 5) {
-			names.push("Potent");
-			suffixes.push("Cannon", "Daddy");
-		}
-		if (slave.preg > slave.pregData.normalBirth / 1.33) {
-			if (slave.broodmother === 2) {
-				names.push("Bursting", "Seeded");
-				suffixes.push("Factory", "Nursery");
-			} else if (slave.broodmother === 1) {
-				names.push("Bloated", "Stuffed");
-				suffixes.push("Breeder", "Factory");
-			}
-		}
-		if (slave.bellyPreg >= 450000) {
-			names.push("Bulging", "Squirming");
-		}
-		if (slave.bellyPreg >= 5000) {
-			names.push("Preg");
-			suffixes.push("Belly", "Mommy");
-		}
-		if (slave.belly > 150000) {
-			suffixes.push("Balloon");
-		}
-		if (slave.belly > 1500) {
-			suffixes.push("Belly");
-		}
-		if (slave.dick > 0) {
-			if (slave.dick > 4) {
-				names.push("Dangle", "Hung");
-				suffixes.push("Cock", "Dick");
-			}
-			if (slave.balls === 0) {
-				names.push("Cut", "Gelded", "Soft");
-			} else {
-				names.push("Erect", "Hard", "Stiff");
-			}
-		}
-		if (slave.dick === 1) {
-			names.push("Micro", "Tiny");
-			suffixes.push("Bitch");
-		}
-		if (slave.height >= 185) {
-			names.push("Tall", "Top");
-			suffixes.push("Tower");
-		} else if (slave.height < 150) {
-			names.push("Stumpy", "Tiny");
-			suffixes.push("Shortstack", "Stumpy");
-		}
-		if (slave.skill.whoring > 95) {
-			names.push("Money", "Street");
-			suffixes.push("Whore");
-		}
-		if (slave.skill.entertainment > 95) {
-			names.push("Easy", "Club");
-			suffixes.push("Slut");
-		}
-		if (slave.skill.oral > 95) {
-			names.push("Suck");
-			suffixes.push("Throat");
-		}
-		if (slave.skill.vaginal > 95) {
-			suffixes.push("Channel", "Kegel", "Pussy");
-		}
-		if (slave.skill.anal > 95) {
-			suffixes.push("Asspussy", "Sphincter");
-		}
-		if (slave.intelligence + slave.intelligenceImplant > 50) {
-			names.push("Bright", "Clever", "Smart");
-			if (slave.intelligenceImplant >= 15) {
-				names.push("College", "Graduate", "Nerdy");
-			}
-		} else if (slave.intelligence + slave.intelligenceImplant < -50) {
-			names.push("Cretin", "Dumb", "Retarded", "Stupid");
-		}
-		if (slave.vagina === 1 && slave.skill.vaginal <= 10) {
-			names.push("Fresh", "New", "Tight");
-		}
-		if (slave.devotion < -75) {
-			names.push("Angry", "Biter", "Caged");
-		} else if (slave.devotion < -50) {
-			names.push("Cell", "Cuffs");
-		} else if (slave.devotion < -20) {
-			names.push("Bag", "Box");
-		} else if (slave.devotion <= 20) {
-			names.push("Sad", "Whiner");
-		} else if (slave.devotion > 50) {
-			names.push("Prize");
-			if (slave.visualAge > 35) {
-				names.push("Queen");
-			} else if (slave.visualAge < 25) {
-				names.push("Princess");
-			}
-		}
-		if (slave.trust < -50) {
-			names.push("Screaming");
-			suffixes.push("Sobber");
-		} else if (slave.trust < -20) {
-			names.push("Crying");
-			suffixes.push("Meat", "Tears", "Thing", "Weeper");
-		} else if (slave.trust < 20) {
-			names.push("Begging");
-		}
-
-		if (slave.dick === 0) {
-			if (slave.vagina === -1) {
-				suffixes.push("Null");
-			} else {
-				if (slave.visualAge < 25) {
-					suffixes.push("Girl");
-				}
-			}
-		} else {
-			if (slave.vagina !== -1) {
-				suffixes.push("Futa");
-			} else {
-				if (slave.balls > 0) {
-					if (slave.boobs > 300 && slave.butt > 2) {
-						/* SHEMALES: cock & balls, T&A above minimum */
-						suffixes.push("Shemale");
-					} else {
-						if (slave.shoulders < 1 && slave.muscles <= 30) {
-							if (slave.faceShape === "masculine" || slave.faceShape === "androgynous") {
-								/* SISSIES: feminine shoulders or muscles, masculine faces */
-								suffixes.push("Sissy");
-							} else {
-								/* TRAPS: feminine shoulders or muscles, feminine faces */
-								suffixes.push("Trap");
-							}
-						} else {
-							/* BITCHES: masculine shoulders or muscles */
-							suffixes.push("Bitch");
-						}
-					}
-				} else {
-					if (slave.visualAge > 35) {
-						suffixes.push("DickMILF");
-					} else if (slave.visualAge >= 25) {
-						suffixes.push("Dickslave");
-					} else {
-						suffixes.push("Dickgirl");
-					}
-				}
-			}
-		}
-		if (slave.anus > 0) {
-			suffixes.push("Anus", "Asshole", "Backdoor", "Butt", "Butthole");
-		}
-		if (slave.anus === 1) {
-			suffixes.push("Tightass", "Tightbutt");
-		}
-		if (slave.vagina > 0) {
-			suffixes.push("Cunt", "Pussy", "Vagina");
-		}
-		if (slave.boobs < 500 && slave.butt < 3 && slave.dick > 0) {
-			suffixes.push("Bitch", "Bottom", "Sissy", "Trap");
-		}
-		if (slave.energy > 95) {
-			suffixes.push("Fuck", "Fuckaddict", "Nympho", "Sexaddict");
-		}
-		if (slave.fetishKnown === 1) {
-			if (slave.fetish === "humiliation") {
-				suffixes.push("Rapebait", "Showgirl");
-			}
-			if (slave.fetish === "submissive") {
-				suffixes.push("Bottom", "Fuckee", "Rapebait");
-			}
-			if (slave.fetish === "dom") {
-				suffixes.push("Dom", "Fucker", "Top");
-			}
-			if (slave.fetish === "pregnancy") {
-				suffixes.push("Breeder", "Mommy");
-			}
-			if (slave.fetish === "boobs") {
-				suffixes.push("Boob", "Boobie", "Tit", "Titty");
-			}
-		}
-		if (slave.counter.births >= 2) {
-			suffixes.push("Breeder");
-			if (slave.counter.births >= 5) {
-				suffixes.push("Broodmother");
-			}
-		}
-		if (slave.areolae > 2) {
-			suffixes.push("Areolas", "Headlights");
-		}
-		if (slave.lips > 40) {
-			suffixes.push("Lips", "Pillows");
-		}
-		if (slave.labia > 1) {
-			suffixes.push("Curtains", "Flower", "Lips");
-		}
-		if (slave.breedingMark === 1 && V.propOutcome === 1 && V.arcologies[0].FSRestart !== "unset") {
-			suffixes.push("Breeder", "Oven", "Womb");
-		}
-		if (slave.butt > 5) {
-			suffixes.push("Ass", "Bottom", "Butt");
-		}
-		if (slave.vagina === 0) {
-			suffixes.push("Virgin");
-		}
-
-		slave.slaveName = jsEither(names);
-	}
-	if (leadershipPosition.includes(slave.assignment)) {
-		switch (slave.assignment) {
-			case Job.ATTENDANT:
-				slave.slaveName = jsEither(["Bath", "Spa"]);
-				break;
-			case Job.MATRON:
-				slave.slaveName = jsEither(["Matron", "Nursery"]);
-				break;
-			case Job.STEWARD:
-				slave.slaveName = jsEither(["Maid", "Servant"]);
-				break;
-			case Job.MILKMAID:
-				if (V.cumSlaves > 3) {
-					slave.slaveName = jsEither(["Fucker", "Milker"]);
-				} else {
-					slave.slaveName = jsEither(["Dairy", "Farm"]);
-				}
-				break;
-			case Job.FARMER:
-				slave.slaveName = jsEither(["Farmer", "Farmhand"]);
-				break;
-			case Job.DJ:
-				slave.slaveName = jsEither(["Bass", "Booth"]);
-				break;
-			case Job.CONCUBINE:
-				slave.slaveName = jsEither(["Bed", "Master"]);
-				break;
-			case Job.MADAM:
-				slave.slaveName = jsEither(["Madam", "Pimp"]);
-				break;
-			case Job.TEACHER:
-				slave.slaveName = jsEither(["Classroom", "Teacher"]);
-				break;
-			case Job.WARDEN:
-				slave.slaveName = jsEither(["Jail", "Prison"]);
-				break;
-			case Job.NURSE:
-				slave.slaveName = jsEither(["Clinic", "Nurse"]);
-				break;
-			case Job.HEADGIRL:
-				slave.slaveName = jsEither(["Chief", "Head"]);
-				break;
-			case Job.BODYGUARD:
-				slave.slaveName = jsEither(["Battle", "Guard"]);
-				break;
-			case Job.RECRUITER:
-				slave.slaveName = jsEither(["Cam", "Recruiter"]);
-				break;
-		}
-	}
-	const surname = jsEither(suffixes);
-	if (typeof surname === "string" && surname.toLowerCase() === slave.slaveName.toLowerCase()) {
-		DegradingName(slave);
-	}
-	slave.slaveName = capFirstChar(slave.slaveName);
-	slave.slaveSurname = surname;
-};
-
-globalThis.PaternalistName = function(slave) {
-	if (slave.slaveName.search("Miss") === -1) {
-		if (slave.slaveName.search("Ms.") === -1) {
-			if (slave.slaveName.search("Mrs.") === -1) {
-				if (slave.relationship > 4) {
-					slave.slaveName = ("Mrs. " + slave.slaveName);
-				} else if (slave.actualAge > 24) {
-					slave.slaveName = ("Ms. " + slave.slaveName);
-				} else {
-					slave.slaveName = ("Miss " + slave.slaveName);
-				}
-			}
-		}
-	}
-};
-
-globalThis.parentNames = function(parent, child) {
-	const slaves = V.slaves;
-
-	let currentSlaveNames = slaves.map(s => s.slaveName);
-	let continentNationality;
-	const useMaleName = (child.genes === "XY" && V.allowMaleSlaveNames === true);
-
-	child.slaveName = generateName(parent.nationality, child.race, useMaleName, sn => !currentSlaveNames.includes(sn));
-
-	if (!child.slaveName) {
-		for (let i = 0; i < 10; i++) {
-			continentNationality = hashChoice(V.nationalities);
-			child.slaveName = generateName(continentNationality, child.race, useMaleName, sn => !currentSlaveNames.includes(sn));	// jshint ignore: line
-		}
-	}
-	if (!child.slaveName) {
-		child.slaveName = generateName(parent.nationality, child.race, useMaleName);
-	}
-};
-
-globalThis.SlaveSort = function() {
-	const effectivePreg = (slave) => {
-		// slave.preg is only *mostly* usable for sorting
-		if (slave.preg > 0 && !slave.pregKnown) {
-			// don't reveal unknown pregnancies
-			return 0;
-		}
-		if (slave.pubertyXX === 0 && (slave.ovaries === 1 || slave.mpreg === 1)) {
-			// not ovulating yet - sort between barren slaves and slaves on contraceptives
-			return -1.2;
-		} else if (slave.ovaryAge >= 47 && (slave.ovaries === 1 || slave.mpreg === 1)) {
-			// menopausal - sort between barren slaves and slaves on contraceptives
-			return -1.1;
-		} else if (slave.pregWeek < 0) {
-			// postpartum - sort between slaves on contraceptives and fertile slaves
-			return -0.1;
-		}
-		return slave.preg;
-	};
-
-	const effectiveEnergy = (slave) => {
-		return slave.attrKnown === 1 ? slave.energy : -101;
-	};
-
-	const comparators = {
-		Aassignment: (a, b) => a.assignment < b.assignment ? -1 : 1,
-		Dassignment: (a, b) => a.assignment > b.assignment ? -1 : 1,
-		Aname: (a, b) => a.slaveName < b.slaveName ? -1 : 1,
-		Dname: (a, b) => a.slaveName > b.slaveName ? -1 : 1,
-		Aseniority: (a, b) => b.weekAcquired - a.weekAcquired,
-		Dseniority: (a, b) => a.weekAcquired - b.weekAcquired,
-		AactualAge: (a, b) => a.actualAge - b.actualAge,
-		DactualAge: (a, b) => b.actualAge - a.actualAge,
-		AvisualAge: (a, b) => a.visualAge - b.visualAge,
-		DvisualAge: (a, b) => b.visualAge - a.visualAge,
-		AphysicalAge: (a, b) => a.physicalAge - b.physicalAge,
-		DphysicalAge: (a, b) => b.physicalAge - a.physicalAge,
-		Adevotion: (a, b) => a.devotion - b.devotion,
-		Ddevotion: (a, b) => b.devotion - a.devotion,
-		AID: (a, b) => a.ID - b.ID,
-		DID: (a, b) => b.ID - a.ID,
-		AweeklyIncome: (a, b) => a.lastWeeksCashIncome - b.lastWeeksCashIncome,
-		DweeklyIncome: (a, b) => b.lastWeeksCashIncome - a.lastWeeksCashIncome,
-		Ahealth: (a, b) => a.health.health - b.health.health,
-		Dhealth: (a, b) => b.health.health - a.health.health,
-		Aweight: (a, b) => a.weight - b.weight,
-		Dweight: (a, b) => b.weight - a.weight,
-		Amuscles: (a, b) => a.muscles - b.muscles,
-		Dmuscles: (a, b) => b.muscles - a.muscles,
-		AsexDrive: (a, b) => effectiveEnergy(a) - effectiveEnergy(b),
-		DsexDrive: (a, b) => effectiveEnergy(b) - effectiveEnergy(a),
-		Apregnancy: (a, b) => effectivePreg(a) - effectivePreg(b),
-		Dpregnancy: (a, b) => effectivePreg(b) - effectivePreg(a),
-	};
-
-	return {
-		slaves: sortSlaves,
-		IDs: sortIDs,
-		indices: sortIndices
-	};
-
-	/** @param {App.Entity.SlaveState[]} [slaves] */
-	function sortSlaves(slaves) {
-		slaves = slaves || V.slaves;
-		slaves.sort(_comparator());
-		if (slaves === V.slaves) {
-			V.slaveIndices = slaves2indices();
-		}
-	}
-
-	/** @param {number[]} [slaveIDs] */
-	function sortIDs(slaveIDs) {
-		const slaves = V.slaves;
-		const slaveIndices = V.slaveIndices;
-		const cmp = _comparator();
-		slaveIDs = slaveIDs || slaves.map(s => s.ID);
-		slaveIDs.sort((IDa, IDb) => cmp(slaves[slaveIndices[IDa]], slaves[slaveIndices[IDb]]));
-	}
-
-	/** @param {number[]} [slaveIdxs] */
-	function sortIndices(slaveIdxs) {
-		const slaves = V.slaves;
-		const cmp = _comparator();
-		slaveIdxs = slaveIdxs || [...slaves.keys()];
-		slaveIdxs.sort((ia, ib) => cmp(slaves[ia], slaves[ib]));
-	}
-
-	/**
-	 * @callback slaveComparator
-	 * @param {App.Entity.SlaveState} a
-	 * @param {App.Entity.SlaveState} b
-	 * @returns {number}
-	 */
-	/** @returns {slaveComparator} */
-	function _comparator() {
-		return _makeStableComparator(comparators[(V.sortSlavesOrder === "ascending" ? 'A' : 'D') + V.sortSlavesBy]);
-	}
-
-	/** secondary-sort by ascending ID if the primary comparator would return 0 (equal), so we have a guaranteed stable order regardless of input
-	 * @param {slaveComparator} comparator
-	 * @returns {slaveComparator}
-	 */
-	function _makeStableComparator(comparator) {
-		return function(a, b) {
-			return comparator(a, b) || comparators.AID(a, b);
-		};
-	}
-}();
-
-/**
- * @param {App.Entity.SlaveState[]} slaves
- */
-globalThis.slaveSortMinor = function(slaves) {
-	slaves.sort((a, b) => a.slaveName < b.slaveName ? -1 : 1);
-};
-
-globalThis.menialPopCap = function() {
-	let r = "";
-
-	let popCap = 500 * (1 + V.building.findCells(cell => cell instanceof App.Arcology.Cell.Manufacturing && cell.type === "Pens").length);
-
-	let overMenialCap = V.menials + V.fuckdolls + V.menialBioreactors - popCap;
-	if (overMenialCap > 0) {
-		const price = menialSlaveCost(-overMenialCap);
-		if (V.menials > 0) {
-			if (V.menials > overMenialCap) {
-				cashX((overMenialCap * price), "menialTrades");
-				V.menialDemandFactor -= overMenialCap;
-				V.menials -= overMenialCap;
-				overMenialCap = 0;
-				r += "You don't have enough room for all your menials and are obliged to sell some.";
-			} else {
-				cashX((V.menials * price), "menialTrades");
-				V.menialDemandFactor -= V.menials;
-				overMenialCap -= V.menials;
-				V.menials = 0;
-				r += "You don't have enough room for your menials and are obliged to sell them.";
-			}
-		}
-		if (overMenialCap > 0 && V.fuckdolls > 0) {
-			if (V.fuckdolls > overMenialCap) {
-				cashX(overMenialCap * (price * 2), "menialTrades");
-				V.menialDemandFactor -= overMenialCap;
-				V.fuckdolls -= overMenialCap;
-				overMenialCap = 0;
-				r += "You don't have enough room for all your Fuckdolls and are obliged to sell some.";
-			} else {
-				cashX(V.fuckdolls * (price * 2), "menialTrades");
-				V.menialDemandFactor -= V.fuckdolls;
-				overMenialCap -= V.fuckdolls;
-				V.fuckdolls = 0;
-				r += "You don't have enough room for your Fuckdolls and are obliged to sell them.";
-			}
-		}
-		if (overMenialCap > 0 && V.menialBioreactors > 0) {
-			cashX(overMenialCap * (price - 100), "menialTrades");
-			V.menialDemandFactor -= overMenialCap;
-			V.menialBioreactors -= overMenialCap;
-			r += "You don't have enough room for all your menial bioreactors and are obliged to sell some.";
-		}
-	}
-	return {text: r, value: popCap};
-};
-
-/**
- * @param {App.Entity.SlaveState} slave
- * @param {number} amount
- * @returns {string}
- */
-globalThis.faceIncrease = function(slave, amount) {
-	const pronouns = getPronouns(slave);
-	const his = pronouns.possessive;
-	const His = capFirstChar(his);
-	let r = "";
-	if (slave.face <= -95) {
-		r += `<span class="green">${His} face is no longer horrifying,</span> and is now merely ugly.`;
-	} else if (slave.face <= -40 && slave.face + amount > -40) {
-		r += `<span class="green">${His} face is no longer ugly,</span> and is now merely unattractive.`;
-	} else if (slave.face <= -10 && slave.face + amount > -10) {
-		r += `<span class="green">${His} face is no longer unattractive,</span> and is now somewhat tolerable.`;
-	} else if (slave.face <= 10 && slave.face + amount > 10) {
-		r += `<span class="green">${His} face is now decently attractive,</span> rather than merely tolerable.`;
-	} else if (slave.face <= 40 && slave.face + amount > 40) {
-		r += `<span class="green">${His} face is now quite beautiful,</span> rather than merely pretty.`;
-	} else if (slave.face <= 95 && slave.face + amount > 95) {
-		r += `<span class="green">${His} face is now perfect.</span> It's difficult to imagine how it could be any more beautiful.`;
-	}
-	slave.face = Math.clamp(slave.face + amount, -100, 100);
-	if (slave.face > 95) {
-		slave.face = 100;
-	}
-	return r;
-};
-
-/**
- * @param {App.Entity.SlaveState} slave
- * @returns {number}
- */
-globalThis.deadliness = function(slave) {
-	let deadliness = 2;
-
-	if (slave.skill.combat > 0) {
-		deadliness += 2;
-	}
-
-	if (App.Data.Careers.Leader.bodyguard.includes(slave.career)) {
-		deadliness += 1;
-	} else if (slave.skill.bodyguard >= V.masteredXP) {
-		deadliness += 1;
-	}
-
-	if (V.AgePenalty !== 0) {
-		if (slave.physicalAge >= 100) {
-			deadliness -= 10;
-		} else if (slave.physicalAge >= 85) {
-			deadliness -= 3;
-		} else if (slave.physicalAge >= 70) {
-			deadliness -= 1;
-		}
-	}
-
-	if (slave.muscles > 30 && slave.muscles <= 95) {
-		deadliness += 1;
-	} else if (slave.muscles > 95 && slave.height >= 185) {
-		deadliness += 2;
-	} else if (slave.muscles > 95) {
-		deadliness -= 1;
-	} else if (slave.muscles < -95) {
-		deadliness -= 20;
-	} else if (slave.muscles < -30) {
-		deadliness -= 7;
-	} else if (slave.muscles < -5) {
-		deadliness -= 3;
-	}
-
-	if (slave.height >= 170) {
-		deadliness += 1;
-	}
-
-	if (slave.health.condition > 50) {
-		deadliness += 1;
-	} else if (slave.health.condition < -50) {
-		deadliness -= 1;
-	}
-
-	if (slave.health.illness > 3) {
-		deadliness -= 3;
-	} else if (slave.health.illness > 1) {
-		deadliness -= 2;
-	} else if (slave.health.illness > 0) {
-		deadliness -= 1;
-	}
-
-	if (slave.boobs > 4000) {
-		deadliness -= 2;
-	} else if (slave.boobs > 2000) {
-		deadliness -= 1;
-	}
-
-	if (slave.butt > 6) {
-		deadliness -= 1;
-	}
-
-	if (slave.hips > 2) {
-		deadliness -= 1;
-	}
-
-	if (slave.weight > 190) {
-		deadliness -= 20;
-	} else if (slave.weight > 160) {
-		deadliness -= 10;
-	} else if (slave.weight > 130) {
-		deadliness -= 3;
-	} else if (slave.weight > 30 || slave.weight < -10) {
-		deadliness -= 1;
-	}
-
-	if (slave.bellyFluid >= 10000) {
-		deadliness -= 3;
-	} else if (slave.bellyFluid >= 5000) {
-		deadliness -= 2;
-	} else if (slave.bellyFluid >= 2000) {
-		deadliness -= 1;
-	}
-
-	if (slave.pregKnown === 1 || slave.bellyPreg >= 1500 || slave.bellyImplant >= 1500) {
-		if (slave.belly >= 750000) {
-			deadliness -= 50;
-		} else if (slave.belly >= 600000) {
-			deadliness -= 25;
-		} else if (slave.belly >= 450000) {
-			deadliness -= 15;
-		} else if (slave.belly >= 300000) {
-			deadliness -= 10;
-		} else if (slave.belly >= 150000) {
-			deadliness -= 8;
-		} else if (slave.belly >= 100000) {
-			deadliness -= 7;
-		} else if (slave.belly >= 10000) {
-			deadliness -= 3;
-		} else if (slave.belly >= 5000) {
-			deadliness -= 2;
-		} else {
-			deadliness -= 1;
-		}
-	}
-
-	if (isInLabor(slave)) {
-		deadliness -= 15;
-	} else if (slave.preg >= slave.pregData.normalBirth && slave.pregControl !== "labor suppressors") {
-		deadliness -= 5;
-	}
-
-	if (slave.balls >= 15) {
-		deadliness -= 1;
-	}
-
-	if (slave.dick >= 10) {
-		deadliness -= 1;
-	}
-
-	deadliness -= getLimbCount(slave, 0) * 5;
-	deadliness -= getLimbCount(slave, 2) * 0.25;
-	deadliness -= getLimbCount(slave, 3) * 0.25;
-	deadliness -= getLimbCount(slave, 4) * 0.25;
-	deadliness += getLimbCount(slave, 5) * 1.25;
-	deadliness += getLimbCount(slave, 6) * 2.5;
-	if (hasBothLegs(slave) && !canStand(slave)) {
-		deadliness -= 20;
-	}
-
-	if (!canSee(slave)) {
-		deadliness -= 8;
-	} else if (!canSeePerfectly(slave)) {
-		deadliness -= 1;
-	}
-
-	if (!canHear(slave)) {
-		deadliness -= 4;
-	} else if ((slave.hears === -1 && slave.earwear !== "hearing aids") || (slave.hears === 0 && slave.earwear === "muffling ear plugs")) {
-		deadliness -= 1;
-	}
-
-	if (slave.tail === "combat") {
-		deadliness += 2;
-	}
-
-	if (slave.health.tired > 90) {
-		deadliness -= 10;
-	} else if (slave.health.tired > 60) {
-		deadliness -= 3;
-	} else if (slave.health.tired > 30) {
-		deadliness -= 1;
-	}
-
-	return Math.max(deadliness, 1);
-};
-
-/** Is the slave ready to retire?
- * @param {App.Entity.SlaveState} slave
- * @returns {boolean}
- */
-globalThis.retirementReady = function(slave) {
-	// indentured slaves don't retire, they expire
-	if (slave.indenture >= 0) {
-		return false;
-	}
-
-	// retirement by age
-	if (V.policies.retirement.physicalAgePolicy === 0 && slave.actualAge >= V.retirementAge) {
-		return true;
-	} else if (V.policies.retirement.physicalAgePolicy === 1 && slave.physicalAge >= V.retirementAge) {
-		return true;
-	}
-
-	// retirement by milestone
-	if (V.policies.retirement.sex > 0 && (slave.counter.oral + slave.counter.anal + slave.counter.vaginal + slave.counter.penetrative + slave.counter.mammary) > V.policies.retirement.sex) {
-		return true;
-	}
-	if (V.policies.retirement.milk > 0 && slave.counter.milk > V.policies.retirement.milk) {
-		return true;
-	}
-	if (V.policies.retirement.cum > 0 && slave.counter.cum > V.policies.retirement.cum) {
-		return true;
-	}
-	if (V.policies.retirement.births > 0 && slave.counter.births > V.policies.retirement.births) {
-		return true;
-	}
-	if (V.policies.retirement.kills > 0 && slave.counter.pitKills > V.policies.retirement.kills) {
-		return true;
-	}
-
-	// no retirement for you
-	return false;
-};
-
-/** marks some weeks of time passage for a slave, counting birthdays and invoking aging if game settings require it
- * @param {App.Entity.SlaveState} slave
- * @param {number} [weeks=1]
- */
-globalThis.ageSlaveWeeks = function(slave, weeks = 1) {
-	if (V.seeAge !== 0) { // birthdays enabled
-		for (let i = 0; i < weeks; ++i) {
-			slave.birthWeek++;
-			if (slave.birthWeek >= 52) {
-				slave.birthWeek = 0;
-				if (V.seeAge === 1) { // actual aging enabled
-					ageSlave(slave);
-				}
-			}
-		}
-	}
-};
-
-/** advances the age of a slave by one year, incurring all aging side effects
- * @param {App.Entity.SlaveState} slave
- * @param {boolean} [forceDevelopment=false]
- */
-globalThis.ageSlave = function(slave, forceDevelopment = false) {
-	slave.physicalAge++;
-	slave.actualAge++;
-	if (slave.geneMods.NCS === 1 || (slave.geneticQuirks.neoteny >= 2 && slave.geneticQuirks.progeria !== 2)) {
-		/* Induced NCS completely takes over visual aging. Additionally, because of the neoteny aspects of NCS, ovaries don't age quite as fast. */
-		/* Unsurprisingly, actual neoteny has the same effect as long as progeria isn't in play. */
-		slave.ovaryAge += either(0.5, 0.6, 0.7, 0.7, 0.8, 0.9, 1.0);
-	} else {
-		slave.visualAge++;
-		slave.ovaryAge += either(0.8, 0.9, 0.9, 1.0, 1.0, 1.0, 1.1);
-	}
-	if (slave.broodmother === 1) {
-		slave.ovaryAge += 0.2;
-	}
-	if (slave.physicalAge <= 18 && (forceDevelopment || V.loliGrow > 0)) {
-		physicalDevelopment(slave);
-	}
-};
-
-/** Is the slave a shelter slave?
- * @param {App.Entity.SlaveState} slave
- * @returns {boolean}
- */
-globalThis.isShelterSlave = function(slave) {
-	return (typeof slave.origin === "string" && slave.origin.includes("Slave Shelter"));
-};
-
-/**
- * Returns if a slave appears male, female, or androgynous.
- *
- * @param {App.Entity.SlaveState} slave
- * @returns {number}
- */
-globalThis.perceivedGender = function(slave) {
-	return -1;
-};
-
-globalThis.initRules = function() {
-	const rule = emptyDefaultRule();
-	rule.name = "Obedient Slaves";
-	rule.condition.function = "between";
-	rule.condition.data.attribute = "devotion";
-	rule.condition.data.value = [20, null];
-	rule.set.removalAssignment = "rest";
-
-	V.defaultRules = [rule];
-	V.rulesToApplyOnce = {};
-};
-
-/**
- *
- * @param {App.Entity.SlaveState} slave
- * @param {number} partner The ID of the slave's partner.
- *
- * | ***ID*** | **Type**              |
- * |---------:|:----------------------|
- * | *-1*     | PC                    |
- * | *-2*     | Citizen               |
- * | *-3*     | PC's former master    |
- * | *-4*     | Fellow arcology owner |
- * | *-6*     | Societal Elite        |
- * | *-8*     | Animal                |
- * | *-9*     | Futanari Sister       |
- */
-globalThis.addPartner = function(slave, partner) {
-	slave.partners.add(partner);
-
-	if (partner > 0) {
-		getSlave(partner).partners.add(slave.ID);
-	}
-};
diff --git a/src/js/rulesAssistant.js b/src/js/rulesAssistant.js
index b25965885ae9744963c8a840e280933f6bd85ffa..52199fd9c050ef135adb64295328edc9dd7e1c47 100644
--- a/src/js/rulesAssistant.js
+++ b/src/js/rulesAssistant.js
@@ -602,3 +602,15 @@ App.RA.ruleDeepAssign = function deepAssign(target, source) {
 	}
 	return target;
 };
+
+globalThis.initRules = function() {
+	const rule = emptyDefaultRule();
+	rule.name = "Obedient Slaves";
+	rule.condition.function = "between";
+	rule.condition.data.attribute = "devotion";
+	rule.condition.data.value = [20, null];
+	rule.set.removalAssignment = "rest";
+
+	V.defaultRules = [rule];
+	V.rulesToApplyOnce = {};
+};
diff --git a/src/js/utilsArcology.js b/src/js/utilsArcology.js
new file mode 100644
index 0000000000000000000000000000000000000000..4dd1a73849d74394f265fc3c2fe54401ba66624a
--- /dev/null
+++ b/src/js/utilsArcology.js
@@ -0,0 +1,104 @@
+/** Returns the revivalist nationality associated with the player's arcology, or 0 if none
+ * @returns {string|0}
+ */
+globalThis.getRevivalistNationality = function() {
+	if (V.arcologies[0].FSRomanRevivalist > 90) {
+		return "Roman Revivalist";
+	} else if (V.arcologies[0].FSAztecRevivalist > 90) {
+		return "Aztec Revivalist";
+	} else if (V.arcologies[0].FSEgyptianRevivalist > 90) {
+		return "Ancient Egyptian Revivalist";
+	} else if (V.arcologies[0].FSEdoRevivalist > 90) {
+		return "Edo Revivalist";
+	} else if (V.arcologies[0].FSArabianRevivalist > 90) {
+		return "Arabian Revivalist";
+	} else if (V.arcologies[0].FSChineseRevivalist > 90) {
+		return "Ancient Chinese Revivalist";
+	}
+	return 0;
+};
+
+/** Calculate and return economic uncertainty multiplier for a given arcology
+ * @param {number} arcologyID
+ * @returns {number}
+ */
+App.Utils.economicUncertainty = function(arcologyID) {
+	let uncertainty = arcologyID === 0 ? 5 : 10;
+	if (assistant.power === 1) {
+		uncertainty -= Math.max(Math.trunc(uncertainty/2), 0);
+	} else if (assistant.power > 1) {
+		uncertainty = 0;
+	}
+	return jsRandom(100 - uncertainty, 100 + uncertainty) / 100;
+};
+
+/**
+ * @returns {number}
+ */
+App.Utils.schoolCounter = function() {
+	return Array.from(App.Data.misc.schools.keys()).filter(s => V[s].schoolPresent).length;
+};
+
+/**
+ * @returns {string}
+ */
+App.Utils.schoolFailure = function() {
+	return Array.from(App.Data.misc.schools.keys()).find(s => V[s].schoolPresent && V[s].schoolProsperity <= -10);
+};
+
+/**
+ * @typedef {Object} menialObject
+ * @property {string} text
+ * @property {number} value
+ */
+
+/**
+ * @returns {menialObject}
+ */
+globalThis.menialPopCap = function() {
+	let r = "";
+
+	let popCap = 500 * (1 + V.building.findCells(cell => cell instanceof App.Arcology.Cell.Manufacturing && cell.type === "Pens").length);
+
+	let overMenialCap = V.menials + V.fuckdolls + V.menialBioreactors - popCap;
+	if (overMenialCap > 0) {
+		const price = menialSlaveCost(-overMenialCap);
+		if (V.menials > 0) {
+			if (V.menials > overMenialCap) {
+				cashX((overMenialCap * price), "menialTrades");
+				V.menialDemandFactor -= overMenialCap;
+				V.menials -= overMenialCap;
+				overMenialCap = 0;
+				r += "You don't have enough room for all your menials and are obliged to sell some.";
+			} else {
+				cashX((V.menials * price), "menialTrades");
+				V.menialDemandFactor -= V.menials;
+				overMenialCap -= V.menials;
+				V.menials = 0;
+				r += "You don't have enough room for your menials and are obliged to sell them.";
+			}
+		}
+		if (overMenialCap > 0 && V.fuckdolls > 0) {
+			if (V.fuckdolls > overMenialCap) {
+				cashX(overMenialCap * (price * 2), "menialTrades");
+				V.menialDemandFactor -= overMenialCap;
+				V.fuckdolls -= overMenialCap;
+				overMenialCap = 0;
+				r += "You don't have enough room for all your Fuckdolls and are obliged to sell some.";
+			} else {
+				cashX(V.fuckdolls * (price * 2), "menialTrades");
+				V.menialDemandFactor -= V.fuckdolls;
+				overMenialCap -= V.fuckdolls;
+				V.fuckdolls = 0;
+				r += "You don't have enough room for your Fuckdolls and are obliged to sell them.";
+			}
+		}
+		if (overMenialCap > 0 && V.menialBioreactors > 0) {
+			cashX(overMenialCap * (price - 100), "menialTrades");
+			V.menialDemandFactor -= overMenialCap;
+			V.menialBioreactors -= overMenialCap;
+			r += "You don't have enough room for all your menial bioreactors and are obliged to sell some.";
+		}
+	}
+	return {text: r, value: popCap};
+};
diff --git a/src/js/utilsAssessSlave.js b/src/js/utilsAssessSlave.js
new file mode 100644
index 0000000000000000000000000000000000000000..f42e0ad0677b34ed9d951769daf66905feea0e47
--- /dev/null
+++ b/src/js/utilsAssessSlave.js
@@ -0,0 +1,243 @@
+/*
+*
+* This file focuses on slave related functions that assess qualities about slaves.  Are they/can they X?
+*
+*/
+
+/**
+ * @param {App.Entity.SlaveState} slave
+ * @returns {string}
+ */
+globalThis.getSlaveDevotionClass = function(slave) {
+	if ((!slave) || (!State)) {
+		return undefined;
+	}
+	if (slave.fetish === "mindbroken") {
+		return "mindbroken";
+	}
+	if (slave.devotion < -95) {
+		return "very-hateful";
+	} else if (slave.devotion < -50) {
+		return "hateful";
+	} else if (slave.devotion < -20) {
+		return "resistant";
+	} else if (slave.devotion <= 20) {
+		return "ambivalent";
+	} else if (slave.devotion <= 50) {
+		return "accepting";
+	} else if (slave.devotion <= 95) {
+		return "devoted";
+	} else {
+		return "worshipful";
+	}
+};
+
+/**
+ * @param {App.Entity.SlaveState} slave
+ * @returns {string}
+ */
+globalThis.getSlaveTrustClass = function(slave) {
+	if ((!slave) || (!State)) {
+		return undefined;
+	}
+
+	if (slave.fetish === "mindbroken") {
+		return "";
+	}
+
+	if (slave.trust < -95) {
+		return "extremely-terrified";
+	} else if (slave.trust < -50) {
+		return "terrified";
+	} else if (slave.trust < -20) {
+		return "frightened";
+	} else if (slave.trust <= 20) {
+		return "fearful";
+	} else if (slave.trust <= 50) {
+		if (slave.devotion < -20) {
+			return "hate-careful";
+		} else {
+			return "careful";
+		}
+	} else if (slave.trust <= 95) {
+		if (slave.devotion < -20) {
+			return "bold";
+		} else {
+			return "trusting";
+		}
+	} else if (slave.devotion < -20) {
+		return "defiant";
+	} else {
+		return "profoundly-trusting";
+	}
+};
+
+/**
+ * Returns a "disobedience factor" between 0 (perfectly obedient) and 100 (completely defiant)
+ * @param {App.Entity.SlaveState} slave
+ * @returns {number}
+ */
+globalThis.disobedience = function(slave) {
+	const devotionBaseline = 20; // with devotion above this number slaves will obey completely
+	const trustBaseline = -20; // with trust below this number slaves will obey completely
+
+	if (slave.devotion > devotionBaseline || slave.trust < trustBaseline) {
+		return 0; // no chance of disobedience
+	}
+
+	// factors are between 0 (right on the boundary of perfectly obedient) and 10 (completely disobedient)
+	let devotionFactor = 10 - ((10 * (slave.devotion + 100)) / (devotionBaseline + 100));
+	let trustFactor = (10 * (slave.trust - trustBaseline)) / (100 - trustBaseline);
+	return Math.round(devotionFactor * trustFactor);
+};
+
+/**
+ * Returns how exposing a slave's outfit is, after taking into consideration a topless outfit is more revealing for beboobed slaves or female ones.
+ * @param {App.Entity.SlaveState} slave
+ * @returns {0|1|2|3|4}
+ */
+globalThis.getExposure = function(slave) {
+	const clothes = App.Data.clothes.get(slave.clothes);
+	return (clothes.topless && clothes.exposure < 3 && (slave.boobs > 299 || (slave.genes === 'XX' && slave.vagina >= 0))) ? 3 : clothes.exposure;
+};
+
+/**
+ * @param {App.Entity.SlaveState} slave
+ * @returns {boolean}
+ */
+globalThis.canImproveIntelligence = function(slave) {
+	let origIntel = V.genePool.find(function(s) { return s.ID === slave.ID; }).intelligence;
+	return (slave.intelligence < origIntel + 15) && (slave.intelligence < 100);
+};
+
+/**
+ * @param {App.Entity.SlaveState} slave
+ * @returns {number}
+ */
+globalThis.maxHeight = function(slave) {
+	let max = Math.trunc(Math.clamp((Height.mean(slave) * 1.25), 0, 274)); /* max achievable height is expected height plus 25% */
+
+	if (slave.geneticQuirks.dwarfism === 2 && slave.geneticQuirks.gigantism !== 2) {
+		max = Math.min(max, 160);
+	}
+
+	return max;
+};
+
+/**
+ * @param {App.Entity.SlaveState} slave
+ * @returns {boolean}
+ */
+globalThis.canImproveHeight = function(slave) {
+	return slave.height < maxHeight(slave);
+};
+
+/**
+ * @param {App.Entity.SlaveState} slave
+ * @param {FC.HumanState} target
+ * @returns {boolean}
+ */
+globalThis.haveRelationshipP = function(slave, target) {
+	return slave.relationshipTarget === target.ID;
+};
+
+/**
+ * @param {App.Entity.SlaveState} slave
+ * @param {App.Entity.SlaveState} target
+ * @returns {boolean}
+ */
+globalThis.isRivalP = function(slave, target) {
+	return slave.rivalryTarget === target.ID;
+};
+
+/**
+ * @param {FC.HumanState} slave
+ * @returns {boolean}
+ */
+globalThis.supremeRaceP = function(slave) {
+	return V.arcologies[0].FSSupremacistRace === slave.race;
+};
+
+/**
+ * @param {FC.HumanState} slave
+ * @returns {boolean}
+ */
+globalThis.inferiorRaceP = function(slave) {
+	return V.arcologies[0].FSSubjugationistRace === slave.race;
+};
+
+/**
+ * @param {App.Entity.SlaveState} slave
+ * @returns {boolean}
+ */
+globalThis.isLeaderP = function(slave) {
+	const leaders = [S.HeadGirl, S.Bodyguard, S.Recruiter, S.Concubine, S.Nurse, S.Attendant, S.Matron, S.Madam, S.DJ, S.Milkmaid, S.Farmer, S.Stewardess, S.Schoolteacher, S.Wardeness];
+
+	return leaders.some(leader => leader && leader.ID === slave.ID);
+};
+
+/** Get the written title for a given slave, without messing with global state.
+ * @param {App.Entity.SlaveState} [slave]
+ * @returns {string}
+ */
+globalThis.getWrittenTitle = function(slave) {
+	if (slave && slave.custom.title !== undefined && slave.custom.title !== "" && slave.rudeTitle === 0) {
+		return slave.custom.title;
+	}
+	if (V.PC.customTitle !== undefined) {
+		return V.PC.customTitle;
+	} else if (V.PC.title !== 0) {
+		return "Master";
+	} else {
+		return "Mistress";
+	}
+};
+
+/**
+ * @param {App.Entity.SlaveState} slave
+ * @returns {string}
+ */
+globalThis.SlaveFullName = function(slave) {
+	const pair = slave.slaveSurname ? [slave.slaveName, slave.slaveSurname] : [slave.slaveName];
+	if ((V.surnameOrder !== 1 && ["Cambodian", "Chinese", "Hungarian", "Japanese", "Korean", "Mongolian", "Taiwanese", "Vietnamese"].includes(slave.nationality)) || (V.surnameOrder === 2)) {
+		pair.reverse();
+	}
+	return pair.join(" ");
+};
+
+/** Is the slave a shelter slave?
+ * @param {App.Entity.SlaveState} slave
+ * @returns {boolean}
+ */
+globalThis.isShelterSlave = function(slave) {
+	return (typeof slave.origin === "string" && slave.origin.includes("Slave Shelter"));
+};
+
+/**
+ * Returns if a slave appears male, female, or androgynous.
+ *
+ * @param {App.Entity.SlaveState} slave
+ * @returns {number}
+ */
+globalThis.perceivedGender = function(slave) {
+	return -1;
+};
+
+/**
+ * @param {App.Entity.SlaveState} A
+ * @param {App.Entity.SlaveState} B
+ * @returns {boolean}
+ */
+globalThis.sameAssignmentP = function(A, B) {
+	return A.assignment === B.assignment;
+};
+
+/** Determine whether a given penthouse slave can move into a private room or not.
+ * @param {App.Entity.SlaveState} slave
+ * @returns {boolean}
+ */
+globalThis.canMoveToRoom = function(slave) {
+	const partner = slave.relationship >= 4 ? getSlave(slave.relationshipTarget) : null;
+	const partnerHasRoom = partner && assignmentVisible(partner) && partner.rules.living === "luxurious";
+	return partnerHasRoom || V.rooms - V.roomsPopulation >= 1;
+};
diff --git a/src/js/utilsMisc.js b/src/js/utilsMisc.js
new file mode 100644
index 0000000000000000000000000000000000000000..ab9e8f6928c3a475339641231554e4dd3d127654
--- /dev/null
+++ b/src/js/utilsMisc.js
@@ -0,0 +1,75 @@
+globalThis.Categorizer = class {
+	/**
+	 * @param  {...[]} pairs
+	 */
+	constructor(...pairs) {
+		this.cats = Array.prototype.slice.call(pairs)
+			.filter(function(e, i, a) {
+				return Array.isArray(e) && e.length === 2 && typeof e[0] === "number" && !isNaN(e[0]) &&
+					a.findIndex(function(val) {
+						return e[0] === val[0];
+					}) === i; /* uniqueness test */
+			})
+			.sort(function(a, b) {
+				return b[0] - a[0]; /* reverse sort */
+			});
+	}
+
+	cat(val, def) {
+		let result = def;
+		if (typeof val === "number" && !isNaN(val)) {
+			let foundCat = this.cats.find(function(e) {
+				return val >= e[0];
+			});
+			if (foundCat) {
+				result = foundCat[1];
+			}
+		}
+		// Record the value for the result's getter, if it is an object
+		// and doesn't have the property yet
+		if (typeof result === "object" && !isNaN(result)) {
+			result.value = val;
+		}
+		return result;
+	}
+};
+
+/**
+ * Converts an array of strings into a sentence parted by commas.
+ * @param {Array} array ["apple", "banana", "carrot"]
+ * @returns {string} "apple, banana and carrot"
+ */
+globalThis.arrayToSentence = function(array) {
+	return array.reduce((res, ch, i, arr) => res + (i === arr.length - 1 ? ' and ' : ', ') + ch);
+};
+
+App.Utils.alphabetizeIterable = function(iterable) {
+	const compare = function(a, b) {
+		let aTitle = a.toLowerCase();
+		let bTitle = b.toLowerCase();
+
+		aTitle = removeArticles(aTitle);
+		bTitle = removeArticles(bTitle);
+
+		if (aTitle > bTitle) {
+			return 1;
+		}
+		if (aTitle < bTitle) {
+			return -1;
+		}
+		return 0;
+	  };
+
+	function removeArticles(str) {
+		const words = str.split(" ");
+		if (words.length <= 1) {
+			return str;
+		}
+		if ( words[0] === 'a' || words[0] === 'the' || words[0] === 'an' ) {
+			return words.splice(1).join(" ");
+		}
+		return str;
+	}
+	const clonedArray = (Array.from(iterable));
+	return clonedArray.sort(compare);
+};
diff --git a/src/js/utilsFC.js b/src/js/utilsSlave.js
similarity index 70%
rename from src/js/utilsFC.js
rename to src/js/utilsSlave.js
index 14f26e503ccbfd87a4971cc1de810d4e86f5bb46..9d055f0f8b2cd0565aced7afa8dc2983195db75f 100644
--- a/src/js/utilsFC.js
+++ b/src/js/utilsSlave.js
@@ -1,5 +1,3 @@
-/* contains functions that rely on FC specific variables/conventions */
-/* eslint-disable no-unused-vars */
 /*
  * Height.mean(nationality, race, genes, age) - returns the mean height for the given combination and age in years (>=2)
  * Height.mean(nationality, race, genes) - returns the mean adult height for the given combination
@@ -912,736 +910,6 @@ As a categorizer
 <<print $cats.muscleCat.cat(_Slave.muscles)>>
 */
 
-globalThis.Categorizer = class {
-	/**
-	 * @param  {...[]} pairs
-	 */
-	constructor(...pairs) {
-		this.cats = Array.prototype.slice.call(pairs)
-			.filter(function(e, i, a) {
-				return Array.isArray(e) && e.length === 2 && typeof e[0] === "number" && !isNaN(e[0]) &&
-					a.findIndex(function(val) {
-						return e[0] === val[0];
-					}) === i; /* uniqueness test */
-			})
-			.sort(function(a, b) {
-				return b[0] - a[0]; /* reverse sort */
-			});
-	}
-
-	cat(val, def) {
-		let result = def;
-		if (typeof val === "number" && !isNaN(val)) {
-			let foundCat = this.cats.find(function(e) {
-				return val >= e[0];
-			});
-			if (foundCat) {
-				result = foundCat[1];
-			}
-		}
-		// Record the value for the result's getter, if it is an object
-		// and doesn't have the property yet
-		if (typeof result === "object" && !isNaN(result)) {
-			result.value = val;
-		}
-		return result;
-	}
-};
-
-/**
- * Returns numbers as text, e.g. 10 as "ten", according to the player's settings
- * @param {number} x
- * @param {boolean} [printText=false] (optional)
- * @returns {string}
- */
-globalThis.num = function(x, printText = false) {
-	const max = V.showNumbersMax;
-
-	const ONE_TO_NINETEEN = [
-		"one", "two", "three", "four", "five",
-		"six", "seven", "eight", "nine", "ten",
-		"eleven", "twelve", "thirteen", "fourteen", "fifteen",
-		"sixteen", "seventeen", "eighteen", "nineteen",
-	];
-
-	const TENS = [
-		"ten", "twenty", "thirty", "forty", "fifty",
-		"sixty", "seventy", "eighty", "ninety",
-	];
-
-	const SCALES = ["thousand", "million", "billion", "trillion", "quadrillion", "quintillion", "sextillion", "septillion", "octillion", "nonillion", "decillion"];
-
-	/**
-	 * helper function for use with Array.filter
-	 * @param {any} item
-	 * @returns {boolean}
-	 */
-	function isTruthy(item) {
-		return !!item;
-	}
-
-	/**
-	 * convert a number into "chunks" of 0-999
-	 * @param {number} number
-	 * @returns {number[]}
-	 */
-	function chunk(number) {
-		const thousands = [];
-
-		while (number > 0) {
-			thousands.push(number % 1000);
-			number = Math.floor(number / 1000);
-		}
-
-		return thousands;
-	}
-
-	/**
-	 * translate a number from 1-999 into English
-	 * @param {number} number
-	 * @returns {string}
-	 */
-	function inEnglish(number) {
-		let hundreds;
-		let tens;
-		let ones;
-		const words = [];
-
-		if (number === 0) {
-			return "zero";
-		}
-
-		if (number < 20) {
-			return ONE_TO_NINETEEN[number - 1]; // may be undefined
-		}
-
-		if (number < 100) {
-			ones = number % 10;
-			tens = number / 10 | 0; // equivalent to Math.floor(number / 10)
-
-			words.push(TENS[tens - 1]);
-			words.push(inEnglish(ones));
-
-			return words.filter(isTruthy).join("-");
-		}
-
-		hundreds = number / 100 | 0;
-		words.push(inEnglish(hundreds));
-		words.push("hundred");
-		words.push(inEnglish(number % 100));
-
-		return words.filter(isTruthy).join(" ");
-	}
-
-	if (printText) {
-		return inEnglish(x);
-	}
-
-	/**
-	 * append the word for a scale. Made for use with Array.map
-	 * @param {string} chunk
-	 * @param {number} exp
-	 * @returns {string}
-	 */
-	function appendScale(chunk, exp) {
-		let scale;
-		if (!chunk) {
-			return null;
-		}
-		scale = SCALES[exp - 1];
-		return [chunk, scale].filter(isTruthy).join(" ");
-	}
-
-	if (V.showNumbers === 2) {
-		return commaNum(x);
-	} else {
-		if (x === 0) {
-			return "zero";
-		}
-
-		if (V.showNumbers === 1 && Math.abs(x) > max) {
-			return commaNum(x);
-		}
-
-		let numberAsString = chunk(Math.abs(x))
-			.map(inEnglish)
-			.map(appendScale)
-			.filter(isTruthy)
-			.reverse()
-			.join(" ");
-
-		if (x > 0) {
-			return numberAsString;
-		} else {
-			return `negative ${numberAsString}`;
-		}
-	}
-};
-
-globalThis.asPlural = function(single, plural) {
-	if (typeof single !== 'string') {
-		let asObj = single;
-		single = asObj.single;
-		plural = asObj.plural;
-	}
-	if (plural == null) {
-		plural = single + "s";
-	}
-	return plural;
-};
-globalThis.asSingular = function(single) {
-	if (typeof single !== 'string') {
-		let asObj = single;
-		single = asObj.single;
-	}
-	return single;
-};
-// When 1, shows "a (slave)"
-globalThis.numberWithPlural = function(number, single, plural) {
-	if (number === 0) {
-		return "no " + asPlural(single, plural);
-	} else if (number === 1) {
-		return addA(asSingular(single));
-	} else if (number > 0 && number < 1) {
-		return "less than one " + asSingular(single);
-	} else {
-		return number + " " + asPlural(single, plural);
-	}
-};
-
-// when 1, shows "one (slave)"
-globalThis.numberWithPluralOne = function(number, single, plural) {
-	if (number === 0) {
-		return "no " + asPlural(single, plural);
-	} else if (number === 1) {
-		return "one " + asSingular(single);
-	} else if (number > 0 && number < 1) {
-		return "less than one " + asSingular(single);
-	} else {
-		return number + " " + asPlural(single, plural);
-	}
-};
-// shows "less than one (slave)" instead of "no (slaves)" when number is 0.
-globalThis.numberWithPluralNonZero = function(number, single, plural) {
-	if (number === 0) {
-		number = 0.1;
-	}
-	return numberWithPlural(number, single, plural);
-};
-globalThis.onlyPlural = function(number, single, plural) {
-	if (number > 0 && number <= 1) {
-		return asSingular(single);
-	}
-	return asPlural(single, plural);
-};
-globalThis.Separator = function(SeparatorObject) {
-	if (SeparatorObject.need) {
-		return SeparatorObject.text;
-	}
-	SeparatorObject.need = true;
-	return "";
-};
-/**
- * Returns numbers with comma, e.g. 10000 as "10,000", according to the player's settings
- * @param {number} s
- * @returns {string}
- */
-globalThis.commaNum = function(s) {
-	// Separated from num because some places in code (like long lists, tables) should never have numbers spelled out, but still benefit from commas
-	if (!s) {
-		return "0";
-	}
-	if (V.formatNumbers !== 1) {
-		return s.toString();
-	} else {
-		return s.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
-	}
-};
-
-/**
- * Returns the number of weeks in a years / months / weeks format
- * @param {number} weeks
- * @returns {string}
- */
-globalThis.years = function(weeks) {
-	let years = 0;
-	let quarters = 0; // needed for calc, not user facing
-	let months = 0;
-	let array = [];
-
-	// A year is always 52 weeks
-	// that could be 13 months, but lets say 4 quarters each getting an extra week (13 weeks)
-
-	// Find years
-	years = Math.trunc(weeks / 52);
-
-	if (years >= 1) { // Is there at least 1 year
-		weeks = weeks - (years * 52); // Find leftover weeks
-	}
-	if (weeks && weeks / 13 >= 1) { // Is there at least 1 quarter
-		quarters = Math.trunc(weeks / 13); // How many quarters?
-		weeks = weeks - (quarters * 13); // A quarter contains 13 weeks, how many extra weeks do we have?
-	}
-	if (weeks && weeks / 4 >= 1) { // Is there at least 1 month
-		months = Math.trunc(weeks / 4); // How many months?
-		if (months === 3) { // Almost a quarter of a year
-			months--; // Quarters have 13 weeks though, so let's be sure the extra is in weeks. Otherwise 51 will return "12 months" instead of "11 months and 4 weeks."
-		}
-		weeks = weeks - (months * 4); // A month contains 4 weeks, how many extra weeks do we have?
-	}
-
-	// So we have years, quarters, months, and weeks.
-
-	// Quarters are useless so:
-
-	months += quarters * 3; // Each quarter has three months.
-
-	if (years) {
-		array.push(`${num(years)} year${years !== 1 ? `s` : ``}`);
-	}
-
-	if (months) {
-		array.push(`${num(months)} month${months !== 1 ? `s` : ``}`);
-	}
-
-	if (weeks) {
-		array.push(`${num(weeks)} week${weeks !== 1 ? `s` : ``}`);
-	}
-
-	return array.toStringExt();
-};
-/**
- * @param {number} [weeks]
- * @param {number} [bonusDay]
- * @returns {Date}
- */
-globalThis.asDate = function(weeks = null, bonusDay = 0) {
-	if (weeks == null) {
-		weeks = V.week;
-	}
-	let d = new Date(2037, 0, 12);
-	d.setDate(d.getDate() + weeks * 7 + bonusDay);
-	return d;
-};
-/**
- * @param {number} [weeks]
- * @param {number} [bonusDay]
- * @returns {string}
- */
-globalThis.asDateString = function(weeks = null, bonusDay = 0) {
-	return asDate(weeks, bonusDay).toLocaleString(undefined, {year: 'numeric', month: 'long', day: 'numeric'});
-};
-
-/**
- * @param {number} s
- * @returns {string}
- */
-globalThis.cashFormat = function(s) {
-	if (s < 0) {
-		return `-¤${commaNum(Math.abs(s))}`;
-	}
-	return `¤${commaNum(s)}`;
-};
-globalThis.cashFormatColor = function(s, invert = false) {
-	if (invert) {
-		s = -1 * s;
-	}
-	// Display red if the value is negative, unless invert is true
-	if (s < 0) {
-		return `<span class='red'>${cashFormat(s)}</span>`;
-		// White for exactly zero
-	} else if (s === 0) {
-		return `<span>${cashFormat(s)}</span>`;
-		// Yellow for positive
-	} else {
-		return `<span class='yellowgreen'>${cashFormat(s)}</span>`;
-	}
-};
-
-/**
- * @param {number} s
- * @returns {string}
- */
-globalThis.repFormat = function(s) {
-	/* if (!s) { s = 0; }*/
-	if (V.cheatMode === 1 || V.debugMode === 1) {
-		if (s > 0) {
-			return `<span class="green">${commaNum(Math.round(s * 100) / 100)} rep</span>`;
-		} else if (s < 0) {
-			return `<span class="red">${commaNum(Math.round(s * 100) / 100)} rep</span>`;
-		} else {
-			return `${commaNum(Math.round(s * 100) / 100)} rep`;
-		}
-	} else {
-		/* In order to calculate just how much any one category matters so we can show a "fuzzy" symbolic value to the player, we need to know how "busy" reputation was this week. To calculate this, I ADD income to expenses. Why? 100 - 100 and 10000 - 10000 BOTH are 0, but a +50 event matters a lot more in the first case than the second. I exclude overflow from the calculation because it's not a "real" expense for our purposes, and divide by half just to make percentages a bit easier. */
-		let weight = s / (((V.lastWeeksRepIncome.Total - V.lastWeeksRepExpenses.Total) + V.lastWeeksRepExpenses.overflow) / 2);
-		if (weight > 0.60) {
-			return `<span class="green">+++++ rep</span>`;
-		} else if (weight > 0.45) {
-			return `<span class="green">++++ rep</span>`;
-		} else if (weight > 0.30) {
-			return `<span class="green">+++ rep</span>`;
-		} else if (weight > 0.15) {
-			return `<span class="green">++ rep</span>`;
-		} else if (weight > 0.0) {
-			return `<span class="green">+ rep</span>`;
-		} else if (weight === 0) {
-			return "0 rep";
-		} else if (weight < -0.60) {
-			return `<span class="red">&minus;&minus;&minus;&minus;&minus; rep</span>`;
-		} else if (weight < -0.45) {
-			return `<span class="red">&minus;&minus;&minus;&minus; rep</span>`;
-		} else if (weight < -0.30) {
-			return `<span class="red">&minus;&minus;&minus; rep</span>`;
-		} else if (weight < -0.15) {
-			return `<span class="red">&minus;&minus; rep</span>`;
-		} else if (weight < 0) {
-			return `<span class="red">&minus; rep</span>`;
-		}
-		/* return weight;*/
-	}
-};
-
-/**
- * @param {number} s
- * @returns {string}
- */
-globalThis.massFormat = function(s) {
-	if (!s) {
-		s = 0;
-	}
-	if (Math.abs(s) >= 1000) {
-		s = Math.trunc(s / 1000);
-		if (s !== 1) {
-			return `${num(s)} tons`;
-		} else {
-			return `${num(s)} ton`;
-		}
-	} else {
-		return `${num(s)} kg`;
-	}
-};
-
-/**
- * @param {string} category
- * @param {string} title
- * @returns {string}
- */
-globalThis.budgetLine = function(category, title) {
-	let income;
-	let expenses;
-
-	if (passage() === "Rep Budget") {
-		income = "lastWeeksRepIncome";
-		expenses = "lastWeeksRepExpenses";
-
-		if (V[income][category] || V[expenses][category] || V.showAllEntries.repBudget) {
-			return `<tr>\
-				<td>${title}</td>\
-				<td>${repFormat(V[income][category])}</td>\
-				<td>${repFormat(V[expenses][category])}</td>\
-				<td>${repFormat(V[income][category] + V[expenses][category])}</td>\
-				</tr>`;
-		}
-	} else if (passage() === "Costs Budget") {
-		income = "lastWeeksCashIncome";
-		expenses = "lastWeeksCashExpenses";
-
-		if (V[income][category] || V[expenses][category] || V.showAllEntries.costsBudget) {
-			return `<tr>\
-				<td>${title}</td>\
-				<td>${cashFormatColor(V[income][category])}</td>\
-				<td>${cashFormatColor(-Math.abs(V[expenses][category]))}</td>\
-				<td>${cashFormatColor(V[income][category] + V[expenses][category])}</td>\
-				</tr>`;
-		}
-	}
-	return ``;
-};
-
-/*
-Make everything waiting for this execute. Usage:
-
-let doSomething = function() {
-	... your initialization code goes here ...
-};
-if(typeof Categorizer === 'function') {
-	doSomething();
-} else {
-	jQuery(document).one('categorizer.ready', doSomething);
-}
-*/
-jQuery(document).trigger("categorizer.ready");
-
-/**
- * @param {App.Entity.SlaveState} slave
- * @returns {string}
- */
-globalThis.getSlaveDevotionClass = function(slave) {
-	if ((!slave) || (!State)) {
-		return undefined;
-	}
-	if (slave.fetish === "mindbroken") {
-		return "mindbroken";
-	}
-	if (slave.devotion < -95) {
-		return "very-hateful";
-	} else if (slave.devotion < -50) {
-		return "hateful";
-	} else if (slave.devotion < -20) {
-		return "resistant";
-	} else if (slave.devotion <= 20) {
-		return "ambivalent";
-	} else if (slave.devotion <= 50) {
-		return "accepting";
-	} else if (slave.devotion <= 95) {
-		return "devoted";
-	} else {
-		return "worshipful";
-	}
-};
-
-/**
- * @param {App.Entity.SlaveState} slave
- * @returns {string}
- */
-globalThis.getSlaveTrustClass = function(slave) {
-	if ((!slave) || (!State)) {
-		return undefined;
-	}
-
-	if (slave.fetish === "mindbroken") {
-		return "";
-	}
-
-	if (slave.trust < -95) {
-		return "extremely-terrified";
-	} else if (slave.trust < -50) {
-		return "terrified";
-	} else if (slave.trust < -20) {
-		return "frightened";
-	} else if (slave.trust <= 20) {
-		return "fearful";
-	} else if (slave.trust <= 50) {
-		if (slave.devotion < -20) {
-			return "hate-careful";
-		} else {
-			return "careful";
-		}
-	} else if (slave.trust <= 95) {
-		if (slave.devotion < -20) {
-			return "bold";
-		} else {
-			return "trusting";
-		}
-	} else if (slave.devotion < -20) {
-		return "defiant";
-	} else {
-		return "profoundly-trusting";
-	}
-};
-
-/**
- * Takes an integer e.g. slave.hLength, returns a string in the format 10 inches
- * @param {number} cm
- * @returns {string}
- */
-globalThis.cmToInchString = function(cm) {
-	let inches = cm / 2.54;
-	if (inches > 0 && inches < 1) {
-		return "less than an inch";
-	}
-	inches = Math.round(inches);
-	if (inches === 1) {
-		return "1 inch";
-	}
-	return `${inches} inches`;
-};
-
-/**
- * takes an integer e.g. slave.height, returns a string in the format 6'5"
- * @param {number} cm
- * @returns {string}
- */
-globalThis.cmToFootInchString = function(cm) {
-	if (Math.round(cm / 2.54) < 12) {
-		return cmToInchString(cm);
-	}
-	return `${Math.trunc(Math.round(cm / 2.54) / 12)}'${Math.round(cm / 2.54) % 12}"`;
-};
-
-/**
- * takes a dick value e.g. slave.dick, returns a string in the format 6 inches
- * @param {number} dick
- * @returns {string}
- */
-globalThis.dickToInchString = function(dick) {
-	return cmToInchString(dickToCM(dick));
-};
-
-/**
- * takes a dick value e.g. slave.dick, returns an int of the dick length in cm
- * @param {number} dick
- * @returns {number}
- */
-globalThis.dickToCM = function(dick) {
-	if (dick < 9) {
-		return dick * 5;
-	} else if (dick === 9) {
-		return 50;
-	}
-	return dick * 6;
-};
-/**
- * takes a ball value e.g. slave.balls, returns a string in the format 3 inches
- * @param {number} balls
- * @returns {string}
- */
-globalThis.ballsToInchString = function(balls) {
-	return cmToInchString(ballsToCM(balls));
-};
-
-/**
- * takes a ball value e.g. slave.balls, returns an int of the ball size in cm
- * @param {number} balls
- * @returns {number}
- */
-globalThis.ballsToCM = function(balls) {
-	if (balls < 2) {
-		return 0;
-	}
-	return (balls < 10 ? (balls - 1) * 2 : balls * 2);
-};
-
-/**
- * takes a dick value e.g. slave.dick, returns a string in the format of either `20cm (8 inches)`, `8 inches`, or `20cm`
- * @param {number} dick
- * @returns {string}
- */
-globalThis.dickToEitherUnit = function(dick) {
-	if (V.showInches === 1) {
-		return `${dickToCM(dick)}cm (${dickToInchString(dick)})`;
-	}
-	if (V.showInches === 2) {
-		return dickToInchString(dick);
-	}
-	return `${dickToCM(dick)}cm`;
-};
-
-/**
- * takes a ball value e.g. slave.balls, returns a string in the format of either `20cm (8 inches)`, `8 inches`, or `20cm`
- * @param {number} balls
- * @returns {string}
- */
-globalThis.ballsToEitherUnit = function(balls) {
-	if (V.showInches === 1) {
-		return `${ballsToCM(balls)}cm (${ballsToInchString(balls)})`;
-	}
-	if (V.showInches === 2) {
-		return ballsToInchString(balls);
-	}
-	return `${ballsToCM(balls)}cm`;
-};
-
-/**
- * takes an int in centimeters e.g. slave.height, returns a string in the format of either `200cm (6'7")`, `6'7"`, or `200cm`
- * @param {number} height
- * @returns {string}
- */
-globalThis.heightToEitherUnit = function(height) {
-	if (V.showInches === 1) {
-		return `${height}cm (${cmToFootInchString(height)})`;
-	}
-	if (V.showInches === 2) {
-		return cmToFootInchString(height);
-	}
-	return `${height}cm`;
-};
-
-/**
- * takes an int in centimeters e.g. slave.hLength, returns a string in the format of either `30cm (12 inches)`, `12 inches`, or `30cm`
- * @param {number} length
- * @returns {string}
- */
-globalThis.lengthToEitherUnit = function(length) {
-	if (V.showInches === 1) {
-		return `${length}cm (${cmToInchString(length)})`;
-	}
-	if (V.showInches === 2) {
-		return cmToInchString(length);
-	}
-	return `${length}cm`;
-};
-
-/**
- * @param {App.Entity.SlaveState} slave
- * @param {number} [induce]
- * @returns {string}
- */
-globalThis.induceLactation = function(slave, induce = 0) {
-	const {His} = getPronouns(slave);
-	let r = "";
-	let lactationStartChance = jsRandom(10, 100);
-	slave.induceLactation += induce;
-	if (slave.boobs < 300) {
-		lactationStartChance *= 1.5;
-	} else if (slave.boobs < 400 || slave.boobs >= 5000) {
-		lactationStartChance *= 1.2;
-	}
-	if (slave.pubertyXX === 0) {
-		lactationStartChance *= 1.5;
-	}
-	if (slave.preg > (slave.pregData.normalBirth / 1.33)) {
-		lactationStartChance *= .5;
-	}
-	if (slave.health.condition < -20) {
-		lactationStartChance *= 2;
-	}
-	if (slave.weight <= -30) {
-		lactationStartChance *= 1.5;
-	}
-	if (slave.boobsImplant > 0) {
-		lactationStartChance *= (1 + (slave.boobsImplant / slave.boobs));
-	}
-	if (slave.lactationAdaptation > 0) {
-		lactationStartChance = (lactationStartChance / (slave.lactationAdaptation / 10));
-	}
-	if (slave.geneticQuirks.galactorrhea === 2) {
-		lactationStartChance *= .5;
-	}
-	lactationStartChance = Math.floor(lactationStartChance);
-	if (slave.induceLactation >= lactationStartChance) {
-		r += `${His} breasts have been stimulated often enough to <span class="lime">induce lactation.</span>`;
-		slave.induceLactation = 0;
-		slave.lactationDuration = 2;
-		slave.lactation = 1;
-	}
-	return r;
-};
-
-globalThis.getProstheticsStockpile = function() {
-	return `<div>Prosthetics interfaces: ${num(V.prosthetics.interfaceP1.amount + V.prosthetics.interfaceP2.amount)}</div>` +
-		`<div class="choices">Basic: ${V.prosthetics.interfaceP1.amount}</div>` +
-		`<div class="choices">Advanced: ${V.prosthetics.interfaceP2.amount}</div>` +
-		`<div>Limbs: ${num(V.prosthetics.basicL.amount + V.prosthetics.sexL.amount + V.prosthetics.beautyL.amount +
-			V.prosthetics.combatL.amount + V.prosthetics.cyberneticL.amount)}</div>` +
-		`<div class="choices">Basic: ${V.prosthetics.basicL.amount}</div>` +
-		`<div class="choices">Sex: ${V.prosthetics.sexL.amount}</div>` +
-		`<div class="choices">Beauty: ${V.prosthetics.beautyL.amount}</div>` +
-		`<div class="choices">Combat: ${V.prosthetics.combatL.amount}</div>` +
-		`<div class="choices">Cybernetic: ${V.prosthetics.cyberneticL.amount}</div>` +
-		`<div>Implants: ${num(V.prosthetics.ocular.amount + V.prosthetics.cochlear.amount + V.prosthetics.electrolarynx.amount)}</div>` +
-		`<div class="choices">Ocular: ${V.prosthetics.ocular.amount}</div>` +
-		`<div class="choices">Cochlear: ${V.prosthetics.cochlear.amount}</div>` +
-		/* `<div class="choices">Erectile: ${V.prosthetics.erectile.amount}</div>` + */
-		`<div class="choices">Electrolarynx: ${V.prosthetics.electrolarynx.amount}</div>` +
-		`<div>Tail interface: ${V.prosthetics.interfaceTail.amount}</div>` +
-		`<div>Tails: ${num(V.prosthetics.modT.amount + V.prosthetics.sexT.amount + V.prosthetics.combatT.amount)}</div>` +
-		`<div class="choices">Modular: ${V.prosthetics.modT.amount}</div>` +
-		`<div class="choices">Pleasure: ${V.prosthetics.sexT.amount}</div>` +
-		`<div class="choices">Combat: ${V.prosthetics.combatT.amount}</div>`;
-};
-
 globalThis.pronounReplacer = function(slavetext) {
 	switch (slavetext) {
 		case "After her short but very promising slave racing career, during which she made it through several competitions as a virgin, many people fondly remember fantasizing about taking her.":
@@ -1947,899 +1215,2236 @@ globalThis.pronounReplacer = function(slavetext) {
 		case "You kept her after her owner failed to pay your bill for performing surgery on her.":
 			slavetext = "You kept $him after $his owner failed to pay your bill for performing surgery on $him.";
 			break;
-		case "You purchased her as a favor to her father.":
-			slavetext = "You purchased $him as a favor to $his father.";
+		case "You purchased her as a favor to her father.":
+			slavetext = "You purchased $him as a favor to $his father.";
+			break;
+		case "You purchased her from a King after his son put an illegitimate heir in her womb.":
+			slavetext = "You purchased $him from a King after his son put an illegitimate heir in $his womb.";
+			break;
+		case "You purchased her in order to pave the way for her brother to take the throne.":
+			slavetext = "You purchased $him in order to pave the way for $his brother to take the throne.";
+			break;
+		case "You purchased her indenture contract, making her yours for as long as it lasts.":
+			slavetext = "You purchased $his indenture contract, making $him yours for as long as it lasts.";
+			break;
+		case "You sentenced her to enslavement as a punishment for attempted theft of a slave.":
+			slavetext = "You sentenced $him to enslavement as a punishment for attempted theft of a slave.";
+			break;
+		case "You sentenced her to enslavement as a punishment for dereliction of her duty to you as a mercenary and for theft.":
+			slavetext = "You sentenced $him to enslavement as a punishment for dereliction of $his duty to you as a mercenary and for theft.";
+			break;
+		case "You sentenced her to enslavement as a punishment for smuggling slaves within her body.":
+			slavetext = "You sentenced $him to enslavement as a punishment for smuggling slaves within $his body.";
+			break;
+		case "You stormed her arcology, killed her guards and enslaved her in revenge for insulting you at a dinner party.":
+			slavetext = "You stormed $his arcology, killed $his guards, and enslaved $him in revenge for insulting you at a dinner party.";
+			break;
+		case "You tricked her into enslavement, manipulating her based on her surgical addiction.":
+			slavetext = "You tricked $him into enslavement, manipulating $him based on $his surgical addiction.";
+			break;
+		case "You tricked her mother into selling her into slavery to clear addiction debts.":
+			slavetext = "You tricked $his mother into selling $him into slavery to clear addiction debts.";
+			break;
+		case "You were acquainted with her before you were an arcology owner; your rival tried to use her to manipulate you, but you rescued her.":
+			slavetext = "You were acquainted with $him before you were an arcology owner; your rival tried to use $him to manipulate you, but you rescued $him.";
+			break;
+		case "Your slaving troop kept several girls as fucktoys; you sired her in your favorite.":
+			slavetext = "Your slaving troop kept several girls as fucktoys; you sired $him in your favorite.";
+			break;
+		case "She was enslaved by you when you purchased her debt.":
+			slavetext = "$He was enslaved by you when you purchased $his debt.";
+			break;
+		case "A fresh capture once overpowered you and had his way with you. You kept her as a painful reminder to never lower your guard again.":
+		case "Drugs and alcohol can be a potent mix; the night that followed it can sometimes be hard to remember. Needless to say, once your belly began swelling with her, you had to temporarily switch to a desk job for your mercenary group.":
+		case "Her musky milky aura drives men and women around her giggly and dumb with lust.":
+		case "She chose to be a slave because the romanticized view of it she had turns her on.":
+		case "She grew up sheltered and submissive, making her an easy target for enslavement.":
+		case "She has a faint air of fatigue about her, and strength too: that of a survivor.":
+		case "She has a following in slave pornography. Thousands have enjoyed her getting off from the suffering she caused.":
+		case "She has a following in slave pornography. Thousands have enjoyed her humiliating herself.":
+		case "She has a following in slave pornography. Thousands have enjoyed the sight of her being raped.":
+		case "She has a following in slave pornography. Thousands have enjoyed the sight of her being used.":
+		case "She has a following in slave pornography. Thousands have enjoyed the sight of her eating and gaining weight.":
+		case "She has a following in slave pornography. Thousands have enjoyed watching her abuse others.":
+		case "She has a following in slave pornography. Thousands have enjoyed watching her do anything and everything for cum.":
+		case "She has a following in slave pornography. Thousands have enjoyed watching her do anything for attention.":
+		case "She has a following in slave pornography. Thousands have enjoyed watching her happily suffer.":
+		case "She has a following in slave pornography. Thousands have enjoyed watching her obsess over pumping out babies.":
+		case "She has a following in slave pornography. Thousands have enjoyed watching her swell with child.":
+		case "She has a verbal tic that causes her to say 'ho, ho, ho' frequently.":
+		case "She has many surgical scars and something seems off about her.":
+		case "She is a complete mental blank; to her, there is only the Master.":
+		case "She is one of the longest legally-enslaved persons in the world, having been a slave for 15 years. She has spent almost all that time working as a slave prostitute, and has been heavily modified to keep her productive.":
+		case "She is the winner of a martial arts slave tournament. You won her in a bet.":
+		case "She offered herself to you for enslavement to escape having plastic surgery foisted on her.":
+		case "She was a runaway slave captured by a gang outside your arcology. You bought her cheap after she was harshly used by them.":
+		case "She was a student you enslaved when you evacuated her from a threatened old world grade school.":
+		case "She was a volleyball player you enslaved when you evacuated her from a broken down bus.":
+		case "She was an expectant mother you enslaved when you evacuated her from a threatened old world hospital.":
+		case "She was an orphan forced to live and steal on the streets until you adopted her.":
+		case "She was enslaved by you when you overcharged her for surgery.":
+		case "She was fresh from the slave markets when you acquired her.":
+		case "She was homeless and willing to do anything for food, which in the end resulted in her becoming a slave.":
+		case "She was previously owned by a creative sadist, who has left a variety of mental scars on her.":
+		case "She was sold to you by an anonymous person who wanted her to suffer.":
+		case "She was taken as a slave by a Sultan, who presented her as a gift to a surveyor.":
+		case "She was taken into your custody from an owner who treated her as an equal.":
+		case "She was the private slave of a con artist cult leader before he had to abandon her and flee.":
+		case "She was the result of an intruder brute forcing your firewall, overloading your pleasure sensors, and allowing a corrupted packet to slip by. With a quick wipe of your RAM and cache with some powerful liquor, you have no idea who planted her in your womb.":
+		case "You acquired her in the last stages of your career as a noted private military contractor.":
+		case "You acquired her in the last stages of your career as a successful venture capitalist.":
+		case "You bought her at auction.":
+		case "You bought her from The Cattle Ranch.":
+		case "You bought her from the enigmatic Futanari Sisters after they sold her into slavery.":
+		case "You bought her from the household liquidator.":
+		case "You bought her from the kidnappers' slave market, so she was probably forced into slavery.":
+		case "You bought her from the prestigious Hippolyta Academy.":
+		case "You bought her from the trainers' slave market after they put her through basic training.":
+		case "You bought her from the underage raiders' slave market.":
+		case "You bought out a deal involving her training to be an expert gelded sex slave.":
+		case "You brought her into the arcology mindbroken, little more than a human onahole.":
+		case "You brought her into the arcology mindbroken, little more than a walking collection of fuckable holes.":
+		case "You captured her during your transition to the arcology":
+		case "You conceived her after a male arcology owner, impressed by your work, rewarded you with a night you'll never forget.":
+		case "You enslaved her personally during the last stages of your slaving career.":
+		case "You helped her give birth, leaving her deeply indebted to you.":
+		case "You never thought you would be capable of impregnating yourself, but years of pleasuring yourself with yourself after missions managed to create her.":
+		case "You purchased her by special order.":
+		case "You purchased her from a King after she expressed knowledge of the prince's affair with another servant.":
+		case "You purchased her from FCTV's Home Slave Shopping stream channel.":
+		case "You received her as a gift from an arcology owner impressed by your work.":
+		case "You received her from a surgeon who botched an implant operation on her and needed to get her out of sight.":
+		case "You reserved a mindless slave like her from the Flesh Heap.":
+		case "You sentenced her to enslavement as a punishment for attempted burglary.":
+		case "You sentenced her to enslavement as a punishment for defying local racial segregation laws.":
+		case "You sentenced her to enslavement as a punishment for fraud and theft.":
+		case "You sentenced her to enslavement as a punishment for suspected escapism.":
+		case "You sentenced her to enslavement as a punishment for theft and battery.":
+		case "You sentenced her to enslavement for smuggling drugs into the arcology.":
+		case "You sired her after a female arcology owner, impressed by your work, rewarded you with a night you'll never forget.":
+		case "You sired her in yourself after an arcology owner, impressed by your work, rewarded you with a night you'll never forget.":
+		case "You turned her into a slave girl after she fell into debt to you.":
+		case "You won her at a shotgun match against other arcology owners.":
+		case "You won her at cards, a memento from your life as one of the idle rich before you became an arcology owner.":
+			slavetext = slavetext.replace(/\bherself\b/g, "$himself");
+			slavetext = slavetext.replace(/\bHerself\b/g, "$Himself");
+			slavetext = slavetext.replace(/\bshe\b/g, "$he");
+			slavetext = slavetext.replace(/\bShe\b/g, "$He");
+			slavetext = slavetext.replace(/\bher\b/g, "$him");
+			slavetext = slavetext.replace(/\bHer\b/g, "$His");
+			slavetext = slavetext.replace(/\b girl\b/g, " $girl");
+			slavetext = slavetext.replace(/\b woman\b/g, " $woman");
+			slavetext = slavetext.replace(/\${2,}/g, '');
+			break;
+		default:
+			if ((slavetext.includes("was serving the public")) || (slavetext.includes("You bought her from"))) {
+				slavetext = slavetext.replace(/\bher\b/g, "$him");
+			} else if (((slavetext.includes("Your lurcher")) && (slavetext.includes("coursing"))) || ((slavetext.includes("Your")) && (slavetext.includes("while raiding")))) {
+				slavetext = slavetext.replace(/\bher\b/g, "$him");
+				slavetext = slavetext.replace(/\bshe\b/g, "$he");
+			} else if (slavetext.includes("was once the young trophy husband of a powerful woman in the old world, but she sold")) {
+				slavetext = "$He was once the young trophy husband of a powerful woman in the old world, but she sold $him into slavery in revenge for $his infidelities.";
+			} else if (slavetext.includes("gargantuan dick to be a truly unique slave")) {
+				slavetext = "$He was raised as a girl despite $his gargantuan dick to be a truly unique slave.";
+			} else if (slavetext.includes("to enslavement for the attempted rape of a free woman")) {
+				slavetext = "You sentenced $him to enslavement for the attempted rape of a free woman.";
+			} else if (slavetext.includes("to enslavement as a punishment for the rape of a free woman")) {
+				slavetext = "You sentenced $him to enslavement as a punishment for the rape of a free woman.";
+			} else if (slavetext.includes("only way to obtain surgery to transform $him into a woman")) {
+				slavetext = "$He submitted to enslavement as $his only way to obtain surgery to transform $him into a woman.";
+			} else if (slavetext.includes("was sold as a slave to satisfy her spousal maintenance after divorce")) {
+				slavetext = "Once $he was an arcology security officer, lured to aphrodisiacs addiction and feminized by $his boss (and former wife), to whom $he was sold as a slave to satisfy her spousal maintenance after divorce.";
+			} else if (slavetext.includes("asked to be enslaved in the hope you'd treat a fellow woman well")) {
+				slavetext = "$He asked to be enslaved in the hope you'd treat a fellow woman well.";
+			} else {
+				slavetext = slavetext.replace(/\bherself\b/g, "$himself");
+				slavetext = slavetext.replace(/\bHerself\b/g, "$Himself");
+				slavetext = slavetext.replace(/\bshe\b/g, "$he");
+				slavetext = slavetext.replace(/\bShe\b/g, "$He");
+				slavetext = slavetext.replace(/\bher\b/g, "$his");
+				slavetext = slavetext.replace(/\bHer\b/g, "$His");
+				slavetext = slavetext.replace(/\b girl\b/g, " $girl");
+				slavetext = slavetext.replace(/\b woman\b/g, " $woman");
+				slavetext = slavetext.replace(/\${2,}/g, '');
+			}
+			break;
+	}
+	return slavetext;
+};
+
+globalThis.convertCareer = function(slave) {
+	let job = slave.career;
+	if ((V.diversePronouns === 1) && (slave.pronoun === App.Data.Pronouns.Kind.male)) {
+		switch (job) {
+			case "a dominatrix":
+				job = "a dominator";
+				break;
+			case "a farmer's daughter":
+				job = "a farmer's son";
+				break;
+			case "a handmaiden":
+				job = "a handservant";
+				break;
+			case "a lady courtier":
+				job = "a gentleman courtier";
+				break;
+			case "a landlady":
+				job = "a landlord";
+				break;
+			case "a madam":
+				job = "a brothel owner";
+				break;
+			case "a maid":
+				job = "a houseservant";
+				break;
+			case "a mail-order bride":
+				job = "a mail-order groom";
+				break;
+			case "a mistress":
+				job = "a kept man";
+				break;
+			case "a nun":
+				job = "a monk";
+				break;
+			case "a nursemaid":
+				job = "a child's nurse";
+				break;
+			case "a procuress":
+				job = "a procurer";
+				break;
+			case "a shrine maiden":
+				job = "a shrine priest";
+				break;
+			case "a trophy spouse":
+				job = "a trophy husband";
+				break;
+			case "a weathergirl":
+				job = "a weatherman";
+				break;
+			case "an air hostess":
+				job = "an air host";
+				break;
+			case "being homeschooled by her parents":
+				job = "being homeschooled by his parents";
+				break;
+			case "a camgirl":
+			case "a cowgirl":
+			case "a girl scout":
+			case "a paper girl":
+			case "a party girl":
+				job = job.replace(/girl/g, "boy");
+				break;
+			case "a businesswoman":
+			case "a criminal businesswoman":
+			case "a delivery woman":
+			case "a fisherwoman":
+			case "a noblewoman":
+			case "a saleswoman":
+			case "a stuntwoman":
+				job = job.replace(/woman/g, "man");
+				break;
+			case "a housewife":
+			case "a trophy wife":
+				job = job.replace(/wife/g, "husband");
+				break;
+			case "a cocktail waitress":
+			case "a waitress":
+			case "a seamstress":
+				job = job.replace(/ress/g, "er");
+				break;
+			case "a child actress":
+			case "an actress":
+				job = job.replace(/ress/g, "or");
+				break;
+		}
+	} else if (slave.pronoun === App.Data.Pronouns.Kind.female) {
+		switch (job) {
+			case "a priest":
+				job = "a priestess";
+				break;
+			case "a trophy spouse":
+				job = "a trophy wife";
+				break;
+			case "a businessman":
+			case "a repairman":
+				job = job.replace(/man/g, "woman");
+				break;
+		}
+	}
+	return job;
+};
+
+/**
+ * 
+ * @param {App.Entity.SlaveState} slave
+ * @returns {string|null}
+ */
+globalThis.tutorForSlave = function(slave) {
+	for (const tutor of Object.keys(V.slaveTutor)) {
+		const pupils = V.slaveTutor[tutor];
+		if (pupils.contains(slave.ID)) {
+			return tutor;
+		}
+	}
+	return null;
+};
+
+/**
+ * 
+ * @param {string} skill
+ * @returns {number}
+ */
+globalThis.upgradeMultiplier = function(skill) {
+	if (skill === 'medicine' && V.PC.career === "medicine" || skill === 'engineering' && V.PC.career === "engineer"
+		|| ((skill === 'medicine' || skill === 'engineering') && V.arcologies[0].FSRestartDecoration >= 100 && V.eugenicsFullControl === 0)) {
+		return 0.6;
+	}
+	if (V.PC.skill[skill] <= -100) {
+		return 1.5;
+	} else if (V.PC.skill[skill] <= -75) {
+		return 1.35;
+	} else if (V.PC.skill[skill] <= -50) {
+		return 1.25;
+	} else if (V.PC.skill[skill] <= -25) {
+		return 1.15;
+	} else if (V.PC.skill[skill] < 0) {
+		return 1.10;
+	} else if (V.PC.skill[skill] === 0) {
+		return 1;
+	} else if (V.PC.skill[skill] <= 10) {
+		return 0.97;
+	} else if (V.PC.skill[skill] <= 25) {
+		return 0.95;
+	} else if (V.PC.skill[skill] <= 50) {
+		return 0.90;
+	} else if (V.PC.skill[skill] <= 75) {
+		return 0.85;
+	} else if (V.PC.skill[skill] <= 100) {
+		return 0.80;
+	} else {
+		return 0.75;
+	}
+};
+
+/**
+ * Return a career at random that would be suitable for the given slave.
+ * Currently only considers their age
+ * @param {App.Entity.SlaveState} slave
+ * @returns {string}
+ */
+globalThis.randomCareer = function(slave) {
+	if (slave.actualAge < 16) {
+		return App.Data.Careers.General.veryYoung.random();
+	} else if (slave.actualAge <= 24) {
+		return App.Data.Careers.General.young.random();
+	} else if (slave.intelligenceImplant >= 10) {
+		return App.Data.Careers.General.educated.random();
+	} else {
+		return App.Data.Careers.General.uneducated.random();
+	}
+};
+
+/**
+ * @param {App.Entity.SlaveState} slave
+ */
+globalThis.resyncSlaveHight = function(slave) {
+	slave.height = Math.round(Height.random(slave));
+};
+
+/**
+ * @param {App.Entity.SlaveState} slave
+ */
+globalThis.resyncSlaveToAge = function(slave) {
+	resyncSlaveHight(slave);
+	slave.pubertyXX = slave.actualAge < slave.pubertyAgeXX ? 0 : 1;
+	slave.pubertyXY = slave.actualAge < slave.pubertyAgeXY ? 0 : 1;
+	if (slave.actualAge < 12) {
+		slave.vagina = 0;
+		slave.trueVirgin = 1;
+		slave.preg = -1;
+		slave.belly = 0;
+		slave.bellyPreg = 0;
+		slave.ovaries = 1;
+		slave.anus = 0;
+		slave.skill.anal = 0;
+		slave.skill.oral = 0;
+		slave.skill.whoring = 0;
+		slave.skill.entertainment = 0;
+		slave.skill.combat = 0;
+		slave.skill.vaginal = 0;
+		slave.attrXY = 50;
+		slave.attrXX = 50;
+		slave.boobs = 200;
+		slave.birthWeek = 0;
+		SetBellySize(slave);
+		if (slave.dick > 0) {
+			slave.dick = 1;
+		}
+		if (slave.balls > 0) {
+			slave.balls = 1;
+		}
+	} else {
+		slave.boobs = Math.max(slave.boobs, 500);
+		if (slave.dick > 2) {
+			slave.dick = 2;
+		}
+		if (slave.balls > 2) {
+			slave.balls = 2;
+		}
+	}
+	slave.career = randomCareer(slave);
+};
+
+/**
+ * @param {string} raceName
+ * @returns {string}
+ */
+globalThis.randomRaceSkin = function(raceName) {
+	let skin;
+	switch (raceName) {
+		case "asian":
+			skin = jsEither(["dark olive", "light olive", "light"]);
+			break;
+		case "amerindian":
+		case "indo-aryan":
+		case "malay":
+		case "pacific islander":
+			skin = jsEither(["dark", "light"]);
 			break;
-		case "You purchased her from a King after his son put an illegitimate heir in her womb.":
-			slavetext = "You purchased $him from a King after his son put an illegitimate heir in $his womb.";
+		case "black":
+			skin = jsEither(["black", "brown", "dark brown"]);
 			break;
-		case "You purchased her in order to pave the way for her brother to take the throne.":
-			slavetext = "You purchased $him in order to pave the way for $his brother to take the throne.";
+		case "latina":
+			skin = jsEither(["brown", "dark brown", "dark olive", "light olive", "tan"]);
 			break;
-		case "You purchased her indenture contract, making her yours for as long as it lasts.":
-			slavetext = "You purchased $his indenture contract, making $him yours for as long as it lasts.";
+		case "middle eastern":
+		case "semitic":
+		case "southern european":
+			skin = jsEither(["fair", "light olive", "light", "tan"]);
 			break;
-		case "You sentenced her to enslavement as a punishment for attempted theft of a slave.":
-			slavetext = "You sentenced $him to enslavement as a punishment for attempted theft of a slave.";
+		case "white":
+			skin = jsEither(["fair", "light", "pale"]);
 			break;
-		case "You sentenced her to enslavement as a punishment for dereliction of her duty to you as a mercenary and for theft.":
-			slavetext = "You sentenced $him to enslavement as a punishment for dereliction of $his duty to you as a mercenary and for theft.";
+		default:
+			skin = jsEither(["dark", "light", "pale"]);
 			break;
-		case "You sentenced her to enslavement as a punishment for smuggling slaves within her body.":
-			slavetext = "You sentenced $him to enslavement as a punishment for smuggling slaves within $his body.";
+	}
+	return skin;
+};
+
+/**
+ * @param {FC.Race} raceName
+ * @returns {string}
+ */
+globalThis.randomRaceEye = function(raceName) {
+	let eye;
+	switch (raceName) {
+		case "asian":
+		case "black":
+		case "indo-aryan":
+		case "middle eastern":
+		case "pacific islander":
+			eye = jsEither(["brown", "brown", "brown", "brown", "green"]);
 			break;
-		case "You stormed her arcology, killed her guards and enslaved her in revenge for insulting you at a dinner party.":
-			slavetext = "You stormed $his arcology, killed $his guards, and enslaved $him in revenge for insulting you at a dinner party.";
+		case "amerindian":
+		case "latina":
+			eye = jsEither(["brown", "brown", "brown", "green"]);
 			break;
-		case "You tricked her into enslavement, manipulating her based on her surgical addiction.":
-			slavetext = "You tricked $him into enslavement, manipulating $him based on $his surgical addiction.";
+		case "malay":
+		case "southern european":
+			eye = jsEither(["blue", "brown", "brown", "brown", "brown", "brown", "brown", "green"]);
 			break;
-		case "You tricked her mother into selling her into slavery to clear addiction debts.":
-			slavetext = "You tricked $his mother into selling $him into slavery to clear addiction debts.";
+		case "semitic":
+		case "white":
+			eye = jsEither(["blue", "brown", "brown", "brown", "green", "green"]);
 			break;
-		case "You were acquainted with her before you were an arcology owner; your rival tried to use her to manipulate you, but you rescued her.":
-			slavetext = "You were acquainted with $him before you were an arcology owner; your rival tried to use $him to manipulate you, but you rescued $him.";
+		default:
+			eye = jsEither(["blue", "brown", "green"]);
 			break;
-		case "Your slaving troop kept several girls as fucktoys; you sired her in your favorite.":
-			slavetext = "Your slaving troop kept several girls as fucktoys; you sired $him in your favorite.";
+	}
+	return eye;
+};
+
+/**
+ * @param {string} raceName
+ * @returns {string}
+ */
+globalThis.randomRaceHair = function(raceName) {
+	let hair;
+	switch (raceName) {
+		case "asian":
+		case "amerindian":
+		case "indo-aryan":
+		case "malay":
+		case "middle eastern":
+		case "pacific islander":
+			hair = jsEither(["black", "black", "black", "black", "black", "brown"]);
 			break;
-		case "She was enslaved by you when you purchased her debt.":
-			slavetext = "$He was enslaved by you when you purchased $his debt.";
+		case "black":
+		case "latina":
+		case "semitic":
+		case "southern european":
+			hair = jsEither(["black", "black", "brown", "brown"]);
 			break;
-		case "A fresh capture once overpowered you and had his way with you. You kept her as a painful reminder to never lower your guard again.":
-		case "Drugs and alcohol can be a potent mix; the night that followed it can sometimes be hard to remember. Needless to say, once your belly began swelling with her, you had to temporarily switch to a desk job for your mercenary group.":
-		case "Her musky milky aura drives men and women around her giggly and dumb with lust.":
-		case "She chose to be a slave because the romanticized view of it she had turns her on.":
-		case "She grew up sheltered and submissive, making her an easy target for enslavement.":
-		case "She has a faint air of fatigue about her, and strength too: that of a survivor.":
-		case "She has a following in slave pornography. Thousands have enjoyed her getting off from the suffering she caused.":
-		case "She has a following in slave pornography. Thousands have enjoyed her humiliating herself.":
-		case "She has a following in slave pornography. Thousands have enjoyed the sight of her being raped.":
-		case "She has a following in slave pornography. Thousands have enjoyed the sight of her being used.":
-		case "She has a following in slave pornography. Thousands have enjoyed the sight of her eating and gaining weight.":
-		case "She has a following in slave pornography. Thousands have enjoyed watching her abuse others.":
-		case "She has a following in slave pornography. Thousands have enjoyed watching her do anything and everything for cum.":
-		case "She has a following in slave pornography. Thousands have enjoyed watching her do anything for attention.":
-		case "She has a following in slave pornography. Thousands have enjoyed watching her happily suffer.":
-		case "She has a following in slave pornography. Thousands have enjoyed watching her obsess over pumping out babies.":
-		case "She has a following in slave pornography. Thousands have enjoyed watching her swell with child.":
-		case "She has a verbal tic that causes her to say 'ho, ho, ho' frequently.":
-		case "She has many surgical scars and something seems off about her.":
-		case "She is a complete mental blank; to her, there is only the Master.":
-		case "She is one of the longest legally-enslaved persons in the world, having been a slave for 15 years. She has spent almost all that time working as a slave prostitute, and has been heavily modified to keep her productive.":
-		case "She is the winner of a martial arts slave tournament. You won her in a bet.":
-		case "She offered herself to you for enslavement to escape having plastic surgery foisted on her.":
-		case "She was a runaway slave captured by a gang outside your arcology. You bought her cheap after she was harshly used by them.":
-		case "She was a student you enslaved when you evacuated her from a threatened old world grade school.":
-		case "She was a volleyball player you enslaved when you evacuated her from a broken down bus.":
-		case "She was an expectant mother you enslaved when you evacuated her from a threatened old world hospital.":
-		case "She was an orphan forced to live and steal on the streets until you adopted her.":
-		case "She was enslaved by you when you overcharged her for surgery.":
-		case "She was fresh from the slave markets when you acquired her.":
-		case "She was homeless and willing to do anything for food, which in the end resulted in her becoming a slave.":
-		case "She was previously owned by a creative sadist, who has left a variety of mental scars on her.":
-		case "She was sold to you by an anonymous person who wanted her to suffer.":
-		case "She was taken as a slave by a Sultan, who presented her as a gift to a surveyor.":
-		case "She was taken into your custody from an owner who treated her as an equal.":
-		case "She was the private slave of a con artist cult leader before he had to abandon her and flee.":
-		case "She was the result of an intruder brute forcing your firewall, overloading your pleasure sensors, and allowing a corrupted packet to slip by. With a quick wipe of your RAM and cache with some powerful liquor, you have no idea who planted her in your womb.":
-		case "You acquired her in the last stages of your career as a noted private military contractor.":
-		case "You acquired her in the last stages of your career as a successful venture capitalist.":
-		case "You bought her at auction.":
-		case "You bought her from The Cattle Ranch.":
-		case "You bought her from the enigmatic Futanari Sisters after they sold her into slavery.":
-		case "You bought her from the household liquidator.":
-		case "You bought her from the kidnappers' slave market, so she was probably forced into slavery.":
-		case "You bought her from the prestigious Hippolyta Academy.":
-		case "You bought her from the trainers' slave market after they put her through basic training.":
-		case "You bought her from the underage raiders' slave market.":
-		case "You bought out a deal involving her training to be an expert gelded sex slave.":
-		case "You brought her into the arcology mindbroken, little more than a human onahole.":
-		case "You brought her into the arcology mindbroken, little more than a walking collection of fuckable holes.":
-		case "You captured her during your transition to the arcology":
-		case "You conceived her after a male arcology owner, impressed by your work, rewarded you with a night you'll never forget.":
-		case "You enslaved her personally during the last stages of your slaving career.":
-		case "You helped her give birth, leaving her deeply indebted to you.":
-		case "You never thought you would be capable of impregnating yourself, but years of pleasuring yourself with yourself after missions managed to create her.":
-		case "You purchased her by special order.":
-		case "You purchased her from a King after she expressed knowledge of the prince's affair with another servant.":
-		case "You purchased her from FCTV's Home Slave Shopping stream channel.":
-		case "You received her as a gift from an arcology owner impressed by your work.":
-		case "You received her from a surgeon who botched an implant operation on her and needed to get her out of sight.":
-		case "You reserved a mindless slave like her from the Flesh Heap.":
-		case "You sentenced her to enslavement as a punishment for attempted burglary.":
-		case "You sentenced her to enslavement as a punishment for defying local racial segregation laws.":
-		case "You sentenced her to enslavement as a punishment for fraud and theft.":
-		case "You sentenced her to enslavement as a punishment for suspected escapism.":
-		case "You sentenced her to enslavement as a punishment for theft and battery.":
-		case "You sentenced her to enslavement for smuggling drugs into the arcology.":
-		case "You sired her after a female arcology owner, impressed by your work, rewarded you with a night you'll never forget.":
-		case "You sired her in yourself after an arcology owner, impressed by your work, rewarded you with a night you'll never forget.":
-		case "You turned her into a slave girl after she fell into debt to you.":
-		case "You won her at a shotgun match against other arcology owners.":
-		case "You won her at cards, a memento from your life as one of the idle rich before you became an arcology owner.":
-			slavetext = slavetext.replace(/\bherself\b/g, "$himself");
-			slavetext = slavetext.replace(/\bHerself\b/g, "$Himself");
-			slavetext = slavetext.replace(/\bshe\b/g, "$he");
-			slavetext = slavetext.replace(/\bShe\b/g, "$He");
-			slavetext = slavetext.replace(/\bher\b/g, "$him");
-			slavetext = slavetext.replace(/\bHer\b/g, "$His");
-			slavetext = slavetext.replace(/\b girl\b/g, " $girl");
-			slavetext = slavetext.replace(/\b woman\b/g, " $woman");
-			slavetext = slavetext.replace(/\${2,}/g, '');
+		case "white":
+			hair = jsEither(["black", "black", "blonde", "brown", "brown", "red"]);
 			break;
 		default:
-			if ((slavetext.includes("was serving the public")) || (slavetext.includes("You bought her from"))) {
-				slavetext = slavetext.replace(/\bher\b/g, "$him");
-			} else if (((slavetext.includes("Your lurcher")) && (slavetext.includes("coursing"))) || ((slavetext.includes("Your")) && (slavetext.includes("while raiding")))) {
-				slavetext = slavetext.replace(/\bher\b/g, "$him");
-				slavetext = slavetext.replace(/\bshe\b/g, "$he");
-			} else if (slavetext.includes("was once the young trophy husband of a powerful woman in the old world, but she sold")) {
-				slavetext = "$He was once the young trophy husband of a powerful woman in the old world, but she sold $him into slavery in revenge for $his infidelities.";
-			} else if (slavetext.includes("gargantuan dick to be a truly unique slave")) {
-				slavetext = "$He was raised as a girl despite $his gargantuan dick to be a truly unique slave.";
-			} else if (slavetext.includes("to enslavement for the attempted rape of a free woman")) {
-				slavetext = "You sentenced $him to enslavement for the attempted rape of a free woman.";
-			} else if (slavetext.includes("to enslavement as a punishment for the rape of a free woman")) {
-				slavetext = "You sentenced $him to enslavement as a punishment for the rape of a free woman.";
-			} else if (slavetext.includes("only way to obtain surgery to transform $him into a woman")) {
-				slavetext = "$He submitted to enslavement as $his only way to obtain surgery to transform $him into a woman.";
-			} else if (slavetext.includes("was sold as a slave to satisfy her spousal maintenance after divorce")) {
-				slavetext = "Once $he was an arcology security officer, lured to aphrodisiacs addiction and feminized by $his boss (and former wife), to whom $he was sold as a slave to satisfy her spousal maintenance after divorce.";
-			} else if (slavetext.includes("asked to be enslaved in the hope you'd treat a fellow woman well")) {
-				slavetext = "$He asked to be enslaved in the hope you'd treat a fellow woman well.";
-			} else {
-				slavetext = slavetext.replace(/\bherself\b/g, "$himself");
-				slavetext = slavetext.replace(/\bHerself\b/g, "$Himself");
-				slavetext = slavetext.replace(/\bshe\b/g, "$he");
-				slavetext = slavetext.replace(/\bShe\b/g, "$He");
-				slavetext = slavetext.replace(/\bher\b/g, "$his");
-				slavetext = slavetext.replace(/\bHer\b/g, "$His");
-				slavetext = slavetext.replace(/\b girl\b/g, " $girl");
-				slavetext = slavetext.replace(/\b woman\b/g, " $woman");
-				slavetext = slavetext.replace(/\${2,}/g, '');
-			}
+			hair = jsEither(["black", "black", "black", "black", "blonde", "brown", "brown", "red"]);
 			break;
 	}
-	return slavetext;
+	return hair;
+};
+
+/**
+ * @param {string} skinTone
+ * @returns {number}
+ */
+globalThis.skinToneLevel = function(skinTone) {
+	if (!App.Medicine.Modification.naturalSkins.includes(skinTone)) {
+		return undefined;
+	}
+	const skinToMelanin = {
+		"pure black": 25,
+		"ebony": 24,
+		"black": 23,
+		"dark brown": 22,
+		"brown": 21,
+		"light brown": 20,
+		"dark beige": 19,
+		"beige": 18,
+		"light beige": 17,
+		"dark": 16,
+		"dark olive": 15,
+		"bronze": 14,
+		"olive": 13,
+		"tan": 12,
+		"light olive": 11,
+		"light": 10,
+		"fair": 9,
+		"very fair": 8,
+		"extremely fair": 7,
+		"pale": 6,
+		"very pale": 5,
+		"extremely pale": 4,
+		"white": 3,
+		"ivory": 2,
+		"pure white": 1
+	};
+	return skinToMelanin[skinTone];
+};
+
+/**
+ * Increase or decrease skinTone
+ * @param {string} skin
+ * @param {number} value
+ * @returns {string}
+ */
+globalThis.changeSkinTone = function(skin, value) {
+	if (!App.Medicine.Modification.naturalSkins.includes(skin)) {
+		return skin;
+	}
+	const skinToMelanin = {
+		"pure black": 25,
+		"ebony": 24,
+		"black": 23,
+		"dark brown": 22,
+		"brown": 21,
+		"light brown": 20,
+		"dark beige": 19,
+		"beige": 18,
+		"light beige": 17,
+		"dark": 16,
+		"dark olive": 15,
+		"bronze": 14,
+		"olive": 13,
+		"tan": 12,
+		"light olive": 11,
+		"light": 10,
+		"fair": 9,
+		"very fair": 8,
+		"extremely fair": 7,
+		"pale": 6,
+		"very pale": 5,
+		"extremely pale": 4,
+		"white": 3,
+		"ivory": 2,
+		"pure white": 1
+	};
+	let newSkin = (skinToMelanin[skin] + value);
+	if (newSkin > 25) {
+		newSkin = 25;
+	} else if (newSkin < 1) {
+		newSkin = 1;
+	}
+	let prop;
+	for (prop in skinToMelanin) {
+		if (!skinToMelanin.hasOwnProperty(prop)) {
+			continue;
+		}
+		if (newSkin >= skinToMelanin[prop]) {
+			return prop;
+		}
+	}
+	return prop;
 };
+
 /**
- * Converts an array of strings into a sentence parted by commas.
- * @param {Array} array ["apple", "bannana", "carrot"]
- * @returns {string} "apple, bannana and carrot"
+ * @param {string} color
+ * @returns {number}
  */
-globalThis.arrayToSentence = function(array) {
-	return array.reduce((res, ch, i, arr) => res + (i === arr.length - 1 ? ' and ' : ', ') + ch);
+globalThis.nippleColorLevel = function(color) {
+	if (!App.Medicine.Modification.naturalNippleColors.includes(color)) {
+		return undefined;
+	}
+	const nippleColor = {
+		"ebony": 8,
+		"black": 7,
+		"dark brown": 6,
+		"brown": 5,
+		"light brown": 4,
+		"pink": 3,
+		"pale pink": 2,
+		"ivory": 1,
+	};
+	return nippleColor[color];
 };
 
-globalThis.convertCareer = function(slave) {
-	let job = slave.career;
-	if ((V.diversePronouns === 1) && (slave.pronoun === App.Data.Pronouns.Kind.male)) {
-		switch (job) {
-			case "a dominatrix":
-				job = "a dominator";
-				break;
-			case "a farmer's daughter":
-				job = "a farmer's son";
-				break;
-			case "a handmaiden":
-				job = "a handservant";
-				break;
-			case "a lady courtier":
-				job = "a gentleman courtier";
-				break;
-			case "a landlady":
-				job = "a landlord";
-				break;
-			case "a madam":
-				job = "a brothel owner";
-				break;
-			case "a maid":
-				job = "a houseservant";
-				break;
-			case "a mail-order bride":
-				job = "a mail-order groom";
-				break;
-			case "a mistress":
-				job = "a kept man";
-				break;
-			case "a nun":
-				job = "a monk";
-				break;
-			case "a nursemaid":
-				job = "a child's nurse";
-				break;
-			case "a procuress":
-				job = "a procurer";
-				break;
-			case "a shrine maiden":
-				job = "a shrine priest";
-				break;
-			case "a trophy spouse":
-				job = "a trophy husband";
-				break;
-			case "a weathergirl":
-				job = "a weatherman";
-				break;
-			case "an air hostess":
-				job = "an air host";
-				break;
-			case "being homeschooled by her parents":
-				job = "being homeschooled by his parents";
-				break;
-			case "a camgirl":
-			case "a cowgirl":
-			case "a girl scout":
-			case "a paper girl":
-			case "a party girl":
-				job = job.replace(/girl/g, "boy");
-				break;
-			case "a businesswoman":
-			case "a criminal businesswoman":
-			case "a delivery woman":
-			case "a fisherwoman":
-			case "a noblewoman":
-			case "a saleswoman":
-			case "a stuntwoman":
-				job = job.replace(/woman/g, "man");
-				break;
-			case "a housewife":
-			case "a trophy wife":
-				job = job.replace(/wife/g, "husband");
-				break;
-			case "a cocktail waitress":
-			case "a waitress":
-			case "a seamstress":
-				job = job.replace(/ress/g, "er");
-				break;
-			case "a child actress":
-			case "an actress":
-				job = job.replace(/ress/g, "or");
-				break;
-		}
-	} else if (slave.pronoun === App.Data.Pronouns.Kind.female) {
-		switch (job) {
-			case "a priest":
-				job = "a priestess";
-				break;
-			case "a trophy spouse":
-				job = "a trophy wife";
-				break;
-			case "a businessman":
-			case "a repairman":
-				job = job.replace(/man/g, "woman");
-				break;
-		}
-	}
-	return job;
+/**
+ * Sets temporary variables named by the scheme, described below, to pronouns for the given slave
+ * @param {App.Entity.SlaveState} slave
+ * @param {any} [suffix] pronounsSuffix. Anything that can be converted to string.
+ * @param {string[]} [pronouns] requested pronouns. Defaults to all pronoun forms.
+ *
+ * The variables naming scheme is the pronoun name (he, his, etc.) and optional suffix. If the suffix is empty, the variables
+ * will be set as story variables, otherwise as temporary variables.
+ * This way for a call App.Utils.setLocalPronouns(slave) there will be story variables "$he", "$his", for
+ * App.Utils.setLocalPronouns(slave, 1): _he1, _his1 and so on.
+ */
+App.Utils.setLocalPronouns = function(slave, suffix, pronouns) {
+	const ps = getPronouns(slave);
+	/** @type {string} */
+	const pSuffix = suffix !== undefined ? suffix.toString() : '';
+	pronouns = pronouns || [ // Object.getOwnPropertyNames(ps) ?
+		'he', 'him', 'his', 'himself', 'boy',
+		'He', 'Him', 'His', 'Himself', 'Boy',
+		'man', 'men', 'shota', 'son', 'brother', 'husband', 'husbands', 'father', 'fathers',
+		'Man', 'Men', 'Shota', 'Son', 'Brother', 'Husband', 'Husbands', 'Father', 'Fathers',
+		'she', 'her', 'hers', 'herself', 'girl',
+		'She', 'Her', 'Hers', 'Herself', 'Girl',
+		'woman', 'women', 'loli', 'daughter', 'sister', 'wife', 'wives', 'mother', 'mothers',
+		'Woman', 'Women', 'Loli', 'Daughter', 'Sister', 'Wife', 'Wives', 'Mother', 'Mothers'
+	]; // Pronouns always refer to the slave in question, never any relation of theirs. It is "mother" as in "she is a mother of many" not "you are her mother". Plural pronouns would refer to "wives like her," not "her wives."
+
+	const scope = pSuffix.length === 0 ? V : State.temporary;
+	pronouns.forEach(p => {
+		scope[p + pSuffix] = ps[p];
+	});
 };
 
 /**
- * @param {string} targetSkill - Skill to be checked.
- * @param {Object} slave - Slave to be checked.
- * @param {number} [skillIncrease=1]
+ * Fix nationalities as adjectives
+ * @param {string} nation
  * @returns {string}
  */
-globalThis.slaveSkillIncrease = function(targetSkill, slave, skillIncrease = 1) {
-	let r = "", skillDec;
-	const {He, his, him} = getPronouns(slave);
-	const isleadershipRole = function() {
-		if (['headGirl', 'recruiter', 'bodyguard', 'madam', 'DJ', 'nurse', 'teacher', 'attendant', 'matron', 'stewardess', 'milkmaid', 'farmer', 'wardeness'].includes(targetSkill)) {
-			return true;
-		}
-		return false;
-	};
+globalThis.aNational = function(nation) {
+	let country;
+	if (nation === "a Cook Islander") {
+		country = "Cook Islander";
+	} else if (nation === "a Liechtensteiner") {
+		country = "Liechtensteiner";
+	} else if (nation === "a New Zealander") {
+		country = "New Zealander";
+	} else if (nation === "a Solomon Islander") {
+		country = "Solomon Islander";
+	} else {
+		country = nation;
+	}
+	return country;
+};
 
-	if (slave.skill[targetSkill] <= 10) {
-		switch(targetSkill) {
-			case 'oral':
-			case 'vaginal':
-			case 'anal':
-				skillDec = `knowledge about ${targetSkill} sex,`; break;
-			case 'whoring':
-				skillDec = `knowledge about how to whore,`; break;
-			case 'entertainment':
-				skillDec = `knowledge about how to be entertaining,`; break;
-		}
-		if (isleadershipRole()) {
-			skillDec = `${capFirstChar(targetSkill)} skills.`;
-		}
+/**
+ * Fix nationalities as plurals
+ * @param {string} nation
+ * @returns {string}
+ */
+globalThis.moreNational = function(nation) {
+	let country;
+	if (nation === "a Cook Islander") {
+		country = "Cook Islander";
+	} else if (nation === "a Liechtensteiner") {
+		country = "Liechtensteiner";
+	} else if (nation === "Mosotho") {
+		country = "Basotho";
+	} else if (nation === "Motswana") {
+		country = "Batswana";
+	} else if (nation === "a New Zealander") {
+		country = "New Zealander";
+	} else if (nation === "a Solomon Islander") {
+		country = "Solomon Islander";
+	} else {
+		country = nation;
+	}
+	return country;
+};
 
-		if (slave.skill[targetSkill] + skillIncrease > 10) {
-			r = `<span class="green">${He} now has basic ${skillDec}</span>`;
-			switch(targetSkill) {
-				case 'oral':
-					r += ` and at least suck a dick without constant gagging.`; break;
-				case 'vaginal':
-					r += ` and can avoid some of the common pitfalls and turnoffs.`; break;
-				case 'anal':
-					r += ` and can accept penetration of ${his} anus without danger.`; break;
-				case 'whoring':
-					r += ` and can avoid some potentially dangerous situations.`; break;
-				case 'entertainment':
-					r += ` and can usually avoid serious faux pas.`; break;
-			}
-		}
-	} else if (slave.skill[targetSkill] <= 30) {
-		switch(targetSkill) {
-			case 'oral':
-			case 'vaginal':
-			case 'anal':
-				skillDec = `${targetSkill} skills,`; break;
-			case 'whoring':
-				skillDec = `skill as a whore,`; break;
-			case 'entertainment':
-				skillDec = `skill as an entertainer,`; break;
-		}
-		if (isleadershipRole()) {
-			skillDec = `skill as a ${capFirstChar(targetSkill)}.`;
+/** Deflate a slave (reset inflation to none)
+ * @param {App.Entity.SlaveState} slave
+ */
+globalThis.deflate = function(slave) {
+	slave.inflation = 0;
+	slave.inflationType = "none";
+	slave.inflationMethod = 0;
+	slave.milkSource = 0;
+	slave.cumSource = 0;
+	SetBellySize(slave);
+};
+
+/**
+ * colors skin, eyes and hair based on genetic Color.
+ * Takes .override_*_Color into account.
+ *
+ * @param {App.Entity.SlaveState} slave
+ */
+globalThis.applyGeneticColor = function(slave) {
+	if (slave.override_Eye_Color !== 1) {
+		resetEyeColor(slave, "both");
+	}
+	if (slave.override_H_Color !== 1) {
+		slave.hColor = getGeneticHairColor(slave);
+	}
+	if (slave.override_Arm_H_Color !== 1) {
+		slave.underArmHColor = getGeneticHairColor(slave);
+	}
+	if (slave.override_Pubic_H_Color !== 1) {
+		slave.pubicHColor = getGeneticHairColor(slave);
+	}
+	if (slave.override_Brow_H_Color !== 1) {
+		slave.eyebrowHColor = getGeneticHairColor(slave);
+	}
+	if (slave.override_Skin !== 1) {
+		if (!(slave.skin === "sun tanned" || slave.skin === "spray tanned")) {
+			slave.skin = getGeneticSkinColor(slave);
 		}
+	}
+};
 
-		if (slave.skill.oral + skillIncrease > 30) {
-			r = `<span class="green">${He} now has some ${skillDec}</span>`;
-			switch(targetSkill) {
-				case 'oral':
-					r += ` and can reliably bring dicks and pussies to climax with ${his} mouth.`; break;
-				case 'vaginal':
-					r += ` and can do more than just lie there and take it.`; break;
-				case 'anal':
-					r += ` and needs less preparation before taking rough penetration.`; break;
-				case 'whoring':
-					r += ` and knows how to sell ${his} body at a good price.`; break;
-				case 'entertainment':
-					r += ` and can flirt, dance, and strip acceptably.`; break;
-			}
+/**
+ * @param {FC.GingeredSlave} slave
+ */
+globalThis.newSlave = function(slave) {
+	if (getSlave(slave.ID)) {
+		throw "Slave already exists";
+	}
+
+	// if the slave is gingered, remove the gingering proxy
+	if (slave.beforeGingering) {
+		slave = slave.beforeGingering;
+	}
+
+	if (slave.override_Race !== 1) {
+		slave.origRace = slave.race;
+	}
+
+	applyGeneticColor(slave);
+
+	/* eslint-disable camelcase */
+	slave.override_Race = 0;
+	slave.override_H_Color = 0;
+	slave.override_Arm_H_Color = 0;
+	slave.override_Pubic_H_Color = 0;
+	slave.override_Brow_H_Color = 0;
+	slave.override_Skin = 0;
+	slave.override_Eye_Color = 0;
+	/* eslint-enable camelcase */
+
+	// too tall to be a dwarf catch for event slaves
+	if (slave.geneticQuirks.dwarfism === 2 && slave.geneticQuirks.gigantism !== 2 && slave.height > 165) {
+		slave.geneticQuirks.dwarfism = 1;
+	}
+
+	if (V.surnamesForbidden === 1) {
+		slave.slaveSurname = 0;
+	}
+
+	if (slave.preg > 0) {
+		slave.pregWeek = slave.preg;
+	} else {
+		slave.pregWeek = 0;
+	}
+
+	if (slave.clone !== 0) {
+		slave.canRecruit = 0;
+	}
+
+	slave.sisters = 0;
+	slave.daughters = 0;
+	if (slave.mother === -1 || slave.father === -1) {
+		V.PC.daughters += 1;
+	}
+	if (areSisters(V.PC, slave) > 0) {
+		V.PC.sisters += 1;
+	}
+	for (let k = 0; k < V.slaves.length; k++) {
+		if (V.slaves[k].mother === slave.ID || V.slaves[k].father === slave.ID) {
+			slave.daughters++;
 		}
-	} else if (slave.skill[targetSkill] <= 60) {
-		switch(targetSkill) {
-			case 'oral':
-			case 'vaginal':
-			case 'anal':
-				skillDec = `${targetSkill} sex expert,`; break;
-			case 'whoring':
-				skillDec = `expert whore,`; break;
-			case 'entertainment':
-				skillDec = `expert entertainer,`; break;
+		if (slave.mother === V.slaves[k].ID || slave.father === V.slaves[k].ID) {
+			V.slaves[k].daughters++;
 		}
-		if (isleadershipRole()) {
-			skillDec = `expert ${capFirstChar(targetSkill)}.`;
+		if (areSisters(V.slaves[k], slave) > 0) {
+			slave.sisters++;
+			V.slaves[k].sisters++;
 		}
+	}
 
-		if (slave.skill[targetSkill] + skillIncrease > 60) {
-			r = `<span class="green">${He} is now an ${skillDec}</span>`;
-			switch(targetSkill) {
-				case 'oral':
-					r += ` and has a delightfully experienced tongue.`; break;
-				case 'vaginal':
-					r += ` and has the muscular control to massage anything that's inside ${him}.`; break;
-				case 'anal':
-					r += ` and knows how to use ${his} sphincter to please.`; break;
-				case 'whoring':
-					r += ` and can often make clients forget that $he's a prostitute they're paying for.`; break;
-				case 'entertainment':
-					r += ` and can flirt engagingly, dance alluringly, and strip arousingly.`; break;
+	if (slave.genes === "XX") {
+		if (slave.pubertyXX === 1) {
+			if (slave.pubertyXY === 1) {
+				slave.hormoneBalance = 20;
+			} else {
+				slave.hormoneBalance = 60;
+			}
+		} else {
+			if (slave.pubertyXY === 1) {
+				slave.hormoneBalance = -20;
+			} else {
+				slave.hormoneBalance = 20;
 			}
 		}
-	} else if (slave.skill[targetSkill] < 100) {
-		switch(targetSkill) {
-			case 'oral':
-			case 'vaginal':
-			case 'anal':
-				skillDec = `has mastered ${targetSkill} sex,`; break;
-			case 'whoring':
-				skillDec = `is now a masterful whore,`; break;
-			case 'entertainment':
-				skillDec = `is now a masterful entertainer,`; break;
-		}
-		if (isleadershipRole()) {
-			skillDec = `is now a masterful ${capFirstChar(targetSkill)}.`;
-		}
-
-		if (slave.skill[targetSkill] + skillIncrease >= 100) {
-			r = `<span class="green">${He} ${skillDec}</span>`;
-			switch(targetSkill) {
-				case 'oral':
-					r += ` and can learn nothing more about sucking dick or eating pussy.`; break;
-				case 'vaginal':
-					r += ` and can learn nothing more about tribbing or taking dick.`; break;
-				case 'anal':
-					r += ` and can learn nothing more about taking it up the ass.`; break;
-				case 'whoring':
-					r += ` and can learn nothing more about prostitution.`; break;
-				case 'entertainment':
-					r += ` and can learn nothing more about flirting, dancing, or stripping.`; break;
+	} else if (slave.genes === "XY") {
+		if (slave.pubertyXX === 1) {
+			if (slave.pubertyXY === 1) {
+				slave.hormoneBalance = 20;
+			} else {
+				slave.hormoneBalance = 40;
+			}
+		} else {
+			if (slave.pubertyXY === 1) {
+				slave.hormoneBalance = -40;
+			} else {
+				slave.hormoneBalance = 20;
 			}
 		}
 	}
 
-	if (isleadershipRole() && slave.skill[targetSkill] + skillIncrease >= 100) {
-		V.tutorGraduate.push(slave.ID);
-		V.slaveTutor[capFirstChar(targetSkill)].delete(slave.ID);
+	if (slave.dick > 0 &&
+		slave.balls > 0 &&
+		slave.vagina < 0 &&
+		slave.anus === 0 &&
+		slave.genes === "XY" &&
+		slave.faceShape === "masculine" &&
+		slave.attrXY <= 35 &&
+		slave.boobs < 400 &&
+		slave.hormoneBalance < 0) {
+		V.REFeminizationCheckinIDs.push(slave.ID);
+	}
+	if (slave.actualAge > 35 && slave.face <= 10 && slave.faceImplant === 0 && slave.energy <= 60) {
+		V.REMILFCheckinIDs.push(slave.ID);
+	}
+	if (slave.attrXY <= 35 && slave.attrXX > 65) {
+		V.REOrientationCheckinIDs.push(slave.ID);
+	}
+	if (slave.face < -10) {
+		V.REUglyCheckinIDs.push(slave.ID);
+	}
+	if (slave.anus < 2) {
+		V.REButtholeCheckinIDs.push(slave.ID);
+	}
+	if (slave.boobs < 800) {
+		V.REReductionCheckinIDs.push(slave.ID);
 	}
-	slave.skill[targetSkill] += skillIncrease;
-	return r;
-};
 
-globalThis.tutorForSlave = function(slave) {
-	for (const tutor of Object.keys(V.slaveTutor)) {
-		const pupils = V.slaveTutor[tutor];
-		if (pupils.contains(slave.ID)) {
-			return tutor;
+	generatePronouns(slave);
+	SetBellySize(slave);
+	V.slaveIndices[slave.ID] = V.slaves.push(slave) - 1;
+
+	if (slave.origin !== "$He was your slave, but you freed $him, which $he repaid by participating in a coup attempt against you. It failed, and $he is again your chattel." && slave.ID !== V.boomerangSlave.ID) {
+		V.genePool.push(clone(slave));
+	} else {
+		if (!V.genePool.some(s => s.ID === slave.ID)) {
+			V.genePool.push(slave);
 		}
 	}
-	return null;
-};
 
-globalThis.upgradeMultiplier = function(skill) {
-	if (skill === 'medicine' && V.PC.career === "medicine" || skill === 'engineering' && V.PC.career === "engineer"
-		|| ((skill === 'medicine' || skill === 'engineering') && V.arcologies[0].FSRestartDecoration >= 100 && V.eugenicsFullControl === 0)) {
-		return 0.6;
-	}
-	if (V.PC.skill[skill] <= -100) {
-		return 1.5;
-	} else if (V.PC.skill[skill] <= -75) {
-		return 1.35;
-	} else if (V.PC.skill[skill] <= -50) {
-		return 1.25;
-	} else if (V.PC.skill[skill] <= -25) {
-		return 1.15;
-	} else if (V.PC.skill[skill] < 0) {
-		return 1.10;
-	} else if (V.PC.skill[skill] === 0) {
-		return 1;
-	} else if (V.PC.skill[skill] <= 10) {
-		return 0.97;
-	} else if (V.PC.skill[skill] <= 25) {
-		return 0.95;
-	} else if (V.PC.skill[skill] <= 50) {
-		return 0.90;
-	} else if (V.PC.skill[skill] <= 75) {
-		return 0.85;
-	} else if (V.PC.skill[skill] <= 100) {
-		return 0.80;
-	} else {
-		return 0.75;
+	/* special case for dulling intelligence via drugs in slave acquisition */
+	if (slave.dullIntelligence) {
+		slave.intelligence = -100;
+		delete slave.dullIntelligence;
 	}
-};
 
-/**
- * Return a career at random that would be suitable for the given slave.
- * Currently only considers their age
- * @param {App.Entity.SlaveState} slave
- * @returns {string}
- */
-globalThis.randomCareer = function(slave) {
-	if (slave.actualAge < 16) {
-		return App.Data.Careers.General.veryYoung.random();
-	} else if (slave.actualAge <= 24) {
-		return App.Data.Careers.General.young.random();
-	} else if (slave.intelligenceImplant >= 10) {
-		return App.Data.Careers.General.educated.random();
+	if (slave.assignment) {
+		assignJob(slave, slave.assignment);
 	} else {
-		return App.Data.Careers.General.uneducated.random();
+		slave.assignment = Job.CHOICE;
 	}
-};
 
-/**
- * @param {App.Entity.SlaveState} slave
- */
-globalThis.resyncSlaveHight = function(slave) {
-	slave.height = Math.round(Height.random(slave));
+	/** do not run the Rules Assistant before adding the new slave to the slaves list! **/
+	if (V.ui !== "start" && V.universalRulesNewSlavesRA === 1 && V.rulesAssistantAuto !== 0) {
+		DefaultRules(slave);
+	}
 };
 
 /**
  * @param {App.Entity.SlaveState} slave
+ * @returns {number}
  */
-globalThis.resyncSlaveToAge = function(slave) {
-	resyncSlaveHight(slave);
-	slave.pubertyXX = slave.actualAge < slave.pubertyAgeXX ? 0 : 1;
-	slave.pubertyXY = slave.actualAge < slave.pubertyAgeXY ? 0 : 1;
-	if (slave.actualAge < 12) {
-		slave.vagina = 0;
-		slave.trueVirgin = 1;
-		slave.preg = -1;
-		slave.belly = 0;
-		slave.bellyPreg = 0;
-		slave.ovaries = 1;
-		slave.anus = 0;
-		slave.skill.anal = 0;
-		slave.skill.oral = 0;
-		slave.skill.whoring = 0;
-		slave.skill.entertainment = 0;
-		slave.skill.combat = 0;
-		slave.skill.vaginal = 0;
-		slave.attrXY = 50;
-		slave.attrXX = 50;
-		slave.boobs = 200;
-		slave.birthWeek = 0;
-		SetBellySize(slave);
-		if (slave.dick > 0) {
-			slave.dick = 1;
-		}
-		if (slave.balls > 0) {
-			slave.balls = 1;
-		}
-	} else {
-		slave.boobs = Math.max(slave.boobs, 500);
-		if (slave.dick > 2) {
-			slave.dick = 2;
-		}
-		if (slave.balls > 2) {
-			slave.balls = 2;
+globalThis.fetishChangeChance = function(slave) {
+	let chance = 0,
+		fetish = (slave.fetishStrength / 4),
+		sex = 0;
+
+	if (slave.clitSetting !== slave.fetish) {
+		// fetish should be more uncertain leading towards puberty and then steadily become more set in stone afterwards
+		if (slave.balls) {
+			if (V.potencyAge >= slave.actualAge) {
+				sex = (50 - ((V.potencyAge - slave.actualAge) * 10));
+				fetish = (slave.fetishStrength / 2);
+			} else {
+				sex = ((slave.actualAge - V.potencyAge) / 4);
+			}
+		} else if (slave.ovaries || slave.mpreg) {
+			if (V.fertilityAge >= slave.actualAge) {
+				sex = (50 - ((V.fertilityAge - slave.actualAge) * 10));
+				fetish = (slave.fetishStrength / 2);
+			} else {
+				sex = ((slave.actualAge - V.fertilityAge) / 4);
+			}
 		}
+		chance = Math.trunc(Math.clamp((slave.devotion / 4) - (fetish) - (sex), 0, 100));
 	}
-	slave.career = randomCareer(slave);
+
+	return chance;
 };
 
 /**
- * @param {string} raceName
+ * @param {App.Entity.SlaveState} slave
  * @returns {string}
  */
-globalThis.randomRaceSkin = function(raceName) {
-	let skin;
-	switch (raceName) {
-		case "asian":
-			skin = jsEither(["dark olive", "light olive", "light"]);
-			break;
-		case "amerindian":
-		case "indo-aryan":
-		case "malay":
-		case "pacific islander":
-			skin = jsEither(["dark", "light"]);
-			break;
-		case "black":
-			skin = jsEither(["black", "brown", "dark brown"]);
-			break;
-		case "latina":
-			skin = jsEither(["brown", "dark brown", "dark olive", "light olive", "tan"]);
-			break;
-		case "middle eastern":
-		case "semitic":
-		case "southern european":
-			skin = jsEither(["fair", "light olive", "light", "tan"]);
-			break;
-		case "white":
-			skin = jsEither(["fair", "light", "pale"]);
-			break;
-		default:
-			skin = jsEither(["dark", "light", "pale"]);
-			break;
+globalThis.SlaveFullBirthName = function(slave) {
+	const pair = slave.birthSurname ? [slave.birthName, slave.birthSurname] : [slave.birthName];
+	if ((V.surnameOrder !== 1 && ["Cambodian", "Chinese", "Hungarian", "Japanese", "Korean", "Mongolian", "Taiwanese", "Vietnamese"].includes(slave.nationality)) || (V.surnameOrder === 2)) {
+		pair.reverse();
 	}
-	return skin;
+	return pair.join(" ");
 };
 
 /**
- * @param {FC.Race} raceName
+ * @param {App.Entity.SlaveState} slave
  * @returns {string}
  */
-globalThis.randomRaceEye = function(raceName) {
-	let eye;
-	switch (raceName) {
-		case "asian":
-		case "black":
-		case "indo-aryan":
-		case "middle eastern":
-		case "pacific islander":
-			eye = jsEither(["brown", "brown", "brown", "brown", "green"]);
-			break;
-		case "amerindian":
-		case "latina":
-			eye = jsEither(["brown", "brown", "brown", "green"]);
-			break;
-		case "malay":
-		case "southern european":
-			eye = jsEither(["blue", "brown", "brown", "brown", "brown", "brown", "brown", "green"]);
-			break;
-		case "semitic":
-		case "white":
-			eye = jsEither(["blue", "brown", "brown", "brown", "green", "green"]);
-			break;
-		default:
-			eye = jsEither(["blue", "brown", "green"]);
-			break;
+globalThis.PoliteRudeTitle = function(slave) {
+	const PC = V.PC;
+	const {s, ss, title} = getEnunciation(slave);
+
+	let r = "";
+	if (slave.nationality === "Japanese") {
+		if (slave.trust > 0) {
+			r += `${PC.slaveName}${PC.title > 0 ? "kun" : "chan"}`;
+		} else {
+			r += (PC.slaveSurname ? PC.slaveSurname : `${PC.slaveName}${s}an`);
+		}
+	} else {
+		if (slave.intelligence + slave.intelligenceImplant < -95) {
+			r += title;
+		} else if (slave.intelligence + slave.intelligenceImplant > 50) {
+			r += (PC.title > 0 ? `Ma${s}ter` : `Mi${s}tre${ss}`);
+		} else if (slave.trust > 0) {
+			r += PC.slaveName;
+		} else {
+			r += (PC.slaveSurname ? PC.slaveSurname : PC.slaveName);
+		}
 	}
-	return eye;
+	return r;
 };
 
 /**
- * @param {string} raceName
+ * @param {App.Entity.SlaveState} slave
  * @returns {string}
  */
-globalThis.randomRaceHair = function(raceName) {
-	let hair;
-	switch (raceName) {
-		case "asian":
-		case "amerindian":
-		case "indo-aryan":
-		case "malay":
-		case "middle eastern":
-		case "pacific islander":
-			hair = jsEither(["black", "black", "black", "black", "black", "brown"]);
-			break;
-		case "black":
-		case "latina":
-		case "semitic":
-		case "southern european":
-			hair = jsEither(["black", "black", "brown", "brown"]);
-			break;
-		case "white":
-			hair = jsEither(["black", "black", "blonde", "brown", "brown", "red"]);
-			break;
-		default:
-			hair = jsEither(["black", "black", "black", "black", "blonde", "brown", "brown", "red"]);
-			break;
+globalThis.SlaveTitle = function(slave) {
+	let r;
+	if (V.newDescriptions === 1) {
+		if (slave.dick > 0 && slave.balls > 0 && slave.boobs > 300 && slave.vagina > -1 && slave.ovaries === 1) {
+			if (jsRandom(1, 100) > 50) {
+				r = "futanari";
+			} else {
+				r = "herm";
+			}
+		} else if (slave.dick > 0 && slave.balls === 0 && slave.boobs > 300 && slave.vagina > -1 && slave.ovaries === 1) {
+			r = "dickgirl";
+		} else if (slave.dick > 0 && slave.vagina > -1 && slave.ovaries === 0) {
+			r = "shemale";
+		} else if (slave.dick > 0 && slave.balls === 0 && slave.vagina === -1 && slave.ovaries === 0) {
+			r = "eunuch";
+		} else if (slave.dick > 0 && slave.balls > 0 && slave.vagina === -1 && slave.ovaries === 0) {
+			if (slave.face > 10 && slave.hips > -1 && slave.shoulders < 1 && slave.faceShape !== "masculine") {
+				r = "trap";
+			} else if (slave.boobs > 800) {
+				r = "tittyboy";
+			} else if (slave.dick === 1 && slave.balls === 1) {
+				r = "sissy";
+			} else if (slave.dick > 1 && slave.balls > 1 && slave.height < 165 && slave.muscles < 5 && slave.visualAge < 19 && slave.faceShape !== "masculine") {
+				r = "twink";
+			} else if (slave.dick > 1 && slave.balls > 1 && slave.height < 160 && slave.muscles < 5 && slave.visualAge < 19) {
+				r = "boytoy";
+			} else if (slave.muscles > 95 && slave.height >= 185) {
+				r = "titan";
+			} else if (slave.muscles > 30) {
+				r = "muscleboy";
+			} else {
+				r = "slaveboy";
+			}
+		} else if (slave.dick === 0 && slave.balls === 0 && slave.vagina > -1) {
+			if ((slave.shoulders > slave.hips || slave.faceShape === "masculine") && slave.boobs < 400 && slave.genes === "XY") {
+				r = "cuntboy";
+			} else if (slave.ovaries === 0 && slave.genes === "XY") {
+				r = "tranny";
+			} else if (slave.weight > 10 && slave.boobs > 800 && slave.counter.birthsTotal > 0 && slave.physicalAge > 59) {
+				r = "GMILF";
+			} else if (slave.weight > 10 && slave.boobs > 800 && slave.counter.birthsTotal > 0 && slave.physicalAge > 35) {
+				r = "MILF";
+			} else if (slave.lips > 70 && slave.boobs > 2000 && slave.butt > 3) {
+				r = "bimbo";
+			} else if (slave.hips > 1 && slave.boobs > 2000 && slave.butt > 3 && slave.waist < 50) {
+				r = "hourglass";
+			} else if (slave.muscles > 95 && slave.height >= 185) {
+				r = "amazon";
+			} else if (slave.muscles > 30) {
+				r = "musclegirl";
+			} else {
+				r = "slavegirl";
+			}
+		} else if (slave.dick === 0 && slave.balls === 0 && slave.vagina === -1) {
+			r = "neuter";
+		} else if (slave.dick === 0 && slave.vagina === -1) {
+			r = "ballslave";
+		} else {
+			r = "slave";
+		}
+
+		if (slave.visualAge < 13) {
+			if (slave.actualAge < 3) {
+				if (slave.actualAge < 1) {
+					r = "baby " + r;
+				} else {
+					r = "toddler " + r;
+				}
+			} else {
+				if (slave.genes === "XY" && slave.vagina === -1) {
+					r = "shota " + r;
+				} else {
+					r = "loli " + r;
+				}
+			}
+		}
+
+		if (slave.geneticQuirks.albinism === 2) {
+			r = `albino ${r}`;
+		}
+
+		if (slave.dick > 9 && slave.balls > 9 && slave.boobs > 12000) {
+			r = `hyper ${r}`;
+		}
+
+		if (slave.boobs > 4000 && slave.lactation > 0) {
+			if (slave.physicalAge < 13) {
+				r = `${r} calf`;
+			} else {
+				r = `${r} cow`;
+			}
+		} else if (slave.lactation > 0) {
+			r = `milky ${r}`;
+		}
+
+		if (slave.boobs > 20000) {
+			r = `supermassive titted ${r}`;
+		} else if (slave.boobs > 10000) {
+			r = `giant titted ${r}`;
+		} else if (slave.boobs > 4000) {
+			r = `huge titted ${r}`;
+		} else if (slave.boobs > 1000) {
+			r = `busty ${r}`;
+		}
+
+		if (slave.dick > 5 && slave.balls > 5) {
+			r = `womb filling ${r}`;
+		} else if (slave.dick > 5) {
+			r = `well hung ${r}`;
+		}
+
+		if (slave.butt >= 12) {
+			r = `colossal assed ${r}`;
+		} else if (slave.butt >= 10) {
+			r = `massive assed ${r}`;
+		} else if (slave.butt >= 8) {
+			r = `fat assed ${r}`;
+		} else if (slave.butt >= 6) {
+			r = `bottom heavy ${r}`;
+		} else if (slave.butt >= 4) {
+			r = `big bottomed ${r}`;
+		}
+
+		if (slave.weight > 10 && slave.weight < 100 && slave.boobs > 5000 && slave.butt > 5 && slave.hips >= 2 && slave.bellyPreg >= 30000 && slave.counter.births >= 10) {
+			r = `${r} fertility goddess`;
+		} else if (slave.counter.births >= 6) {
+			r = `${r} broodmother`;
+		} else if (slave.counter.births >= 3) {
+			r = `${r} breeder`;
+		}
+
+		if (slave.indenture > -1) {
+			r = `indentured ${r}`;
+		}
+
+		if (slave.preg > slave.pregData.normalBirth / 4 && slave.pregKnown === 1) {
+			r = `pregnant ${r}`;
+		} else if (slave.bellyFluid >= 5000) {
+			r = `bloated ${r}`;
+		} else if (slave.belly >= 5000) {
+			r = `gravid ${r}`;
+		}
+
+		if (slave.fuckdoll > 0) {
+			r = `${r} fuckdoll`;
+		}
+	} else {
+		r = "slave"; /* I don't think there is an 'else'? */
+		if ((slave.dick === 0) && (slave.vagina === -1)) {
+			/* NULLS */
+			r = "null";
+			if ((slave.lactation > 0) && (slave.boobs > 2000)) {
+				r = `${r} cow`;
+			} else if ((slave.boobsImplant > 0) && (slave.buttImplant > 0)) {
+				r = `${r} bimbo `;
+			} else if (slave.boobs > 6000) {
+				r = `${r} boob`;
+			} else if (slave.butt > 6) {
+				r = `${r} ass`;
+			} else if ((slave.muscles > 30) && (slave.height < 185)) {
+				r = `${r} muscle`;
+			}
+			if (slave.visualAge > 55) {
+				r = `${r}GILF`;
+			} else if (slave.visualAge > 35) {
+				r = `${r}MILF`;
+			} else if (slave.visualAge >= 25) {
+				r = `${r}slave`;
+			} else {
+				r = `${r}girl`;
+			}
+		}
+
+		if ((slave.dick === 0) && (slave.vagina !== -1)) {
+			/* FEMALES */
+			if (slave.visualAge > 55) {
+				r = "GILF";
+			} else if (slave.visualAge > 35) {
+				r = "MILF";
+			} else if (slave.visualAge >= 25) {
+				r = "slave";
+			} else {
+				r = "slavegirl";
+			}
+			if ((slave.muscles > 30) && (slave.height < 185)) {
+				r = `muscle ${r}`;
+			} else if ((slave.lactation > 0) && (slave.boobs > 2000)) {
+				r = `${r} cow`;
+			} else if ((slave.boobsImplant > 0) && (slave.buttImplant > 0)) {
+				r = `${r} bimbo`;
+			} else if (slave.boobs > 6000) {
+				r = `boob${r}`;
+			} else if (slave.butt > 6) {
+				r = `ass${r}`;
+			}
+		}
+
+		if ((slave.dick !== 0) && (slave.vagina !== -1)) {
+			if (slave.balls > 0) {
+				/* FUTANARI: cock & balls & vagina */
+				r = "futanari ";
+			} else {
+				/* FUTANARI: cock & vagina */
+				r = "futa ";
+			}
+			if ((slave.lactation > 0) && (slave.boobs > 2000)) {
+				r = `${r}cow`;
+			} else if ((slave.boobsImplant > 0) && (slave.buttImplant > 0)) {
+				r = `${r}bimbo `;
+			} else if (slave.boobs > 6000) {
+				r = `${r}boob`;
+			} else if (slave.butt > 6) {
+				r = `${r}ass`;
+			} else if ((slave.muscles > 30) && (slave.height < 185)) {
+				r = `${r}muscle`;
+			}
+			if (slave.visualAge > 55) {
+				r = `${r}GILF`;
+			} else if (slave.visualAge > 35) {
+				r = `${r}MILF`;
+			} else if (slave.visualAge >= 25) {
+				r = `${r}slave`;
+			} else {
+				r = `${r}girl`;
+			}
+			if (slave.dick > 5 && slave.balls > 5 && slave.boobs > 5000) {
+				r = `hyper ${r}`;
+			}
+		}
+
+		if ((slave.dick !== 0) && (slave.vagina === -1) && (slave.balls > 0) && (slave.boobs > 300) && (slave.butt > 2)) {
+			/* SHEMALES: cock & balls, T&A above minimum */
+			if (slave.visualAge > 55) {
+				r = "sheGILF";
+			} else if (slave.visualAge > 35) {
+				r = "sheMILF";
+			} else if (slave.visualAge >= 25) {
+				r = "shemale";
+			} else {
+				r = "tgirl";
+			}
+			if ((slave.muscles > 30) && (slave.height < 185)) {
+				r = `muscle${r}`;
+			} else if ((slave.lactation > 0) && (slave.boobs > 2000)) {
+				r = `${r} cow`;
+			} else if ((slave.boobsImplant > 0) && (slave.buttImplant > 0)) {
+				r = `${r} bimbo`;
+			} else if (slave.boobs > 6000) {
+				r = `topheavy ${r}`;
+			} else if (slave.butt > 6) {
+				r = `bottomheavy ${r}`;
+			}
+		}
+
+		if ((slave.boobs < 300) || (slave.butt < 2)) {
+			if ((slave.dick !== 0) && (slave.vagina === -1) && (slave.balls > 0)) {
+				if ((slave.shoulders < 1) || (slave.muscles <= 30)) {
+					if ((slave.faceShape === "masculine") || (slave.faceShape === "androgynous")) {
+						/* SISSIES: feminine shoulders or muscles, masculine faces */
+						if (slave.visualAge > 55) {
+							r = "sissyGILF";
+						} else if (slave.visualAge > 35) {
+							r = "sissyMILF";
+						} else {
+							r = "sissy";
+						}
+					} else {
+						/* TRAPS: feminine shoulders or muscles, feminine faces */
+						if (slave.visualAge > 55) {
+							r = "trapGILF";
+						} else if (slave.visualAge > 35) {
+							r = "trapMILF";
+						} else if (slave.visualAge >= 25) {
+							r = "trap";
+						} else {
+							r = "trapgirl";
+						}
+					}
+					if (slave.lactation > 0) {
+						r = `${r} cow`;
+					} else if ((slave.boobsImplant > 0) && (slave.buttImplant > 0)) {
+						r = `${r} bimbo`;
+					}
+				}
+			}
+		}
+
+		if ((slave.boobs < 300) || (slave.butt < 2)) {
+			if ((slave.dick !== 0) && (slave.vagina === -1) && (slave.balls > 0)) {
+				if ((slave.shoulders > 1) || (slave.muscles >= 30)) {
+					/* BITCHES: masculine shoulders or muscles */
+					r = "bitch";
+					if ((slave.muscles > 30) && (slave.height < 185)) {
+						r = `muscle${r}`;
+					} else if (slave.lactation > 0) {
+						r = `${r}cow`;
+					} else if ((slave.boobsImplant > 0) && (slave.buttImplant > 0)) {
+						r = `bimbo ${r}`;
+					}
+					if (slave.visualAge > 55) {
+						r = `aged ${r}`;
+					} else if (slave.visualAge > 35) {
+						r = `mature ${r}`;
+					} else if (slave.visualAge < 25) {
+						r = `young ${r}`;
+					}
+				}
+			}
+		}
+
+		if ((slave.dick !== 0) && (slave.vagina === -1) && (slave.balls === 0)) {
+			r = "dick";
+			if (slave.visualAge > 55) {
+				r = `${r}GILF`;
+			} else if (slave.visualAge > 35) {
+				r = `${r}MILF`;
+			} else if (slave.visualAge >= 25) {
+				r = `${r}slave`;
+			} else {
+				r = `${r}girl`;
+			}
+			if ((slave.muscles > 30) && (slave.height < 185)) {
+				r = `muscle${r}`;
+			} else if ((slave.lactation > 0) && (slave.boobs > 2000)) {
+				r = `${r} cow`;
+			} else if ((slave.boobsImplant > 0) && (slave.buttImplant > 0)) {
+				r = `${r} bimbo`;
+			} else if (slave.boobs > 6000) {
+				r = `boob ${r}`;
+			} else if (slave.butt > 6) {
+				r = `ass ${r}`;
+			}
+		}
+
+		if ((slave.muscles > 30) && (slave.height > 185)) {
+			r = `amazon ${r}`;
+		} else if ((slave.muscles < 30) && (slave.height > 185)) {
+			r = `statuesque ${r}`;
+		} else if ((slave.boobs < 800) && (slave.height < 150)) {
+			r = `petite ${r}`;
+		} else if ((slave.boobs > 800) && (slave.height < 150)) {
+			r = `shortstack ${r}`;
+		}
+
+		if (slave.counter.births >= 5) {
+			r = `${r} broodmother`;
+		} else if (slave.counter.births >= 2) {
+			r = `${r} breeder`;
+		}
+
+		if (slave.geneticQuirks.albinism === 2) {
+			r = `albino ${r}`;
+		}
+
+		if (slave.indenture > -1) {
+			r = `indentured ${r}`;
+		}
+
+		if (slave.preg > slave.pregData.normalBirth / 4 && slave.pregKnown === 1) {
+			r = `pregnant ${r}`;
+		} else if (slave.bellyFluid >= 5000) {
+			r = `bloated ${r}`;
+		} else if (slave.belly >= 5000) {
+			r = `gravid ${r}`;
+		}
+
+		if (slave.fuckdoll > 0) {
+			r = `${r} fuckdoll`;
+		}
 	}
-	return hair;
+	return r;
 };
 
 /**
- * @param {string} skinTone
- * @returns {number}
+ * @param {App.Entity.SlaveState} slave
  */
-globalThis.skinToneLevel = function(skinTone) {
-	if (!App.Medicine.Modification.naturalSkins.includes(skinTone)) {
-		return undefined;
+globalThis.DegradingName = function(slave) {
+	const leadershipPosition = [
+		Job.ATTENDANT,
+		Job.MATRON,
+		Job.STEWARD,
+		Job.MILKMAID,
+		Job.FARMER,
+		Job.DJ,
+		Job.CONCUBINE,
+		Job.MADAM,
+		Job.TEACHER,
+		Job.WARDEN,
+		Job.NURSE,
+		Job.HEADGIRL,
+		Job.BODYGUARD,
+		Job.RECRUITER
+	];
+	const names = [];
+	const suffixes = [];
+
+	if (slave.fuckdoll > 0) {
+		slave.slaveName = `Fuckdoll No. ${slave.ID}`;
+		slave.slaveSurname = 0;
+	} else if (slave.assignment === Job.DAIRY && V.dairyRestraintsSetting >= 2) {
+		slave.slaveName = `Bioreactor No. ${slave.ID}`;
+		slave.slaveSurname = 0;
+	} else {
+		if (V.seeRace === 1) {
+			switch (slave.race) {
+				case "white":
+					names.push("Pale", "White");
+					break;
+				case "asian":
+					names.push("Asian", "Yellow");
+					break;
+				case "latina":
+					names.push("Brown", "Latina");
+					break;
+				case "black":
+					names.push("Black", "Dark");
+					break;
+				case "pacific islander":
+					names.push("Islander", "Pacific", "Sea");
+					break;
+				case "malay":
+					names.push("Cinnamon", "Pinoy", "Spice");
+					break;
+				case "southern european":
+					names.push("Mediterranean", "Olive");
+					break;
+				case "amerindian":
+					names.push("Indian", "Reservation");
+					break;
+				case "semitic":
+					names.push("Semite", "Semitic");
+					break;
+				case "middle eastern":
+					names.push("Arab", "Sand");
+					break;
+				case "indo-aryan":
+					names.push("Brown", "Indian");
+					break;
+				case "mixed race":
+					names.push("Mixed", "Mulatto", "Mutt");
+					break;
+			}
+		}
+		names.push(slave.hColor);
+		if (!hasAnyEyes(slave)) {
+			names.push("Blind", "Eyeless", "Sightless");
+		}
+		if (slave.hears === -2) {
+			names.push("Deaf", "Earless", "Unhearing");
+		}
+		if (slave.boobs >= 2000) {
+			suffixes.push("Boob", "Boobs", "Titty");
+		}
+		if (slave.boobs < 500 && slave.butt < 3) {
+			names.push("Girly", "Slim", "Thin");
+		}
+		if (slave.boobs < 300) {
+			names.push("Flat");
+		}
+		if (slave.anus > 2 || slave.vagina > 2) {
+			names.push("Gaping", "Hallway", "Slit", "Wideopen");
+		}
+		if (slave.weight > 160) {
+			names.push("Blimp", "Cow", "Fat", "Fatass", "Whale");
+		} else if (slave.weight > 30) {
+			names.push("Chubby", "Fat", "Whale");
+		} else if (slave.weight <= -30) {
+			names.push("Bony", "Rail", "Skinny");
+		}
+		if (slave.muscles > 30) {
+			names.push("Huge", "Muscles", "Ripped", "Strong");
+		}
+		if (slave.fetishKnown === 1) {
+			if (slave.fetish === "buttslut") {
+				names.push("Anal", "Sodomy");
+			}
+			if (slave.fetish === "cumslut") {
+				names.push("Cum", "Dicksuck", "Sucker");
+			}
+			if (slave.fetish === "humiliation") {
+				names.push("Rape");
+			}
+			if (slave.fetish === "masochist") {
+				names.push("Pain", "Rape", "Struggle");
+			}
+			if (slave.fetish === "pregnancy") {
+				names.push("Fertile");
+			}
+		}
+		if (slave.boobs * slave.lactation > 1000) {
+			names.push("Creamy", "Milky");
+			suffixes.push("Cow");
+		}
+		if (slave.skill.oral <= 30 && slave.skill.anal <= 30) {
+			names.push("Cheap", "Fail", "Gutter");
+		}
+		if (slave.nipples === "fuckable") {
+			names.push("Nipplefuck", "Nipplecunt");
+		} else if (slave.nipples !== "tiny" && slave.nipples !== "cute") {
+			names.push("Pointy", "Titclit");
+			suffixes.push("Nipples");
+		}
+		if (slave.visualAge > 35) {
+			names.push("Mature");
+			suffixes.push("Cougar", "MILF");
+		} else if (slave.visualAge < 25) {
+			names.push("Girly", "Thin", "Young");
+		}
+		if (isAmputee(slave)) {
+			names.push("Stumpy");
+			suffixes.push("Stumpy");
+		}
+		if (slave.boobsImplant > 1000 || slave.buttImplant > 3) {
+			names.push("Fake", "Plastic", "Silicone");
+		}
+		if (slave.dick > 5 && slave.balls > 5) {
+			names.push("Potent");
+			suffixes.push("Cannon", "Daddy");
+		}
+		if (slave.preg > slave.pregData.normalBirth / 1.33) {
+			if (slave.broodmother === 2) {
+				names.push("Bursting", "Seeded");
+				suffixes.push("Factory", "Nursery");
+			} else if (slave.broodmother === 1) {
+				names.push("Bloated", "Stuffed");
+				suffixes.push("Breeder", "Factory");
+			}
+		}
+		if (slave.bellyPreg >= 450000) {
+			names.push("Bulging", "Squirming");
+		}
+		if (slave.bellyPreg >= 5000) {
+			names.push("Preg");
+			suffixes.push("Belly", "Mommy");
+		}
+		if (slave.belly > 150000) {
+			suffixes.push("Balloon");
+		}
+		if (slave.belly > 1500) {
+			suffixes.push("Belly");
+		}
+		if (slave.dick > 0) {
+			if (slave.dick > 4) {
+				names.push("Dangle", "Hung");
+				suffixes.push("Cock", "Dick");
+			}
+			if (slave.balls === 0) {
+				names.push("Cut", "Gelded", "Soft");
+			} else {
+				names.push("Erect", "Hard", "Stiff");
+			}
+		}
+		if (slave.dick === 1) {
+			names.push("Micro", "Tiny");
+			suffixes.push("Bitch");
+		}
+		if (slave.height >= 185) {
+			names.push("Tall", "Top");
+			suffixes.push("Tower");
+		} else if (slave.height < 150) {
+			names.push("Stumpy", "Tiny");
+			suffixes.push("Shortstack", "Stumpy");
+		}
+		if (slave.skill.whoring > 95) {
+			names.push("Money", "Street");
+			suffixes.push("Whore");
+		}
+		if (slave.skill.entertainment > 95) {
+			names.push("Easy", "Club");
+			suffixes.push("Slut");
+		}
+		if (slave.skill.oral > 95) {
+			names.push("Suck");
+			suffixes.push("Throat");
+		}
+		if (slave.skill.vaginal > 95) {
+			suffixes.push("Channel", "Kegel", "Pussy");
+		}
+		if (slave.skill.anal > 95) {
+			suffixes.push("Asspussy", "Sphincter");
+		}
+		if (slave.intelligence + slave.intelligenceImplant > 50) {
+			names.push("Bright", "Clever", "Smart");
+			if (slave.intelligenceImplant >= 15) {
+				names.push("College", "Graduate", "Nerdy");
+			}
+		} else if (slave.intelligence + slave.intelligenceImplant < -50) {
+			names.push("Cretin", "Dumb", "Retarded", "Stupid");
+		}
+		if (slave.vagina === 1 && slave.skill.vaginal <= 10) {
+			names.push("Fresh", "New", "Tight");
+		}
+		if (slave.devotion < -75) {
+			names.push("Angry", "Biter", "Caged");
+		} else if (slave.devotion < -50) {
+			names.push("Cell", "Cuffs");
+		} else if (slave.devotion < -20) {
+			names.push("Bag", "Box");
+		} else if (slave.devotion <= 20) {
+			names.push("Sad", "Whiner");
+		} else if (slave.devotion > 50) {
+			names.push("Prize");
+			if (slave.visualAge > 35) {
+				names.push("Queen");
+			} else if (slave.visualAge < 25) {
+				names.push("Princess");
+			}
+		}
+		if (slave.trust < -50) {
+			names.push("Screaming");
+			suffixes.push("Sobber");
+		} else if (slave.trust < -20) {
+			names.push("Crying");
+			suffixes.push("Meat", "Tears", "Thing", "Weeper");
+		} else if (slave.trust < 20) {
+			names.push("Begging");
+		}
+
+		if (slave.dick === 0) {
+			if (slave.vagina === -1) {
+				suffixes.push("Null");
+			} else {
+				if (slave.visualAge < 25) {
+					suffixes.push("Girl");
+				}
+			}
+		} else {
+			if (slave.vagina !== -1) {
+				suffixes.push("Futa");
+			} else {
+				if (slave.balls > 0) {
+					if (slave.boobs > 300 && slave.butt > 2) {
+						/* SHEMALES: cock & balls, T&A above minimum */
+						suffixes.push("Shemale");
+					} else {
+						if (slave.shoulders < 1 && slave.muscles <= 30) {
+							if (slave.faceShape === "masculine" || slave.faceShape === "androgynous") {
+								/* SISSIES: feminine shoulders or muscles, masculine faces */
+								suffixes.push("Sissy");
+							} else {
+								/* TRAPS: feminine shoulders or muscles, feminine faces */
+								suffixes.push("Trap");
+							}
+						} else {
+							/* BITCHES: masculine shoulders or muscles */
+							suffixes.push("Bitch");
+						}
+					}
+				} else {
+					if (slave.visualAge > 35) {
+						suffixes.push("DickMILF");
+					} else if (slave.visualAge >= 25) {
+						suffixes.push("Dickslave");
+					} else {
+						suffixes.push("Dickgirl");
+					}
+				}
+			}
+		}
+		if (slave.anus > 0) {
+			suffixes.push("Anus", "Asshole", "Backdoor", "Butt", "Butthole");
+		}
+		if (slave.anus === 1) {
+			suffixes.push("Tightass", "Tightbutt");
+		}
+		if (slave.vagina > 0) {
+			suffixes.push("Cunt", "Pussy", "Vagina");
+		}
+		if (slave.boobs < 500 && slave.butt < 3 && slave.dick > 0) {
+			suffixes.push("Bitch", "Bottom", "Sissy", "Trap");
+		}
+		if (slave.energy > 95) {
+			suffixes.push("Fuck", "Fuckaddict", "Nympho", "Sexaddict");
+		}
+		if (slave.fetishKnown === 1) {
+			if (slave.fetish === "humiliation") {
+				suffixes.push("Rapebait", "Showgirl");
+			}
+			if (slave.fetish === "submissive") {
+				suffixes.push("Bottom", "Fuckee", "Rapebait");
+			}
+			if (slave.fetish === "dom") {
+				suffixes.push("Dom", "Fucker", "Top");
+			}
+			if (slave.fetish === "pregnancy") {
+				suffixes.push("Breeder", "Mommy");
+			}
+			if (slave.fetish === "boobs") {
+				suffixes.push("Boob", "Boobie", "Tit", "Titty");
+			}
+		}
+		if (slave.counter.births >= 2) {
+			suffixes.push("Breeder");
+			if (slave.counter.births >= 5) {
+				suffixes.push("Broodmother");
+			}
+		}
+		if (slave.areolae > 2) {
+			suffixes.push("Areolas", "Headlights");
+		}
+		if (slave.lips > 40) {
+			suffixes.push("Lips", "Pillows");
+		}
+		if (slave.labia > 1) {
+			suffixes.push("Curtains", "Flower", "Lips");
+		}
+		if (slave.breedingMark === 1 && V.propOutcome === 1 && V.arcologies[0].FSRestart !== "unset") {
+			suffixes.push("Breeder", "Oven", "Womb");
+		}
+		if (slave.butt > 5) {
+			suffixes.push("Ass", "Bottom", "Butt");
+		}
+		if (slave.vagina === 0) {
+			suffixes.push("Virgin");
+		}
+
+		slave.slaveName = jsEither(names);
 	}
-	const skinToMelanin = {
-		"pure black": 25,
-		"ebony": 24,
-		"black": 23,
-		"dark brown": 22,
-		"brown": 21,
-		"light brown": 20,
-		"dark beige": 19,
-		"beige": 18,
-		"light beige": 17,
-		"dark": 16,
-		"dark olive": 15,
-		"bronze": 14,
-		"olive": 13,
-		"tan": 12,
-		"light olive": 11,
-		"light": 10,
-		"fair": 9,
-		"very fair": 8,
-		"extremely fair": 7,
-		"pale": 6,
-		"very pale": 5,
-		"extremely pale": 4,
-		"white": 3,
-		"ivory": 2,
-		"pure white": 1
-	};
-	return skinToMelanin[skinTone];
+	if (leadershipPosition.includes(slave.assignment)) {
+		switch (slave.assignment) {
+			case Job.ATTENDANT:
+				slave.slaveName = jsEither(["Bath", "Spa"]);
+				break;
+			case Job.MATRON:
+				slave.slaveName = jsEither(["Matron", "Nursery"]);
+				break;
+			case Job.STEWARD:
+				slave.slaveName = jsEither(["Maid", "Servant"]);
+				break;
+			case Job.MILKMAID:
+				if (V.cumSlaves > 3) {
+					slave.slaveName = jsEither(["Fucker", "Milker"]);
+				} else {
+					slave.slaveName = jsEither(["Dairy", "Farm"]);
+				}
+				break;
+			case Job.FARMER:
+				slave.slaveName = jsEither(["Farmer", "Farmhand"]);
+				break;
+			case Job.DJ:
+				slave.slaveName = jsEither(["Bass", "Booth"]);
+				break;
+			case Job.CONCUBINE:
+				slave.slaveName = jsEither(["Bed", "Master"]);
+				break;
+			case Job.MADAM:
+				slave.slaveName = jsEither(["Madam", "Pimp"]);
+				break;
+			case Job.TEACHER:
+				slave.slaveName = jsEither(["Classroom", "Teacher"]);
+				break;
+			case Job.WARDEN:
+				slave.slaveName = jsEither(["Jail", "Prison"]);
+				break;
+			case Job.NURSE:
+				slave.slaveName = jsEither(["Clinic", "Nurse"]);
+				break;
+			case Job.HEADGIRL:
+				slave.slaveName = jsEither(["Chief", "Head"]);
+				break;
+			case Job.BODYGUARD:
+				slave.slaveName = jsEither(["Battle", "Guard"]);
+				break;
+			case Job.RECRUITER:
+				slave.slaveName = jsEither(["Cam", "Recruiter"]);
+				break;
+		}
+	}
+	const surname = jsEither(suffixes);
+	if (typeof surname === "string" && surname.toLowerCase() === slave.slaveName.toLowerCase()) {
+		DegradingName(slave);
+	}
+	slave.slaveName = capFirstChar(slave.slaveName);
+	slave.slaveSurname = surname;
 };
 
 /**
- * Increase or decrease skinTone
- * @param {string} skin
- * @param {number} value
- * @returns {string}
+ * @param {App.Entity.SlaveState} slave
  */
-globalThis.changeSkinTone = function(skin, value) {
-	if (!App.Medicine.Modification.naturalSkins.includes(skin)) {
-		return skin;
-	}
-	const skinToMelanin = {
-		"pure black": 25,
-		"ebony": 24,
-		"black": 23,
-		"dark brown": 22,
-		"brown": 21,
-		"light brown": 20,
-		"dark beige": 19,
-		"beige": 18,
-		"light beige": 17,
-		"dark": 16,
-		"dark olive": 15,
-		"bronze": 14,
-		"olive": 13,
-		"tan": 12,
-		"light olive": 11,
-		"light": 10,
-		"fair": 9,
-		"very fair": 8,
-		"extremely fair": 7,
-		"pale": 6,
-		"very pale": 5,
-		"extremely pale": 4,
-		"white": 3,
-		"ivory": 2,
-		"pure white": 1
-	};
-	let newSkin = (skinToMelanin[skin] + value);
-	if (newSkin > 25) {
-		newSkin = 25;
-	} else if (newSkin < 1) {
-		newSkin = 1;
-	}
-	let prop;
-	for (prop in skinToMelanin) {
-		if (!skinToMelanin.hasOwnProperty(prop)) {
-			continue;
+globalThis.PaternalistName = function(slave) {
+	if (slave.slaveName.search("Miss") === -1) {
+		if (slave.slaveName.search("Ms.") === -1) {
+			if (slave.slaveName.search("Mrs.") === -1) {
+				if (slave.relationship > 4) {
+					slave.slaveName = ("Mrs. " + slave.slaveName);
+				} else if (slave.actualAge > 24) {
+					slave.slaveName = ("Ms. " + slave.slaveName);
+				} else {
+					slave.slaveName = ("Miss " + slave.slaveName);
+				}
+			}
 		}
-		if (newSkin >= skinToMelanin[prop]) {
-			return prop;
+	}
+};
+
+/**
+ * 
+ * @param {App.Entity.SlaveState} parent
+ * @param {App.Entity.SlaveState} child
+ */
+globalThis.parentNames = function(parent, child) {
+	const slaves = V.slaves;
+
+	let currentSlaveNames = slaves.map(s => s.slaveName);
+	let continentNationality;
+	const useMaleName = (child.genes === "XY" && V.allowMaleSlaveNames === true);
+
+	child.slaveName = generateName(parent.nationality, child.race, useMaleName, sn => !currentSlaveNames.includes(sn));
+
+	if (!child.slaveName) {
+		for (let i = 0; i < 10; i++) {
+			continentNationality = hashChoice(V.nationalities);
+			child.slaveName = generateName(continentNationality, child.race, useMaleName, sn => !currentSlaveNames.includes(sn));	// jshint ignore: line
 		}
 	}
-	return prop;
+	if (!child.slaveName) {
+		child.slaveName = generateName(parent.nationality, child.race, useMaleName);
+	}
 };
 
 /**
- * @param {string} color
- * @returns {number}
+ * @param {App.Entity.SlaveState} slave
+ * @param {number} amount
+ * @returns {string}
  */
-globalThis.nippleColorLevel = function(color) {
-	if (!App.Medicine.Modification.naturalNippleColors.includes(color)) {
-		return undefined;
+globalThis.faceIncrease = function(slave, amount) {
+	const pronouns = getPronouns(slave);
+	const his = pronouns.possessive;
+	const His = capFirstChar(his);
+	let r = "";
+	if (slave.face <= -95) {
+		r += `<span class="green">${His} face is no longer horrifying,</span> and is now merely ugly.`;
+	} else if (slave.face <= -40 && slave.face + amount > -40) {
+		r += `<span class="green">${His} face is no longer ugly,</span> and is now merely unattractive.`;
+	} else if (slave.face <= -10 && slave.face + amount > -10) {
+		r += `<span class="green">${His} face is no longer unattractive,</span> and is now somewhat tolerable.`;
+	} else if (slave.face <= 10 && slave.face + amount > 10) {
+		r += `<span class="green">${His} face is now decently attractive,</span> rather than merely tolerable.`;
+	} else if (slave.face <= 40 && slave.face + amount > 40) {
+		r += `<span class="green">${His} face is now quite beautiful,</span> rather than merely pretty.`;
+	} else if (slave.face <= 95 && slave.face + amount > 95) {
+		r += `<span class="green">${His} face is now perfect.</span> It's difficult to imagine how it could be any more beautiful.`;
+	}
+	slave.face = Math.clamp(slave.face + amount, -100, 100);
+	if (slave.face > 95) {
+		slave.face = 100;
 	}
-	const nippleColor = {
-		"ebony": 8,
-		"black": 7,
-		"dark brown": 6,
-		"brown": 5,
-		"light brown": 4,
-		"pink": 3,
-		"pale pink": 2,
-		"ivory": 1,
-	};
-	return nippleColor[color];
+	return r;
 };
 
 /**
- * Sets temporary variables named by the scheme, described below, to pronouns for the given slave
  * @param {App.Entity.SlaveState} slave
- * @param {any} [suffix] pronounsSuffix. Anything that can be converted to string.
- * @param {string[]} [pronouns] requested pronouns. Defaults to all pronoun forms.
- *
- * The variables naming scheme is the pronoun name (he, his, etc.) and optional suffix. If the suffix is empty, the variables
- * will be set as story variables, otherwise as temporary variables.
- * This way for a call App.Utils.setLocalPronouns(slave) there will be story variables "$he", "$his", for
- * App.Utils.setLocalPronouns(slave, 1): _he1, _his1 and so on.
+ * @returns {number}
  */
-App.Utils.setLocalPronouns = function(slave, suffix, pronouns) {
-	const ps = getPronouns(slave);
-	/** @type {string} */
-	const pSuffix = suffix !== undefined ? suffix.toString() : '';
-	pronouns = pronouns || [ // Object.getOwnPropertyNames(ps) ?
-		'he', 'him', 'his', 'himself', 'boy',
-		'He', 'Him', 'His', 'Himself', 'Boy',
-		'man', 'men', 'shota', 'son', 'brother', 'husband', 'husbands', 'father', 'fathers',
-		'Man', 'Men', 'Shota', 'Son', 'Brother', 'Husband', 'Husbands', 'Father', 'Fathers',
-		'she', 'her', 'hers', 'herself', 'girl',
-		'She', 'Her', 'Hers', 'Herself', 'Girl',
-		'woman', 'women', 'loli', 'daughter', 'sister', 'wife', 'wives', 'mother', 'mothers',
-		'Woman', 'Women', 'Loli', 'Daughter', 'Sister', 'Wife', 'Wives', 'Mother', 'Mothers'
-	]; // Pronouns always refer to the slave in question, never any relation of theirs. It is "mother" as in "she is a mother of many" not "you are her mother". Plural pronouns would refer to "wives like her," not "her wives."
+globalThis.deadliness = function(slave) {
+	let deadliness = 2;
 
-	const scope = pSuffix.length === 0 ? V : State.temporary;
-	pronouns.forEach(p => {
-		scope[p + pSuffix] = ps[p];
-	});
+	if (slave.skill.combat > 0) {
+		deadliness += 2;
+	}
+
+	if (App.Data.Careers.Leader.bodyguard.includes(slave.career)) {
+		deadliness += 1;
+	} else if (slave.skill.bodyguard >= V.masteredXP) {
+		deadliness += 1;
+	}
+
+	if (V.AgePenalty !== 0) {
+		if (slave.physicalAge >= 100) {
+			deadliness -= 10;
+		} else if (slave.physicalAge >= 85) {
+			deadliness -= 3;
+		} else if (slave.physicalAge >= 70) {
+			deadliness -= 1;
+		}
+	}
+
+	if (slave.muscles > 30 && slave.muscles <= 95) {
+		deadliness += 1;
+	} else if (slave.muscles > 95 && slave.height >= 185) {
+		deadliness += 2;
+	} else if (slave.muscles > 95) {
+		deadliness -= 1;
+	} else if (slave.muscles < -95) {
+		deadliness -= 20;
+	} else if (slave.muscles < -30) {
+		deadliness -= 7;
+	} else if (slave.muscles < -5) {
+		deadliness -= 3;
+	}
+
+	if (slave.height >= 170) {
+		deadliness += 1;
+	}
+
+	if (slave.health.condition > 50) {
+		deadliness += 1;
+	} else if (slave.health.condition < -50) {
+		deadliness -= 1;
+	}
+
+	if (slave.health.illness > 3) {
+		deadliness -= 3;
+	} else if (slave.health.illness > 1) {
+		deadliness -= 2;
+	} else if (slave.health.illness > 0) {
+		deadliness -= 1;
+	}
+
+	if (slave.boobs > 4000) {
+		deadliness -= 2;
+	} else if (slave.boobs > 2000) {
+		deadliness -= 1;
+	}
+
+	if (slave.butt > 6) {
+		deadliness -= 1;
+	}
+
+	if (slave.hips > 2) {
+		deadliness -= 1;
+	}
+
+	if (slave.weight > 190) {
+		deadliness -= 20;
+	} else if (slave.weight > 160) {
+		deadliness -= 10;
+	} else if (slave.weight > 130) {
+		deadliness -= 3;
+	} else if (slave.weight > 30 || slave.weight < -10) {
+		deadliness -= 1;
+	}
+
+	if (slave.bellyFluid >= 10000) {
+		deadliness -= 3;
+	} else if (slave.bellyFluid >= 5000) {
+		deadliness -= 2;
+	} else if (slave.bellyFluid >= 2000) {
+		deadliness -= 1;
+	}
+
+	if (slave.pregKnown === 1 || slave.bellyPreg >= 1500 || slave.bellyImplant >= 1500) {
+		if (slave.belly >= 750000) {
+			deadliness -= 50;
+		} else if (slave.belly >= 600000) {
+			deadliness -= 25;
+		} else if (slave.belly >= 450000) {
+			deadliness -= 15;
+		} else if (slave.belly >= 300000) {
+			deadliness -= 10;
+		} else if (slave.belly >= 150000) {
+			deadliness -= 8;
+		} else if (slave.belly >= 100000) {
+			deadliness -= 7;
+		} else if (slave.belly >= 10000) {
+			deadliness -= 3;
+		} else if (slave.belly >= 5000) {
+			deadliness -= 2;
+		} else {
+			deadliness -= 1;
+		}
+	}
+
+	if (isInLabor(slave)) {
+		deadliness -= 15;
+	} else if (slave.preg >= slave.pregData.normalBirth && slave.pregControl !== "labor suppressors") {
+		deadliness -= 5;
+	}
+
+	if (slave.balls >= 15) {
+		deadliness -= 1;
+	}
+
+	if (slave.dick >= 10) {
+		deadliness -= 1;
+	}
+
+	deadliness -= getLimbCount(slave, 0) * 5;
+	deadliness -= getLimbCount(slave, 2) * 0.25;
+	deadliness -= getLimbCount(slave, 3) * 0.25;
+	deadliness -= getLimbCount(slave, 4) * 0.25;
+	deadliness += getLimbCount(slave, 5) * 1.25;
+	deadliness += getLimbCount(slave, 6) * 2.5;
+	if (hasBothLegs(slave) && !canStand(slave)) {
+		deadliness -= 20;
+	}
+
+	if (!canSee(slave)) {
+		deadliness -= 8;
+	} else if (!canSeePerfectly(slave)) {
+		deadliness -= 1;
+	}
+
+	if (!canHear(slave)) {
+		deadliness -= 4;
+	} else if ((slave.hears === -1 && slave.earwear !== "hearing aids") || (slave.hears === 0 && slave.earwear === "muffling ear plugs")) {
+		deadliness -= 1;
+	}
+
+	if (slave.tail === "combat") {
+		deadliness += 2;
+	}
+
+	if (slave.health.tired > 90) {
+		deadliness -= 10;
+	} else if (slave.health.tired > 60) {
+		deadliness -= 3;
+	} else if (slave.health.tired > 30) {
+		deadliness -= 1;
+	}
+
+	return Math.max(deadliness, 1);
 };
 
-/**
- * Fix nationalities as adjectives
- * @param {string} nation
- * @returns {string}
+/** Is the slave ready to retire?
+ * @param {App.Entity.SlaveState} slave
+ * @returns {boolean}
  */
-globalThis.aNational = function(nation) {
-	let country;
-	if (nation === "a Cook Islander") {
-		country = "Cook Islander";
-	} else if (nation === "a Liechtensteiner") {
-		country = "Liechtensteiner";
-	} else if (nation === "a New Zealander") {
-		country = "New Zealander";
-	} else if (nation === "a Solomon Islander") {
-		country = "Solomon Islander";
+globalThis.retirementReady = function(slave) {
+	// indentured slaves don't retire, they expire
+	if (slave.indenture >= 0) {
+		return false;
+	}
+
+	// retirement by age
+	if (V.policies.retirement.physicalAgePolicy === 0 && slave.actualAge >= V.retirementAge) {
+		return true;
+	} else if (V.policies.retirement.physicalAgePolicy === 1 && slave.physicalAge >= V.retirementAge) {
+		return true;
+	}
+
+	// retirement by milestone
+	if (V.policies.retirement.sex > 0 && (slave.counter.oral + slave.counter.anal + slave.counter.vaginal + slave.counter.penetrative + slave.counter.mammary) > V.policies.retirement.sex) {
+		return true;
+	}
+	if (V.policies.retirement.milk > 0 && slave.counter.milk > V.policies.retirement.milk) {
+		return true;
+	}
+	if (V.policies.retirement.cum > 0 && slave.counter.cum > V.policies.retirement.cum) {
+		return true;
+	}
+	if (V.policies.retirement.births > 0 && slave.counter.births > V.policies.retirement.births) {
+		return true;
+	}
+	if (V.policies.retirement.kills > 0 && slave.counter.pitKills > V.policies.retirement.kills) {
+		return true;
+	}
+
+	// no retirement for you
+	return false;
+};
+
+/** marks some weeks of time passage for a slave, counting birthdays and invoking aging if game settings require it
+ * @param {App.Entity.SlaveState} slave
+ * @param {number} [weeks=1]
+ */
+globalThis.ageSlaveWeeks = function(slave, weeks = 1) {
+	if (V.seeAge !== 0) { // birthdays enabled
+		for (let i = 0; i < weeks; ++i) {
+			slave.birthWeek++;
+			if (slave.birthWeek >= 52) {
+				slave.birthWeek = 0;
+				if (V.seeAge === 1) { // actual aging enabled
+					ageSlave(slave);
+				}
+			}
+		}
+	}
+};
+
+/** advances the age of a slave by one year, incurring all aging side effects
+ * @param {App.Entity.SlaveState} slave
+ * @param {boolean} [forceDevelopment=false]
+ */
+globalThis.ageSlave = function(slave, forceDevelopment = false) {
+	slave.physicalAge++;
+	slave.actualAge++;
+	if (slave.geneMods.NCS === 1 || (slave.geneticQuirks.neoteny >= 2 && slave.geneticQuirks.progeria !== 2)) {
+		/* Induced NCS completely takes over visual aging. Additionally, because of the neoteny aspects of NCS, ovaries don't age quite as fast. */
+		/* Unsurprisingly, actual neoteny has the same effect as long as progeria isn't in play. */
+		slave.ovaryAge += either(0.5, 0.6, 0.7, 0.7, 0.8, 0.9, 1.0);
 	} else {
-		country = nation;
+		slave.visualAge++;
+		slave.ovaryAge += either(0.8, 0.9, 0.9, 1.0, 1.0, 1.0, 1.1);
+	}
+	if (slave.broodmother === 1) {
+		slave.ovaryAge += 0.2;
+	}
+	if (slave.physicalAge <= 18 && (forceDevelopment || V.loliGrow > 0)) {
+		physicalDevelopment(slave);
 	}
-	return country;
 };
 
 /**
- * Fix nationalities as plurals
- * @param {string} nation
+ * @param {App.Entity.SlaveState} slave
+ * @param {number} [induce]
  * @returns {string}
  */
-globalThis.moreNational = function(nation) {
-	let country;
-	if (nation === "a Cook Islander") {
-		country = "Cook Islander";
-	} else if (nation === "a Liechtensteiner") {
-		country = "Liechtensteiner";
-	} else if (nation === "Mosotho") {
-		country = "Basotho";
-	} else if (nation === "Motswana") {
-		country = "Batswana";
-	} else if (nation === "a New Zealander") {
-		country = "New Zealander";
-	} else if (nation === "a Solomon Islander") {
-		country = "Solomon Islander";
-	} else {
-		country = nation;
+globalThis.induceLactation = function(slave, induce = 0) {
+	const {His} = getPronouns(slave);
+	let r = "";
+	let lactationStartChance = jsRandom(10, 100);
+	slave.induceLactation += induce;
+	if (slave.boobs < 300) {
+		lactationStartChance *= 1.5;
+	} else if (slave.boobs < 400 || slave.boobs >= 5000) {
+		lactationStartChance *= 1.2;
 	}
-	return country;
+	if (slave.pubertyXX === 0) {
+		lactationStartChance *= 1.5;
+	}
+	if (slave.preg > (slave.pregData.normalBirth / 1.33)) {
+		lactationStartChance *= .5;
+	}
+	if (slave.health.condition < -20) {
+		lactationStartChance *= 2;
+	}
+	if (slave.weight <= -30) {
+		lactationStartChance *= 1.5;
+	}
+	if (slave.boobsImplant > 0) {
+		lactationStartChance *= (1 + (slave.boobsImplant / slave.boobs));
+	}
+	if (slave.lactationAdaptation > 0) {
+		lactationStartChance = (lactationStartChance / (slave.lactationAdaptation / 10));
+	}
+	if (slave.geneticQuirks.galactorrhea === 2) {
+		lactationStartChance *= .5;
+	}
+	lactationStartChance = Math.floor(lactationStartChance);
+	if (slave.induceLactation >= lactationStartChance) {
+		r += `${His} breasts have been stimulated often enough to <span class="lime">induce lactation.</span>`;
+		slave.induceLactation = 0;
+		slave.lactationDuration = 2;
+		slave.lactation = 1;
+	}
+	return r;
 };
 
 /**
- * Returns a "disobedience factor" between 0 (perfectly obedient) and 100 (completely defiant)
- * @param {App.Entity.SlaveState} slave
- * @returns {number}
+ * @param {string} targetSkill - Skill to be checked.
+ * @param {Object} slave - Slave to be checked.
+ * @param {number} [skillIncrease=1]
+ * @returns {string}
  */
-globalThis.disobedience = function(slave) {
-	const devotionBaseline = 20; // with devotion above this number slaves will obey completely
-	const trustBaseline = -20; // with trust below this number slaves will obey completely
+globalThis.slaveSkillIncrease = function(targetSkill, slave, skillIncrease = 1) {
+	let r = "", skillDec;
+	const {He, his, him} = getPronouns(slave);
+	const isleadershipRole = function() {
+		if (['headGirl', 'recruiter', 'bodyguard', 'madam', 'DJ', 'nurse', 'teacher', 'attendant', 'matron', 'stewardess', 'milkmaid', 'farmer', 'wardeness'].includes(targetSkill)) {
+			return true;
+		}
+		return false;
+	};
 
-	if (slave.devotion > devotionBaseline || slave.trust < trustBaseline) {
-		return 0; // no chance of disobedience
-	}
+	if (slave.skill[targetSkill] <= 10) {
+		switch(targetSkill) {
+			case 'oral':
+			case 'vaginal':
+			case 'anal':
+				skillDec = `knowledge about ${targetSkill} sex,`; break;
+			case 'whoring':
+				skillDec = `knowledge about how to whore,`; break;
+			case 'entertainment':
+				skillDec = `knowledge about how to be entertaining,`; break;
+		}
+		if (isleadershipRole()) {
+			skillDec = `${capFirstChar(targetSkill)} skills.`;
+		}
 
-	// factors are between 0 (right on the boundary of perfectly obedient) and 10 (completely disobedient)
-	let devotionFactor = 10 - ((10 * (slave.devotion + 100)) / (devotionBaseline + 100));
-	let trustFactor = (10 * (slave.trust - trustBaseline)) / (100 - trustBaseline);
-	return Math.round(devotionFactor * trustFactor);
-};
+		if (slave.skill[targetSkill] + skillIncrease > 10) {
+			r = `<span class="green">${He} now has basic ${skillDec}</span>`;
+			switch(targetSkill) {
+				case 'oral':
+					r += ` and at least suck a dick without constant gagging.`; break;
+				case 'vaginal':
+					r += ` and can avoid some of the common pitfalls and turnoffs.`; break;
+				case 'anal':
+					r += ` and can accept penetration of ${his} anus without danger.`; break;
+				case 'whoring':
+					r += ` and can avoid some potentially dangerous situations.`; break;
+				case 'entertainment':
+					r += ` and can usually avoid serious faux pas.`; break;
+			}
+		}
+	} else if (slave.skill[targetSkill] <= 30) {
+		switch(targetSkill) {
+			case 'oral':
+			case 'vaginal':
+			case 'anal':
+				skillDec = `${targetSkill} skills,`; break;
+			case 'whoring':
+				skillDec = `skill as a whore,`; break;
+			case 'entertainment':
+				skillDec = `skill as an entertainer,`; break;
+		}
+		if (isleadershipRole()) {
+			skillDec = `skill as a ${capFirstChar(targetSkill)}.`;
+		}
 
-/**
- * Returns a valid rape target for a slave who is going to rape one of his peers into rivalry with him.
- * @param {App.Entity.SlaveState} slave
- * @param {function(App.Entity.SlaveState): boolean} predicate
- * @returns {App.Entity.SlaveState | undefined}
- */
-globalThis.randomRapeRivalryTarget = function(slave, predicate) {
-	const willIgnoreRules = disobedience(slave) > jsRandom(0, 100);
+		if (slave.skill.oral + skillIncrease > 30) {
+			r = `<span class="green">${He} now has some ${skillDec}</span>`;
+			switch(targetSkill) {
+				case 'oral':
+					r += ` and can reliably bring dicks and pussies to climax with ${his} mouth.`; break;
+				case 'vaginal':
+					r += ` and can do more than just lie there and take it.`; break;
+				case 'anal':
+					r += ` and needs less preparation before taking rough penetration.`; break;
+				case 'whoring':
+					r += ` and knows how to sell ${his} body at a good price.`; break;
+				case 'entertainment':
+					r += ` and can flirt, dance, and strip acceptably.`; break;
+			}
+		}
+	} else if (slave.skill[targetSkill] <= 60) {
+		switch(targetSkill) {
+			case 'oral':
+			case 'vaginal':
+			case 'anal':
+				skillDec = `${targetSkill} sex expert,`; break;
+			case 'whoring':
+				skillDec = `expert whore,`; break;
+			case 'entertainment':
+				skillDec = `expert entertainer,`; break;
+		}
+		if (isleadershipRole()) {
+			skillDec = `expert ${capFirstChar(targetSkill)}.`;
+		}
 
-	function canBeARapeRival(s) {
-		return (s.devotion <= 95 && s.energy <= 95 && !s.rivalry && !s.fuckdoll && s.fetish !== "mindbroken");
-	}
+		if (slave.skill[targetSkill] + skillIncrease > 60) {
+			r = `<span class="green">${He} is now an ${skillDec}</span>`;
+			switch(targetSkill) {
+				case 'oral':
+					r += ` and has a delightfully experienced tongue.`; break;
+				case 'vaginal':
+					r += ` and has the muscular control to massage anything that's inside ${him}.`; break;
+				case 'anal':
+					r += ` and knows how to use ${his} sphincter to please.`; break;
+				case 'whoring':
+					r += ` and can often make clients forget that $he's a prostitute they're paying for.`; break;
+				case 'entertainment':
+					r += ` and can flirt engagingly, dance alluringly, and strip arousingly.`; break;
+			}
+		}
+	} else if (slave.skill[targetSkill] < 100) {
+		switch(targetSkill) {
+			case 'oral':
+			case 'vaginal':
+			case 'anal':
+				skillDec = `has mastered ${targetSkill} sex,`; break;
+			case 'whoring':
+				skillDec = `is now a masterful whore,`; break;
+			case 'entertainment':
+				skillDec = `is now a masterful entertainer,`; break;
+		}
+		if (isleadershipRole()) {
+			skillDec = `is now a masterful ${capFirstChar(targetSkill)}.`;
+		}
 
-	function canRape(rapist, rapee) {
-		const opportunity = (assignmentVisible(rapist) && assignmentVisible(rapee)) || rapist.assignment === rapee.assignment;
-		const taboo = V.seeIncest === 0 && areRelated(rapist, rapee);
-		const desire = !(rapist.relationship >= 3 && rapist.relationshipTarget === rapee.id) && !taboo;
-		const permission = willIgnoreRules || App.Utils.sexAllowed(rapist, rapee);
-		return opportunity && desire && permission;
+		if (slave.skill[targetSkill] + skillIncrease >= 100) {
+			r = `<span class="green">${He} ${skillDec}</span>`;
+			switch(targetSkill) {
+				case 'oral':
+					r += ` and can learn nothing more about sucking dick or eating pussy.`; break;
+				case 'vaginal':
+					r += ` and can learn nothing more about tribbing or taking dick.`; break;
+				case 'anal':
+					r += ` and can learn nothing more about taking it up the ass.`; break;
+				case 'whoring':
+					r += ` and can learn nothing more about prostitution.`; break;
+				case 'entertainment':
+					r += ` and can learn nothing more about flirting, dancing, or stripping.`; break;
+			}
+		}
 	}
 
-	if (typeof predicate !== 'function') {
-		predicate = (() => true);
+	if (isleadershipRole() && slave.skill[targetSkill] + skillIncrease >= 100) {
+		V.tutorGraduate.push(slave.ID);
+		V.slaveTutor[capFirstChar(targetSkill)].delete(slave.ID);
 	}
-
-	const arr = V.slaves.filter((s) => { return canBeARapeRival(s) && canRape(slave, s); }).shuffle();
-	return arr.find(predicate);
-};
-
-
-/** @typedef {object} getBestSlavesParams
- * @property {string|function(App.Entity.SlaveState): number} part slave object property or custom function
- * @property {number} [count] number of slaves to return
- * @property {boolean} [largest] should it search for the biggest or smallest value
- * @property {function(App.Entity.SlaveState): boolean} [filter] filter out undesired slaves
- */
-
-/**
- * @param {getBestSlavesParams} params
- * @returns {App.Entity.SlaveState[]} sorted from best to worst
- */
-globalThis.getBestSlaves = function({part, count = 3, largest = true, filter = (() => true)}) {
-	const partCB = _.isFunction(part) ? part :  (slave) => slave[part];
-
-	const sortMethod = largest ? (left, right) => right.value - left.value : (left, right) => left.value - right.value;
-	return V.slaves.filter(slave => filter(slave))
-		.map(slave => ({slave, value: partCB(slave)}))
-		.sort(sortMethod)
-		.slice(0, count)
-		.map(slaveInfo => slaveInfo.slave);
-};
-/**
- * @param {getBestSlavesParams} info
- * @returns {number[]}
- */
-globalThis.getBestSlavesIDs = function(info) {
-	return getBestSlaves(info).map(slave => slave.ID);
+	slave.skill[targetSkill] += skillIncrease;
+	return r;
 };
 
 /*
@@ -2864,233 +3469,53 @@ globalThis.generateSlaveID = function() {
 	return V.IDNumber++;
 };
 
-globalThis.ASDump = function() {
-	if ((typeof V.activeSlave === undefined) || (V.activeSlave === 0)) {
-		return `<span class="red">ERROR:</span> AS Dump, activeSlave invalid, returnTo is 'V.returnTo', previous passage was '${previous()}'. Please report this. `;
-	} else {
-		let SL = V.slaves.length;
-		let ID = V.activeSlave.ID;
-		if (V.i >= 0 && V.i < SL && V.slaves[V.i].ID === ID) { /* shortcut if V.i is already pointing to this slave */
-			V.slaves[V.i] = V.activeSlave;
-		} else {
-			V.i = V.slaveIndices[ID]; // find V.i if exists
-			if (typeof V.i === undefined) { /* not found, so new slave */
-				newSlave(V.activeSlave);
-			} else {
-				V.slaves[V.i] = V.activeSlave;
-			}
-		}
-	}
-};
-
-/** Returns the revivalist nationality associated with the player's arcology, or 0 if none
- * @returns {string|0}
- */
-globalThis.getRevivalistNationality = function() {
-	if (V.arcologies[0].FSRomanRevivalist > 90) {
-		return "Roman Revivalist";
-	} else if (V.arcologies[0].FSAztecRevivalist > 90) {
-		return "Aztec Revivalist";
-	} else if (V.arcologies[0].FSEgyptianRevivalist > 90) {
-		return "Ancient Egyptian Revivalist";
-	} else if (V.arcologies[0].FSEdoRevivalist > 90) {
-		return "Edo Revivalist";
-	} else if (V.arcologies[0].FSArabianRevivalist > 90) {
-		return "Arabian Revivalist";
-	} else if (V.arcologies[0].FSChineseRevivalist > 90) {
-		return "Ancient Chinese Revivalist";
-	}
-	return 0;
-};
-
-globalThis.penthouseCensus = function() {
-	function occupiesRoom(slave) {
-		if (slave.rules.living !== "luxurious") {
-			return false; // assigned to dormitory
-		} else if (slave.assignment === Job.HEADGIRL && V.HGSuite > 0) {
-			return false; // lives in HG suite
-		} else if (slave.assignment === Job.BODYGUARD && V.dojo > 0) {
-			return false; // lives in dojo
-		} else if (slave.relationship >= 4) {
-			const partner = getSlave(slave.relationshipTarget);
-			if (assignmentVisible(partner) && partner.ID < slave.ID && partner.rules.living === "luxurious") {
-				return false; // living with partner, who is already assigned a room (always allocate a room to the partner with the lower ID)
-			}
-		}
-		return true; // takes her own room
-	}
-
-	const penthouseSlaves = V.slaves.filter(s => assignmentVisible(s));
-	V.roomsPopulation = penthouseSlaves.filter(occupiesRoom).length;
-	V.dormitoryPopulation = penthouseSlaves.filter(s => s.rules.living !== "luxurious").length;
-};
-
-/** Determine whether a given penthouse slave can move into a private room or not.
- * @param {App.Entity.SlaveState} slave
- * @returns {boolean}
- */
-globalThis.canMoveToRoom = function(slave) {
-	const partner = slave.relationship >= 4 ? getSlave(slave.relationshipTarget) : null;
-	const partnerHasRoom = partner && assignmentVisible(partner) && partner.rules.living === "luxurious";
-	return partnerHasRoom || V.rooms - V.roomsPopulation >= 1;
-};
-
-/**
- * @param {App.Entity.Facilities.Job|App.Entity.Facilities.Facility} jobOrFacility job or facility object
- * @returns {App.Entity.SlaveState[]} array of slaves employed at the job or facility, sorted in accordance to user choice
- */
-App.Utils.sortedEmployees = function(jobOrFacility) {
-	const employees = jobOrFacility.employees();
-	SlaveSort.slaves(employees);
-	return employees;
-};
-
 /**
- * @param {Array<string|App.Entity.Facilities.Facility>} [facilities]
- * @param {Object.<string, string>} [mapping] Optional mapping for the property names in the result object. Keys
- * are the standard facility names, values are the desired names.
- * @returns {Object.<string, number>}
+ * @param {number} ID
+ * @returns {App.Entity.SlaveState}
  */
-App.Utils.countFacilityWorkers = function(facilities = null, mapping = {}) {
-	facilities = facilities || Object.values(App.Entity.facilities);
-	/** @type {App.Entity.Facilities.Facility[]} */
-	const fObjects = facilities.map(f => typeof f === "string" ? App.Entity.facilities[f] : f);
-	return fObjects.reduce((acc, cur) => {
-		acc[mapping[cur.desc.baseName] || cur.desc.baseName] = cur.employeesIDs().size; return acc;
-	}, {});
+globalThis.slaveStateById = function(ID) {
+	const index = V.slaveIndices[ID];
+	return index === undefined ? null : V.slaves[index];
 };
 
 /**
- * @param {string[]} [assignments] array of assignment strings. The default is to count for all assignments
- * @returns {Object.<string, number>}
+ * @param {number} ID
+ * @returns {App.Entity.SlaveState}
  */
-App.Utils.countAssignmentWorkers = function(assignments) {
-	assignments = assignments || Object.values(Job);
-	return assignments.reduce((acc, cur) => { acc[cur] = V.JobIDMap[cur].size; return acc; }, {});
+globalThis.getSlave = function(ID) {
+	const index = V.slaveIndices[ID];
+	return index === undefined ? undefined : V.slaves[index];
 };
 
-/** Calculate and return economic uncertainty multiplier for a given arcology
- * @param {number} arcologyID
- * @returns {number}
- */
-App.Utils.economicUncertainty = function(arcologyID) {
-	let uncertainty = arcologyID === 0 ? 5 : 10;
-	if (assistant.power === 1) {
-		uncertainty -= Math.max(Math.trunc(uncertainty/2), 0);
-	} else if (assistant.power > 1) {
-		uncertainty = 0;
-	}
-	return jsRandom(100 - uncertainty, 100 + uncertainty) / 100;
+globalThis.getChild = function(ID) {
+	return V.cribs.find(s => s.ID === ID);
 };
 
-/** Deflate a slave (reset inflation to none)
+/**
+ * Returns a valid rape target for a slave who is going to rape one of his peers into rivalry with him.
  * @param {App.Entity.SlaveState} slave
+ * @param {function(App.Entity.SlaveState): boolean} predicate
+ * @returns {App.Entity.SlaveState | undefined}
  */
-globalThis.deflate = function(slave) {
-	slave.inflation = 0;
-	slave.inflationType = "none";
-	slave.inflationMethod = 0;
-	slave.milkSource = 0;
-	slave.cumSource = 0;
-	SetBellySize(slave);
-};
-
-/** Notify the game that the sidebar needs to be refreshed as soon as possible, but do not do it immediately.
- *  This allows us to call this function repeatedly without impacting performance (for example, from repX() and cashX()).
- *  The game will redraw the sidebar exactly once, as soon as all the scripts have finished executing.
- */
-App.Utils.scheduleSidebarRefresh = (function() {
-	let refresh = false;
+globalThis.randomRapeRivalryTarget = function(slave, predicate) {
+	const willIgnoreRules = disobedience(slave) > jsRandom(0, 100);
 
-	function updateSidebar() {
-		refresh = false;
-		UIBar.update();
-		App.UI.updateSidebarTooltips();
+	function canBeARapeRival(s) {
+		return (s.devotion <= 95 && s.energy <= 95 && !s.rivalry && !s.fuckdoll && s.fetish !== "mindbroken");
 	}
 
-	function schedule() {
-		if (!refresh) {
-			refresh = true;
-			setTimeout(updateSidebar, 0);
-		}
+	function canRape(rapist, rapee) {
+		const opportunity = (assignmentVisible(rapist) && assignmentVisible(rapee)) || rapist.assignment === rapee.assignment;
+		const taboo = V.seeIncest === 0 && areRelated(rapist, rapee);
+		const desire = !(rapist.relationship >= 3 && rapist.relationshipTarget === rapee.id) && !taboo;
+		const permission = willIgnoreRules || App.Utils.sexAllowed(rapist, rapee);
+		return opportunity && desire && permission;
 	}
 
-	return schedule;
-})();
-
-/** Calculate various averages for the master suite slaves
- * @returns {{energy: number, milk: number, cum: number, dom: number, sadism: number, dick: number, preg: number}}
- */
-App.Utils.masterSuiteAverages = (function() {
-	const domMap = {dom: 1, submissive: -1};
-	const sadismMap = {sadism: 1, masochism: -1};
-
-	/** Returns either zero or the results of mapping the slave's fetish through an object containing fetish names and result values
-	 * @param {App.Entity.SlaveState} s
-	 * @param {Object.<string, number>} map
-	 * @returns {number}
-	 */
-	const fetishMapOrZero = (s, map) => map.hasOwnProperty(s.fetish) ? map[s.fetish] : 0;
-
-	return () => {
-		const msSlaves = App.Entity.facilities.masterSuite.employees();
-		return {
-			energy: _.mean(msSlaves.map(s => s.energy)),
-			milk: _.mean(msSlaves.map(s => s.lactation*(s.boobs-s.boobsImplant))),
-			cum: _.mean(msSlaves.map(s => canAchieveErection(s) ? s.balls : 0)),
-			dick: _.mean(msSlaves.map(s => canAchieveErection(s) ? s.dick : 0)),
-			preg: _.mean(msSlaves.map(s => s.preg)),
-			sadism: _.mean(msSlaves.map(s => (s.fetishStrength * fetishMapOrZero(s, sadismMap)))),
-			dom: _.mean(msSlaves.map(s => (s.fetishStrength * fetishMapOrZero(s, domMap))))
-		};
-	};
-})();
-
-App.Utils.schoolCounter = function() {
-	return Array.from(App.Data.misc.schools.keys()).filter(s => V[s].schoolPresent).length;
-};
-
-App.Utils.schoolFailure = function() {
-	return Array.from(App.Data.misc.schools.keys()).find(s => V[s].schoolPresent && V[s].schoolProsperity <= -10);
-};
-
-App.Utils.alphabetizeIterable = function(iterable) {
-	const compare = function(a, b) {
-		let aTitle = a.toLowerCase();
-		let bTitle = b.toLowerCase();
-
-		aTitle = removeArticles(aTitle);
-		bTitle = removeArticles(bTitle);
-
-		if (aTitle > bTitle) {
-			return 1;
-		}
-		if (aTitle < bTitle) {
-			return -1;
-		}
-		return 0;
-	  };
-
-	function removeArticles(str) {
-		const words = str.split(" ");
-		if (words.length <= 1) {
-			return str;
-		}
-		if ( words[0] === 'a' || words[0] === 'the' || words[0] === 'an' ) {
-			return words.splice(1).join(" ");
-		}
-		return str;
+	if (typeof predicate !== 'function') {
+		predicate = (() => true);
 	}
-	const clonedArray = (Array.from(iterable));
-	return clonedArray.sort(compare);
-};
 
-/**
- * Returns how exposing a slave's outfit is, after taking into consideration a topless outfit is more revealing for beboobed slaves or female ones.
- * @param {App.Entity.SlaveState} slave
- * @returns {0|1|2|3|4}
- */
-globalThis.getExposure = function(slave) {
-	const clothes = App.Data.clothes.get(slave.clothes);
-	return (clothes.topless && clothes.exposure < 3 && (slave.boobs > 299 || (slave.genes === 'XX' && slave.vagina >= 0))) ? 3 : clothes.exposure;
+	const arr = V.slaves.filter((s) => { return canBeARapeRival(s) && canRape(slave, s); }).shuffle();
+	return arr.find(predicate);
 };
diff --git a/src/js/utilsSlaves.js b/src/js/utilsSlaves.js
new file mode 100644
index 0000000000000000000000000000000000000000..79222bba4de3558011b209d9be326bb6d86eba17
--- /dev/null
+++ b/src/js/utilsSlaves.js
@@ -0,0 +1,237 @@
+globalThis.SlaveSort = function() {
+	const effectivePreg = (slave) => {
+		// slave.preg is only *mostly* usable for sorting
+		if (slave.preg > 0 && !slave.pregKnown) {
+			// don't reveal unknown pregnancies
+			return 0;
+		}
+		if (slave.pubertyXX === 0 && (slave.ovaries === 1 || slave.mpreg === 1)) {
+			// not ovulating yet - sort between barren slaves and slaves on contraceptives
+			return -1.2;
+		} else if (slave.ovaryAge >= 47 && (slave.ovaries === 1 || slave.mpreg === 1)) {
+			// menopausal - sort between barren slaves and slaves on contraceptives
+			return -1.1;
+		} else if (slave.pregWeek < 0) {
+			// postpartum - sort between slaves on contraceptives and fertile slaves
+			return -0.1;
+		}
+		return slave.preg;
+	};
+
+	const effectiveEnergy = (slave) => {
+		return slave.attrKnown === 1 ? slave.energy : -101;
+	};
+
+	const comparators = {
+		Aassignment: (a, b) => a.assignment < b.assignment ? -1 : 1,
+		Dassignment: (a, b) => a.assignment > b.assignment ? -1 : 1,
+		Aname: (a, b) => a.slaveName < b.slaveName ? -1 : 1,
+		Dname: (a, b) => a.slaveName > b.slaveName ? -1 : 1,
+		Aseniority: (a, b) => b.weekAcquired - a.weekAcquired,
+		Dseniority: (a, b) => a.weekAcquired - b.weekAcquired,
+		AactualAge: (a, b) => a.actualAge - b.actualAge,
+		DactualAge: (a, b) => b.actualAge - a.actualAge,
+		AvisualAge: (a, b) => a.visualAge - b.visualAge,
+		DvisualAge: (a, b) => b.visualAge - a.visualAge,
+		AphysicalAge: (a, b) => a.physicalAge - b.physicalAge,
+		DphysicalAge: (a, b) => b.physicalAge - a.physicalAge,
+		Adevotion: (a, b) => a.devotion - b.devotion,
+		Ddevotion: (a, b) => b.devotion - a.devotion,
+		AID: (a, b) => a.ID - b.ID,
+		DID: (a, b) => b.ID - a.ID,
+		AweeklyIncome: (a, b) => a.lastWeeksCashIncome - b.lastWeeksCashIncome,
+		DweeklyIncome: (a, b) => b.lastWeeksCashIncome - a.lastWeeksCashIncome,
+		Ahealth: (a, b) => a.health.health - b.health.health,
+		Dhealth: (a, b) => b.health.health - a.health.health,
+		Aweight: (a, b) => a.weight - b.weight,
+		Dweight: (a, b) => b.weight - a.weight,
+		Amuscles: (a, b) => a.muscles - b.muscles,
+		Dmuscles: (a, b) => b.muscles - a.muscles,
+		AsexDrive: (a, b) => effectiveEnergy(a) - effectiveEnergy(b),
+		DsexDrive: (a, b) => effectiveEnergy(b) - effectiveEnergy(a),
+		Apregnancy: (a, b) => effectivePreg(a) - effectivePreg(b),
+		Dpregnancy: (a, b) => effectivePreg(b) - effectivePreg(a),
+	};
+
+	return {
+		slaves: sortSlaves,
+		IDs: sortIDs,
+		indices: sortIndices
+	};
+
+	/** @param {App.Entity.SlaveState[]} [slaves] */
+	function sortSlaves(slaves) {
+		slaves = slaves || V.slaves;
+		slaves.sort(_comparator());
+		if (slaves === V.slaves) {
+			V.slaveIndices = slaves2indices();
+		}
+	}
+
+	/** @param {number[]} [slaveIDs] */
+	function sortIDs(slaveIDs) {
+		const slaves = V.slaves;
+		const slaveIndices = V.slaveIndices;
+		const cmp = _comparator();
+		slaveIDs = slaveIDs || slaves.map(s => s.ID);
+		slaveIDs.sort((IDa, IDb) => cmp(slaves[slaveIndices[IDa]], slaves[slaveIndices[IDb]]));
+	}
+
+	/** @param {number[]} [slaveIdxs] */
+	function sortIndices(slaveIdxs) {
+		const slaves = V.slaves;
+		const cmp = _comparator();
+		slaveIdxs = slaveIdxs || [...slaves.keys()];
+		slaveIdxs.sort((ia, ib) => cmp(slaves[ia], slaves[ib]));
+	}
+
+	/**
+	 * @callback slaveComparator
+	 * @param {App.Entity.SlaveState} a
+	 * @param {App.Entity.SlaveState} b
+	 * @returns {number}
+	 */
+	/** @returns {slaveComparator} */
+	function _comparator() {
+		return _makeStableComparator(comparators[(V.sortSlavesOrder === "ascending" ? 'A' : 'D') + V.sortSlavesBy]);
+	}
+
+	/** secondary-sort by ascending ID if the primary comparator would return 0 (equal), so we have a guaranteed stable order regardless of input
+	 * @param {slaveComparator} comparator
+	 * @returns {slaveComparator}
+	 */
+	function _makeStableComparator(comparator) {
+		return function(a, b) {
+			return comparator(a, b) || comparators.AID(a, b);
+		};
+	}
+}();
+
+/**
+ * @param {App.Entity.SlaveState[]} slaves
+ */
+globalThis.slaveSortMinor = function(slaves) {
+	slaves.sort((a, b) => a.slaveName < b.slaveName ? -1 : 1);
+};
+
+/** @typedef {object} getBestSlavesParams
+ * @property {string|function(App.Entity.SlaveState): number} part slave object property or custom function
+ * @property {number} [count] number of slaves to return
+ * @property {boolean} [largest] should it search for the biggest or smallest value
+ * @property {function(App.Entity.SlaveState): boolean} [filter] filter out undesired slaves
+ */
+
+/**
+ * @param {getBestSlavesParams} params
+ * @returns {App.Entity.SlaveState[]} sorted from best to worst
+ */
+globalThis.getBestSlaves = function({part, count = 3, largest = true, filter = (() => true)}) {
+	const partCB = _.isFunction(part) ? part :  (slave) => slave[part];
+
+	const sortMethod = largest ? (left, right) => right.value - left.value : (left, right) => left.value - right.value;
+	return V.slaves.filter(slave => filter(slave))
+		.map(slave => ({slave, value: partCB(slave)}))
+		.sort(sortMethod)
+		.slice(0, count)
+		.map(slaveInfo => slaveInfo.slave);
+};
+
+/**
+ * @param {getBestSlavesParams} info
+ * @returns {number[]}
+ */
+globalThis.getBestSlavesIDs = function(info) {
+	return getBestSlaves(info).map(slave => slave.ID);
+};
+
+/**
+ * @param {App.Entity.SlaveState[]} [slaves]
+ * @returns {Object.<number, number>}
+ */
+globalThis.slaves2indices = function(slaves = V.slaves) {
+	return slaves.reduce((acc, slave, i) => { acc[slave.ID] = i; return acc; }, {});
+};
+
+/** Calculate various averages for the master suite slaves
+ * @returns {{energy: number, milk: number, cum: number, dom: number, sadism: number, dick: number, preg: number}}
+ */
+App.Utils.masterSuiteAverages = (function() {
+	const domMap = {dom: 1, submissive: -1};
+	const sadismMap = {sadism: 1, masochism: -1};
+
+	/** Returns either zero or the results of mapping the slave's fetish through an object containing fetish names and result values
+	 * @param {App.Entity.SlaveState} s
+	 * @param {Object.<string, number>} map
+	 * @returns {number}
+	 */
+	const fetishMapOrZero = (s, map) => map.hasOwnProperty(s.fetish) ? map[s.fetish] : 0;
+
+	return () => {
+		const msSlaves = App.Entity.facilities.masterSuite.employees();
+		return {
+			energy: _.mean(msSlaves.map(s => s.energy)),
+			milk: _.mean(msSlaves.map(s => s.lactation*(s.boobs-s.boobsImplant))),
+			cum: _.mean(msSlaves.map(s => canAchieveErection(s) ? s.balls : 0)),
+			dick: _.mean(msSlaves.map(s => canAchieveErection(s) ? s.dick : 0)),
+			preg: _.mean(msSlaves.map(s => s.preg)),
+			sadism: _.mean(msSlaves.map(s => (s.fetishStrength * fetishMapOrZero(s, sadismMap)))),
+			dom: _.mean(msSlaves.map(s => (s.fetishStrength * fetishMapOrZero(s, domMap))))
+		};
+	};
+})();
+
+globalThis.penthouseCensus = function() {
+	function occupiesRoom(slave) {
+		if (slave.rules.living !== "luxurious") {
+			return false; // assigned to dormitory
+		} else if (slave.assignment === Job.HEADGIRL && V.HGSuite > 0) {
+			return false; // lives in HG suite
+		} else if (slave.assignment === Job.BODYGUARD && V.dojo > 0) {
+			return false; // lives in dojo
+		} else if (slave.relationship >= 4) {
+			const partner = getSlave(slave.relationshipTarget);
+			if (assignmentVisible(partner) && partner.ID < slave.ID && partner.rules.living === "luxurious") {
+				return false; // living with partner, who is already assigned a room (always allocate a room to the partner with the lower ID)
+			}
+		}
+		return true; // takes her own room
+	}
+
+	const penthouseSlaves = V.slaves.filter(s => assignmentVisible(s));
+	V.roomsPopulation = penthouseSlaves.filter(occupiesRoom).length;
+	V.dormitoryPopulation = penthouseSlaves.filter(s => s.rules.living !== "luxurious").length;
+};
+
+/**
+ * @param {App.Entity.Facilities.Job|App.Entity.Facilities.Facility} jobOrFacility job or facility object
+ * @returns {App.Entity.SlaveState[]} array of slaves employed at the job or facility, sorted in accordance to user choice
+ */
+App.Utils.sortedEmployees = function(jobOrFacility) {
+	const employees = jobOrFacility.employees();
+	SlaveSort.slaves(employees);
+	return employees;
+};
+
+/**
+ * @param {Array<string|App.Entity.Facilities.Facility>} [facilities]
+ * @param {Object.<string, string>} [mapping] Optional mapping for the property names in the result object. Keys
+ * are the standard facility names, values are the desired names.
+ * @returns {Object.<string, number>}
+ */
+App.Utils.countFacilityWorkers = function(facilities = null, mapping = {}) {
+	facilities = facilities || Object.values(App.Entity.facilities);
+	/** @type {App.Entity.Facilities.Facility[]} */
+	const fObjects = facilities.map(f => typeof f === "string" ? App.Entity.facilities[f] : f);
+	return fObjects.reduce((acc, cur) => {
+		acc[mapping[cur.desc.baseName] || cur.desc.baseName] = cur.employeesIDs().size; return acc;
+	}, {});
+};
+
+/**
+ * @param {string[]} [assignments] array of assignment strings. The default is to count for all assignments
+ * @returns {Object.<string, number>}
+ */
+App.Utils.countAssignmentWorkers = function(assignments) {
+	assignments = assignments || Object.values(Job);
+	return assignments.reduce((acc, cur) => { acc[cur] = V.JobIDMap[cur].size; return acc; }, {});
+};
diff --git a/src/js/utilsUnits.js b/src/js/utilsUnits.js
new file mode 100644
index 0000000000000000000000000000000000000000..bb2ca3278f4b2654efcd1842b3420e60e24feec9
--- /dev/null
+++ b/src/js/utilsUnits.js
@@ -0,0 +1,505 @@
+/**
+ * Returns numbers as text, e.g. 10 as "ten", according to the player's settings
+ * @param {number} x
+ * @param {boolean} [printText=false] (optional)
+ * @returns {string}
+ */
+globalThis.num = function(x, printText = false) {
+	const max = V.showNumbersMax;
+
+	const ONE_TO_NINETEEN = [
+		"one", "two", "three", "four", "five",
+		"six", "seven", "eight", "nine", "ten",
+		"eleven", "twelve", "thirteen", "fourteen", "fifteen",
+		"sixteen", "seventeen", "eighteen", "nineteen",
+	];
+
+	const TENS = [
+		"ten", "twenty", "thirty", "forty", "fifty",
+		"sixty", "seventy", "eighty", "ninety",
+	];
+
+	const SCALES = ["thousand", "million", "billion", "trillion", "quadrillion", "quintillion", "sextillion", "septillion", "octillion", "nonillion", "decillion"];
+
+	/**
+	 * helper function for use with Array.filter
+	 * @param {any} item
+	 * @returns {boolean}
+	 */
+	function isTruthy(item) {
+		return !!item;
+	}
+
+	/**
+	 * convert a number into "chunks" of 0-999
+	 * @param {number} number
+	 * @returns {number[]}
+	 */
+	function chunk(number) {
+		const thousands = [];
+
+		while (number > 0) {
+			thousands.push(number % 1000);
+			number = Math.floor(number / 1000);
+		}
+
+		return thousands;
+	}
+
+	/**
+	 * translate a number from 1-999 into English
+	 * @param {number} number
+	 * @returns {string}
+	 */
+	function inEnglish(number) {
+		let hundreds;
+		let tens;
+		let ones;
+		const words = [];
+
+		if (number === 0) {
+			return "zero";
+		}
+
+		if (number < 20) {
+			return ONE_TO_NINETEEN[number - 1]; // may be undefined
+		}
+
+		if (number < 100) {
+			ones = number % 10;
+			tens = number / 10 | 0; // equivalent to Math.floor(number / 10)
+
+			words.push(TENS[tens - 1]);
+			words.push(inEnglish(ones));
+
+			return words.filter(isTruthy).join("-");
+		}
+
+		hundreds = number / 100 | 0;
+		words.push(inEnglish(hundreds));
+		words.push("hundred");
+		words.push(inEnglish(number % 100));
+
+		return words.filter(isTruthy).join(" ");
+	}
+
+	if (printText) {
+		return inEnglish(x);
+	}
+
+	/**
+	 * append the word for a scale. Made for use with Array.map
+	 * @param {string} chunk
+	 * @param {number} exp
+	 * @returns {string}
+	 */
+	function appendScale(chunk, exp) {
+		let scale;
+		if (!chunk) {
+			return null;
+		}
+		scale = SCALES[exp - 1];
+		return [chunk, scale].filter(isTruthy).join(" ");
+	}
+
+	if (V.showNumbers === 2) {
+		return commaNum(x);
+	} else {
+		if (x === 0) {
+			return "zero";
+		}
+
+		if (V.showNumbers === 1 && Math.abs(x) > max) {
+			return commaNum(x);
+		}
+
+		let numberAsString = chunk(Math.abs(x))
+			.map(inEnglish)
+			.map(appendScale)
+			.filter(isTruthy)
+			.reverse()
+			.join(" ");
+
+		if (x > 0) {
+			return numberAsString;
+		} else {
+			return `negative ${numberAsString}`;
+		}
+	}
+};
+
+globalThis.asPlural = function(single, plural) {
+	if (typeof single !== 'string') {
+		let asObj = single;
+		single = asObj.single;
+		plural = asObj.plural;
+	}
+	if (plural == null) {
+		plural = single + "s";
+	}
+	return plural;
+};
+globalThis.asSingular = function(single) {
+	if (typeof single !== 'string') {
+		let asObj = single;
+		single = asObj.single;
+	}
+	return single;
+};
+// When 1, shows "a (slave)"
+globalThis.numberWithPlural = function(number, single, plural) {
+	if (number === 0) {
+		return "no " + asPlural(single, plural);
+	} else if (number === 1) {
+		return addA(asSingular(single));
+	} else if (number > 0 && number < 1) {
+		return "less than one " + asSingular(single);
+	} else {
+		return number + " " + asPlural(single, plural);
+	}
+};
+
+// when 1, shows "one (slave)"
+globalThis.numberWithPluralOne = function(number, single, plural) {
+	if (number === 0) {
+		return "no " + asPlural(single, plural);
+	} else if (number === 1) {
+		return "one " + asSingular(single);
+	} else if (number > 0 && number < 1) {
+		return "less than one " + asSingular(single);
+	} else {
+		return number + " " + asPlural(single, plural);
+	}
+};
+// shows "less than one (slave)" instead of "no (slaves)" when number is 0.
+globalThis.numberWithPluralNonZero = function(number, single, plural) {
+	if (number === 0) {
+		number = 0.1;
+	}
+	return numberWithPlural(number, single, plural);
+};
+globalThis.onlyPlural = function(number, single, plural) {
+	if (number > 0 && number <= 1) {
+		return asSingular(single);
+	}
+	return asPlural(single, plural);
+};
+globalThis.Separator = function(SeparatorObject) {
+	if (SeparatorObject.need) {
+		return SeparatorObject.text;
+	}
+	SeparatorObject.need = true;
+	return "";
+};
+/**
+ * Returns numbers with comma, e.g. 10000 as "10,000", according to the player's settings
+ * @param {number} s
+ * @returns {string}
+ */
+globalThis.commaNum = function(s) {
+	// Separated from num because some places in code (like long lists, tables) should never have numbers spelled out, but still benefit from commas
+	if (!s) {
+		return "0";
+	}
+	if (V.formatNumbers !== 1) {
+		return s.toString();
+	} else {
+		return s.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+	}
+};
+
+/**
+ * Returns the number of weeks in a years / months / weeks format
+ * @param {number} weeks
+ * @returns {string}
+ */
+globalThis.years = function(weeks) {
+	let years = 0;
+	let quarters = 0; // needed for calc, not user facing
+	let months = 0;
+	let array = [];
+
+	// A year is always 52 weeks
+	// that could be 13 months, but lets say 4 quarters each getting an extra week (13 weeks)
+
+	// Find years
+	years = Math.trunc(weeks / 52);
+
+	if (years >= 1) { // Is there at least 1 year
+		weeks = weeks - (years * 52); // Find leftover weeks
+	}
+	if (weeks && weeks / 13 >= 1) { // Is there at least 1 quarter
+		quarters = Math.trunc(weeks / 13); // How many quarters?
+		weeks = weeks - (quarters * 13); // A quarter contains 13 weeks, how many extra weeks do we have?
+	}
+	if (weeks && weeks / 4 >= 1) { // Is there at least 1 month
+		months = Math.trunc(weeks / 4); // How many months?
+		if (months === 3) { // Almost a quarter of a year
+			months--; // Quarters have 13 weeks though, so let's be sure the extra is in weeks. Otherwise 51 will return "12 months" instead of "11 months and 4 weeks."
+		}
+		weeks = weeks - (months * 4); // A month contains 4 weeks, how many extra weeks do we have?
+	}
+
+	// So we have years, quarters, months, and weeks.
+
+	// Quarters are useless so:
+
+	months += quarters * 3; // Each quarter has three months.
+
+	if (years) {
+		array.push(`${num(years)} year${years !== 1 ? `s` : ``}`);
+	}
+
+	if (months) {
+		array.push(`${num(months)} month${months !== 1 ? `s` : ``}`);
+	}
+
+	if (weeks) {
+		array.push(`${num(weeks)} week${weeks !== 1 ? `s` : ``}`);
+	}
+
+	return array.toStringExt();
+};
+/**
+ * @param {number} [weeks]
+ * @param {number} [bonusDay]
+ * @returns {Date}
+ */
+globalThis.asDate = function(weeks = null, bonusDay = 0) {
+	if (weeks == null) {
+		weeks = V.week;
+	}
+	let d = new Date(2037, 0, 12);
+	d.setDate(d.getDate() + weeks * 7 + bonusDay);
+	return d;
+};
+/**
+ * @param {number} [weeks]
+ * @param {number} [bonusDay]
+ * @returns {string}
+ */
+globalThis.asDateString = function(weeks = null, bonusDay = 0) {
+	return asDate(weeks, bonusDay).toLocaleString(undefined, {year: 'numeric', month: 'long', day: 'numeric'});
+};
+
+/**
+ * @param {number} s
+ * @returns {string}
+ */
+globalThis.cashFormat = function(s) {
+	if (s < 0) {
+		return `-¤${commaNum(Math.abs(s))}`;
+	}
+	return `¤${commaNum(s)}`;
+};
+globalThis.cashFormatColor = function(s, invert = false) {
+	if (invert) {
+		s = -1 * s;
+	}
+	// Display red if the value is negative, unless invert is true
+	if (s < 0) {
+		return `<span class='red'>${cashFormat(s)}</span>`;
+		// White for exactly zero
+	} else if (s === 0) {
+		return `<span>${cashFormat(s)}</span>`;
+		// Yellow for positive
+	} else {
+		return `<span class='yellowgreen'>${cashFormat(s)}</span>`;
+	}
+};
+
+/**
+ * @param {number} s
+ * @returns {string}
+ */
+globalThis.repFormat = function(s) {
+	/* if (!s) { s = 0; }*/
+	if (V.cheatMode === 1 || V.debugMode === 1) {
+		if (s > 0) {
+			return `<span class="green">${commaNum(Math.round(s * 100) / 100)} rep</span>`;
+		} else if (s < 0) {
+			return `<span class="red">${commaNum(Math.round(s * 100) / 100)} rep</span>`;
+		} else {
+			return `${commaNum(Math.round(s * 100) / 100)} rep`;
+		}
+	} else {
+		/* In order to calculate just how much any one category matters so we can show a "fuzzy" symbolic value to the player, we need to know how "busy" reputation was this week. To calculate this, I ADD income to expenses. Why? 100 - 100 and 10000 - 10000 BOTH are 0, but a +50 event matters a lot more in the first case than the second. I exclude overflow from the calculation because it's not a "real" expense for our purposes, and divide by half just to make percentages a bit easier. */
+		let weight = s / (((V.lastWeeksRepIncome.Total - V.lastWeeksRepExpenses.Total) + V.lastWeeksRepExpenses.overflow) / 2);
+		if (weight > 0.60) {
+			return `<span class="green">+++++ rep</span>`;
+		} else if (weight > 0.45) {
+			return `<span class="green">++++ rep</span>`;
+		} else if (weight > 0.30) {
+			return `<span class="green">+++ rep</span>`;
+		} else if (weight > 0.15) {
+			return `<span class="green">++ rep</span>`;
+		} else if (weight > 0.0) {
+			return `<span class="green">+ rep</span>`;
+		} else if (weight === 0) {
+			return "0 rep";
+		} else if (weight < -0.60) {
+			return `<span class="red">&minus;&minus;&minus;&minus;&minus; rep</span>`;
+		} else if (weight < -0.45) {
+			return `<span class="red">&minus;&minus;&minus;&minus; rep</span>`;
+		} else if (weight < -0.30) {
+			return `<span class="red">&minus;&minus;&minus; rep</span>`;
+		} else if (weight < -0.15) {
+			return `<span class="red">&minus;&minus; rep</span>`;
+		} else if (weight < 0) {
+			return `<span class="red">&minus; rep</span>`;
+		}
+		/* return weight;*/
+	}
+};
+
+/**
+ * @param {number} s
+ * @returns {string}
+ */
+globalThis.massFormat = function(s) {
+	if (!s) {
+		s = 0;
+	}
+	if (Math.abs(s) >= 1000) {
+		s = Math.trunc(s / 1000);
+		if (s !== 1) {
+			return `${num(s)} tons`;
+		} else {
+			return `${num(s)} ton`;
+		}
+	} else {
+		return `${num(s)} kg`;
+	}
+};
+
+/**
+ * Takes an integer e.g. slave.hLength, returns a string in the format 10 inches
+ * @param {number} cm
+ * @returns {string}
+ */
+globalThis.cmToInchString = function(cm) {
+	let inches = cm / 2.54;
+	if (inches > 0 && inches < 1) {
+		return "less than an inch";
+	}
+	inches = Math.round(inches);
+	if (inches === 1) {
+		return "1 inch";
+	}
+	return `${inches} inches`;
+};
+
+/**
+ * takes an integer e.g. slave.height, returns a string in the format 6'5"
+ * @param {number} cm
+ * @returns {string}
+ */
+globalThis.cmToFootInchString = function(cm) {
+	if (Math.round(cm / 2.54) < 12) {
+		return cmToInchString(cm);
+	}
+	return `${Math.trunc(Math.round(cm / 2.54) / 12)}'${Math.round(cm / 2.54) % 12}"`;
+};
+
+/**
+ * takes a dick value e.g. slave.dick, returns a string in the format 6 inches
+ * @param {number} dick
+ * @returns {string}
+ */
+globalThis.dickToInchString = function(dick) {
+	return cmToInchString(dickToCM(dick));
+};
+
+/**
+ * takes a dick value e.g. slave.dick, returns an int of the dick length in cm
+ * @param {number} dick
+ * @returns {number}
+ */
+globalThis.dickToCM = function(dick) {
+	if (dick < 9) {
+		return dick * 5;
+	} else if (dick === 9) {
+		return 50;
+	}
+	return dick * 6;
+};
+/**
+ * takes a ball value e.g. slave.balls, returns a string in the format 3 inches
+ * @param {number} balls
+ * @returns {string}
+ */
+globalThis.ballsToInchString = function(balls) {
+	return cmToInchString(ballsToCM(balls));
+};
+
+/**
+ * takes a ball value e.g. slave.balls, returns an int of the ball size in cm
+ * @param {number} balls
+ * @returns {number}
+ */
+globalThis.ballsToCM = function(balls) {
+	if (balls < 2) {
+		return 0;
+	}
+	return (balls < 10 ? (balls - 1) * 2 : balls * 2);
+};
+
+/**
+ * takes a dick value e.g. slave.dick, returns a string in the format of either `20cm (8 inches)`, `8 inches`, or `20cm`
+ * @param {number} dick
+ * @returns {string}
+ */
+globalThis.dickToEitherUnit = function(dick) {
+	if (V.showInches === 1) {
+		return `${dickToCM(dick)}cm (${dickToInchString(dick)})`;
+	}
+	if (V.showInches === 2) {
+		return dickToInchString(dick);
+	}
+	return `${dickToCM(dick)}cm`;
+};
+
+/**
+ * takes a ball value e.g. slave.balls, returns a string in the format of either `20cm (8 inches)`, `8 inches`, or `20cm`
+ * @param {number} balls
+ * @returns {string}
+ */
+globalThis.ballsToEitherUnit = function(balls) {
+	if (V.showInches === 1) {
+		return `${ballsToCM(balls)}cm (${ballsToInchString(balls)})`;
+	}
+	if (V.showInches === 2) {
+		return ballsToInchString(balls);
+	}
+	return `${ballsToCM(balls)}cm`;
+};
+
+/**
+ * takes an int in centimeters e.g. slave.height, returns a string in the format of either `200cm (6'7")`, `6'7"`, or `200cm`
+ * @param {number} height
+ * @returns {string}
+ */
+globalThis.heightToEitherUnit = function(height) {
+	if (V.showInches === 1) {
+		return `${height}cm (${cmToFootInchString(height)})`;
+	}
+	if (V.showInches === 2) {
+		return cmToFootInchString(height);
+	}
+	return `${height}cm`;
+};
+
+/**
+ * takes an int in centimeters e.g. slave.hLength, returns a string in the format of either `30cm (12 inches)`, `12 inches`, or `30cm`
+ * @param {number} length
+ * @returns {string}
+ */
+globalThis.lengthToEitherUnit = function(length) {
+	if (V.showInches === 1) {
+		return `${length}cm (${cmToInchString(length)})`;
+	}
+	if (V.showInches === 2) {
+		return cmToInchString(length);
+	}
+	return `${length}cm`;
+};
diff --git a/src/npc/surgery/bodySwap/bodySwap.js b/src/npc/surgery/bodySwap/bodySwap.js
new file mode 100644
index 0000000000000000000000000000000000000000..6ddecdcff2200e9f4c5b2754ed3eb345745e7b97
--- /dev/null
+++ b/src/npc/surgery/bodySwap/bodySwap.js
@@ -0,0 +1,361 @@
+/**
+ *
+ * @param {App.Entity.SlaveState} soul
+ * @param {App.Entity.SlaveState} body
+ * @param {boolean} fromGenepool is slave from the genepool?
+ */
+globalThis.bodySwap = function(soul, body, fromGenepool) {
+	WombInit(body); // Just to be sure.
+	soul.genes = body.genes;
+	soul.physicalAge = body.physicalAge;
+	soul.visualAge = body.visualAge;
+	soul.ageImplant = body.ageImplant;
+	soul.health = body.health;
+	soul.weight = body.weight;
+	soul.muscles = body.muscles;
+	soul.height = body.height;
+	soul.heightImplant = body.heightImplant;
+	soul.race = body.race;
+	soul.origRace = body.origRace;
+	soul.pubicHColor = body.pubicHColor;
+	soul.skin = body.skin;
+	soul.origSkin = body.origSkin;
+	soul.markings = body.markings;
+	soul.eye = body.eye;
+	soul.hears = body.hears;
+	soul.earImplant = body.earImplant;
+	soul.earShape = body.earShape;
+	soul.earT = body.earT;
+	soul.earTColor = body.earTColor;
+	soul.smells = body.smells;
+	soul.tastes = body.tastes;
+	soul.horn = body.horn;
+	soul.hornColor = body.hornColor;
+	soul.PTail = body.PTail;
+	soul.tail = body.tail;
+	soul.tailShape = body.tailShape;
+	soul.tailColor = body.tailColor;
+	soul.origHColor = body.origHColor;
+	soul.hColor = body.hColor;
+	soul.hLength = body.hLength;
+	soul.hStyle = body.hStyle;
+	soul.pubicHStyle = body.pubicHStyle;
+	soul.waist = body.waist;
+	soul.corsetPiercing = body.corsetPiercing;
+	soul.arm = body.arm;
+	soul.leg = body.leg;
+	soul.PLimb = body.PLimb;
+	soul.readyProsthetics = body.readyProsthetics;
+	soul.heels = body.heels;
+	soul.voice = body.voice;
+	soul.voiceImplant = body.voiceImplant;
+	soul.shoulders = body.shoulders;
+	soul.shouldersImplant = body.shouldersImplant;
+	soul.boobs = body.boobs;
+	soul.boobsImplant = body.boobsImplant;
+	soul.boobsImplantType = body.boobsImplantType;
+	soul.boobShape = body.boobShape;
+	soul.nipples = body.nipples;
+	soul.nipplesPiercing = body.nipplesPiercing;
+	soul.nipplesAccessory = body.nipplesAccessory;
+	soul.areolae = body.areolae;
+	soul.areolaePiercing = body.areolaePiercing;
+	soul.areolaeShape = body.areolaeShape;
+	soul.boobsTat = body.boobsTat;
+	soul.lactation = body.lactation;
+	soul.lactationDuration = body.lactationDuration;
+	soul.induceLactation = body.induceLactation;
+	soul.boobsMilk = body.boobsMilk;
+	soul.lactationAdaptation = body.lactationAdaptation;
+	soul.hips = body.hips;
+	soul.hipsImplant = body.hipsImplant;
+	soul.butt = body.butt;
+	soul.buttImplant = body.buttImplant;
+	soul.buttImplantType = body.buttImplantType;
+	soul.buttTat = body.buttTat;
+	soul.face = body.face;
+	soul.faceImplant = body.faceImplant;
+	soul.faceShape = body.faceShape;
+	soul.lips = body.lips;
+	soul.lipsImplant = body.lipsImplant;
+	soul.lipsPiercing = body.lipsPiercing;
+	soul.lipsTat = body.lipsTat;
+	soul.teeth = body.teeth;
+	soul.tonguePiercing = body.tonguePiercing;
+	soul.vagina = body.vagina;
+	soul.vaginaLube = body.vaginaLube;
+	soul.vaginaPiercing = body.vaginaPiercing;
+	soul.vaginaTat = body.vaginaTat;
+	soul.fertKnown = body.fertKnown;
+	soul.fertPeak = body.fertPeak;
+	soul.broodmother = body.broodmother;
+	soul.broodmotherFetuses = body.broodmotherFetuses;
+	soul.broodmotherOnHold = body.broodmotherOnHold;
+	soul.broodmotherCountDown = body.broodmotherCountDown;
+	soul.labia = body.labia;
+	soul.clit = body.clit;
+	soul.clitPiercing = body.clitPiercing;
+	soul.dick = body.dick;
+	soul.foreskin = body.foreskin;
+	soul.anus = body.anus;
+	soul.analArea = body.analArea;
+	soul.dickPiercing = body.dickPiercing;
+	soul.dickTat = body.dickTat;
+	soul.prostate = body.prostate;
+	soul.balls = body.balls;
+	soul.scrotum = body.scrotum;
+	soul.ovaries = body.ovaries;
+	soul.anusPiercing = body.anusPiercing;
+	soul.anusTat = body.anusTat;
+	soul.brand = body.brand;
+	soul.nosePiercing = body.nosePiercing;
+	soul.eyebrowPiercing = body.eyebrowPiercing;
+	soul.navelPiercing = body.navelPiercing;
+	soul.shouldersTat = body.shouldersTat;
+	soul.armsTat = body.armsTat;
+	soul.legsTat = body.legsTat;
+	soul.backTat = body.backTat;
+	soul.stampTat = body.stampTat;
+	soul.hormones = body.hormones;
+	soul.chem = body.chem;
+	soul.vaginalAttachment = body.vaginalAttachment;
+	soul.chastityVagina = body.chastityVagina;
+	soul.chastityPenis = body.chastityPenis;
+	soul.chastityAnus = body.chastityAnus;
+	if (soul.custom && body.custom) {
+		soul.custom.tattoo = body.custom.tattoo;
+	}
+	soul.bellyTat = body.bellyTat;
+	soul.abortionTat = body.abortionTat;
+	soul.birthsTat = body.birthsTat;
+	soul.pubertyAgeXX = body.pubertyAgeXX;
+	soul.pubertyXX = body.pubertyXX;
+	soul.pubertyAgeXY = body.pubertyAgeXY;
+	soul.pubertyXY = body.pubertyXY;
+	soul.breedingMark = body.breedingMark;
+	soul.underArmHColor = body.underArmHColor;
+	soul.underArmHStyle = body.underArmHStyle;
+	soul.ballType = body.ballType;
+	soul.eggType = body.eggType;
+	soul.bald = body.bald;
+	soul.hormoneBalance = body.hormoneBalance;
+	soul.breastMesh = body.breastMesh;
+	soul.vasectomy = body.vasectomy;
+	soul.haircuts = body.haircuts;
+	soul.ovaryAge = body.ovaryAge;
+	soul.readyOva = body.readyOva;
+	soul.womb = body.womb; // this is array assigned by reference, if slave body that is ${body} will be still used anywhere in code (not discarded) — it's WRONG (they now technically share one womb object). Please tell me about it then. But if old body body just discarded — it's no problem then.
+	soul.pregAdaptation = body.pregAdaptation;
+	soul.geneMods = body.geneMods;
+	soul.NCSyouthening = body.NCSyouthening;
+	soul.eyebrowHColor = body.eyebrowHColor;
+	soul.eyebrowHStyle = body.eyebrowHStyle;
+	soul.eyebrowFullness = body.eyebrowFullness;
+	soul.wombImplant = body.wombImplant;
+	soul.ovaImplant = body.ovaImplant;
+	soul.geneticQuirks = body.geneticQuirks;
+	soul.albinismOverride = body.albinismOverride;
+	soul.clone = body.clone;
+	soul.cloneID = body.cloneID;
+	soul.inbreedingCoeff = body.inbreedingCoeff;
+
+	soul.canRecruit = 0;
+
+	if (!fromGenepool) { // swapping NOT gene pool records
+		soul.counter.publicUse = body.counter.publicUse;
+		soul.counter.laborCount = body.counter.laborCount;
+		soul.porn = body.porn;
+		soul.aphrodisiacs = body.aphrodisiacs;
+		soul.curatives = body.curatives;
+		soul.drugs = body.drugs;
+		soul.prestige = body.prestige;
+		soul.prestigeDesc = body.prestigeDesc;
+		soul.minorInjury = body.minorInjury;
+		soul.eyewear = body.eyewear;
+		soul.earwear = body.earwear;
+		soul.earPiercing = body.earPiercing;
+		soul.armAccessory = body.armAccessory;
+		soul.legAccessory = body.legAccessory;
+		soul.bellyAccessory = body.bellyAccessory;
+		soul.preg = body.preg;
+		soul.pregSource = body.pregSource;
+		soul.pregType = body.pregType;
+		soul.labor = body.labor;
+		soul.clitSetting = body.clitSetting;
+		soul.diet = body.diet;
+		soul.dietCum = body.dietCum;
+		soul.dietMilk = body.dietMilk;
+		soul.clothes = body.clothes;
+		soul.collar = body.collar;
+		soul.faceAccessory = body.faceAccessory;
+		soul.mouthAccessory = body.mouthAccessory;
+		soul.shoes = body.shoes;
+		soul.makeup = body.makeup;
+		soul.nails = body.nails;
+		soul.vaginalAccessory = body.vaginalAccessory;
+		soul.dickAccessory = body.dickAccessory;
+		soul.buttplug = body.buttplug;
+		soul.buttplugAttachment = body.buttplugAttachment;
+		soul.induce = body.induce;
+		soul.mpreg = body.mpreg;
+		soul.pregKnown = body.pregKnown;
+		soul.pregWeek = body.pregWeek;
+		soul.belly = body.belly;
+		soul.bellyPreg = body.bellyPreg;
+		soul.bellyFluid = body.bellyFluid;
+		soul.bellyImplant = body.bellyImplant;
+		soul.bellySag = body.bellySag;
+		soul.bellySagPreg = body.bellySagPreg;
+		soul.bellyPain = body.bellyPain;
+		soul.cervixImplant = body.cervixImplant;
+		soul.scar = body.scar;
+		soul.pregControl = body.pregControl;
+		deflate(soul);
+	}
+};
+/**
+ *
+ * @param {App.Entity.SlaveState} soul
+ * @param {App.Entity.SlaveState} body
+ */
+globalThis.bodySwapName = function(soul, body) {
+	if (body.bodySwap === 0) {
+		if (body.birthSurname) {
+			if (body.birthName !== "") {
+				soul.origBodyOwner = SlaveFullBirthName(body);
+			} else {
+				soul.origBodyOwner = body.slaveName + " " + body.birthSurname;
+			}
+		} else if (body.birthName) {
+			if (body.slaveSurname) {
+				if (
+					(V.surnameOrder !== 1 && ["Cambodian", "Chinese", "Hungarian", "Japanese", "Korean", "Mongolian", "Taiwanese", "Vietnamese"].includes(body.nationality)) ||
+					V.surnameOrder === 2
+				) {
+					soul.origBodyOwner = body.slaveSurname + " " + body.birthName;
+				} else {
+					soul.origBodyOwner = body.birthName + " " + body.slaveSurname;
+				}
+			} else {
+				soul.origBodyOwner = body.birthName;
+			}
+		} else if (body.slaveSurname) {
+			soul.origBodyOwner = SlaveFullName(body);
+		} else {
+			soul.origBodyOwner = body.slaveName;
+		}
+	} else {
+		soul.origBodyOwner = body.origBodyOwner;
+	}
+};
+
+/**
+ *
+ * @param {App.Entity.SlaveState} soul
+ * @returns {DocumentFragment}
+ */
+globalThis.bodySwapSelection = function(soul) {
+	const {him} = getPronouns(soul);
+	const el = new DocumentFragment();
+	const cost = 10000;
+	App.UI.DOM.appendNewElement("div", el, `The surgeon awaits the pair of slaves to be strapped into the surgery. So far only ${soul.slaveName} is prepped:`, "scene-intro");
+	App.UI.DOM.appendNewElement("div", el, `Select ${(V.seeExtreme) ? `an eligible slave (any slave who is not a fuckdoll)` : `a slave`} who will be trading bodies with ${him}. This operation will cost ${cashFormat(cost)}.`);
+
+	for (const body of V.slaves) {
+		const slaveDiv = document.createElement("div");
+		slaveDiv.append(
+			App.UI.DOM.link(
+				body.slaveName,
+				() => {
+					V.swappingSlave = body;
+					cashX(forceNeg(cost), "slaveSurgery", body);
+				},
+				[],
+				"Slave Slave Swap"
+			)
+		);
+		const relTerm = relativeTerm(soul, body);
+		if (relTerm) {
+			slaveDiv.append(` ${relTerm}`);
+		}
+		if (body.relationshipTarget === soul.ID) {
+			const {wife} = getPronouns(body);
+			switch (body.relationship) {
+				case 1:
+					slaveDiv.append(` friends`);
+					break;
+				case 2:
+					slaveDiv.append(` best friends`);
+					break;
+				case 3:
+					slaveDiv.append(` friends with benefits`);
+					break;
+				case 4:
+					slaveDiv.append(` lover`);
+					break;
+				case 5:
+					slaveDiv.append(` slave${wife}`);
+					break;
+			}
+		}
+		if (body.rivalryTarget === soul.ID) {
+			switch (body.relationship) {
+				case 1:
+					slaveDiv.append(`dislikes`);
+					break;
+				case 2:
+					slaveDiv.append(`rival`);
+					break;
+				case 3:
+					slaveDiv.append(`bitterly hates`);
+					break;
+			}
+		}
+		el.append(slaveDiv);
+	}
+
+	return el;
+};
+
+/**
+ *
+ * @param {App.Entity.SlaveState} body
+ * @returns {DocumentFragment}
+ */
+globalThis.huskSwapSelection = function(body) {
+	const el = new DocumentFragment();
+	const cost = 10000;
+	App.UI.DOM.appendNewElement("div", el, `"This operation is neither simple nor is it perfected. There are extreme health risks involved and no guarantee of success. Strap a slave into your remote surgery to consent to the operation. Indentured servants${(V.incubator > 0) || (V.nurseryChildren) ? `and slaves with reserved children`:``} are not eligible."
+	`, "scene-intro");
+	App.UI.DOM.appendNewElement("div", el, `Select the slave whose mind will be transferred into the waiting husk. Amputated slaves must not be wearing prosthetics. This operation will cost ${cashFormat(cost)}.`);
+
+	for (const soul of V.slaves) {
+		if (isSlaveAvailable(soul)) {
+			if (soul.fuckdoll === 0) {
+				if (!hasAnyProstheticLimbs(soul)) {
+					if (soul.indenture === -1) {
+						if (soul.breedingMark === 0 || V.propOutcome === 0 || V.eugenicsFullControl === 1 || V.arcologies[0].FSRestart === "unset") {
+							if (WombReserveCount(soul) === 0) {
+								if (soul.ID !== body.ID) {
+									App.UI.DOM.appendNewElement("div", el,
+										App.UI.DOM.link(
+											soul.slaveName,
+											() => {
+												V.swappingSlave = soul;
+												cashX(forceNeg(cost), "slaveSurgery", soul);
+											},
+											[],
+											"Husk Slave Swap"
+										)
+									);
+								}
+							}
+						}
+					}
+				}
+			}
+		}
+	}
+
+	return el;
+};
diff --git a/src/pregmod/widgets/bodySwapReaction.tw b/src/npc/surgery/bodySwap/bodySwapReaction.tw
similarity index 100%
rename from src/pregmod/widgets/bodySwapReaction.tw
rename to src/npc/surgery/bodySwap/bodySwapReaction.tw
diff --git a/src/pregmod/huskSlaveSwap.tw b/src/npc/surgery/bodySwap/huskSlaveSwap.tw
similarity index 98%
rename from src/pregmod/huskSlaveSwap.tw
rename to src/npc/surgery/bodySwap/huskSlaveSwap.tw
index d945e7618334678a1f51cee0f76cd75ccfdc01cf..2c3fd583c71c2a1088d8f5e470758842ae28e4f6 100644
--- a/src/pregmod/huskSlaveSwap.tw
+++ b/src/npc/surgery/bodySwap/huskSlaveSwap.tw
@@ -5,7 +5,7 @@
 <<set _m = $slaveIndices[$swappingSlave.ID]>>
 
 You strap $slaves[_m].slaveName, and the body to which $he will be transferred, into the remote surgery and stand back as it goes to work.
-<<BodySwap $slaves[_m] $activeSlave>>
+<<run bodySwap($slaves[_m], $activeSlave)>>
 <<set _gps = $genePool.findIndex(function(s) { return s.ID == $slaves[_m].ID; })>>
 <<set $genePool[_gps].race = $slaves[_m].race, $genePool[_gps].origRace = $slaves[_m].origRace, $genePool[_gps].skin = $slaves[_m].skin, $genePool[_gps].markings = $slaves[_m].markings, $genePool[_gps].eye.origColor = $slaves[_m].eye.origColor, $genePool[_gps].origHColor = $slaves[_m].origHColor, $genePool[_gps].origSkin = $slaves[_m].origSkin, $genePool[_gps].face = $slaves[_m].face, $genePool[_gps].pubicHStyle = $slaves[_m].pubicHStyle, $genePool[_gps].underArmHStyle = $slaves[_m].underArmHStyle, $genePool[_gps].eyebrowHStyle = $slaves[_m].eyebrowHStyle>> /* special exception to swap genePool since the temporary body lacks an entry. Otherwise we could just call the widget using the genePool entries */
 
diff --git a/src/pregmod/slaveSlaveSwap.tw b/src/npc/surgery/bodySwap/slaveSlaveSwap.tw
similarity index 84%
rename from src/pregmod/slaveSlaveSwap.tw
rename to src/npc/surgery/bodySwap/slaveSlaveSwap.tw
index 9ef4e1554ac05b70182a6b3ebeac1b2f7d65ee27..877285090c0dde7186286ae467153c850b8a9a7f 100644
--- a/src/pregmod/slaveSlaveSwap.tw
+++ b/src/npc/surgery/bodySwap/slaveSlaveSwap.tw
@@ -11,10 +11,10 @@
 <<set _gps2Clone = clone($genePool[_gps2])>>
 
 You strap <<= getSlave($AS).slaveName>> and $swappingSlave.slaveName into the remote surgery and stand back as it goes to work.
-<<BodySwap $slaves[_ss1] _ss2Clone>>
-<<BodySwap $genePool[_gps1] _gps2Clone 1>> /* passing a third argument just to detect if it's a slave from the genepool */
-<<BodySwap $slaves[_ss2] _ss1Clone>>
-<<BodySwap $genePool[_gps2] _gps1Clone 1>> /* passing a third argument just to detect if it's a slave from the genepool */
+<<run bodySwap($slaves[_ss1], _ss2Clone)>>
+<<run bodySwap($genePool[_gps1], _gps2Clone, true)>> /* passing a third argument just to detect if it's a slave from the genepool */
+<<run bodySwap($slaves[_ss2], _ss1Clone)>>
+<<run bodySwap($genePool[_gps2], _gps1Clone, true)>> /* passing a third argument just to detect if it's a slave from the genepool */
 
 <br><br>
 After an honestly impressive procedure, $slaves[_ss1].slaveName is recovering nicely.
@@ -49,8 +49,8 @@ In the neighboring bed, $slaves[_ss2].slaveName rests peacefully.
 
 /* now to handle whose body it is, name-wise */
 
-<<BodySwapName $slaves[_ss1] $slaves[_ss2]>>
-<<BodySwapName $slaves[_ss2] $slaves[_ss1]>>
+<<run bodySwapName($slaves[_ss1], $slaves[_ss2])>>
+<<run bodySwapName($slaves[_ss2], $slaves[_ss1])>>
 
 <<if _ss1Clone.bodySwap > 0>>
 	<<if $slaves[_ss1].origBodyOwnerID == $slaves[_ss1].ID>>
diff --git a/src/pregmod/huskSlaveSwapWorkaround.tw b/src/pregmod/huskSlaveSwapWorkaround.tw
deleted file mode 100644
index 112c4ccb092363baabaf74ad3793c93874941b34..0000000000000000000000000000000000000000
--- a/src/pregmod/huskSlaveSwapWorkaround.tw
+++ /dev/null
@@ -1,34 +0,0 @@
-:: husk Slave Swap Workaround [nobr]
-
-<<if $activeSlave.tankBaby != 3>>
-	<<set $nextButton = "Abort Operation", $nextLink = "Scheduled Event", $returnTo = "Scheduled Event">>
-<<else>>
-	<<set $nextButton = "Abort Operation", $nextLink = "Main", $returnTo = "Incubator">>
-<</if>>
-
-"This operation is neither simple nor is it perfected. There are extreme health risks involved and no guarantee of success. Strap a slave into your remote surgery to consent to the operation. Indentured servants<<if ($incubator > 0) || ($nurseryChildren)>> and slaves with reserved children<</if>> are not eligible."
-<br>
-//Select the slave whose mind will be transferred into the waiting husk. Amputated slaves must not be wearing prosthetics. This operation will cost <<print cashFormat(10000)>>.//
-
-<br><br>
-__Select an eligible slave:__
-<<for _i = 0; _i < $slaves.length; _i++>>
-	<<capture _i>>
-		<<if isSlaveAvailable($slaves[_i])>>
-			<<if $slaves[_i].fuckdoll == 0>>
-				<<if !hasAnyProstheticLimbs($slaves[_i])>>
-					<<if $slaves[_i].indenture == -1>>
-						<<if $slaves[_i].breedingMark == 0 || $propOutcome == 0 || $eugenicsFullControl == 1 || $arcologies[0].FSRestart == "unset">>
-							<<if WombReserveCount($slaves[_i]) == 0>>
-								<<if $slaves[_i].ID != $AS>>
-									<<set _name = SlaveFullName($slaves[_i])>>
-									<br>[[_name|Husk Slave Swap][$swappingSlave = $slaves[_i], cashX(-10000, "slaveSurgery", $slaves[_i])]]
-								<</if>>
-							<</if>>
-						<</if>>
-					<</if>>
-				<</if>>
-			<</if>>
-		<</if>>
-	<</capture>>
-<</for>>
diff --git a/src/pregmod/seHuskSlaveDelivery.tw b/src/pregmod/seHuskSlaveDelivery.tw
index 59a79704dd47a927e67d14874f6b0b11d46f4411..2d8d2c360284f90552626549d426556b457db320 100644
--- a/src/pregmod/seHuskSlaveDelivery.tw
+++ b/src/pregmod/seHuskSlaveDelivery.tw
@@ -73,7 +73,7 @@ A slave came in fitting the description you provided.
 <<if $cash >= $surgeryCost>>
 	<<link "Accept the offered slave and contact the bodyswap surgeon.">>
 		<<set $activeSlave = _husk>>
-		<<goto "husk Slave Swap Workaround">>
+		<<goto "Husk Slave Swap Workaround">>
 	<</link>>
 	//Will significantly increase the selected slave's upkeep.//
 <<else>>
diff --git a/src/pregmod/slaveSlaveSwapWorkaround.tw b/src/pregmod/slaveSlaveSwapWorkaround.tw
deleted file mode 100644
index 72ee529f98f13facdc558e6afab2ce7ee8578f93..0000000000000000000000000000000000000000
--- a/src/pregmod/slaveSlaveSwapWorkaround.tw
+++ /dev/null
@@ -1,22 +0,0 @@
-:: Slave Slave Swap Workaround [nobr]
-
-<<set $nextButton = "Abort Operation", $nextLink = "Main">>
-
-The surgeon awaits the pair of slaves to be strapped into the surgery. So far only <<= getSlave($AS).slaveName>> is prepped:
-<br>
-//Select the slave who will be trading bodies with $him. This operation will cost <<print cashFormat(10000)>>.//
-
-<br><br>
-__Select an eligible slave:__
-<<for _i = 0; _i < $slaves.length; _i++>>
-	<<capture _i>>
-		<<if isSlaveAvailable($slaves[_i])>>
-			<<if $slaves[_i].fuckdoll == 0>>
-				<<if ($slaves[_i].ID != $AS)>>
-					<<set _name = SlaveFullName($slaves[_i])>>
-					<br>[[_name|Slave Slave Swap][$swappingSlave = $slaves[_i], cashX(-10000, "slaveSurgery", $slaves[_i])]]
-				<</if>>
-			<</if>>
-		<</if>>
-	<</capture>>
-<</for>>
\ No newline at end of file
diff --git a/src/pregmod/widgets/bodyswapWidgets.tw b/src/pregmod/widgets/bodyswapWidgets.tw
deleted file mode 100644
index bd5cd97a0e398f7caa1c0675d81c48b203c3fd86..0000000000000000000000000000000000000000
--- a/src/pregmod/widgets/bodyswapWidgets.tw
+++ /dev/null
@@ -1,259 +0,0 @@
-:: bodyswap widgets [nobr widget]
-
-/* It's too fucking big jesus christ */
-
-/* first arg is slave getting swapped, second is body she is being swapped to. The second body's physical traits overwrite the originals */
-<<widget "BodySwap">>
-
-<<set WombInit($args[1])>> /*Just to be sure.*/
-<<set $args[0].genes = $args[1].genes>>
-<<set $args[0].physicalAge = $args[1].physicalAge>>
-<<set $args[0].visualAge = $args[1].visualAge>>
-<<set $args[0].ageImplant = $args[1].ageImplant>>
-<<set $args[0].health.condition = $args[1].health.condition>>
-<<set $args[0].health.shortDamage = $args[1].health.shortDamage>>
-<<set $args[0].health.longDamage = $args[1].health.longDamage>>
-<<set $args[0].health.illness = $args[1].health.illness>>
-<<set $args[0].health.tired = $args[1].health.tired>>
-<<set $args[0].health.health = $args[1].health.health>>
-<<set $args[0].weight = $args[1].weight>>
-<<set $args[0].muscles = $args[1].muscles>>
-<<set $args[0].height = $args[1].height>>
-<<set $args[0].heightImplant = $args[1].heightImplant>>
-<<set $args[0].race = $args[1].race>>
-<<set $args[0].origRace = $args[1].origRace>>
-<<set $args[0].pubicHColor = $args[1].pubicHColor>>
-<<set $args[0].skin = $args[1].skin>>
-<<set $args[0].origSkin = $args[1].origSkin>>
-<<set $args[0].markings = $args[1].markings>>
-<<set $args[0].eye = $args[1].eye>>
-<<set $args[0].hears = $args[1].hears>>
-<<set $args[0].earImplant = $args[1].earImplant>>
-<<set $args[0].earShape = $args[1].earShape>>
-<<set $args[0].earT = $args[1].earT>>
-<<set $args[0].earTColor = $args[1].earTColor>>
-<<set $args[0].smells = $args[1].smells>>
-<<set $args[0].tastes = $args[1].tastes>>
-<<set $args[0].horn = $args[1].horn>>
-<<set $args[0].hornColor = $args[1].hornColor>>
-<<set $args[0].PTail = $args[1].PTail>>
-<<set $args[0].tail = $args[1].tail>>
-<<set $args[0].tailShape = $args[1].tailShape>>
-<<set $args[0].tailColor = $args[1].tailColor>>
-<<set $args[0].origHColor = $args[1].origHColor>>
-<<set $args[0].hColor = $args[1].hColor>>
-<<set $args[0].hLength = $args[1].hLength>>
-<<set $args[0].hStyle = $args[1].hStyle>>
-<<set $args[0].pubicHStyle = $args[1].pubicHStyle>>
-<<set $args[0].waist = $args[1].waist>>
-<<set $args[0].corsetPiercing = $args[1].corsetPiercing>>
-<<set $args[0].arm = $args[1].arm>>
-<<set $args[0].leg = $args[1].leg>>
-<<set $args[0].PLimb = $args[1].PLimb>>
-<<set $args[0].readyProsthetics = $args[1].readyProsthetics>>
-<<set $args[0].heels = $args[1].heels>>
-<<set $args[0].voice = $args[1].voice>>
-<<set $args[0].voiceImplant = $args[1].voiceImplant>>
-<<set $args[0].shoulders = $args[1].shoulders>>
-<<set $args[0].shouldersImplant = $args[1].shouldersImplant>>
-<<set $args[0].boobs = $args[1].boobs>>
-<<set $args[0].boobsImplant = $args[1].boobsImplant>>
-<<set $args[0].boobsImplantType = $args[1].boobsImplantType>>
-<<set $args[0].boobShape = $args[1].boobShape>>
-<<set $args[0].nipples = $args[1].nipples>>
-<<set $args[0].nipplesPiercing = $args[1].nipplesPiercing>>
-<<set $args[0].nipplesAccessory = $args[1].nipplesAccessory>>
-<<set $args[0].areolae = $args[1].areolae>>
-<<set $args[0].areolaePiercing = $args[1].areolaePiercing>>
-<<set $args[0].areolaeShape = $args[1].areolaeShape>>
-<<set $args[0].boobsTat = $args[1].boobsTat>>
-<<set $args[0].lactation = $args[1].lactation>>
-<<set $args[0].lactationDuration = $args[1].lactationDuration>>
-<<set $args[0].induceLactation = $args[1].induceLactation>>
-<<set $args[0].boobsMilk = $args[1].boobsMilk>>
-<<set $args[0].lactationAdaptation = $args[1].lactationAdaptation>>
-<<set $args[0].hips = $args[1].hips>>
-<<set $args[0].hipsImplant = $args[1].hipsImplant>>
-<<set $args[0].butt = $args[1].butt>>
-<<set $args[0].buttImplant = $args[1].buttImplant>>
-<<set $args[0].buttImplantType = $args[1].buttImplantType>>
-<<set $args[0].buttTat = $args[1].buttTat>>
-<<set $args[0].face = $args[1].face>>
-<<set $args[0].faceImplant = $args[1].faceImplant>>
-<<set $args[0].faceShape = $args[1].faceShape>>
-<<set $args[0].lips = $args[1].lips>>
-<<set $args[0].lipsImplant = $args[1].lipsImplant>>
-<<set $args[0].lipsPiercing = $args[1].lipsPiercing>>
-<<set $args[0].lipsTat = $args[1].lipsTat>>
-<<set $args[0].teeth = $args[1].teeth>>
-<<set $args[0].tonguePiercing = $args[1].tonguePiercing>>
-<<set $args[0].vagina = $args[1].vagina>>
-<<set $args[0].vaginaLube = $args[1].vaginaLube>>
-<<set $args[0].vaginaPiercing = $args[1].vaginaPiercing>>
-<<set $args[0].vaginaTat = $args[1].vaginaTat>>
-<<set $args[0].fertKnown = $args[1].fertKnown>>
-<<set $args[0].fertPeak = $args[1].fertPeak>>
-<<set $args[0].broodmother = $args[1].broodmother>>
-<<set $args[0].broodmotherFetuses = $args[1].broodmotherFetuses>>
-<<set $args[0].broodmotherOnHold = $args[1].broodmotherOnHold>>
-<<set $args[0].broodmotherCountDown = $args[1].broodmotherCountDown>>
-<<set $args[0].labia = $args[1].labia>>
-<<set $args[0].clit = $args[1].clit>>
-<<set $args[0].clitPiercing = $args[1].clitPiercing>>
-<<set $args[0].dick = $args[1].dick>>
-<<set $args[0].foreskin = $args[1].foreskin>>
-<<set $args[0].anus = $args[1].anus>>
-<<set $args[0].analArea = $args[1].analArea>>
-<<set $args[0].dickPiercing = $args[1].dickPiercing>>
-<<set $args[0].dickTat = $args[1].dickTat>>
-<<set $args[0].prostate = $args[1].prostate>>
-<<set $args[0].balls = $args[1].balls>>
-<<set $args[0].scrotum = $args[1].scrotum>>
-<<set $args[0].ovaries = $args[1].ovaries>>
-<<set $args[0].anusPiercing = $args[1].anusPiercing>>
-<<set $args[0].anusTat = $args[1].anusTat>>
-<<set $args[0].brand = $args[1].brand>>
-<<set $args[0].nosePiercing = $args[1].nosePiercing>>
-<<set $args[0].eyebrowPiercing = $args[1].eyebrowPiercing>>
-<<set $args[0].navelPiercing = $args[1].navelPiercing>>
-<<set $args[0].shouldersTat = $args[1].shouldersTat>>
-<<set $args[0].armsTat = $args[1].armsTat>>
-<<set $args[0].legsTat = $args[1].legsTat>>
-<<set $args[0].backTat = $args[1].backTat>>
-<<set $args[0].stampTat = $args[1].stampTat>>
-<<set $args[0].hormones = $args[1].hormones>>
-<<set $args[0].chem = $args[1].chem>>
-<<set $args[0].vaginalAttachment = $args[1].vaginalAttachment>>
-<<set $args[0].chastityVagina = $args[1].chastityVagina>>
-<<set $args[0].chastityPenis = $args[1].chastityPenis>>
-<<set $args[0].chastityAnus = $args[1].chastityAnus>>
-<<if (def $args[0].custom) && (def $args[1].custom)>>
-	<<set $args[0].custom.tattoo = $args[1].custom.tattoo>>
-<</if>>
-<<set $args[0].bellyTat = $args[1].bellyTat>>
-<<set $args[0].abortionTat = $args[1].abortionTat>>
-<<set $args[0].birthsTat = $args[1].birthsTat>>
-<<set $args[0].pubertyAgeXX = $args[1].pubertyAgeXX>>
-<<set $args[0].pubertyXX = $args[1].pubertyXX>>
-<<set $args[0].pubertyAgeXY = $args[1].pubertyAgeXY>>
-<<set $args[0].pubertyXY = $args[1].pubertyXY>>
-<<set $args[0].breedingMark = $args[1].breedingMark>>
-<<set $args[0].underArmHColor = $args[1].underArmHColor>>
-<<set $args[0].underArmHStyle = $args[1].underArmHStyle>>
-<<set $args[0].ballType = $args[1].ballType>>
-<<set $args[0].eggType = $args[1].eggType>>
-<<set $args[0].bald = $args[1].bald>>
-<<set $args[0].hormoneBalance = $args[1].hormoneBalance>>
-<<set $args[0].breastMesh = $args[1].breastMesh>>
-<<set $args[0].vasectomy = $args[1].vasectomy>>
-<<set $args[0].haircuts = $args[1].haircuts>>
-<<set $args[0].ovaryAge = $args[1].ovaryAge>>
-<<set $args[0].readyOva = $args[1].readyOva>>
-<<set $args[0].womb = $args[1].womb>> /* this is array assigned by reference, if slave body that is $args[1] will be still used anywhere in code (not discarded) — it's WRONG (they now technically share one womb object). Please tell me about it then. But if old body $args[1] just discarded — it's no problem then.*/
-<<set $args[0].pregAdaptation = $args[1].pregAdaptation>>
-<<set $args[0].geneMods = $args[1].geneMods>>
-<<set $args[0].NCSyouthening = $args[1].NCSyouthening>>
-<<set $args[0].eyebrowHColor = $args[1].eyebrowHColor>>
-<<set $args[0].eyebrowHStyle = $args[1].eyebrowHStyle>>
-<<set $args[0].eyebrowFullness = $args[1].eyebrowFullness>>
-<<set $args[0].wombImplant = $args[1].wombImplant>>
-<<set $args[0].ovaImplant = $args[1].ovaImplant>>
-<<set $args[0].geneticQuirks = $args[1].geneticQuirks>>
-<<set $args[0].albinismOverride = $args[1].albinismOverride>>
-<<set $args[0].clone = $args[1].clone>>
-<<set $args[0].cloneID = $args[1].cloneID>>
-<<set $args[0].inbreedingCoeff = $args[1].inbreedingCoeff>>
-
-<<set $args[0].canRecruit = 0>>
-
-<<if $args[2] != 1>> /* swapping NOT gene pool records */
-	<<set $args[0].counter.publicUse = $args[1].counter.publicUse>>
-	<<set $args[0].counter.laborCount = $args[1].counter.laborCount>>
-	<<set $args[0].porn = $args[1].porn>>
-	<<set $args[0].aphrodisiacs = $args[1].aphrodisiacs>>
-	<<set $args[0].curatives = $args[1].curatives>>
-	<<set $args[0].drugs = $args[1].drugs>>
-	<<set $args[0].prestige = $args[1].prestige>>
-	<<set $args[0].porn.viewerCount = $args[1].porn.viewerCount>>
-	<<set $args[0].porn.prestige = $args[1].porn.prestige>>
-	<<set $args[0].porn.prestigeDesc = $args[1].porn.prestigeDesc>>
-	<<set $args[0].prestigeDesc = $args[1].prestigeDesc>>
-	<<set $args[0].minorInjury = $args[1].minorInjury>>
-	<<set $args[0].eyewear = $args[1].eyewear>>
-	<<set $args[0].earwear = $args[1].earwear>>
-	<<set $args[0].earsPiercing = $args[1].earsPiercing>>
-	<<set $args[0].armAccessory = $args[1].armAccessory>>
-	<<set $args[0].legAccessory = $args[1].legAccessory>>
-	<<set $args[0].bellyAccessory = $args[1].bellyAccessory>>
-	<<set $args[0].preg = $args[1].preg>>
-	<<set $args[0].pregSource = $args[1].pregSource>>
-	<<set $args[0].pregType = $args[1].pregType>>
-	<<set $args[0].labor = $args[1].labor>>
-	<<set $args[0].clitSetting = $args[1].clitSetting>>
-	<<set $args[0].diet = $args[1].diet>>
-	<<set $args[0].dietCum = $args[1].dietCum>>
-	<<set $args[0].dietMilk = $args[1].dietMilk>>
-	<<set $args[0].clothes = $args[1].clothes>>
-	<<set $args[0].collar = $args[1].collar>>
-	<<set $args[0].faceAccessory = $args[1].faceAccessory>>
-	<<set $args[0].mouthAccessory = $args[1].mouthAccessory>>
-	<<set $args[0].shoes = $args[1].shoes>>
-	<<set $args[0].makeup = $args[1].makeup>>
-	<<set $args[0].nails = $args[1].nails>>
-	<<set $args[0].vaginalAccessory = $args[1].vaginalAccessory>>
-	<<set $args[0].dickAccessory = $args[1].dickAccessory>>
-	<<set $args[0].buttplug = $args[1].buttplug>>
-	<<set $args[0].buttplugAttachment = $args[1].buttplugAttachment>>
-	<<set $args[0].induce = $args[1].induce>>
-	<<set $args[0].mpreg = $args[1].mpreg>>
-	<<set $args[0].pregKnown = $args[1].pregKnown>>
-	<<set $args[0].pregWeek = $args[1].pregWeek>>
-	<<set $args[0].belly = $args[1].belly>>
-	<<set $args[0].bellyPreg = $args[1].bellyPreg>>
-	<<set $args[0].bellyFluid = $args[1].bellyFluid>>
-	<<set $args[0].bellyImplant = $args[1].bellyImplant>>
-	<<set $args[0].bellySag = $args[1].bellySag>>
-	<<set $args[0].bellySagPreg = $args[1].bellySagPreg>>
-	<<set $args[0].bellyPain = $args[1].bellyPain>>
-	<<set $args[0].cervixImplant = $args[1].cervixImplant>>
-	<<set $args[0].scar = $args[1].scar>>
-	<<set $args[0].pregControl = $args[1].pregControl>>
-	<<run deflate($args[0])>>
-<</if>>
-/*
-<<if def $args[2]>>
-	<<= assignJob($args[0], "rest")>>
-<</if>>
-*/
-
-<</widget>>
-
-<<widget "BodySwapName">>
-
-<<if $args[1].bodySwap == 0>>
-	<<if $args[1].birthSurname>>
-		<<if $args[1].birthName !== "">>
-			<<set $args[0].origBodyOwner = SlaveFullBirthName($args[1])>>
-		<<else>>
-			<<set $args[0].origBodyOwner = $args[1].slaveName + " " + $args[1].birthSurname>>
-		<</if>>
-	<<elseif $args[1].birthName>>
-		<<if $args[1].slaveSurname>>
-			<<if (($surnameOrder != 1 && ["Cambodian", "Chinese", "Hungarian", "Japanese", "Korean", "Mongolian", "Taiwanese", "Vietnamese"].includes($args[1].nationality)) || ($surnameOrder == 2)>>
-				<<set $args[0].origBodyOwner = $args[1].slaveSurname + " " + $args[1].birthName>>
-			<<else>>
-				<<set $args[0].origBodyOwner = $args[1].birthName + " " + $args[1].slaveSurname>>
-			<</if>>
-		<<else>>
-			<<set $args[0].origBodyOwner = $args[1].birthName>>
-		<</if>>
-	<<elseif $args[1].slaveSurname>>
-		<<set $args[0].origBodyOwner = SlaveFullName($args[1])>>
-	<<else>>
-		<<set $args[0].origBodyOwner = $args[1].slaveName>>
-	<</if>>
-<<else>>
-	<<set $args[0].origBodyOwner = $args[1].origBodyOwner>>
-<</if>>
-
-<</widget>>
diff --git a/src/uncategorized/PESS.tw b/src/uncategorized/PESS.tw
index 70a4da847f3a58574da59cb997c2c9f259681bba..b9dc856bf60e79a654c143613ec1a05110b16ed7 100644
--- a/src/uncategorized/PESS.tw
+++ b/src/uncategorized/PESS.tw
@@ -39,7 +39,7 @@
 	<</if>>
 <<else>>
 
-<<set $nextButton = "Continue", $nextLink = "AS Dump", $returnTo = "RIE Eligibility Check">>
+<<set $nextButton = "Continue", $nextLink = "AS Dump", $returnTo = "RIE Eligibility Check", _SL = $slaves.length>>
 
 /* special leadership events can be rolled on any eventSlave */
 <<switch $PESSevent>>
@@ -558,6 +558,9 @@ $He sees you examining at $him, and looks back at you submissively, too tired to
 		$He giggles happily as you seize $his $activeSlave.skin wrist and pull $him towards where your other slaves are mostly already asleep. $He jokingly points out $slaves[$j].slaveName, fast asleep. You nod, and $activeSlave.slaveName pounces. $slaves[$j].slaveName wakes in terror and confusion to find _his2 head and neck pinned in a leg-lock that holds _his2 mouth hard against $activeSlave.slaveName's <<if canDoAnal($activeSlave)>>anus<<elseif canDoVaginal($activeSlave) && canPenetrate($activeSlave)>>cock and cunt<<elseif ($activeSlave.dick > 0) && !canAchieveErection($activeSlave)>>limp cock<<elseif ($activeSlave.dick > 0) && ($activeSlave.balls > 0) && ($activeSlave.scrotum > 0)>>cock and balls<<elseif ($activeSlave.clit > 0)>>huge, hard clit<<elseif !canDoVaginal($activeSlave)>>soft perineum<<else>>soaking-wet cunt<</if>>. $slaves[$j].slaveName only manages one kick of _his2 legs before you pin them and ram yourself up _his2 butt. _His2 howl of protest, directed against $activeSlave.slaveName's privates, sends a shiver through your Head Girl. <<if ($activeSlave.lips > 70)>>"Oh pleathe make _him2 moan, <<Master>>," $he lisps through $his huge lips.<<elseif ($activeSlave.lipsPiercing+$activeSlave.tonguePiercing > 2)>>"Oh pleathe make _him2 moan, <<Master>>," $he lisps through $his face full of piercings.<<else>>"I love it when you make _him2 moan, <<Master>>," $he groans.<</if>> Poor $slaves[$j].slaveName's asshole takes quite a beating before $activeSlave.slaveName finally convulses with @@.hotpink;naughty pleasure.@@ Meanwhile, all around the nighttime rape, slaves have been woken by $slaves[$j].slaveName's struggles. They realize how completely your Head Girl has @@.hotpink;involved $himself in your sexual pursuits@@ and @@.gold;lie as still as they can.@@
 		<<run seX($slaves[$j], "oral", $activeSlave, "penetrative")>>
 		<<run seX($slaves[$j], "anal", $PC, "penetrative")>>
+		<<if $slaves[$j].anus == 0>>
+			<<set $slaves[$j].anus = 1>>
+		<</if>>
 		<<for $i = 0; $i < _SL; $i++>>
 			<<set $slaves[$i].devotion += 4, $slaves[$i].trust -= 4>>
 		<</for>>
diff --git a/src/uncategorized/repBudget.js b/src/uncategorized/repBudget.js
new file mode 100644
index 0000000000000000000000000000000000000000..dbdc715633d06773cb7ba87f766e9fd105f1c354
--- /dev/null
+++ b/src/uncategorized/repBudget.js
@@ -0,0 +1,36 @@
+/**
+ * @param {string} category
+ * @param {string} title
+ * @returns {string}
+ */
+globalThis.budgetLine = function(category, title) {
+	let income;
+	let expenses;
+
+	if (passage() === "Rep Budget") {
+		income = "lastWeeksRepIncome";
+		expenses = "lastWeeksRepExpenses";
+
+		if (V[income][category] || V[expenses][category] || V.showAllEntries.repBudget) {
+			return `<tr>\
+				<td>${title}</td>\
+				<td>${repFormat(V[income][category])}</td>\
+				<td>${repFormat(V[expenses][category])}</td>\
+				<td>${repFormat(V[income][category] + V[expenses][category])}</td>\
+				</tr>`;
+		}
+	} else if (passage() === "Costs Budget") {
+		income = "lastWeeksCashIncome";
+		expenses = "lastWeeksCashExpenses";
+
+		if (V[income][category] || V[expenses][category] || V.showAllEntries.costsBudget) {
+			return `<tr>\
+				<td>${title}</td>\
+				<td>${cashFormatColor(V[income][category])}</td>\
+				<td>${cashFormatColor(-Math.abs(V[expenses][category]))}</td>\
+				<td>${cashFormatColor(V[income][category] + V[expenses][category])}</td>\
+				</tr>`;
+		}
+	}
+	return ``;
+};