diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index d9f8c5d756489d1bf27156e11a1776e190504a62..7a65b78b2e51d6715047f525f9c395a1bdd8a30f 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -18,7 +18,7 @@ variables:
   # precompiled / assets
   # the URLs are the same as inside the game, change both if updating
   WEBGL_NAME: "WebGL art assets"
-  WEBGL_URL: "https://mega.nz/folder/m1JQ3JAQ#2GJsM7csBBvBu0DX8SB2kA"
+  WEBGL_URL: "https://mega.nz/folder/DtoyiSwC#5FLXDYr1Uy90PZjxmfrKXA"
   RENDER_NAME: "Rendered imagepack (outdated)"
   RENDER_URL: "https://mega.nz/file/upoAlBaZ#EbZ5wCixxZxBhMN_ireJTXt0SIPOywO2JW9XzTIPhe0"
 
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 19ba1147def5d74b0710e3d69aa2392ad224d989..a422617ad8b134281fb8e123e2848e01d4868965 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
 
 ## Unreleased
 
+* Fixes.
+* New Security Expansion features (boost rep gain and both relationship with the peacekeeper General as an alternative to selling slaves).
+* c-section option available during report birth.
+* Updated slave art.
+* Edo or Chinese revivalist slave's name now follow the surname-name order.
+* Can purchase larger slave capacity slots for some facilities.
+* Move Special Force deployment focuses from AfterActionReport to Firebase.
+* Bodyguard's sword can now reflect some Future Societies.
+
 ## 0.10.7.1-4.0.0-alpha.14 - 2022-03-25
 
 * added loans
diff --git a/css/general/textColors.css b/css/general/textColors.css
index 8cb1d1ff69d7acc19ea2d7d4b0654b2411404929..7cd98be1785c161dd64e47bcac96c257916b80c8 100644
--- a/css/general/textColors.css
+++ b/css/general/textColors.css
@@ -152,17 +152,6 @@
 	color: red
 }
 
-/* FIXME: not working correctly - displays at top right corner of screen instead of after content in container */
-div > span.warning.notification::after {
-	font-family: "tme-fa-icons";
-	position: absolute;
-	right: 5px;
-	top: 0;
-	content: "\e80c";
-	color: red;
-	font-style: normal;
-}
-
 .seagreen, .seagreen a, .trust.prof-trusting, .trust.prof-trusting a {
 	color: seagreen
 }
diff --git a/css/rulesAssistant/conditionEditor.css b/css/rulesAssistant/conditionEditor.css
index 49465343054f99c5de93f69fc939ee667b573699..a84be73f23a161bb6857d698520f36decc11be97 100644
--- a/css/rulesAssistant/conditionEditor.css
+++ b/css/rulesAssistant/conditionEditor.css
@@ -47,6 +47,10 @@
     cursor: grab;
 }
 
+.rule-draggable:hover{
+    background: #2c2c2c;
+}
+
 .rule-drag-element {
     display: inline-block;
     background-image: repeating-linear-gradient(0, #1a1a1a, transparent 0.2em, transparent 0.2em, #1a1a1a 0.4em),
@@ -56,6 +60,11 @@
     vertical-align: middle;
 }
 
+.rule-drag-element:hover{
+    background-image: repeating-linear-gradient(0, #2c2c2c, transparent 0.2em, transparent 0.2em, #2c2c2c 0.4em),
+    repeating-linear-gradient(90deg, #2c2c2c, #888 0.2em, #888 0.2em, #2c2c2c 0.4em);
+}
+
 .rule-drop-location {
     border: #999 dashed 1px;
     border-radius: 4px;
@@ -64,9 +73,24 @@
     width: 5em;
     height: 1.5em;
     vertical-align: middle;
-    margin: 0 4px
+    margin: 0 4px;
 }
 
+.part-dragging .condition-viewer .rule-drop-location {
+    animation: 1.5s ease-in-out infinite rule-drop-location-blink;
+}
+
+@keyframes rule-drop-location-blink {
+    0% {
+        background: #555;
+    }
+    50% {
+        background: #638022;
+    }
+    100% {
+        background: #555;
+    }
+}
 
 .rule-builder input[type="text"] {
     margin-left: 0.2em;
@@ -80,6 +104,15 @@
     margin-left: 0.2em;
 }
 
+.rule-custom-controls {
+    display: inline-block;
+    vertical-align: top;
+}
+
+.rule-custom-mode {
+    text-align: center;
+}
+
 .rule-trash {
     border: #F55 1px;
     border-radius: 4px;
diff --git a/devTools/types/FC/desc.d.ts b/devTools/types/FC/desc.d.ts
index 9c7d8a19156049a697812bb8bd7d6989e5de1c5c..913ff2eaff7dbd03d921fba98109432dbfe290c0 100644
--- a/devTools/types/FC/desc.d.ts
+++ b/devTools/types/FC/desc.d.ts
@@ -2,8 +2,8 @@ declare namespace FC {
 	namespace Desc {
 		interface LongSlaveOptions {
 			descType?: DescType;
-			market?: Zeroable<SlaveMarketName | "starting">;
-			prisonCrime?: string;
+			market?: Zeroable<SlaveMarketName | SpecialMarketName | "starting">;
+			marketText?: string; /* extra text to be appended to the first line of the description, dependent on the original market */
 			noArt?: boolean;
 		}
 	}
diff --git a/devTools/types/FC/facilities.d.ts b/devTools/types/FC/facilities.d.ts
index a530277fb63a9128b896b10c528d91b7fbde606d..d506384103756c3cad822b247186462f4e2a3beb 100644
--- a/devTools/types/FC/facilities.d.ts
+++ b/devTools/types/FC/facilities.d.ts
@@ -59,20 +59,11 @@ declare namespace FC {
 				/** The value to set `property` to when the rule is active. */
 				value: any;
 				/** Any handler to run upon setting the value. */
-				handler?: () => void;
+				handler?: (value: any) => void;
 				/** Any additional information to display with on the link. */
 				note?: string;
 				/** Any prerequisites that must be met for the option to be displayed. */
-				prereqs?: Array<() => boolean>
-				/** Any dialog to display upon setting the value. */
-				dialog?: {
-					/** The content displayed in the dialog. */
-					content: string|HTMLElement|DocumentFragment;
-					/** The name of the dialog to display. */
-					name?: string;
-					/** Any prerequisites that must be met for the dialog to be displayed. */
-					prereqs?: Array<() => boolean>
-				};
+				prereqs?: Array<() => boolean>;
 			}>
 			/** Any additional nodes to attach. */
 			nodes?: Array<string|HTMLElement|DocumentFragment>
diff --git a/devTools/types/FC/financial.d.ts b/devTools/types/FC/financial.d.ts
index ab8535a32cdf82d91c841bf41cf21add10b29827..d108e80567dc5b2b488a379d1ad820adf1acd0b5 100644
--- a/devTools/types/FC/financial.d.ts
+++ b/devTools/types/FC/financial.d.ts
@@ -15,4 +15,21 @@ declare namespace FC {
 		/** The full amount that will be paid. */
 		full: number;
 	}
+
+	interface SlaveStatisticData {
+		/** ID of relevant slave */
+		ID: number,
+		slaveName: string,
+		customLabel: string,
+		income: number,
+		adsIncome: number,
+		rep: number,
+		food: number,
+		cost: number,
+		customers: number,
+
+		milk?:number,
+		cum?:number,
+		fluid?:number,
+	}
 }
diff --git a/devTools/types/FC/gameState.d.ts b/devTools/types/FC/gameState.d.ts
index 748bce5e5d823197620ee8a2d6a21eae3fea1dad..7226eeb1442ceba8177858fd7c32d68225308b7f 100644
--- a/devTools/types/FC/gameState.d.ts
+++ b/devTools/types/FC/gameState.d.ts
@@ -34,7 +34,7 @@ declare namespace FC {
 		ID?: number;
 		FS: {
 			name: string;
-			race?: FC.Race; 
+			race?: FC.Race;
 			adopted?: number;
 		}
 		/**
@@ -103,7 +103,6 @@ declare namespace FC {
 	interface TemporaryVariablesInTheGameState {
 		gameover?: string;
 		sortQuickList?: string;
-		slaveAfterRA?: SlaveState;
 		/** @deprecated */
 		returnTo: string;
 
diff --git a/devTools/types/FC/misc.d.ts b/devTools/types/FC/misc.d.ts
index 1fc19d9677d26ac73472a89cbc3e7531007e19f9..db86221fbeedac74bed8caf9e4271c8d23cd37c2 100644
--- a/devTools/types/FC/misc.d.ts
+++ b/devTools/types/FC/misc.d.ts
@@ -4,6 +4,7 @@ declare namespace FC {
 		"neighbor" | "wetware" | "white collar" | SlaveSchoolName;
 	type OrdinaryMarkets = "kidnappers" | "trainers" | "hunters" | "raiders" | "underage raiders" | "corporate";
 	type SlaveMarketName = LawlessMarkets | OrdinaryMarkets;
+	type SpecialMarketName = "Elite Slave" | "Household Liquidator" | "Custom Slave" | "Husk Slave" | "Slave Shelter" | "JobFulfillmentCenterOrder" | "Prestigious Slave" | "Special Slave";
 	type Gingering = Zeroable<"antidepressant" | "depressant" | "stimulant" | "vasoconstrictor" | "vasodilator" | "aphrodisiac" | "ginger">;
 	type GingeringDetection = Zeroable<"slaver" | "mercenary" | "force">;
 	type SlaveActs = "PCChildrenFathered" | "PCKnockedUp" | "anal" | "births" | "birthsTotal" | "cum" | "laborCount" | "mammary" | "milk" | "oral" | "penetrative" | "pitKills" | "miscarriages" | "publicUse" | "slavesFathered" | "slavesKnockedUp" | "vaginal" | "abortions" | "birth" | "bestiality";
diff --git a/devTools/types/SugarCubeExtensions.d.ts b/devTools/types/SugarCubeExtensions.d.ts
index d4b18a0a50ce841e21a86df911895f1937e34645..a4971ee3f33967a24c476ea9663eef4290014657 100644
--- a/devTools/types/SugarCubeExtensions.d.ts
+++ b/devTools/types/SugarCubeExtensions.d.ts
@@ -5,44 +5,7 @@ declare module "twine-sugarcube" {
 	}
 
 	interface SugarCubeSetupObject {
-		badWords: string[];
-		badNames: string[];
-		chattelReligionistSlaveNames: string[];
-		romanSlaveNames: string[];
-		romanSlaveSurnames: string[];
-		aztecSlaveNames: string[];
-		ancientEgyptianSlaveNames: string[];
-		edoSlaveNames: string[];
-		edoSlaveSurnames: string[];
-		bimboSlaveNames: string[];
-		cowSlaveNames: string[];
-		whiteAmericanMaleNames: string[];
-		whiteAmericanSlaveNames: string[];
-		whiteAmericanSlaveSurnames: string[];
-		catSlaveNames: string [];
-
-		fakeBellies: string[];
-		filterRaces: Map<FC.Race, string>;
-		filterRegions: string[];
-
-		pregData: Record<string, FC.PregnancyData>;
-
-		malenamePoolSelector: Record<string, string[]>;
-		maleSurnamePoolSelector: Record<string, string[]>;
-		namePoolSelector: Record<string, string[]>;
-		surnamePoolSelector: Record<string, string[]>;
-		raceSelector: Record<string, Record<FC.Race, number>>;
-
-		naturalSkins: string[];
-		naturalNippleColors: string[];
-
-		pettyCriminalPool: string[];
-		gangCriminalPool: string[];
-		militaryCriminalPool: string[];
-		whiteCollarCriminalPool: string[];
-
-		baseNationalities: string[];
-		paraphiliaList: string[]; // actually FC.SexualFlaw[]
+		// Any usages of the global setup object should be replaced by App.Data.misc
 	}
 
 	// These are SugarCube private APIs used in the project
diff --git a/js/003-data/gameVariableData.js b/js/003-data/gameVariableData.js
index 2977f3da51072e7e1b4e87e3882b2b56c94703d3..0d7f4a6b8bd84be738d56e4899da8bb196fe5983 100644
--- a/js/003-data/gameVariableData.js
+++ b/js/003-data/gameVariableData.js
@@ -155,6 +155,7 @@ App.Data.defaultGameStateVariables = {
 	setTextureResolution: 1024,
 	setShadowMapping: true,
 	setSSAO: true,
+	setSSS: true,
 	setTonemapping: true,
 	setImageSize: 1,
 	showAgeDetail: 1,
diff --git a/js/003-data/miscDataNames.js b/js/003-data/miscDataNames.js
index b6322e5b1733280dc8296b7676a8fd2ef7c68a1a..3371fe8233c87d1048b9ac440dfeafda2262346b 100644
--- a/js/003-data/miscDataNames.js
+++ b/js/003-data/miscDataNames.js
@@ -1923,7 +1923,7 @@ App.Data.misc.bimboMaleNames = ["A.J.", "Aaron", "Abe", "Adam", "Aiden", "Al", "
 
 /*
 Name pool selector based on nationality and race. Use as follows, given some slave "slave":
-namePool = (setup.namePoolSelector[slave.nationality + "." + slave.race] || setup.namePoolSelector[slave.nationality] || setup.whiteAmericanSlaveNames)
+namePool = (App.Data.misc.namePoolSelector[slave.nationality + "." + slave.race] || App.Data.misc.namePoolSelector[slave.nationality] || App.Data.misc.whiteAmericanSlaveNames)
 Then pick namePool.random(), or display those names as possible choices, or do whatever else you do with name pools.
 */
 App.Data.misc.namePoolSelector = {
diff --git a/js/003-data/slaveMods.js b/js/003-data/slaveMods.js
index 2f68317a67df088a7bfa9a60149b290d95a9cae8..a07bac2d4e89f34d6d5fe634d7891e17275c8016 100644
--- a/js/003-data/slaveMods.js
+++ b/js/003-data/slaveMods.js
@@ -479,7 +479,8 @@ App.Medicine.Modification.eyeShape = [
 	{value: "wide-eyed"}
 ];
 
-App.Medicine.Modification.naturalSkins = ["pure white", "ivory", "white", "extremely pale", "very pale", "pale", "extremely fair", "very fair", "fair", "light", "light olive", "tan", "olive", "bronze", "dark olive", "dark", "light beige", "beige", "dark beige", "light brown", "brown", "dark brown", "black", "ebony", "pure black", "black and white striped", "red", "yellow"];
+App.Medicine.Modification.naturalSkins = ["pure white", "ivory", "white", "extremely pale", "very pale", "pale", "extremely fair", "very fair", "fair", "light", "light olive", "tan", "olive", "bronze", "dark olive", "dark", "light beige", "beige", "dark beige", "light brown", "brown", "dark brown", "black", "ebony", "pure black"];
+App.Medicine.Modification.catgirlNaturalSkins = ["white", "brown", "black", "red", "yellow", "black and white striped"];
 App.Medicine.Modification.dyedSkins = ["camouflage patterned", "dyed blue", "dyed white", "dyed gray", "dyed black", "dyed green", "dyed pink", "dyed red", "tiger striped", "dyed purple", "clown"];
 App.Medicine.Modification.naturalNippleColors = ["black", "brown", "dark brown", "ebony", "ivory", "light brown", "pale pink", "pink"];
 App.Medicine.Modification.eyebrowStyles = new Set(["shaved", "straight", "rounded", "natural", "slanted inwards", "slanted outwards", "high-arched", "elongated", "shortened", "curved"]);
diff --git a/js/rulesAssistant/conditionEditor.js b/js/rulesAssistant/conditionEditor.js
index 349009179603ded4ffdfc94634288bb557f6a601..88a6ac2ba08643609311ea0a8be4656789d9a5ac 100644
--- a/js/rulesAssistant/conditionEditor.js
+++ b/js/rulesAssistant/conditionEditor.js
@@ -59,6 +59,7 @@ App.RA.Activation.Editor = (function() {
 		outerDiv.classList.add("rule-builder");
 
 		const ruleDiv = document.createElement("div");
+		ruleDiv.classList.add("condition-viewer");
 		const errors = [];
 		if (currentRule.validate(errors) === "error") {
 			ruleDiv.append("Condition has errors:");
@@ -97,7 +98,7 @@ App.RA.Activation.Editor = (function() {
 		div.append(new RulePartProvider(() => new RuleMapCheck(App.RA.Activation.getterManager.assignmentDefault)).render());
 		div.append(new RulePartProvider(() => new RuleConstant(0)).render());
 		div.append(new RulePartProvider(() => new RuleBooleanConstant(true)).render());
-		div.append(new RulePartProvider(() => new RuleConstant("string")).render());
+		div.append(new RulePartProvider(() => new RuleConstant("some text")).render());
 		div.append(new RulePartProvider(() => new RuleCustomCheck("bcontext => false")).render());
 		div.append(new RulePartTrash().render());
 		container.append(div);
@@ -857,21 +858,7 @@ App.RA.Activation.Editor = (function() {
 
 		render() {
 			const div = App.UI.DOM.makeElement("div", createDragElement(this), ["rule-part"]);
-			const button = document.createElement("button");
-			button.append(this._stringMode ? "String" : "Number");
-			button.onclick = () => {
-				this._stringMode = !this._stringMode;
-				if (this._stringMode) {
-					this.value = String(this.value);
-				} else {
-					this.value = Number(this.value);
-					if (Number.isNaN(this.value)) {
-						this.value = 0;
-					}
-				}
-				refreshEditor();
-			};
-			div.append(button);
+			div.append(this._stringMode ? " String" : " Number");
 			div.append(makeTextBoxDragSafe(App.UI.DOM.makeTextBox(this.value, (v) => {
 				this.value = v;
 				refreshEditor();
@@ -968,7 +955,34 @@ App.RA.Activation.Editor = (function() {
 		}
 
 		render() {
-			const div = App.UI.DOM.makeElement("div", createDragElement(this), ["rule-part"]);
+			const outerDiv = App.UI.DOM.makeElement("div", null, ["rule-part"]);
+			const leftDiv = document.createElement("div");
+			leftDiv.classList.add("rule-custom-controls");
+
+			const upperDiv = document.createElement("div");
+			upperDiv.append(createDragElement(this), " Custom Getter");
+			leftDiv.append(upperDiv);
+
+			const lowerDiv = document.createElement("div");
+			lowerDiv.classList.add("rule-custom-mode");
+			lowerDiv.append("Mode:", this._makeToggleButton());
+			leftDiv.append(lowerDiv);
+
+			outerDiv.append(leftDiv);
+
+			const textArea = document.createElement("textarea");
+			textArea.append(this.check.slice(1));
+			textArea.onchange = ev => {
+				// @ts-ignore
+				this.check = this._expectedType.first() + ev.target.value;
+				refreshEditor();
+			};
+			outerDiv.append(makeTextBoxDragSafe(textArea, this));
+			this._markValidationError(outerDiv);
+			return outerDiv;
+		}
+
+		_makeToggleButton() {
 			const button = document.createElement("button");
 			button.append(capFirstChar(this._expectedType));
 			button.onclick = () => {
@@ -982,17 +996,7 @@ App.RA.Activation.Editor = (function() {
 				this.check = this._expectedType.first() + this.check.slice(1);
 				refreshEditor();
 			};
-			div.append(button);
-			const textArea = document.createElement("textarea");
-			textArea.append(this.check.slice(1));
-			textArea.onchange = ev => {
-				// @ts-ignore
-				this.check = this._expectedType.first() + ev.target.value;
-				refreshEditor();
-			};
-			div.append(makeTextBoxDragSafe(textArea, this));
-			this._markValidationError(div);
-			return div;
+			return button;
 		}
 
 		validate(errorList) {
@@ -1008,7 +1012,7 @@ App.RA.Activation.Editor = (function() {
 		_validateRule(check) {
 			const slave = createReadonlyProxy(V.slaves[0]);
 			const context = new App.RA.Activation.Context(slave);
-			eval(check)(context);
+			(new Function("c", `return (${check})(c)`))(context);
 			return true;
 		}
 	}
@@ -1045,8 +1049,12 @@ App.RA.Activation.Editor = (function() {
 		node.classList.add("rule-draggable");
 		node.ondragstart = ev => {
 			ev.stopPropagation();
+			editorNode.classList.add("part-dragging");
 			ev.dataTransfer.setData("text/plain", rulePart.id);
 		};
+		node.ondragend = _ => {
+			editorNode.classList.remove("part-dragging");
+		};
 	}
 
 	/**
diff --git a/js/rulesAssistant/conditionEvaluation.js b/js/rulesAssistant/conditionEvaluation.js
index 3ff822dff6cee9fe560d0b5ca47217c423337b9c..c2c2afca4d6ece627ba74ed9480857c2667875c4 100644
--- a/js/rulesAssistant/conditionEvaluation.js
+++ b/js/rulesAssistant/conditionEvaluation.js
@@ -257,7 +257,7 @@ App.RA.Activation.populateGetters = function() {
 	});
 	gm.addBoolean("isunmodded", {
 		name: "Is Unmodded?", description: "If the slave is (relatively) unmodded.",
-		val: c => SlaveStatsChecker.isModded(c.slave)
+		val: c => SlaveStatsChecker.isUnmodded(c.slave)
 	});
 	gm.addBoolean("canmove", {
 		name: "Can Move?", description: "Can the slave move at all?",
@@ -772,8 +772,8 @@ App.RA.Activation.evaluate = function(slave, rule) {
 			: rulePart.charAt(1) === "n" ? "number"
 				: "string";
 		try {
-			// TODO: This should use a cached Function instead of 'eval'ing
-			const value = eval(rulePart.slice(2))(context);
+			// TODO: Can we cache the function (and is that useful)?
+			const value = (new Function("c", `return (${rulePart.slice(2)})(c)`))(context);
 			if (expectedType === "boolean") {
 				stack.pushNumber(value ? 1 : 0);
 			} else if (expectedType === "number") {
diff --git a/src/002-config/fc-version.js b/src/002-config/fc-version.js
index 68da110521d1ebed60d5b4d0f05e16a762c4d4f7..25140c6fcde42c71697a31c88221be8d60a675da 100644
--- a/src/002-config/fc-version.js
+++ b/src/002-config/fc-version.js
@@ -2,5 +2,5 @@ App.Version = {
 	base: "0.10.7.1", // The vanilla version the mod is based off of, this should never be changed.
 	pmod: "4.0.0-alpha.14",
 	commitHash: null,
-	release: 1169, // When getting close to 2000, please remove the check located within the onLoad() function defined at line five of src/js/eventHandlers.js.
+	release: 1170, // When getting close to 2000, please remove the check located within the onLoad() function defined at line five of src/js/eventHandlers.js.
 };
diff --git a/src/004-base/facility.js b/src/004-base/facility.js
index 1063d5cd6f86106b971ba49353ce67f6236598ea..e5c54f0f8969a62f58c43e3185d1d34ab8abdeea 100644
--- a/src/004-base/facility.js
+++ b/src/004-base/facility.js
@@ -207,8 +207,7 @@ App.Entity.Facilities.ManagingJob = class extends App.Entity.Facilities.Job {
 	 * @returns {boolean}
 	 */
 	slaveHasExperience(slave) {
-		return (this.desc.skill !== null && slave.skill[this.desc.skill] >= V.masteredXP) ||
-			(typeof slave.career === 'string' && this.desc.careers.includes(slave.career));
+		return (this.desc.skill && slave.skill[this.desc.skill] >= V.masteredXP) || (this.desc.careers.includes(slave.career));
 	}
 
 	/** @returns {App.Entity.SlaveState} */
diff --git a/src/004-base/facilityFramework.js b/src/004-base/facilityFramework.js
index 7a61d9e83b9d6229361f5ef2cfa1968f36a82c5b..e8351ab0f33a849c499d0c015a27f2c2bf1d2ec3 100644
--- a/src/004-base/facilityFramework.js
+++ b/src/004-base/facilityFramework.js
@@ -225,7 +225,6 @@ App.Facilities.Facility = class Facility {
 
 	/**
 	 * Allows rules to be set up in the facility.
-	 *
 	 * @private
 	 * @returns {HTMLDivElement}
 	 */
@@ -243,29 +242,9 @@ App.Facilities.Facility = class Facility {
 					rule.options.forEach(o => {
 						if (!o.prereqs || o.prereqs.every(prereq => prereq())) {
 							option.addValue(o.link, o.value);
-
-							const dialogHandler = o.dialog ? function() {
-								if (Dialog.isOpen()) {
-									Dialog.close();
-								}
-								if (o.dialog.name) {
-									Dialog.setup(o.dialog.name);
-								}
-								$(Dialog.body()).empty().append(o.dialog.content);
-								Dialog.open();
-							} : null;
-
-							if (dialogHandler && o.handler) {
-								option.addCallback(() => {
-									o.handler();
-									dialogHandler();
-								});
-							} else if (o.handler) {
+							if (o.handler) {
 								option.addCallback(o.handler);
-							} else if (dialogHandler) {
-								option.addCallback(dialogHandler);
 							}
-
 							if (o.note) {
 								option.addComment(o.note);
 							}
diff --git a/src/Mods/Catmod/events/reRecruit/runawayCat.js b/src/Mods/Catmod/events/reRecruit/runawayCat.js
index fffebd00a924fc85cfd6aa80f43fb83db3d96d9a..5eb22633ad35d6dfde31d9e4c8d11fca8ecdeab1 100644
--- a/src/Mods/Catmod/events/reRecruit/runawayCat.js
+++ b/src/Mods/Catmod/events/reRecruit/runawayCat.js
@@ -66,7 +66,7 @@ App.Events.recRunawayCat = class recRunawayCat extends App.Events.BaseEvent {
 			const slave = GenerateNewSlave(null, {minAge: 16, maxAge: 22, race: "catgirl"});
 			slave.face = random(25, 75);
 			slave.origin = "$He was bioengineered by a rival arcology owner. After being mistreated, $he escaped and came to you for protection.";
-			slave.slaveName = setup.catSlaveNames.random();
+			slave.slaveName = App.Data.misc.catSlaveNames.random();
 			slave.birthName = slave.slaveName;
 			slave.weight = 10;
 			slave.muscles = 0;
diff --git a/src/Mods/Catmod/events/scheduled/vatcatboy.js b/src/Mods/Catmod/events/scheduled/vatcatboy.js
index 8f68775b6a28ed1b0b4c14a2c548ee4d1c9f14e3..a6c76a599b13f6022a88fcdaedbf7e5eb33bfbd4 100644
--- a/src/Mods/Catmod/events/scheduled/vatcatboy.js
+++ b/src/Mods/Catmod/events/scheduled/vatcatboy.js
@@ -13,7 +13,7 @@ App.Events.SEVatCatBoy = class SEVatCatBoy extends App.Events.BaseEvent {
 		slave.origin = "$He is a vat-grown catboy created by Dr. Nieskowitz and the science team in your genelab.";
 		slave.face = random(55, 95);
 		slave.faceShape = "feline";
-		slave.slaveName = setup.catSlaveNames.random();
+		slave.slaveName = App.Data.misc.catSlaveNames.random();
 		slave.birthName = slave.slaveName;
 		slave.slaveSurname = "";
 		slave.birthSurname = "";
diff --git a/src/Mods/Catmod/events/scheduled/vatcatgirl.js b/src/Mods/Catmod/events/scheduled/vatcatgirl.js
index 97127918aeccc8550f9dd198e6a45e91f5376fd6..92ccdaadc771ca52ede9205b7cb81870722de349 100644
--- a/src/Mods/Catmod/events/scheduled/vatcatgirl.js
+++ b/src/Mods/Catmod/events/scheduled/vatcatgirl.js
@@ -13,7 +13,7 @@ App.Events.SEVatCatGirl = class SEVatCatGirl extends App.Events.BaseEvent {
 		slave.origin = "$He is a vat-grown catgirl created by Dr. Nieskowitz and the science team in your genelab.";
 		slave.face = random(55, 95);
 		slave.faceShape = "feline";
-		slave.slaveName = setup.catSlaveNames.random();
+		slave.slaveName = App.Data.misc.catSlaveNames.random();
 		slave.birthName = slave.slaveName;
 		slave.slaveSurname = "";
 		slave.birthSurname = "";
diff --git a/src/Mods/DinnerParty/dinnerPartyExecution.tw b/src/Mods/DinnerParty/dinnerPartyExecution.tw
index a78bdb119d45e9bf6fb30c9259c754586490b4a7..4916d9cb4f7d376dda2a6b88241e9c9e41ab2c56 100644
--- a/src/Mods/DinnerParty/dinnerPartyExecution.tw
+++ b/src/Mods/DinnerParty/dinnerPartyExecution.tw
@@ -394,7 +394,7 @@
 		<<if _dinnerRating >= 20>>
 			<br><br>
 			You have earned the 20 stars required for the title of
-			<<set $MOD_DinnerPartyTitleAchievement 0 1>>
+			<<set $MOD_DinnerPartyTitleAchievement = 1>>
 			<<if $PC.title > 0>>
 				@@.yellow;Master of The Culinary Arts.@@
 			<<else>>
@@ -555,7 +555,7 @@
 		<<set _slave.trust = -2>>
 		<<set _slave.oldDevotion = -20>>
 		<<set _slave.origin = "$He was once an arcology owner like yourself, who made the mistake of insulting you.">>
-		<<set _slave.career = 0>>
+		<<set _slave.career = "a slave">>
 		<<set _slave.prestige = 3>>
 		<<set _slave.prestigeDesc = "You stormed $his arcology, killed $his guards, and enslaved $him in revenge for insulting you at a dinner party.">>
 		<<run setHealth(_slave, 20, random(5, 15), 0, 0, 0)>>
diff --git a/src/data/backwardsCompatibility/buildingsBC.js b/src/Mods/SecExp/buildings/SecExpBuildingsBC.js
similarity index 100%
rename from src/data/backwardsCompatibility/buildingsBC.js
rename to src/Mods/SecExp/buildings/SecExpBuildingsBC.js
diff --git a/src/Mods/SecExp/js/Unit.js b/src/Mods/SecExp/js/Unit.js
index 19042f35501f383921a324396b8a6f2c30692732..76b01e3197cfe0f0e1e0467353bf3b8db72d53ad 100644
--- a/src/Mods/SecExp/js/Unit.js
+++ b/src/Mods/SecExp/js/Unit.js
@@ -159,17 +159,16 @@ App.Mods.SecExp.unit = (function() {
 			}
 			));
 		}
-
-		linkArray.push(bulkUpgrade(V.SecExp.units[type].squads));
+		if (bulkUpgrade(V.SecExp.units[type].squads) !== "N/A") {
+			linkArray.push(bulkUpgrade(V.SecExp.units[type].squads));
+		}
 		App.UI.DOM.appendNewElement("div", list, App.UI.DOM.generateLinksStrip(linkArray));
-
 		if (type === "slaves") {
 			App.UI.DOM.appendNewElement("div", list, App.UI.market({menialWorkersOnly: true}));
 		}
 
 		for (const unit of V.SecExp.units[type].squads) {
 			linkArray = [];
-
 			App.UI.DOM.appendNewElement("div", unitDetail, describe(unit, false));
 			linkArray.push(App.UI.DOM.makeTextBox(unit.platoonName, str => { unit.platoonName = str; App.UI.reload(); }));
 			linkArray.push(App.UI.DOM.link(`Disband the unit`, () => {
@@ -215,7 +214,22 @@ App.Mods.SecExp.unit = (function() {
 					));
 				}
 			}
-			linkArray.push(bulkUpgrade(unit));
+			if (bulkUpgrade(unit) !== "N/A") {
+				linkArray.push(bulkUpgrade(unit));
+			}
+			if (V.peacekeepers.state === 3 && type !== "bots") {
+				const unitAdjust = Math.ceil((unit.troops / 10) + (unit.maxTroops / 10) + (unit.training / 10) + unit.equip + unit.commissars + unit.cyber + unit.medics + unit.SF);
+				const cost = forceNeg(25000 * (1.5 + unitAdjust));
+				linkArray.push(App.UI.DOM.link(`Send this unit to improve your relationship with General ${V.peacekeepers.generalName}. Costs ${cashFormat(cost)}.`, () => { 
+					V.peacekeepers.attitude += Math.ceil(unitAdjust / 5);
+					cashX(cost, "securityExpansion");
+					unitFree(type).add(unit.troops);
+					V.SecExp.units[type].squads.deleteAt(unit);
+					V.SecExp.battles.lastSelection.deleteAt(unit);
+					App.UI.reload();
+				}
+				));
+			}
 			App.UI.DOM.appendNewElement("div", unitDetail, App.UI.DOM.generateLinksStrip(linkArray));
 
 			if (V.SecExp.settings.showStats === 1) {
@@ -363,7 +377,6 @@ App.Mods.SecExp.unit = (function() {
 			}
 			App.UI.DOM.appendNewElement("p", list, unitDetail);
 		}
-
 		return list;
 
 		/** Creates a bulk upgrade link for the unit that is passed.
@@ -371,8 +384,19 @@ App.Mods.SecExp.unit = (function() {
 		 */
 		function bulkUpgrade(unit) {
 			unit = Array.isArray(unit) ? unit : [unit];
-			let el = document.createElement("a");
-
+			const el = document.createElement("a");
+			const price = unit.map(getCost).reduce((acc, cur) => acc + cur, 0);
+			if (price !== 0) {
+				el.append(App.UI.DOM.link(`Bulk upgrade for ${cashFormat(price)}`, () => {
+					unit.map(upgradeUnit).reduce((acc, cur) => acc + cur, 0);
+					cashX(price, "securityExpansion");
+					App.UI.reload();
+				}
+				));
+				return el;
+			} else {
+				return "N/A";
+			}
 			/**
 			 * @param {FC.SecExp.PlayerHumanUnitData} x
 			 */
@@ -392,7 +416,6 @@ App.Mods.SecExp.unit = (function() {
 					}
 				}
 			}
-
 			/**
 			 * @param {FC.SecExp.PlayerHumanUnitData} x
 			 */
@@ -418,17 +441,6 @@ App.Mods.SecExp.unit = (function() {
 				}
 				return Math.ceil(cost * 1.1);
 			}
-
-			const price = unit.map(getCost).reduce((acc, cur) => acc + cur, 0);
-			if (price !== 0) {
-				el.append(App.UI.DOM.link(`Bulk upgrade for ${cashFormat(price)}`, () => {
-					unit.map(upgradeUnit).reduce((acc, cur) => acc + cur, 0);
-					cashX(price, "securityExpansion");
-					App.UI.reload();
-				}
-				));
-			}
-			return el;
 		}
 
 		/**
@@ -438,7 +450,7 @@ App.Mods.SecExp.unit = (function() {
 		function upgradeCost(upgrade, unit) {
 			let cost = 0;
 			const isBots = checkID(unit.ID) === "bots";
-			switch(upgrade) {
+			switch (upgrade) {
 				case "squadSize":
 					if (isBots) {
 						if (unit.maxTroops < 80) {
@@ -528,7 +540,7 @@ App.Mods.SecExp.unit = (function() {
 			));
 		}
 
-		App.UI.DOM.appendNewElement("span", el, `${input.platoonName}`, "bold");
+		App.UI.DOM.appendNewElement("span", el, `${input.platoonName}`, ["bold"]);
 		App.UI.DOM.appendNewElement("span", el, `${!brief ? ``:`. `} `);
 		if (unitType !== "bots") {
 			if (brief === 0) {
@@ -646,7 +658,7 @@ App.Mods.SecExp.unit = (function() {
 				el.append(`Training: `);
 				if (input.training <= 33) {
 					el.append(`low. `);
-				} else if(input.training <= 66) {
+				} else if (input.training <= 66) {
 					el.append(`medium. `);
 				} else {
 					el.append(`high. `);
@@ -693,7 +705,7 @@ App.Mods.SecExp.unit = (function() {
 	 */
 	function squads(type = '') {
 		let array = Object.values(V.SecExp.units).map(s => s.squads).flatten();
-		switch(type) {
+		switch (type) {
 			case "human": return array.filter(s => checkID(s.ID) !== "bots");
 		}
 		return array;
@@ -740,7 +752,7 @@ App.Mods.SecExp.unit = (function() {
 		 * @returns {number}
 		 */
 		function print() {
-			switch(type) {
+			switch (type) {
 				case "slaves": return V.menials;
 				case "militia":
 				case "mercs":
@@ -752,7 +764,7 @@ App.Mods.SecExp.unit = (function() {
 		 * @returns {boolean}
 		 */
 		function canUpgrade() {
-			switch(type) {
+			switch (type) {
 				case "bots": return V.cash >= secBotsCost;
 				case "slaves": return V.menials > 0;
 				case "militia":
@@ -766,7 +778,7 @@ App.Mods.SecExp.unit = (function() {
 		 * @returns {void}
 		 */
 		function add(value) {
-			switch(type) {
+			switch (type) {
 				case "slaves": V.menials += value; break;
 				case "militia":
 				case "mercs":
@@ -779,7 +791,7 @@ App.Mods.SecExp.unit = (function() {
 		 * @returns {void}
 		 */
 		function remove(value) {
-			switch(type) {
+			switch (type) {
 				case "slaves": V.menials -= value; break;
 				case "militia":
 				case "mercs":
@@ -792,7 +804,7 @@ App.Mods.SecExp.unit = (function() {
 		 * @returns {void}
 		 */
 		function set(value) {
-			switch(type) {
+			switch (type) {
 				case "slaves": V.menials = value; break;
 				case "militia":
 				case "mercs":
diff --git a/src/Mods/SecExp/js/reportingRelatedFunctions.js b/src/Mods/SecExp/js/reportingRelatedFunctions.js
index d05284998ed30437707130c55ce299f1777822cb..60eb56328f6c808f77a9ac3b4490e65719da3eda 100644
--- a/src/Mods/SecExp/js/reportingRelatedFunctions.js
+++ b/src/Mods/SecExp/js/reportingRelatedFunctions.js
@@ -452,7 +452,7 @@ App.Mods.SecExp.commanderEffectiveness = function(passage) {
 		case "HeadGirl":
 			slave = S[commander];
 			/* eslint-disable no-case-declarations */
-			const CareerGivesBonus = App.Data.Careers.Leader[isBodyguard ? "bodyguard" : "HG"].includes(slave.career) || setup.secExCombatPrestige.includes(slave.prestigeDesc);
+			const CareerGivesBonus = App.Data.Careers.Leader[isBodyguard ? "bodyguard" : "HG"].includes(slave.career) || App.Data.misc.secExCombatPrestige.includes(slave.prestigeDesc);
 			const TotalIntelligence = slave.intelligence + slave.intelligenceImplant;
 
 			if (inHandler) {
diff --git a/src/Mods/SecExp/potentialToDo.txt b/src/Mods/SecExp/potentialToDo.txt
index a53ae5135b300109486c44a4428438ae2a0271c8..67146f66eadf907043ce82daa957c15c0f589b24 100644
--- a/src/Mods/SecExp/potentialToDo.txt
+++ b/src/Mods/SecExp/potentialToDo.txt
@@ -3,10 +3,6 @@ Hexall90's last merged commit: 52dde0b3
 - Can other Arcologies assist with the attacks from your Arcology?
 	Like if you're being attacked by raiders they send some rapid-deployment forces and help you but when it's the Old world they wouldn't in fear of being criticize or something
 - Or sending your troops to help other arcologies to earn reputation, POW as menials and captured military assets for cash, to boost cultural exchange and economic development. Or choosing not to send troops (if rival) to lower its value and to preserve your cultural independence.
-- While at it something for barracks(general? commissar?) and riot center([Insert player title there]'s Will?? Big Sister? ) too would be nice
-- While at it decoupling of propaganda slave and recruiter when?
-- Would it be possible to add option for assigning hacker to security HQ that would work similarly to giving office for recruiter in propaganda hub? Preferably with smiling man slave giving extra bonuses there
-- Does having a large standing army give any bonus to authority/reputation growth?
 
 	Fine, I'll Do It Myself
 	Pirates? Enemy navies? - 328279
@@ -27,6 +23,5 @@ Hexall90's last merged commit: 52dde0b3
 - So would be taking slaves from slave units as personal ones. The slave units would pretty much be menial slaves I'd imagine, not sex slaves.
 - If I think about it having a squad of personal slaves that you equip in whatever gear you want and send out to capture new slaves, raid caravans and cities and shit would be pretty sweet, I would even call it special forces but that's taken, also the combat skill is still a binary thing which makes the whole thing pointless.
 	How about to start off with option to send one or two of your combat trained slaves to assist the merc's when they are on a raid with the possibility of receiving battle wounds.
-- How about to start off with option to send one or two of your combat trained slaves to assist the merc's when they are on a raid with the possibility of receiving battle wounds.
 - If there are choices, they should be along the lines of "higher risk, higher reward" (defeating the enemy with fewer casualties and damage if it goes right, but suffering higher casualties and damage if it goes wrong), or things like accepting more/less military casualties for better/worse protection of economic assets (costing money/damaging economy) and civilians (costing reputation/damaging population).
-- The ability to send units to the general to increase relationship as an alternative to sending slaves.
\ No newline at end of file
+- Support Neo-Imperial Warhounds in battles.
diff --git a/src/arcologyBuilding/ManageArcology.js b/src/arcologyBuilding/ManageArcology.js
index 2f9b55eb32d96a75969c9934f7af9c3f9d52e709..dbd355b89f976ef4665dbd9e45cca16932893c13 100644
--- a/src/arcologyBuilding/ManageArcology.js
+++ b/src/arcologyBuilding/ManageArcology.js
@@ -255,7 +255,7 @@ App.UI.manageArcology = function() {
 
 		App.UI.DOM.appendNewElement("h2", div, "Weather");
 
-		if (V.difficultySwitch === 1 && (V.econWeatherDamage > 0 || V.disasterResponse > 0)) {
+		if (V.difficultySwitch === 1) {
 			if (V.econWeatherDamage > 0) {
 				div.append(
 					`The recent terrible weather has damaged the local infrastructure. It is `,
@@ -287,7 +287,7 @@ App.UI.manageArcology = function() {
 				} else {
 					App.UI.DOM.appendNewElement("div", div, "Your highly capable disaster response unit is rapidly repairing the weather damage.");
 				}
-			} else if (V.disasterResponse > 0) {	// FIXME: this variable never changes
+			} else if (V.disasterResponse > 0) {
 				App.UI.DOM.appendNewElement("div", div, "Your disaster response unit is idle. It will not cost you any upkeep this week.");
 			}
 		}
@@ -612,7 +612,7 @@ App.UI.manageArcology = function() {
 	function foodMarket() {
 		const div = App.UI.DOM.makeElement("div", null, ['margin-bottom']);
 
-		if (V.mods.food.market) {
+		if (V.mods.food.enabled && V.mods.food.market) {
 			App.UI.DOM.appendNewElement("h2", div, "Food Management");
 			div.append(App.UI.foodMarket());
 		} else if (V.eventResults.foodCrisis) {
diff --git a/src/arcologyBuilding/shops.js b/src/arcologyBuilding/shops.js
index 1130c141be8e747ee7b56dbbb9e591be29f1f224..c51ca153cc321bc545b5c94d71299439e32b452f 100644
--- a/src/arcologyBuilding/shops.js
+++ b/src/arcologyBuilding/shops.js
@@ -89,7 +89,7 @@ App.Arcology.Cell.Shop = class extends App.Arcology.Cell.BaseCell {
 						`Interested, you head in to see how the latest styles feel in hand. The fearful slave sales${girl}s offer you complimentary tries at the targets, of course. They barely manage to avoid bursting into tears, knowing that if they make the slightest mistake representing the shop to the arcology owner, they'll be chained up for whip trials, too. The rich handmade leather is supple and handy, and readily extracts throat rending screams from the slaves you're encouraged to try it on.`));
 				break;
 			case "Supremacist":
-				fragment.append(`dedicated to ${A.FSSupremacistRace} supremacism. There are some select social establishments here which don't actually use any slaves at all, offering a surprisingly egalitarian atmosphere in which citizens of the master race can relax in each others' company without any subhuman filth present. `,
+				fragment.append(`dedicated to ${A.FSSupremacistRace} supremacism. There are some select social establishments here which don't actually use any slaves at all, offering a surprisingly egalitarian atmosphere in which citizens of the master race can relax in each other's company without any subhuman filth present. `,
 					App.UI.DOM.linkReplace("Put in an appearance",
 						`You decide to stop in at one of these establishments, and of course your money's no good. You're welcomed with considerable bonhomie, and much less formality than you usually receive at social events in your arcology. Everyone's ${A.FSSupremacistRace} here, and in that you're all equal, and all good friends. Everyone wants to have at least a quick word, and you stay longer than you originally meant to.`));
 				break;
diff --git a/src/art/artJS.js b/src/art/artJS.js
index 64ebc0a0bb7c10546e668ca8a7448e51fb84a533..9ac36f8df0e7ae8959ba94fd979ffb03d91a48cb 100644
--- a/src/art/artJS.js
+++ b/src/art/artJS.js
@@ -142,12 +142,15 @@ App.Art.SlaveArtElement = function(artSlave, artSize, UIDisplay) {
  */
 App.Art.setDynamicCSS = function(newState) {
 	if (newState.imageChoice === 4) { /* Elohiem's Webgl */
-		const width = 300 * newState.setImageSize + 15 + 50; // canvas + scrollbar + gradient
+		const width = 300 * newState.setImageSize;
 		const height = 530 * newState.setImageSize;
 
-		App.Art.dynamicCSS.innerHTML = '.lrgRender { height: '+height+'px; width: '+width+'px; }\n';
-		App.Art.dynamicCSS.innerHTML += '.lrgRender > div.mask { width: 50px }\n';
-		App.Art.dynamicCSS.innerHTML += '.lrgRender > img, .lrgRender > video { margin-left: auto }\n';
+		App.Art.dynamicCSS.innerHTML = '.tinyImg { height: 120px; width: 120px }\n';
+		App.Art.dynamicCSS.innerHTML += '.smlImg { height: 150px; width: 150px }\n';
+		App.Art.dynamicCSS.innerHTML += '.medImg { height: 300px; width: 300px }\n';
+
+		App.Art.dynamicCSS.innerHTML += '.lrgRender { height: '+height+'px; width: '+width+'px;}\n';
+		App.Art.dynamicCSS.innerHTML += '.lrgRender > img { margin-left: auto; height: 530px; width: auto }\n';
 	} else {
 		App.Art.dynamicCSS.innerHTML = '';
 	}
@@ -198,6 +201,7 @@ App.Art.webglInitialize = function() {
 		try {
 			// load model/morphs/textures assets
 			let sceneData = App.Art.sceneGetData();
+			App.Art.sceneGetData = function(){};
 			// load default dictionary containing camera/light/morph/material values
 			let scene = App.Art.sceneGetParams();
 			scene.lockView = false;
@@ -226,12 +230,27 @@ App.Art.webglInitialize = function() {
 	};
 	script.src = "resources/webgl/scene1/scene1.js";
 
+	let settings = document.createElement("script");
+	settings.onload = function() {
+		try {
+			document.head.appendChild(script);
+		} catch (e) {
+			App.Art.errorHandler("engineFailed", 1);
+			LoadScreen.unlock(loadLockID);
+		}
+	};
+	settings.onerror = function() {
+		App.Art.errorHandler("engineFailed", 2);
+		LoadScreen.unlock(loadLockID);
+	};
+	settings.src = "resources/webgl/scene1/settings.json";
+
 	// asynchronously load webgl assets if present
 	let load = document.createElement("script");
 	load.onload = function() {
 		// but only if version is correct
-		if (App.Art.version === "1.10") {
-			document.head.appendChild(script);
+		if (App.Art.version === "1.11") {
+			document.head.appendChild(settings);
 		} else {
 			App.Art.errorHandler("engineFailed", 3);
 			LoadScreen.unlock(loadLockID);
@@ -254,24 +273,28 @@ App.Art.webglArtElement = function(slave, artSize) {
 	let container = document.createElement("div");
 	container.setAttribute("class", "artContainer");
 	container.style.fontSize = "large";
-	container.innerText = "Loading...";
+	// container.innerText = "Loading...";
 
 	container.addEventListener("engineLoaded", function() {
-		// when engine is ready, attach default scene
-		let scene = JSON.parse(JSON.stringify(App.Art.defaultScene));
+		let img = document.createElement("img"); // hack to simulate onload event
+		img.onerror = function() {
+			// when engine is ready, attach default scene
+			let scene = JSON.parse(JSON.stringify(App.Art.defaultScene));
 
-		// apply the model transforms
-		let p = App.Art.getArtParams(slave);
-		App.Art.applyFigures(slave, scene, p);
-		App.Art.applySurfaces(slave, scene, p);
-		App.Art.applyMaterials(slave, scene, p);
-		App.Art.applyMorphs(slave, scene, p);
+			// apply the model transforms
+			let p = App.Art.getArtParams(slave);
+			App.Art.applyFigures(slave, scene, p);
+			App.Art.applySurfaces(slave, scene, p);
+			App.Art.applyMaterials(slave, scene, p);
+			App.Art.applyMorphs(slave, scene, p);
 
-		// console.log(scene);
+			// console.log(scene);
 
-		// create UI and render based on active view
-		container.innerText = "";
-		App.Art.createWebglUI(container, slave, artSize, scene, p);
+			// create UI and render based on active view
+			container.innerText = "";
+			App.Art.createWebglUI(container, slave, artSize, scene, p);
+		};
+		img.src = '';
 	});
 
 	container.addEventListener("engineFailed", function() {
diff --git a/src/art/webgl/art.js b/src/art/webgl/art.js
index cc0efd6538d9f45b7f829e592bd3d581e7a2b7a2..ba23fdb80a5d670bffcc7299cd16c0d640b58a89 100644
--- a/src/art/webgl/art.js
+++ b/src/art/webgl/art.js
@@ -241,7 +241,7 @@ App.Art.applyFigures = function(slave, scene, p) {
 			break;
 		case "a slutty maid outfit":
 			figures.push("Slutty Maid Dress", "Slutty Maid Bands", "Slutty Maid Neck Bow", "Slutty Maid Shoes", "Slutty Maid Headband");
-			p.hideDick = true;
+			p.hideDick = false;
 			p.applyNipples = false;
 			p.hideHair = false;
 			p.applyPumps = true;
@@ -262,7 +262,7 @@ App.Art.applyFigures = function(slave, scene, p) {
 			break;
 		case "a biyelgee costume":
 			figures.push("Biyelgee Dress", "Biyelgee Hat");
-			p.hideDick = true;
+			p.hideDick = false;
 			p.applyNipples = false;
 			p.hideHair = false;
 			break;
@@ -370,7 +370,7 @@ App.Art.applyFigures = function(slave, scene, p) {
 			break;
 		case "an oversized t-shirt":
 			figures.push("Oversized Shirt");
-			p.hideDick = false;
+			p.hideDick = true;
 			p.applyNipples = false;
 			p.hideHair = false;
 			break;
@@ -546,7 +546,7 @@ App.Art.applyFigures = function(slave, scene, p) {
 			break;
 		case "slutty jewelry":
 			figures.push("Bangles");
-			p.hideDick = true;
+			p.hideDick = false;
 			p.applyNipples = true;
 			p.hideHair = false;
 			break;
@@ -586,8 +586,8 @@ App.Art.applyFigures = function(slave, scene, p) {
 			p.hideHair = false;
 			p.applyPanty = true;
 			break;
-		case "kitty lingerie": // placeholder since bunny is duplicate
-			figures.push("Bunny Outfit");
+		case "kitty lingerie":
+			figures.push("Kitty Choker", "Kitty Top", "Kitty Panty");
 			p.applyPanty = false;
 			p.hideDick = true;
 			p.applyNipples = false;
@@ -599,6 +599,34 @@ App.Art.applyFigures = function(slave, scene, p) {
 			p.applyNipples = true;
 			p.hideHair = false;
 			break;
+		case "a schoolgirl outfit":
+			figures.push("School Girl Belly Piercing", "School Girl Choker", "School Girl Panty", "School Girl Skirt", "School Girl Stockings", "School Girl Shirt");
+			p.applyPanty = false;
+			p.hideDick = true;
+			p.applyNipples = false;
+			p.hideHair = false;
+			break;
+		case "slutty business attire":
+			figures.push("Secretary Glasses", "Secretary Skirt", "Secretary Vest", "Secretary Stockings");
+			p.applyPanty = true;
+			p.hideDick = true;
+			p.applyNipples = false;
+			p.hideHair = false;
+			break;
+		case "a monokini":
+			figures.push("Monokini");
+			p.applyPanty = false;
+			p.hideDick = true;
+			p.applyNipples = false;
+			p.hideHair = false;
+			break;
+		case "a string bikini":
+			figures.push("String Bikini Bottom", "String Bikini Top");
+			p.applyPanty = false;
+			p.hideDick = true;
+			p.applyNipples = false;
+			p.hideHair = false;
+			break;
 	}
 
 	if (p.underage) {
@@ -608,17 +636,48 @@ App.Art.applyFigures = function(slave, scene, p) {
 		p.hideDick = true;
 	}
 
-	if (slave.chastityAnus && slave.chastityVagina) {
-		figures.push("Chastity Belt Base", "Chastity Belt Vaginal Cap with Holes", "Chastity Belt Anal Cap with Hole");
-		p.hideDick = true;
+	/*
+	switch (slave.vaginalAccessory) {
+		case "dildo":
+			figures.push("Dildo 1");
+			break;
 	}
-	if (!slave.chastityAnus && slave.chastityVagina) {
-		figures.push("Chastity Belt Base", "Chastity Belt Vaginal Cap with Holes");
-		p.hideDick = true;
+
+	switch (slave.buttplug) {
+		case "plug":
+			figures.push("Anal Plug 1");
+			break;
+		case "large plug":
+			figures.push("Anal Plug 2");
+			break;
+		case "huge plug":
+			figures.push("Anal Plug 3");
+			break;
+		case "long plug":
+			figures.push("Anal Plug 1");
+			break;
+		case "long, large plug":
+			figures.push("Anal Plug 2");
+			break;
+		case "long, huge plug":
+			figures.push("Anal Plug 3");
+			break;
 	}
-	if (slave.chastityAnus && !slave.chastityVagina) {
-		figures.push("Chastity Belt Base", "Chastity Belt Anal Cap with Hole");
-		p.hideDick = true;
+	*/
+
+	if (slave.chastityAnus || slave.chastityVagina || slave.chastityPenis) {
+		figures.push("Chastity Belt Base");
+		if (slave.chastityAnus) {
+			figures.push("Chastity Belt Anal Cap with Hole");
+		}
+		if (slave.chastityVagina) {
+			figures.push("Chastity Belt Vaginal Cap with Holes");
+			p.hideDick = true;
+		}
+		if (slave.chastityPenis) {
+			figures.push("Chastity Belt Vaginal Cap with Holes");
+			p.hideDick = true;
+		}
 	}
 
 	switch (slave.bellyAccessory) {
@@ -656,10 +715,10 @@ App.Art.applyFigures = function(slave, scene, p) {
 			break;
 	}
 
-	if (!hasLeftArm(slave) && slave.PLimb > 0) {
+	if (!slave.arm.left && slave.PLimb > 0) {
 		figures.push("Amputee Cap Arm Left");
 	}
-	if (!hasRightArm(slave) && slave.PLimb > 0) {
+	if (!slave.arm.right && slave.PLimb > 0) {
 		figures.push("Amputee Cap Arm Right");
 	}
 
@@ -1494,15 +1553,15 @@ App.Art.applyMaterials = function(slave, scene, p) {
 		materials.push(["Eyelashes", "map_D", "base2/eyelash/EyeLash_4.jpg"]);
 	}
 
-	let irisColorLeft = App.Art.hexToRgb(extractColor(hasLeftEye(slave) ? extractColor(slave.eye.left.iris) : extractColor("black")));
-	let irisColorRight = App.Art.hexToRgb(extractColor(hasRightEye(slave) ? extractColor(slave.eye.right.iris) : extractColor("black")));
-	let scleraColorLeft = App.Art.hexToRgb(extractColor(hasLeftEye(slave) ? extractColor(slave.eye.left.sclera) : extractColor("black")));
-	let scleraColorRight = App.Art.hexToRgb(extractColor(hasRightEye(slave) ? extractColor(slave.eye.right.sclera) : extractColor("black")));
+	let irisColorLeft = App.Art.hexToRgb(extractColor(slave.eye.left ? extractColor(slave.eye.left.iris) : extractColor("black")));
+	let irisColorRight = App.Art.hexToRgb(extractColor(slave.eye.right ? extractColor(slave.eye.right.iris) : extractColor("black")));
+	let scleraColorLeft = App.Art.hexToRgb(extractColor(slave.eye.left ? extractColor(slave.eye.left.sclera) : extractColor("black")));
+	let scleraColorRight = App.Art.hexToRgb(extractColor(slave.eye.right ? extractColor(slave.eye.right.sclera) : extractColor("black")));
 
-	materials.push(["Iris_Left", "Ka", [irisColorLeft[0]*1.4, irisColorLeft[1]*1.4, irisColorLeft[2]*1.4]]);
-	materials.push(["Iris_Right", "Ka", [irisColorRight[0]*1.4, irisColorRight[1]*1.4, irisColorRight[2]*1.4]]);
-	materials.push(["Sclera_Left", "Ka", [scleraColorLeft[0]*0.75, scleraColorLeft[1]*0.75, scleraColorLeft[2]*0.75]]);
-	materials.push(["Sclera_Right", "Ka", [scleraColorRight[0]*0.75, scleraColorRight[1]*0.75, scleraColorRight[2]*0.75]]);
+	materials.push(["Iris_Left", "Ka", [irisColorLeft[0]*1.4*2.5, irisColorLeft[1]*1.4*2.5, irisColorLeft[2]*1.4*2.5]]);
+	materials.push(["Iris_Right", "Ka", [irisColorRight[0]*1.4*2.5, irisColorRight[1]*1.4*2.5, irisColorRight[2]*1.4*2.5]]);
+	materials.push(["Sclera_Left", "Ka", [scleraColorLeft[0]*0.8*1.8, scleraColorLeft[1]*0.8*2, scleraColorLeft[2]*0.8*2]]);
+	materials.push(["Sclera_Right", "Ka", [scleraColorRight[0]*0.8*1.8, scleraColorRight[1]*0.8*2, scleraColorRight[2]*0.8*2]]);
 
 	// expected skin color
 	let O = App.Art.hexToRgb(skinColorCatcher(slave).skinColor);
@@ -1534,20 +1593,23 @@ App.Art.applyMaterials = function(slave, scene, p) {
 	let Ka;
 	let lKa;
 	let r;
+	let rN;
 	let Ni;
 	let skin;
 	let cockSkin;
 
 	if (slave.clothes === "body oil") {
 		r = 0.4;
+		rN = 0.4;
 		Ni = 1.5;
 	} else {
 		r = 0.7;
+		rN = 0.55;
 		Ni = 1.35;
 	}
 
 	if (sqAO < sqBO && sqAO < sqCO && sqAO < sqDO) {
-		Ks = [2, 2, 2];
+		Ks = [2.5, 2.5, 2.5];
 		Ka = mA;
 		lKa = lA;
 		skin = "WhiteTone";
@@ -1560,7 +1622,7 @@ App.Art.applyMaterials = function(slave, scene, p) {
 		materials.push(["nipple_mask", "map_Ka", "base/skin/torso white.jpg"]);
 		materials.push(["nipple_mask", "Ks", [3, 3, 3]]);
 	} else if (sqBO < sqAO && sqBO < sqCO && sqBO < sqDO) {
-		Ks = [1.75, 1.75, 1.75];
+		Ks = [2.5, 2.5, 2.5];
 		Ka = mB;
 		lKa = lB;
 		skin = "LightTone";
@@ -1573,7 +1635,7 @@ App.Art.applyMaterials = function(slave, scene, p) {
 		materials.push(["nipple_mask", "map_Ka", "base/skin/torso light.jpg"]);
 		materials.push(["nipple_mask", "Ks", [2.5, 2.5, 2.5]]);
 	} else if (sqCO < sqBO && sqCO < sqAO && sqCO < sqDO) {
-		Ks = [1.5, 1.5, 1.5];
+		Ks = [2, 2, 2];
 		Ka = mC;
 		lKa = lC;
 		skin = "MidTone";
@@ -1586,7 +1648,7 @@ App.Art.applyMaterials = function(slave, scene, p) {
 		materials.push(["nipple_mask", "map_Ka", "base/skin/torso mid.jpg"]);
 		materials.push(["nipple_mask", "Ks", [2, 2, 2]]);
 	} else if (sqDO < sqBO && sqDO < sqCO && sqDO < sqAO) {
-		Ks = [0.75, 0.75, 0.75];
+		Ks = [1, 1, 1];
 		Ka = mD;
 		lKa = lD;
 		skin = "DarkTone";
@@ -1634,7 +1696,7 @@ App.Art.applyMaterials = function(slave, scene, p) {
 	materials.push([skin + "Genitalia", "Ks", Ks]);
 	materials.push([skin + "Genitalia", "r", r]);
 	materials.push([skin + "Genitalia", "Ni", Ni]);
-	materials.push(["nipple_mask", "r", r]);
+	materials.push(["nipple_mask", "r", rN]);
 	materials.push(["nipple_mask", "Ni", Ni]);
 
 	let pubicColor = App.Art.hexToRgb(extractColor(slave.pubicHColor));
@@ -1695,7 +1757,7 @@ App.Art.applyMaterials = function(slave, scene, p) {
 	materials.push(["skindetail_fine_arms", "d", fineDetail]);
 	materials.push(["skindetail_fine_legs", "d", fineDetail]);
 
-	let veins = 1;
+	let veins = 0;
 	materials.push(["skindetail_veins_torso", "d", veins]);
 	materials.push(["skindetail_veins_face", "d", veins]);
 	materials.push(["skindetail_veins_arms", "d", veins]);
@@ -1784,10 +1846,10 @@ App.Art.applyMorphs = function(slave, scene, p) {
 		morphs.push(["posesExtremeHeels2", 1]);
 	}
 
-	if (hasBothArms(slave) && hasBothLegs(slave)) {
+	if (slave.arm.right && slave.arm.left && slave.leg.right && slave.leg.left) {
 		if (scene.inspect) {
-			morphs.push(["posesInspect", 1]);
-			morphs.push(["posesInspectGen", 1]);
+			morphs.push(["posesInspect2", 1]);
+			morphs.push(["posesInspectGen2", 1]);
 		} else if (slave.devotion > 50) {
 			morphs.push(["posesHigh", 1]);
 		} else if (slave.devotion > -20) {
@@ -1795,6 +1857,11 @@ App.Art.applyMorphs = function(slave, scene, p) {
 		} else {
 			morphs.push(["posesLow", 1]);
 		}
+
+		if (slave.devotion <= 50) {
+			morphs.push(["posesArmsDown", -slave.weight/300/3.5]);
+			morphs.push(["posesLegsClosed", -slave.weight/300/3.5]);
+		}
 	}
 
 	if (slave.trust < 0) {
@@ -2136,7 +2203,7 @@ App.Art.applyMorphs = function(slave, scene, p) {
 	let foreheadShape = ["foreheadShapeNormal", "foreheadShapeRound", "foreheadShapeSmall"];
 	let forehead = Math.floor(App.Art.random() * foreheadShape.length);
 	if (forehead > 0) {
-		morphs.push(foreheadShape[forehead], 1);
+		morphs.push([foreheadShape[forehead], 1]);
 	}
 
 	switch (slave.faceShape) {
@@ -2154,6 +2221,144 @@ App.Art.applyMorphs = function(slave, scene, p) {
 			morphs.push(["faceShapeExotic", 1]); break;
 	}
 
+
+	/*
+	const morphs_eyes = [
+		["", 1],
+		["mEyeShape1",1],
+		["mEyeShape2",1],
+		["mEyeShape3",0.5],
+		["mEyeShape4",1],
+		["mEyeShape5",1],
+		["mEyeShape6",1],
+		["mEyeShape7",1],
+		["mEyeShape8",1],
+	];
+	const morphs_nose = [
+		["", 1],
+		["mNoseShape1",0.5],
+		["mNoseShape2",1],
+		["mNoseShape3",0.5],
+		["mNoseShape4",0.5],
+		["mNoseShape5",0.5],
+		["mNoseShape6",1],
+		["mNoseShape7",1],
+		["mNoseShape8",1],
+		["mNoseShape9",1],
+		["mNoseShape10",1],
+	];
+	const morphs_jaw = [
+		["", 1],
+		["mJawShape1", 0.5],
+		["mJawShape2", 0.5],
+		["mJawShape3", 0.75],
+		["mJawShape4", 0.5],
+		["mJawShape5", 1],
+		["mJawShape6", 0.5],
+	];
+	const morphs_lips = [
+		["", 1],
+		["mLipsShape1",1],
+		["mLipsShape2",1],
+		["mLipsShape3",1],
+		["mLipsShape4",1],
+		["mLipsShape5",1],
+		["mLipsShape6",1],
+		["mLipsShape7",1],
+		["mLipsShape8", 0.5],
+		["mLipsShape9",1],
+		["mLipsShape10", 0.75],
+		["mLipsShape11",1],
+		["mLipsShape12", 0.5],
+	];
+	const morphs_cheeks = [
+		["", 1],
+		["mCheeksShape1",0.5],
+		["mCheeksShape2",0.5],
+		["mCheeksShape3",0.5],
+		["mCheeksShape4",1],
+		["mCheeksShape5",1],
+		["mCheeksShape6",0.5],
+		["mCheeksShape7",1],
+		["mCheeksShape8",1],
+	];
+
+	const morphs_cheeks2 = [
+		["", 1],
+		["mCheeks2Shape1",0.5],
+		["mCheeks2Shape2",0.5],
+		["mCheeks2Shape3",1],
+		["mCheeks2Shape4",0.5],
+	];
+
+	const morphs_chin = [
+		["", 1],
+		["mChinShape1", 0.5],
+		["mChinShape2", 0.5],
+		["mChinShape3", 0.5],
+		["mChinShape4", 0.5],
+	];
+
+	const morphs_unique = [
+		["", 1],
+		["mUniqueShape1", 1],
+		["mUniqueShape2", 0.75],
+		["mUniqueShape3", 0.5],
+		["mUniqueShape4", 0.5],
+		["mUniqueShape5", 0.5],
+	];
+
+	const eyeSize = App.Art.random();
+	const morphs_other = [
+		["mChinTweak1", 0.3*App.Art.random()],
+		["mEyeTweak1", 0.4*eyeSize],
+		["mEyeTweak2", 0.4*eyeSize],
+		["mEyeTweak3", 0.25*eyeSize],
+		["mEyeTweak4", 0.3*(App.Art.random()*2-1)],
+		["mEyeTweak5", 0.5*(App.Art.random()*2-1)],
+	];
+
+	const morphs_forehead = [
+		["", 1],
+		["foreheadShapeRound", 1],
+		["foreheadShapeSmall", 1],
+	];
+
+	morphs.push(morphs_other[0]);
+	morphs.push(morphs_other[1]);
+	morphs.push(morphs_other[2]);
+	morphs.push(morphs_other[3]);
+	morphs.push(morphs_other[4]);
+	morphs.push(morphs_other[5]);
+
+	let forehead = Math.floor(App.Art.random() * morphs_forehead.length);
+	if (forehead > 0) {	morphs.push(morphs_forehead[forehead]); }
+
+	let unqiue = Math.floor(App.Art.random() * morphs_unique.length);
+	if (unqiue > 0) {	morphs.push(morphs_unique[unqiue]); }
+
+	let chin = Math.floor(App.Art.random() * morphs_chin.length);
+	if (chin > 0) {	morphs.push(morphs_chin[chin]); }
+
+	let cheeks2 = Math.floor(App.Art.random() * morphs_cheeks2.length);
+	if (cheeks2 > 0) {	morphs.push(morphs_cheeks2[cheeks2]); }
+
+	let cheeks = Math.floor(App.Art.random() * morphs_cheeks.length);
+	if (cheeks > 0) {	morphs.push(morphs_cheeks[cheeks]); }
+
+	let lips = Math.floor(App.Art.random() * morphs_lips.length);
+	if (lips > 0) {	morphs.push(morphs_lips[lips]); }
+
+	let jaw = Math.floor(App.Art.random() * morphs_jaw.length);
+	if (jaw > 0) {	morphs.push(morphs_jaw[jaw]); }
+
+	let nose = Math.floor(App.Art.random() * morphs_nose.length);
+	if (nose > 0) {	morphs.push(morphs_nose[nose]); }
+
+	let eyes = Math.floor(App.Art.random() * morphs_eyes.length);
+	if (eyes > 0) {	morphs.push(morphs_eyes[eyes]); }
+	*/
+
 	if (slave.boobs < 600) {
 		morphs.push(["boobShapeSmall", -(slave.boobs-600)/600]);
 	} else {
@@ -2169,7 +2374,7 @@ App.Art.applyMorphs = function(slave, scene, p) {
 			case "downward-facing":
 				morphs.push(["boobShapeDownward", ((slave.boobs-600)**(1/3)/16) * (175/p.height)]); break;
 			case "wide-set":
-				morphs.push(["boobShapeWide", ((slave.boobs-600)**(1/3)/9) * (175/p.height)]); break;
+				morphs.push(["boobShapeWide", ((slave.boobs-600)**(1/3)/4) * (175/p.height)]); break;
 			case "spherical":
 				// special case to make nipple work
 				if (slave.nipples === "flat" || slave.nipples === "inverted" || !p.applyNipples) {
@@ -2201,9 +2406,12 @@ App.Art.applyMorphs = function(slave, scene, p) {
 		}
 	}
 
-	if (slave.foreskin !== 0 && !scene.inspect) {
-		morphs.push(["foreskin", 1]);
+	let shaftShape = ["shaftShape0", "shaftShape1", "shaftShape2", "shaftShape3", "shaftShape4", "shaftShape5", "shaftShape6"];
+	let shaft = Math.floor(App.Art.random() * shaftShape.length);
+	if (shaft > 0) {
+		morphs.push([shaftShape[shaft], 1]);
 	}
+
 	if (slave.dick === 0 && !(slave.scrotum <= 0 || slave.balls <= 0)) {
 		morphs.push(["dickRemove", 1]);
 	} else if (slave.dick !== 0) {
@@ -2220,7 +2428,7 @@ App.Art.applyMorphs = function(slave, scene, p) {
 		morphs.push(["ballsRemove", 1]);
 	} else {
 		if (slave.balls > 1) {
-			morphs.push(["balls", convertRange(2, 10, 0, 0.75, slave.balls * (175/p.height))]);
+			morphs.push(["balls", convertRange(2, 10, 0, 0.75, slave.balls * 0.6 *(175/p.height))]);
 		}
 
 		if (slave.scrotum > 0) {
@@ -2237,11 +2445,7 @@ App.Art.applyMorphs = function(slave, scene, p) {
 		morphs.push(["muscles", slave.muscles/33]);
 	}
 
-	if (scene.inspect) {
-		morphs.push(["belly", Math.max(0.7, slave.belly**(1/3)/24.6)]); // fix
-	} else {
-		morphs.push(["belly", slave.belly**(1/3)/24.6]);
-	}
+	morphs.push(["belly2", slave.belly**(1/3)/24.6]);
 
 	morphs.push(["hips", slave.hips/2]);
 
@@ -2258,7 +2462,7 @@ App.Art.applyMorphs = function(slave, scene, p) {
 	}
 
 	if (slave.weight >= 0) {
-		morphs.push(["weight", slave.weight/75]);
+		morphs.push(["weight2", slave.weight/300]);
 	} else {
 		morphs.push(["weightThin", -slave.weight/80]);
 	}
@@ -2269,20 +2473,24 @@ App.Art.applyMorphs = function(slave, scene, p) {
 		morphs.push(["physicalAgeOld", (slave.visualAge-20)/52]);
 	}
 
-	if (!hasLeftArm(slave)) {
+	if (!slave.arm.left) {
 		morphs.push(["amputeeLeftArm", 1]);
 	}
-	if (!hasRightArm(slave)) {
+	if (!slave.arm.right) {
 		morphs.push(["amputeeRightArm", 1]);
 	}
-	if (!hasLeftLeg(slave)) {
+	if (!slave.leg.left) {
 		morphs.push(["amputeeLeftLeg", 1]);
 	}
-	if (!hasRightLeg(slave)) {
+	if (!slave.leg.right) {
 		morphs.push(["amputeeRightLeg", 1]);
 	}
 
-	morphs.push(["offset", 3]); // only applies to clothes
+	if (slave.dick > 0 || slave.scrotum > 0 || slave.balls > 0) {
+		morphs.push(["bulge", Math.max(slave.dick/4, 0)]);
+	}
+
+	morphs.push(["offset", 2]); // only applies to clothes
 
 	App.Art.resetMorphs(scene);
 
diff --git a/src/art/webgl/engine.js b/src/art/webgl/engine.js
index 5786cc818315cbaf5266b6255cd4612b3d1f83ec..0e93a67728bd7858a36929fb4aba35154c3b830b 100644
--- a/src/art/webgl/engine.js
+++ b/src/art/webgl/engine.js
@@ -13,22 +13,18 @@ App.Art.Engine = class {
 				in vec3 vertexPosition;
 				in vec2 textureCoordinate;
 
-				in vec3 vertexNormalMorph;
-				in vec3 vertexPositionMorph;
-
 				out vec2 textureCoord;
 				out vec3 normal;
 				out vec3 pos;
 				out vec4 shadowMap;
 
 				void main() {
-					vec4 posM = vec4(vertexPosition + vertexPositionMorph, 1.0);
-					gl_Position = matModelViewProjection * posM;
-					normal = normalize((matNormal * vec4(vertexNormal + vertexNormalMorph, 1.0)).xyz);
+					gl_Position = matModelViewProjection * vec4(vertexPosition, 1.0);
+					normal = normalize((matNormal * vec4(vertexNormal, 1.0)).xyz);
 
 					textureCoord = textureCoordinate;
-					pos = (matModelView * posM).xyz;
-					shadowMap = matModelViewProjectionShadow * posM;
+					pos = (matModelView * vec4(vertexPosition, 1.0)).xyz;
+					shadowMap = matModelViewProjectionShadow * vec4(vertexPosition, 1.0);
 				}`;
 	}
 
@@ -62,13 +58,12 @@ App.Art.Engine = class {
 				uniform mat4 matModel;
 
 				in vec3 vertexPosition;
-				in vec3 vertexPositionMorph;
 				in vec2 textureCoordinate;
 
 				out vec2 textureCoord;
 
 				void main() {
-					gl_Position = matModelViewProjectionShadow * vec4(vertexPosition + vertexPositionMorph, 1.0);
+					gl_Position = matModelViewProjectionShadow * vec4(vertexPosition, 1.0);
 					textureCoord = textureCoordinate;
 				}`;
 	}
@@ -188,6 +183,89 @@ App.Art.Engine = class {
 				}`;
 	}
 
+	getFsSourceSSS() {
+		return `#version 300 es
+				precision highp float;
+
+				out float iao;
+
+				in vec2 textureCoord;
+
+				uniform sampler2D gPosition;
+				uniform sampler2D gNormal;
+				uniform sampler2D texNoise;
+
+				uniform vec3 samples[32];
+
+				uniform mat4 projection;
+				uniform mat4 view;
+
+				uniform float radius;
+				uniform float bias;
+				uniform float scale;
+
+				void main() {
+					vec2 resolution = vec2(textureSize(gPosition, 0));
+					vec3 pos = texture(gPosition, textureCoord).xyz;
+					vec3 normal = -normalize(texture(gNormal, textureCoord).rgb);
+					// tile noise texture over screen based on screen dimensions divided by noise size
+					vec3 randomVec = normalize(texture(texNoise, textureCoord * resolution / vec2(3.0, 3.0)).xyz);
+
+					// create TBN
+					vec3 tangent = normalize(randomVec - normal * dot(randomVec, normal));
+					vec3 bitangent = cross(normal, tangent);
+					mat3 TBN = mat3(tangent, bitangent, normal);
+
+					// iterate over the sample kernel and calculate occlusion factor
+					float occlusion = 0.0;
+					for(int i = 0; i < 32; i++)
+					{
+						// get sample position
+						vec3 samplePos = TBN * samples[i]; // from tangent to view-space
+						samplePos = pos + samplePos * radius; 
+						
+						// project sample position
+						vec4 offset = vec4(samplePos, 1.0);
+						offset = projection * offset; // from view to clip-space
+						offset.xy /= offset.w; // perspective divide
+						offset.xy = offset.xy * 0.5 + 0.5; // transform to range 0.0 - 1.0
+						
+						float sampleDepth = texture(gPosition, offset.xy).z;
+						
+						// range check & accumulate
+						float rangeCheck = smoothstep(0.0, 1.0, radius / abs(pos.z - sampleDepth));
+						occlusion += (sampleDepth <= samplePos.z + bias ? 1.0 : 0.0) * rangeCheck;           
+					}
+					iao = 1.0 - (occlusion / scale);
+				}`;
+	}
+
+	getFsSourceSSSBlur() {
+		return `#version 300 es
+				precision highp float;
+
+				out float iao;
+				in vec2 textureCoord;
+
+				uniform sampler2D sssInput;
+				uniform float blur;
+				
+				void main() 
+				{
+					vec2 texelSize = 1.0 / vec2(textureSize(sssInput, 0));
+					float result = 0.0;
+					for (float x = -blur; x <= blur; x++) 
+					{
+						for (float y = -blur; y <= blur; y++) 
+						{
+							vec2 offset = vec2(x, y) * texelSize;
+							result += texture(sssInput, textureCoord + offset).r;
+						}
+					}
+					iao = result / ((blur*2.0+1.0) * (blur*2.0+1.0));
+				}`;
+	}
+
 	getFsSourceForwardPass(dl, pl) {
 		return `#version 300 es
                 precision highp float;
@@ -210,7 +288,7 @@ App.Art.Engine = class {
 				uniform float sssPower;
 				uniform float sssDistortion;
 				uniform float sssIntensity;
-				uniform vec3 sssColor;
+				uniform float sssAmbient;
 
                 uniform float whiteM;
                 uniform float gammaY;
@@ -225,6 +303,7 @@ App.Art.Engine = class {
 				uniform float Ni;
 				uniform float r;
 				uniform float m;
+				uniform float sss;
 
 				uniform vec2 offset;
 				uniform float angle;
@@ -233,6 +312,7 @@ App.Art.Engine = class {
 				uniform float sNormals;
 				uniform float sSSAO;
 				uniform float sAO;
+				uniform float sIAO;
 				uniform float sAmbient;
 				uniform float sAlbedo;
 				uniform float sSpecular;
@@ -250,7 +330,7 @@ App.Art.Engine = class {
                 
                 uniform vec3 cameraPos;
 
-                uniform sampler2D textSampler[9];
+                uniform sampler2D textSampler[10];
 
 				in vec2 textureCoord;
 				in vec3 normal;
@@ -352,6 +432,7 @@ App.Art.Engine = class {
 					vec3 map_Ke = vec3(0.0,0.0,0.0);
 					float map_d = 1.0;
 					float ao = 1.0;
+					float iao = 1.0;
 					float shadow = 1.0;
 					float roughness = 0.0;
 					float metallic = 0.0;
@@ -376,6 +457,9 @@ App.Art.Engine = class {
 					if (sSSAO == 1.0)
 						ao = texture(textSampler[0], vec2(gl_FragCoord.x, gl_FragCoord.y) / resolution).r;
 
+					if (sSSS == 1.0)
+						iao = texture(textSampler[9], vec2(gl_FragCoord.x, gl_FragCoord.y) / resolution).r;
+
 					if (sAlbedo == 1.0)
                         map_Ka = Ka * texture(textSampler[2], coord).rgb;
 
@@ -394,15 +478,15 @@ App.Art.Engine = class {
 						float bias = max(shadowBiasMax * (1.0 - dot(new_normal, shadowDir)), shadowBiasMin); 
 
 						vec2 texelSize = 1.0 / vec2(textureSize(textSampler[8], 0));
-						for(int x = -1; x <= 1; ++x)
+						for(int x = -2; x <= 2; ++x)
 						{
-							for(int y = -1; y <= 1; ++y)
+							for(int y = -2; y <= 2; ++y)
 							{
 								float pcfDepth = texture(textSampler[8], projCoords.xy + vec2(x, y) * texelSize).r; 
 								shadow += currentDepth - bias < pcfDepth ? 1.0 : 0.0;
 							}    
 						}
-						shadow /= 9.0;
+						shadow /= 25.0;
 						shadow = min(shadow + (1.0-shadowIntensity), 1.0);
 					}
 
@@ -429,7 +513,7 @@ App.Art.Engine = class {
 						// cook-torrance brdf
 						float NDF = distributionGGX(N, H, roughness);        
 						float G   = geometrySmith(N, V, L, roughness);      
-						vec3 F    = fresnelSchlick(max(dot(H, V), 0.0), F0);       
+						vec3 F    = fresnelSchlickRoughness(max(dot(H, V), 0.0), F0, roughness);       
 						
 						vec3 numerator = NDF * G * F;
 						float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.00001;
@@ -441,21 +525,26 @@ App.Art.Engine = class {
 						
 						// add to outgoing radiance Lo
 						float NdotL = max(dot(N, L), 0.0);
-						Lo += (kD * albedo / PI + specular * map_Ks) * radiance * NdotL * shadow;
+						if (map_d >= 0.85 || overlay == 1.0) {
+							Lo += (kD * albedo / PI + specular * map_Ks) * radiance * NdotL * shadow;
+						}
+						else {
+							Lo += (kD * albedo / PI + specular * map_Ks) * radiance * NdotL;
+						}
 
 						// ambient lighting
-						vec3 La = kD * albedo * lightAmb[i] * (1.0-pow(NdotL, 0.5));
+						vec3 La = kD * albedo * lightAmb[i];
 
 						if (map_d >= 0.85 || overlay == 1.0) {
-							// ao
-							Lo += La * ao;
-
 							// sss
-							if (sSSS == 1.0) {
+							if (sSSS == 1.0 && sss == 1.0) {
 								vec3 H_d = normalize(L + N * sssDistortion);
 								float VdotH = pow(clamp(dot(V, -H_d), 0.0, 1.0), sssPower);
-								Lo += sssColor * VdotH * kD * albedo * sssIntensity;
+								Lo += (kD * albedo / PI + specular * map_Ks) * (VdotH+sssAmbient) * iao * sssIntensity * NdotL * shadow;
 							}
+
+							// ao
+							Lo += La * ao;
 						} else {
 							Lo += La;
 						}
@@ -473,7 +562,7 @@ App.Art.Engine = class {
 						// cook-torrance brdf
 						float NDF = distributionGGX(N, H, roughness);        
 						float G   = geometrySmith(N, V, L, roughness);      
-						vec3 F    = fresnelSchlick(max(dot(H, V), 0.0), F0);       
+						vec3 F    = fresnelSchlickRoughness(max(dot(H, V), 0.0), F0, roughness);       
 						
 						vec3 numerator = NDF * G * F;
 						float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.00001;
@@ -485,21 +574,26 @@ App.Art.Engine = class {
 						
 						// add to outgoing radiance Lo
 						float NdotL = max(dot(N, L), 0.0);
-						Lo += (kD * albedo / PI + specular * map_Ks) * radiance * NdotL * shadow;
+						if (map_d >= 0.85 || overlay == 1.0) {
+							Lo += (kD * albedo / PI + specular * map_Ks) * radiance * NdotL * shadow;
+						}
+						else {
+							Lo += (kD * albedo / PI + specular * map_Ks) * radiance * NdotL;
+						}
 
 						// ambient lighting
-						vec3 La = kD * albedo * pointLightAmb[i] * (1.0-pow(NdotL, 0.5));
+						vec3 La = kD * albedo * pointLightAmb[i];
 
 						if (map_d >= 0.85 || overlay == 1.0) {
-							// ao
-							Lo += La * ao;
-
 							// sss
-							if (sSSS == 1.0) {
+							if (sSSS == 1.0 && sss == 1.0) {
 								vec3 H_d = normalize(L + N * sssDistortion);
 								float VdotH = pow(clamp(dot(V, -H_d), 0.0, 1.0), sssPower);
-								Lo += sssColor * VdotH * kD * albedo * sssIntensity;
+								Lo += (kD * albedo / PI + specular * map_Ks) * (VdotH+sssAmbient) * iao * sssIntensity * NdotL * shadow;
 							}
+
+							// ao
+							Lo += La * ao;
 						} else {
 							Lo += La;
 						}
@@ -523,11 +617,14 @@ App.Art.Engine = class {
 						c = pow(c, vec3(1.0/gammaY));
 					
 					if (sNormals == 1.0)
-						c = new_normal;
+						c = new_normal*0.5+0.5;
 
 					if (sAO == 1.0)
 						c = vec3(ao);
 
+					if (sIAO == 1.0)
+						c = vec3(iao);
+
 					outputColor = vec4(c*map_d, map_d);
 				}`;
 	}
@@ -563,42 +660,51 @@ App.Art.Engine = class {
 			modelBuffers.verticesPositionBuffer = [];
 			modelBuffers.verticesNormalBuffer = [];
 			modelBuffers.verticesTextureCoordBuffer = [];
-			modelBuffers.vertexCount = [];
-			modelBuffers.verticesMorphBuffer = [];
-			modelBuffers.verticesNormalMorphBuffer = [];
 
+			modelBuffers.vertexPositionBuffer = [];
 			modelBuffers.verticesIndexBuffer = [];
 			modelBuffers.indexSizes = [];
+			modelBuffers.figureIndices = [];
+			modelBuffers.figureSeemMaps = [];
 			for (let i=0, count=0; i < modelData.figures.length; i++) {
 				modelBuffers.verticesPositionBuffer[i] = this.gl.createBuffer();
 				this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesPositionBuffer[i]);
 				this.gl.bufferData(this.gl.ARRAY_BUFFER, this.base64ToFloat(modelData.figures[i].verts), this.gl.STATIC_DRAW);
-				modelBuffers.vertexCount[i] = this.gl.getBufferParameter(this.gl.ARRAY_BUFFER, this.gl.BUFFER_SIZE)/4;
+				modelBuffers.vertexPositionBuffer[i] = this.base64ToFloat(modelData.figures[i].verts);
 
 				modelBuffers.verticesNormalBuffer[i] = this.gl.createBuffer();
 				this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesNormalBuffer[i]);
-				this.gl.bufferData(this.gl.ARRAY_BUFFER, this.base64ToFloat(modelData.figures[i].vertsn), this.gl.STATIC_DRAW);
+				this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(modelBuffers.vertexPositionBuffer[i].length), this.gl.STATIC_DRAW);
 
 				modelBuffers.verticesTextureCoordBuffer[i] = this.gl.createBuffer();
 				this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesTextureCoordBuffer[i]);
 				this.gl.bufferData(this.gl.ARRAY_BUFFER, this.base64ToFloat(modelData.figures[i].texts), this.gl.STATIC_DRAW);
 
-				// return dummy morph
-				modelBuffers.verticesMorphBuffer[i] = this.gl.createBuffer();
-				this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesMorphBuffer[i]);
-				this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(0), this.gl.STATIC_DRAW);
-
-				modelBuffers.verticesNormalMorphBuffer[i] = this.gl.createBuffer();
-				this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesNormalMorphBuffer[i]);
-				this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(0), this.gl.STATIC_DRAW);
-
+				modelBuffers.figureIndices[i] = [];
 				for (let j=0; j < modelData.figures[i].surfaces.length; j++, count++) {
 					modelBuffers.verticesIndexBuffer[count] = this.gl.createBuffer();
 					this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, modelBuffers.verticesIndexBuffer[count]);
 					let intArray = this.base64ToInt(modelData.figures[i].surfaces[j].vertsi);
+					modelBuffers.figureIndices[i] = [...modelBuffers.figureIndices[i], ...intArray];
 					this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, intArray, this.gl.STATIC_DRAW);
 					modelBuffers.indexSizes[count] = intArray.length;
 				}
+
+				let seems = this.base64ToInt(modelData.figures[i].seems);
+				let seemMap = new Int32Array(seems.length*2);
+				let indices = modelBuffers.figureIndices[i];
+				for (let j=0, h=0; j <= seems.length-3; j+=3, h+=6) {
+					let idx = seems[j+1];
+					let value = seems[j+2];
+					indices[seems[j]] = value/3;
+					seemMap[h] = idx;
+					seemMap[h+1] = value;
+					seemMap[h+2] = idx+1;
+					seemMap[h+3] = value+1;
+					seemMap[h+4] = idx+2;
+					seemMap[h+5] = value+2;
+				}
+				modelBuffers.figureSeemMaps[i] = seemMap;
 			}
 
 			this.initMorphs(modelBuffers, modelData, dir);
@@ -627,6 +733,12 @@ App.Art.Engine = class {
 		this.gl.bindTexture(this.gl.TEXTURE_2D, this.buffers.ssaoColorBufferBlur);
 		this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.R16F, screenWidth, screenHeight, 0, this.gl.RED, this.gl.FLOAT, null);
 
+		this.gl.bindTexture(this.gl.TEXTURE_2D, this.buffers.sssColorBuffer);
+		this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.R16F, screenWidth, screenHeight, 0, this.gl.RED, this.gl.FLOAT, null);
+
+		this.gl.bindTexture(this.gl.TEXTURE_2D, this.buffers.sssColorBufferBlur);
+		this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.R16F, screenWidth, screenHeight, 0, this.gl.RED, this.gl.FLOAT, null);
+
 		this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
 	}
 
@@ -709,6 +821,30 @@ App.Art.Engine = class {
 		this.gl.framebufferTexture2D(this.gl.FRAMEBUFFER, this.gl.COLOR_ATTACHMENT0, this.gl.TEXTURE_2D, buffers.ssaoColorBufferBlur, 0);
 		this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
 
+		// SSS color buffer
+		buffers.sssColorBuffer = this.gl.createTexture();
+		this.gl.bindTexture(this.gl.TEXTURE_2D, buffers.sssColorBuffer);
+		this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.NEAREST);
+		this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.NEAREST);
+		this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.R16F, screenWidth, screenHeight, 0, this.gl.RED, this.gl.FLOAT, null);
+
+		buffers.sssFBO = this.gl.createFramebuffer();
+		this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, buffers.sssFBO);
+		this.gl.framebufferTexture2D(this.gl.FRAMEBUFFER, this.gl.COLOR_ATTACHMENT0, this.gl.TEXTURE_2D, buffers.sssColorBuffer, 0);
+		this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
+
+		// SSS blur buffer
+		buffers.sssColorBufferBlur = this.gl.createTexture();
+		this.gl.bindTexture(this.gl.TEXTURE_2D, buffers.sssColorBufferBlur);
+		this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.NEAREST);
+		this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.NEAREST);
+		this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.R16F, screenWidth, screenHeight, 0, this.gl.RED, this.gl.FLOAT, null);
+
+		buffers.sssBlurFBO = this.gl.createFramebuffer();
+		this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, buffers.sssBlurFBO);
+		this.gl.framebufferTexture2D(this.gl.FRAMEBUFFER, this.gl.COLOR_ATTACHMENT0, this.gl.TEXTURE_2D, buffers.sssColorBufferBlur, 0);
+		this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
+
 		const random = function(seed) {
 			let x = Math.sin(seed+1) * 10000;
 			return x - Math.floor(x);
@@ -749,15 +885,12 @@ App.Art.Engine = class {
 
 		let promisedMorphs = [];
 		modelBuffers.vertexPositionMorphs = [];
-		modelBuffers.vertexNormalMorphs = [];
 		modelBuffers.vertexIndexMorphs = [];
 		for (let f=0; f < modelData.figures.length; f++) {
 			modelBuffers.vertexPositionMorphs[f] = [];
-			modelBuffers.vertexNormalMorphs[f] = [];
 			modelBuffers.vertexIndexMorphs[f] = [];
 			for (let m=0; m < modelData.morphCount; m++) {
 				modelBuffers.vertexPositionMorphs[f].push(new Float32Array(0));
-				modelBuffers.vertexNormalMorphs[f].push(new Float32Array(0));
 				modelBuffers.vertexIndexMorphs[f].push(new Int32Array(0));
 			}
 
@@ -783,16 +916,16 @@ App.Art.Engine = class {
 		return new Promise(function(resolve, reject) {
 			let script = document.createElement("script");
 			script.onload = function() {
-				let morph = window.sceneBlocks[path.split("/").slice(-1)[0]];
+				let name = path.split("/").slice(-1)[0];
+				let morph = window.sceneBlocks[name];
 
-				for (let i=0; i < morph.length; i+=3) {
-					modelBuffers.vertexPositionMorphs[m][i/3] = engine.base64ToFloat(morph[i+0]);
-					modelBuffers.vertexNormalMorphs[m][i/3] = engine.base64ToFloat(morph[i+1]);
+				for (let i=0; i < morph.length; i+=2) {
+					modelBuffers.vertexPositionMorphs[m][i/2] = engine.base64ToFloat(morph[i+0]);
 					// reconstruct compressed indices
-					modelBuffers.vertexIndexMorphs[m][i/3] = engine.base64ToInt(morph[i+2]).map((sum => value => sum += value)(0));
+					modelBuffers.vertexIndexMorphs[m][i/2] = engine.base64ToInt(morph[i+1]).map((sum => value => sum += value)(0));
 				}
 
-				morph = null; // let garbage collector clean
+				window.sceneBlocks[name] = null; // let garbage collector clean
 				resolve();
 			};
 			script.onerror = function(e) {
@@ -830,7 +963,8 @@ App.Art.Engine = class {
 		return new Promise(function(resolve, reject) {
 			let script = document.createElement("script");
 			script.onload = function() {
-				let url = window.sceneBlocks[path.split("/").slice(-1)[0]][0];
+				let name = path.split("/").slice(-1)[0];
+				let url = window.sceneBlocks[name][0];
 
 				let img = document.createElement("img");
 				img.onload = function() {
@@ -868,7 +1002,7 @@ App.Art.Engine = class {
 					gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
 					gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, img);
 
-					url = null; // let garbage collector clean
+					window.sceneBlocks[name] = null; // let garbage collector clean
 					resolve();
 				};
 				img.src = url;
@@ -911,6 +1045,14 @@ App.Art.Engine = class {
 		this.gl.shaderSource(fragmentShaderSSAOBlur, this.getFsSourceSSAOBlur());
 		this.gl.compileShader(fragmentShaderSSAOBlur);
 
+		let fragmentShaderSSS = this.gl.createShader(this.gl.FRAGMENT_SHADER);
+		this.gl.shaderSource(fragmentShaderSSS, this.getFsSourceSSS());
+		this.gl.compileShader(fragmentShaderSSS);
+
+		let fragmentShaderSSSBlur = this.gl.createShader(this.gl.FRAGMENT_SHADER);
+		this.gl.shaderSource(fragmentShaderSSSBlur, this.getFsSourceSSSBlur());
+		this.gl.compileShader(fragmentShaderSSSBlur);
+
 		let fragmentShaderForwardPass = this.gl.createShader(this.gl.FRAGMENT_SHADER);
 		this.gl.shaderSource(fragmentShaderForwardPass, this.getFsSourceForwardPass(sceneParams.directionalLights.length, sceneParams.pointLights.length));
 		this.gl.compileShader(fragmentShaderForwardPass);
@@ -935,6 +1077,16 @@ App.Art.Engine = class {
 		this.gl.attachShader(this.shaderProgramSSAOBlur, fragmentShaderSSAOBlur);
 		this.gl.linkProgram(this.shaderProgramSSAOBlur);
 
+		this.shaderProgramSSS = this.gl.createProgram();
+		this.gl.attachShader(this.shaderProgramSSS, vertexShaderQuad);
+		this.gl.attachShader(this.shaderProgramSSS, fragmentShaderSSS);
+		this.gl.linkProgram(this.shaderProgramSSS);
+
+		this.shaderProgramSSSBlur = this.gl.createProgram();
+		this.gl.attachShader(this.shaderProgramSSSBlur, vertexShaderQuad);
+		this.gl.attachShader(this.shaderProgramSSSBlur, fragmentShaderSSSBlur);
+		this.gl.linkProgram(this.shaderProgramSSSBlur);
+
 		this.shaderProgramForwardPass = this.gl.createProgram();
 		this.gl.attachShader(this.shaderProgramForwardPass, vertexShaderGeometry);
 		this.gl.attachShader(this.shaderProgramForwardPass, fragmentShaderForwardPass);
@@ -951,18 +1103,9 @@ App.Art.Engine = class {
 		this.vertexNormalAttribute = this.gl.getAttribLocation(this.shaderProgramGeometry, "vertexNormal");
 		this.gl.enableVertexAttribArray(this.vertexNormalAttribute);
 
-		this.vertexNormalMorphAttribute = this.gl.getAttribLocation(this.shaderProgramGeometry, "vertexNormalMorph");
-		this.gl.enableVertexAttribArray(this.vertexNormalMorphAttribute);
-
-		this.vertexPositionMorphAttribute = this.gl.getAttribLocation(this.shaderProgramGeometry, "vertexPositionMorph");
-		this.gl.enableVertexAttribArray(this.vertexPositionMorphAttribute);
-
 		this.vertexPositionAttribute2 = this.gl.getAttribLocation(this.shaderProgramShadow, "vertexPosition");
 		this.gl.enableVertexAttribArray(this.vertexPositionAttribute2);
 
-		this.vertexPositionMorphAttribute2 = this.gl.getAttribLocation(this.shaderProgramShadow, "vertexPositionMorph");
-		this.gl.enableVertexAttribArray(this.vertexPositionMorphAttribute2);
-
 		this.textureCoordAttribute2 = this.gl.getAttribLocation(this.shaderProgramShadow, "textureCoordinate");
 		this.gl.enableVertexAttribArray(this.textureCoordAttribute2);
 
@@ -971,6 +1114,12 @@ App.Art.Engine = class {
 
 		this.quadTextureAttribute = this.gl.getAttribLocation(this.shaderProgramSSAO, "textureCoordinate");
 		this.gl.enableVertexAttribArray(this.quadTextureAttribute);
+
+		this.quadPositionAttribute3 = this.gl.getAttribLocation(this.shaderProgramSSS, "vertexPosition");
+		this.gl.enableVertexAttribArray(this.quadPositionAttribute3);
+
+		this.quadTextureAttribute3 = this.gl.getAttribLocation(this.shaderProgramSSS, "textureCoordinate");
+		this.gl.enableVertexAttribArray(this.quadTextureAttribute3);
 	}
 
 	bind(sceneData, sceneParams, dir) {
@@ -1036,6 +1185,20 @@ App.Art.Engine = class {
 			this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
 		}
 
+		if (V.setSSS) {
+			this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.buffers.sssFBO);
+			this.gl.clear(this.gl.COLOR_BUFFER_BIT);
+			this.gl.useProgram(this.shaderProgramSSS);
+			this.drawSSS(sceneParams);
+			this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
+
+			this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.buffers.sssBlurFBO);
+			this.gl.clear(this.gl.COLOR_BUFFER_BIT);
+			this.gl.useProgram(this.shaderProgramSSSBlur);
+			this.drawSSSBlur(sceneParams);
+			this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
+		}
+
 		this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
 		this.gl.useProgram(this.shaderProgramForwardPass);
 		this.drawForwardPass(sceneParams);
@@ -1146,12 +1309,6 @@ App.Art.Engine = class {
 				this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesNormalBuffer[i]);
 				this.gl.vertexAttribPointer(this.vertexNormalAttribute, 3, this.gl.FLOAT, false, 0, 0);
 
-				this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesMorphBuffer[i]);
-				this.gl.vertexAttribPointer(this.vertexPositionMorphAttribute, 3, this.gl.FLOAT, false, 0, 0);
-
-				this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesNormalMorphBuffer[i]);
-				this.gl.vertexAttribPointer(this.vertexNormalMorphAttribute, 3, this.gl.FLOAT, false, 0, 0);
-
 				// bind materials per surface and set uniforms
 				for (let j=0; j < modelParams.figures[i].surfaces.length; j++, count++) {
 					if (!modelParams.figures[i].surfaces[j].visible) {
@@ -1202,9 +1359,6 @@ App.Art.Engine = class {
 				this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesPositionBuffer[i]);
 				this.gl.vertexAttribPointer(this.vertexPositionAttribute2, 3, this.gl.FLOAT, false, 0, 0);
 
-				this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesMorphBuffer[i]);
-				this.gl.vertexAttribPointer(this.vertexPositionMorphAttribute2, 3, this.gl.FLOAT, false, 0, 0);
-
 				this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesTextureCoordBuffer[i]);
 				this.gl.vertexAttribPointer(this.textureCoordAttribute2, 2, this.gl.FLOAT, false, 0, 0);
 
@@ -1283,10 +1437,60 @@ App.Art.Engine = class {
 		this.gl.drawElements(this.gl.TRIANGLES, 6, this.gl.UNSIGNED_SHORT, 0);
 	}
 
+	drawSSS(sceneParams) {
+		this.gl.uniformMatrix4fv(this.gl.getUniformLocation(this.shaderProgramSSS, "projection"), false, new Float32Array(this.matrixFlatten(this.buffers.matProj)));
+
+		for (let i = 0; i < 32; ++i) {
+			this.gl.uniform3fv(this.gl.getUniformLocation(this.shaderProgramSSS, "samples[" + i + "]"), this.buffers.ssaoKernel[i]);
+		}
+
+		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramSSS, "radius"), sceneParams.sss.radius);
+		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramSSS, "bias"), sceneParams.sss.bias);
+		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramSSS, "scale"), sceneParams.sss.scale);
+
+		this.gl.activeTexture(this.gl.TEXTURE0);
+		this.gl.bindTexture(this.gl.TEXTURE_2D, this.buffers.gPosition);
+		this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgramSSS, "gPosition"), 0);
+
+		this.gl.activeTexture(this.gl.TEXTURE1);
+		this.gl.bindTexture(this.gl.TEXTURE_2D, this.buffers.gNormal);
+		this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgramSSS, "gNormal"), 1);
+
+		this.gl.activeTexture(this.gl.TEXTURE2);
+		this.gl.bindTexture(this.gl.TEXTURE_2D, this.buffers.noiseTexture);
+		this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgramSSS, "texNoise"), 2);
+
+		this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffers.quadPositionBuffer);
+		this.gl.vertexAttribPointer(this.quadPositionAttribute3, 2, this.gl.FLOAT, false, 0, 0);
+
+		this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffers.quadTextureBuffer);
+		this.gl.vertexAttribPointer(this.quadTextureAttribute3, 2, this.gl.FLOAT, false, 0, 0);
+
+		this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.buffers.quadIndexBuffer);
+		this.gl.drawElements(this.gl.TRIANGLES, 6, this.gl.UNSIGNED_SHORT, 0);
+	}
+
+	drawSSSBlur(sceneParams) {
+		this.gl.activeTexture(this.gl.TEXTURE0);
+		this.gl.bindTexture(this.gl.TEXTURE_2D, this.buffers.sssColorBuffer);
+
+		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramSSSBlur, "blur"), sceneParams.sss.blur);
+
+		this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffers.quadPositionBuffer);
+		this.gl.vertexAttribPointer(this.quadPositionAttribute3, 2, this.gl.FLOAT, false, 0, 0);
+
+		this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffers.quadTextureBuffer);
+		this.gl.vertexAttribPointer(this.quadTextureAttribute3, 2, this.gl.FLOAT, false, 0, 0);
+
+		this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.buffers.quadIndexBuffer);
+		this.gl.drawElements(this.gl.TRIANGLES, 6, this.gl.UNSIGNED_SHORT, 0);
+	}
+
 	drawForwardPass(sceneParams) {
 		// set scene uniforms
 		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sNormals"), sceneParams.settings.normals);
 		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sAO"), sceneParams.settings.ao);
+		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sIAO"), sceneParams.settings.iao);
 		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sSSAO"), V.setSSAO);
 		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sSSS"), sceneParams.settings.sss);
 		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sShadows"), V.setShadowMapping);
@@ -1306,6 +1510,10 @@ App.Art.Engine = class {
 		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "metallic"), sceneParams.pbr.metallic);
 		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "fresnel"), sceneParams.pbr.fresnel);
 		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "ssaoInt"), sceneParams.ssao.intensity);
+		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sssPower"), sceneParams.sss.power);
+		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sssDistortion"), sceneParams.sss.distortion);
+		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sssIntensity"), sceneParams.sss.intensity);
+		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sssAmbient"), sceneParams.sss.ambient);
 
 		for (let i = 0; i < sceneParams.directionalLights.length; i++) {
 			let lightVect = this.polarToCart(this.degreeToRad(sceneParams.directionalLights[i].yr), this.degreeToRad(sceneParams.directionalLights[i].xr));
@@ -1360,12 +1568,6 @@ App.Art.Engine = class {
 				this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesNormalBuffer[i]);
 				this.gl.vertexAttribPointer(this.vertexNormalAttribute, 3, this.gl.FLOAT, false, 0, 0);
 
-				this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesMorphBuffer[i]);
-				this.gl.vertexAttribPointer(this.vertexPositionMorphAttribute, 3, this.gl.FLOAT, false, 0, 0);
-
-				this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesNormalMorphBuffer[i]);
-				this.gl.vertexAttribPointer(this.vertexNormalMorphAttribute, 3, this.gl.FLOAT, false, 0, 0);
-
 				// bind materials per surface and set uniforms
 				for (let j=0; j < modelParams.figures[i].surfaces.length; j++, count++) {
 					if (!modelParams.figures[i].surfaces[j].visible) {
@@ -1423,6 +1625,10 @@ App.Art.Engine = class {
 							this.gl.bindTexture(this.gl.TEXTURE_2D, this.buffers.gShadowDepth);
 							this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgramForwardPass, "textSampler[8]"), 8);
 
+							this.gl.activeTexture(this.gl.TEXTURE9);
+							this.gl.bindTexture(this.gl.TEXTURE_2D, this.buffers.sssColorBufferBlur);
+							this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgramForwardPass, "textSampler[9]"), 9);
+
 							this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "d"), mat.d);
 							this.gl.uniform3fv(this.gl.getUniformLocation(this.shaderProgramForwardPass, "Ka"), mat.Ka);
 							this.gl.uniform3fv(this.gl.getUniformLocation(this.shaderProgramForwardPass, "Ks"), mat.Ks);
@@ -1435,10 +1641,7 @@ App.Art.Engine = class {
 							this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "angle"), mat.transform[2]);
 							this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "scale"), mat.transform[3]);
 
-							this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sssPower"), sceneParams.sss.power);
-							this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sssDistortion"), sceneParams.sss.distortion);
-							this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sssIntensity"), sceneParams.sss.intensity);
-							this.gl.uniform3fv(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sssColor"), mat.Ksss);
+							this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sss"), mat.sss);
 
 							// draw materials
 							this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, modelBuffers.verticesIndexBuffer[count]);
@@ -1457,36 +1660,63 @@ App.Art.Engine = class {
 					continue;
 				}
 
-				let vertexPositionMorph = new Float32Array(modelBuffers.vertexCount[f]);
-				let vertexNormalMorph = new Float32Array(modelBuffers.vertexCount[f]);
+				let vertexPosition = new Float32Array(modelBuffers.vertexPositionBuffer[f]);
+				let vertexNormal = new Float32Array(vertexPosition.length);
 
 				for (let m=0; m < modelParams.morphs.length; m++) {
 					let morphValue = modelParams.morphs[m].value;
 
 					if (morphValue !== 0) {
 						let vp = modelBuffers.vertexPositionMorphs[f][m];
-						let vn = modelBuffers.vertexNormalMorphs[f][m];
 						let vi = modelBuffers.vertexIndexMorphs[f][m];
 
 						if (morphValue === 1) {
 							for (let j = 0; j < vi.length; j++) {
-								vertexPositionMorph[vi[j]] += vp[j];
-								vertexNormalMorph[vi[j]] += vn[j];
+								vertexPosition[vi[j]] += vp[j];
 							}
 						} else {
 							for (let j=0; j < vi.length; j++) {
-								vertexPositionMorph[vi[j]] += vp[j] * morphValue;
-								vertexNormalMorph[vi[j]] += vn[j] * morphValue;
+								vertexPosition[vi[j]] += vp[j] * morphValue;
 							}
 						}
 					}
 				}
 
-				this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesMorphBuffer[f]);
-				this.gl.bufferData(this.gl.ARRAY_BUFFER, vertexPositionMorph, this.gl.STATIC_DRAW);
+				// recalculate normals
+				let indices = modelBuffers.figureIndices[f];
+				for (let j=0; j < indices.length; j+=3) {
+					let idx1 = indices[j]*3;
+					let idx2 = indices[j+1]*3;
+					let idx3 = indices[j+2]*3;
+
+					let v1 = [vertexPosition[idx1], vertexPosition[idx1+1], vertexPosition[idx1+2]];
+					let v2 = [vertexPosition[idx2], vertexPosition[idx2+1], vertexPosition[idx2+2]];
+					let v3 = [vertexPosition[idx3], vertexPosition[idx3+1], vertexPosition[idx3+2]];
+
+					let n = this.vectorNormalize(this.vectorCrossProduct(this.vectorSub(v2, v1), this.vectorSub(v1, v3)));
+
+					vertexNormal[idx1] += n[0];
+					vertexNormal[idx1+1] += n[1];
+					vertexNormal[idx1+2] += n[2];
+					vertexNormal[idx2] += n[0];
+					vertexNormal[idx2+1] += n[1];
+					vertexNormal[idx2+2] += n[2];
+					vertexNormal[idx3] += n[0];
+					vertexNormal[idx3+1] += n[1];
+					vertexNormal[idx3+2] += n[2];
+				}
+
+				// fix edge normals
+				let seemMap = modelBuffers.figureSeemMaps[f];
+				for (let j=0; j <= seemMap.length-2; j+=2) {
+					vertexNormal[seemMap[j]] = vertexNormal[seemMap[j+1]];
+				}
+
+				this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesPositionBuffer[f]);
+				this.gl.bufferData(this.gl.ARRAY_BUFFER, vertexPosition, this.gl.STATIC_DRAW);
 
-				this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesNormalMorphBuffer[f]);
-				this.gl.bufferData(this.gl.ARRAY_BUFFER, vertexNormalMorph, this.gl.STATIC_DRAW);
+				this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesNormalBuffer[f]);
+				this.gl.bufferData(this.gl.ARRAY_BUFFER, vertexNormal, this.gl.STATIC_DRAW);
 			}
 
 			modelBuffers.oldMorphValues = JSON.stringify(modelParams.morphs) + JSON.stringify(modelParams.figures);
diff --git a/src/art/webgl/ui.js b/src/art/webgl/ui.js
index 23a2290a7e8aad613e332e97320263f694f88960..8c7e8e43d3e7bdfdc735a6e75d8f5362717ae7b7 100644
--- a/src/art/webgl/ui.js
+++ b/src/art/webgl/ui.js
@@ -46,6 +46,7 @@ App.Art.createWebglUI = function(container, slave, artSize, scene, p) {
 	// canvas
 	let cvs = document.createElement("canvas");
 	cvs.setAttribute("style", "position: absolute;");
+	cvs.setAttribute("oncontextmenu", "return false;");
 
 	// btnLockView
 	let btnLockView = document.createElement("input");
@@ -82,11 +83,18 @@ App.Art.createWebglUI = function(container, slave, artSize, scene, p) {
 		btnLockView.setAttribute("src", view.lockView ? lockViewDisabled : lockViewEnabled);
 	}
 
+	let panY;
+	let zoomZ;
+	let zoomY;
+	let oz;
+	let oy;
+	let oxr;
+
 	// events
 	btnInspectView.onclick = function(e){
 		updateLinkedButtons("inspect");
 
-		view.yr = 180;
+		view.yr = 0;
 		view.inspect = true;
 		App.Art.Frame(slave, scene, view, p);
 		render();
@@ -100,6 +108,7 @@ App.Art.createWebglUI = function(container, slave, artSize, scene, p) {
 	btnFaceView.onclick = function(e){
 		updateLinkedButtons("face");
 
+		view.camera.x = 0;
 		view.camera.y = p.height-5;
 		view.yr = 0;
 		view.camera.xr = -6;
@@ -124,8 +133,25 @@ App.Art.createWebglUI = function(container, slave, artSize, scene, p) {
 
 		updateLinkedButtons("move");
 
-		view.camera.y = view.camera.y + e.movementY/7*V.setPanSpeed;
-		view.yr = view.yr + e.movementX*5*V.setRotationSpeed;
+		if (e.buttons === 1) { // left click
+			panY = panY + e.movementY/7*V.setPanSpeed;
+			view.camera.y = view.camera.y + e.movementY/7*V.setPanSpeed;
+			view.yr = view.yr + e.movementX*5*V.setRotationSpeed;
+		}
+		if (e.buttons === 2) { // right click
+			view.yr = view.yr + e.movementX*5*V.setRotationSpeed;
+			view.camera.xr = view.camera.xr - e.movementY*1*V.setRotationSpeed;
+			view.camera.xr = Math.clamp(view.camera.xr, -89, 89);
+
+			let r = (-oz-zoomZ)/Math.cos(oxr*Math.PI/180);
+			view.camera.y = oy + panY + zoomY - Math.sin((view.camera.xr)*Math.PI/180) * r + Math.sin(oxr*Math.PI/180)*r;
+			view.camera.z = Math.cos((view.camera.xr)*Math.PI/180) * -r;
+		}
+		if (e.buttons === 4) { // wheel click
+			panY = panY + e.movementY/7*V.setPanSpeed;
+			view.camera.y = view.camera.y + e.movementY/7*V.setPanSpeed;
+			view.camera.x = view.camera.x - e.movementX/7*V.setPanSpeed;
+		}
 		render();
 	};
 
@@ -136,6 +162,12 @@ App.Art.createWebglUI = function(container, slave, artSize, scene, p) {
 			render();
 			return;
 		}
+		oz = view.camera.z;
+		oy = view.camera.y;
+		oxr = view.camera.xr;
+		panY = 0;
+		zoomZ = 0;
+		zoomY = 0;
 		e.preventDefault();
 		e.stopPropagation();
 		App.Art.isDraggingCanvas=true;
@@ -163,10 +195,15 @@ App.Art.createWebglUI = function(container, slave, artSize, scene, p) {
 		// zoom speed based on distance from origin, and along direction of camera
 		let zOld = view.camera.z;
 		let magnitude = e.deltaY/(10/V.setZoomSpeed) * (-view.camera.z/50 + 0.2);
+
 		let zDistance = Math.cos(-view.camera.xr * (Math.PI/180)) * magnitude;
 		view.camera.z -= zDistance;
 		view.camera.z = Math.clamp(view.camera.z, -900, -10);
-		view.camera.y += Math.sin(-view.camera.xr * (Math.PI/180)) * magnitude * -(view.camera.z - zOld)/zDistance;
+		zoomZ -= zDistance;
+
+		let yDistance = Math.sin(-view.camera.xr * (Math.PI/180)) * magnitude * -(view.camera.z - zOld)/zDistance;
+		view.camera.y += yDistance;
+		zoomY += yDistance;
 
 		render();
 		return false;
@@ -212,6 +249,13 @@ App.Art.createWebglUI = function(container, slave, artSize, scene, p) {
 	scene.settings.rwidth = cvs.width * V.setSuperSampling;
 	scene.settings.rheight = cvs.height * V.setSuperSampling;
 
+	scene.shadows.x = -600;
+	scene.shadows.y = 1000;
+	scene.shadows.z = -1000;
+	scene.shadows.fov = 40;
+	scene.shadows.fnear = 5;
+	scene.shadows.ffar = 10000;
+
 	// render state
 	if (view.faceView) {
 		btnFaceView.click();
@@ -224,47 +268,6 @@ App.Art.createWebglUI = function(container, slave, artSize, scene, p) {
 		updateLinkedButtons();
 		render();
 	}
-
-	/*
-	if (artSize === 3) {
-		let cvs2 = document.createElement("canvas");
-		cvs2.setAttribute("style", "position: absolute; top: 530px; border: 4px; border-color: #151515; border-style: solid; margin-top: 15px;");
-
-		let cvs3 = document.createElement("canvas");
-		cvs3.setAttribute("style", "position: absolute; top: 530px; left: 156px; border: 4px; border-color: #151515; border-style: solid; margin-top: 15px;");
-
-		let oldYr = view.transform.yr;
-		// let oldCamera = view.camera;
-
-		view.camera.y = p.height * 8 / 10;
-		view.camera.z = -65 - (Math.sqrt(slave.boobs)/10);
-
-		cvs2.width = 150;
-		cvs2.height = 150;
-		view.rwidth = cvs2.width * V.setSuperSampling;
-		view.rheight = cvs2.height * V.setSuperSampling;
-		App.Art.engine.render(scene, cvs2);
-
-		view.camera.y = p.height * 5.5 / 10;
-		view.camera.z = -65;
-		view.transform.yr = -180;
-
-		cvs3.width = 150;
-		cvs3.height = 150;
-		view.rwidth = cvs3.width * V.setSuperSampling;
-		view.rheight = cvs3.height * V.setSuperSampling;
-		App.Art.engine.render(scene, cvs3);
-
-		container.appendChild(cvs2);
-		container.appendChild(cvs3);
-
-		App.Art.Frame(slave, scene, view, p);
-
-		view.rwidth = cvs.width * V.setSuperSampling;
-		view.rheight = cvs.height * V.setSuperSampling;
-		view.transform.yr = oldYr;
-		// view.camera = oldCamera;
-	}*/
 };
 
 App.Art.Frame = function(slave, scene, view, p) {
@@ -308,11 +311,13 @@ App.Art.AutoFrame = function(view, slaveHeight, cameraHeight, window, offset) {
 
 	view.camera.z = -h;
 	view.camera.y = cameraHeight;
+	view.camera.x = 0;
 	view.camera.xr = -rot;
 };
 
 App.Art.FixedFrame = function(view) {
 	view.camera.z = -282;
 	view.camera.y = 123;
+	view.camera.x = 0;
 	view.camera.xr = -6;
 };
diff --git a/src/cheats/cheatEditSlave.js b/src/cheats/cheatEditSlave.js
index 7bcc1eba3e51358f83322c52ce39e200381413e3..58e81b93669c512596f9f9fdafd205634c368446 100644
--- a/src/cheats/cheatEditSlave.js
+++ b/src/cheats/cheatEditSlave.js
@@ -56,6 +56,12 @@ App.UI.SlaveInteract.cheatEditSlave = function(slave) {
 		App.UI.DOM.appendNewElement("div", el, App.UI.DOM.link(
 			"Apply cheat edits",
 			() => {
+				if (V.tempSlave.devotion !== slave.devotion) {
+					V.tempSlave.oldDevotion = V.tempSlave.devotion;
+				}
+				if (V.tempSlave.trust !== slave.trust) {
+					V.tempSlave.oldTrust = V.tempSlave.trust;
+				}
 				SlaveDatatypeCleanup(V.tempSlave);
 				normalizeRelationship();
 				V.slaves[V.slaveIndices[slave.ID]] = V.tempSlave;
diff --git a/src/data/backwardsCompatibility/backwardsCompatibility.js b/src/data/backwardsCompatibility/backwardsCompatibility.js
index 4b5b6d0372d58a203b1b4516e8b9423c147fce32..8daf86d9e8cf24d7956123b175692927de7cb00d 100644
--- a/src/data/backwardsCompatibility/backwardsCompatibility.js
+++ b/src/data/backwardsCompatibility/backwardsCompatibility.js
@@ -141,7 +141,7 @@ App.Update.globalVariables = function(node) {
 	if (V.hostageAnnounced && !V.slaves.find(s => s.origin.includes("You were acquainted with $him before you were an arcology owner") && s.newGamePlus === 0)) {
 		V.rival.hostageState = 1;
 	}
-	V.rival.hostageState = V.rival.hostageState || 0;	
+	V.rival.hostageState = V.rival.hostageState || 0;
 
 	V.rival.state = V.rival.state || 0;
 	V.rival.prosperity = V.rival.prosperity || 0;
@@ -951,9 +951,6 @@ App.Update.globalVariables = function(node) {
 		if (V.week > 11 && V.assistant.personality === 0) {
 			V.assistant.personality = -1;
 		}
-		if (V.week > 29 && V.eventResults.aid === 0) {
-			V.eventResults.aid = -1;
-		}
 		App.Update.FCTV();
 		if (jQuery.isEmptyObject(V.arcologyUpgrade)) {
 			V.arcologyUpgrade = {
@@ -1462,7 +1459,6 @@ App.Update.slaveIndices = function(node) {
 App.Update.slaveRecords = function(node) {
 	const detachedSlaves = [
 		V.hostage,
-		V.slaveAfterRA,
 		V.boomerangSlave,
 		V.traitor,
 		V.shelterSlave
@@ -1687,6 +1683,7 @@ App.Update.arcologyLocation = function(node) {
 };
 
 App.Update.oldVersions = function(node) {
+	V.slaves.filter(s => s.career === 0).forEach(s => s.career = "a slave");
 	V.slaves.filter(s => s.origin === 0).forEach(s => s.origin = "");
 	if (V.releaseID === 1021 || V.releaseID === 1020 || V.releaseID === 1019 || V.releaseID === 2022) {
 		V.releaseID = 1022;
diff --git a/src/data/backwardsCompatibility/datatypeCleanup.js b/src/data/backwardsCompatibility/datatypeCleanup.js
index c13a5d635e2d7332d00551346e9b4d2b95ba89fc..af14361979dfaa92ceca64a05095bf32b9010935 100644
--- a/src/data/backwardsCompatibility/datatypeCleanup.js
+++ b/src/data/backwardsCompatibility/datatypeCleanup.js
@@ -2575,7 +2575,7 @@ App.Entity.Utils.RARuleDatatypeCleanup = function() {
 			for (const piercing of Object.keys(App.Data.Piercings)) {
 				const oldPiercing = `${oldPiercings.get(piercing) || piercing}Piercing`; // the old variable names were sometimes plural and sometimes singular. The new standard is to use singular when possible.
 				set.piercing[piercing].desc = null;
-				if (piercing === "gentials" && set.piercing[piercing].smart === undefined) {
+				if (piercing === "genitals" && set.piercing[piercing].smart === undefined) {
 					set.piercing[piercing].smart = null;
 				}
 				if (set.hasOwnProperty(oldPiercing)) {
diff --git a/src/data/backwardsCompatibility/updateSlaveObject.js b/src/data/backwardsCompatibility/updateSlaveObject.js
index ef97357da7d3441be89265bcc52db943c7285952..a88e58b40abfceda1f25b300e454fd9fe0c58759 100644
--- a/src/data/backwardsCompatibility/updateSlaveObject.js
+++ b/src/data/backwardsCompatibility/updateSlaveObject.js
@@ -4,6 +4,7 @@
  * @param {boolean} [genepool=false]
  */
 App.Update.Slave = function(slave, genepool = false) {
+	slave.career = slave.career || "a slave";
 	slave.origin = slave.origin || "";
 	const quirks = {};
 	App.Data.geneticQuirks.forEach((value, q) => quirks[q] = 0);
diff --git a/src/endWeek/economics/economics.js b/src/endWeek/economics/economics.js
index 53ba189fccf0ad7891f6b9d0025617250fb04de1..1bf6ae3d620fe531bbe3512a7e1959a40e784399 100644
--- a/src/endWeek/economics/economics.js
+++ b/src/endWeek/economics/economics.js
@@ -5,7 +5,7 @@ App.EndWeek.economics = function() {
 	if (V.cash > -10000) {
 		V.debtWarned = 0;
 	}
-	if (V.mods.food.market &&
+	if (V.mods.food.enabled && V.mods.food.market &&
 		(V.mods.food.amount > App.Facilities.Farmyard.foodConsumption() ||
 		V.cash > App.Facilities.Farmyard.foodConsumption() * V.mods.food.cost)) {
 		V.mods.food.warned = false;
diff --git a/src/endWeek/economics/persBusiness.js b/src/endWeek/economics/persBusiness.js
index 17451f6bb239bb1b9094145691b5be82e522b503..50dbd9cff67f3d7118ea1eb07e157f62540f94e6 100644
--- a/src/endWeek/economics/persBusiness.js
+++ b/src/endWeek/economics/persBusiness.js
@@ -46,7 +46,7 @@ App.EndWeek.personalBusiness = function() {
 			}
 		}
 	}
-	if (V.mods.food.market) {
+	if (V.mods.food.enabled && V.mods.food.market) {
 		if (V.mods.food.amount < App.Facilities.Farmyard.foodConsumption() && V.cash < (App.Facilities.Farmyard.foodConsumption() * V.mods.food.cost)) {
 			r.push(`<span class="red">WARNING: your arcology will starve in the coming week unless action is taken.</span>`);
 			if (V.mods.food.warned) {
diff --git a/src/endWeek/economics/reputation.js b/src/endWeek/economics/reputation.js
index 5c71d91a13a87165d42aae5a8366fe1a2e2febdb..de6e1493a4ca0e41a0a756c893cd8154ba4436f8 100644
--- a/src/endWeek/economics/reputation.js
+++ b/src/endWeek/economics/reputation.js
@@ -315,30 +315,35 @@ App.EndWeek.reputation = function() {
 			r.push(`The grim statue of the Smiling Man outside your arcology <span class="green">reminds the world of who managed to eliminate such a threat.</span>`);
 			repX(100, "architecture");
 		}
-
 		if (V.SecExp.edicts.weaponsLaw === 3) {
 			r.push(`The absence of any kind of restriction on weaponry within your arcology is <span class="green">welcomed by your citizens</span> as sign of your respect for the ideals the Free Cities stand for.`);
 			repX(20, "edicts");
 		}
+		if (V.SecExp.buildings.propHub && V.SecExp.buildings.propHub.upgrades.fakeNews > 0) {
+			r.push(`The authenticity department produces and distributes copious amounts of plausible enough news and reports, <span class="green">increasing your reputation.</span>`);
+			repX(10 * V.SecExp.buildings.propHub.upgrades.fakeNews, "policies");
+		}
+		const activeUnits = App.Mods.SecExp.battle.activeUnits();
+		if (activeUnits >= 4) {
+			r.push(`Your military is`);
+			if (activeUnits >= 9) {
+				r.push(`<span class="green">massive; commanding so many troops greatly</span>`);
+			} else if (activeUnits >= 7) {
+				r.push(`<span class="green">huge; commanding such a number of soldiers</span>`);
+			} else if (activeUnits >= 4) {
+				r.push(`<span class="green">at a decent size; commanding a small army</span>`);
+			}
+			r.push(`increases your reputation.`);
+			repX(12 * activeUnits, "securityExpansion");
+		}
 	}
 
 	if (V.SF.Toggle && V.SF.Active >= 1 && V.SF.UC.Assign > 0) {
 		const sfArray = [];
-		sfArray.push(`Assigning a`);
-		if (V.SF.UC.Assign === 1) {
-			sfArray.push(`small`);
-		} else {
-			sfArray.push(`large`);
-		}
+		sfArray.push(`Assigning a ${V.SF.UC.Assign === 1 ? 'small' : 'large'}`);
 		sfArray.push(`portion of ${V.SF.Lower} to <span class="green">undercover work, slightly boosts your reputation.</span>`);
 		App.Events.addNode(el, sfArray, "div");
-		let value;
-		if (V.SF.UC.Assign === 1) {
-			value = V.SF.ArmySize * 0.05;
-		} else {
-			value = V.SF.ArmySize * 0.25;
-		}
-		repX(value, "specialForces");
+		repX(V.SF.ArmySize * (V.SF.UC.Assign === 1 ? 0.05 : 0.25), "specialForces");
 	} else if (V.SF.FS.BadOutcome === "ISOLATION") {
 		r.push(App.UI.DOM.makeElement("div", `Your citizens are <span class="red">very displeased</span> that you are hosting a legion of heavily armed squatters in your basement.`));
 		repX(forceNeg(V.SF.ArmySize + App.Mods.SF.upgrades.total()), "specialForces");
@@ -904,11 +909,6 @@ App.EndWeek.reputation = function() {
 		repX(500, "policies");
 	}
 
-	if (V.secExpEnabled > 0 && V.SecExp.buildings.propHub && V.SecExp.buildings.propHub.upgrades.fakeNews > 0) {
-		r.push(`The authenticity department produces and distributes copious amounts of plausible enough news and reports, <span class="green">increasing your reputation.</span>`);
-		repX(10 * V.SecExp.buildings.propHub.upgrades.fakeNews, "policies");
-	}
-
 	App.Events.addParagraph(el, r);
 	r = [];
 	const repGain = hashSum(V.lastWeeksRepIncome);
diff --git a/src/endWeek/endWeek.js b/src/endWeek/endWeek.js
index 4b98f232c3bd01f5c704811932d712e55aceb2e1..f83ea91958a48027201993b0dd5e6e414e844a0e 100644
--- a/src/endWeek/endWeek.js
+++ b/src/endWeek/endWeek.js
@@ -181,7 +181,7 @@ globalThis.endWeek = (function() {
 	}
 
 	function food() {
-		if (V.mods.food.market) {
+		if (V.mods.food.enabled && V.mods.food.market) {
 			V.mods.food.amount += App.Facilities.Farmyard.foodProduction();
 		}
 	}
diff --git a/src/endWeek/nextWeek/nextWeek.js b/src/endWeek/nextWeek/nextWeek.js
index 4bf5cea4cd2d5fb5679b83a8301e0aac5db88beb..4688cc0b74bf00f402e40c0577c02c1c5a0e3673 100644
--- a/src/endWeek/nextWeek/nextWeek.js
+++ b/src/endWeek/nextWeek/nextWeek.js
@@ -6,7 +6,7 @@ App.EndWeek.nextWeek = function() {
 
 	const rival = V.arcologies.find(function(s) { return s.direction !== 0 && s.rival === 1; });
 	if (rival && V.rival.prosperity !== 0) {
-		V.rival.prosperity = V.rival.prosperity;
+		V.rival.prosperity = rival.prosperity;
 	} else if (!rival) {
 		V.rival.state = 1;
 	}
@@ -129,7 +129,7 @@ App.EndWeek.nextWeek = function() {
 			V.localEcon = 20;
 		}
 
-		if (V.mods.food.market) {
+		if (V.mods.food.enabled && V.mods.food.market) {
 			if (V.localEcon > 100) {
 				V.mods.food.cost = Math.max(5 / (1 + (Math.trunc(1000 - 100000 / V.localEcon) / 10) / 100), 3.125);
 			} else if (V.localEcon === 100) {
diff --git a/src/endWeek/player/prDiet.js b/src/endWeek/player/prDiet.js
index a9ffa2cb7d206b6dd7cdcc744687f3e36e3de4f9..d2cf00e56fd4b612efdfb86d32c3b45b116ba290 100644
--- a/src/endWeek/player/prDiet.js
+++ b/src/endWeek/player/prDiet.js
@@ -472,7 +472,7 @@ App.EndWeek.Player.diet = function(PC = V.PC) {
 				break;
 			case "cleansing": // chem reduce and health plus
 				if (!canSmell(PC) && !canTaste(PC)) {
-					r.push(`<span class="health inc">You feel spectacular</span> on this health focused diet, even if you find other people try to avoid and that your slaves hold their breath when in your presence.`);
+					r.push(`<span class="health inc">You feel spectacular</span> on this health focused diet, even if you find other people try to avoid you, and that your slaves hold their breath when in your presence.`);
 				} else if (!canSmell(PC) || !canTaste(PC)) {
 					r.push(`Your specialized diet ${canTaste(PC) ? "tastes" : "smells"} awful, but leaves you <span class="health inc">feeling well</span> once you get over it.`);
 				} else {
diff --git a/src/endWeek/player/prHormones.js b/src/endWeek/player/prHormones.js
index 9f60c8a4a447e673e14e2a6c47ceef80b82d43df..7c6d7811917be33dbfef98c6a04c79d0d39d232d 100644
--- a/src/endWeek/player/prHormones.js
+++ b/src/endWeek/player/prHormones.js
@@ -106,7 +106,7 @@ App.EndWeek.Player.hormones = function(PC = V.PC) {
 		if ((PC.hormoneBalance > 20 && PC.genes === "XY" && PC.balls !== 0 && PC.ballType !== "sterile") || (PC.hormoneBalance < -20 && PC.genes === "XX" && (PC.ovaries !== 0 || PC.mpreg !== 0))) {
 			r.push(`Your altered hormonal balance conflicts with your natural hormones, causing occasional moodiness.`);
 			if (PC.energy > 10) {
-				r.push(`These clashing hormones also have the frustrating effect of <span class="libido dec">disrupt your sex drive.</span>`);
+				r.push(`These clashing hormones also have the frustrating effect of <span class="libido dec">disrupting your sex drive.</span>`);
 				PC.energy--;
 			}
 		}
diff --git a/src/endWeek/reports/cellblockReport.js b/src/endWeek/reports/cellblockReport.js
index de18e36d06188d72bf7a3e550c22588fbd236e43..cb063ff01ffec5dcbd7cfc7df7304ae87ad3bfb5 100644
--- a/src/endWeek/reports/cellblockReport.js
+++ b/src/endWeek/reports/cellblockReport.js
@@ -15,7 +15,7 @@ App.EndWeek.cellblockReport = function() {
 	let confinedResults;
 
 	const cellblockNameCaps = capFirstChar(V.cellblockName);
-	V.flSex = App.EndWeek.getFLSex(App.Entity.facilities.cellblock);/* FIXME: should be local, passed as a parameter to saRules */
+	App.EndWeek.saVars.flSex = App.EndWeek.getFLSex(App.Entity.facilities.cellblock);
 
 	if (S.Wardeness) {
 		const {
diff --git a/src/endWeek/reports/clinicReport.js b/src/endWeek/reports/clinicReport.js
index 7ada1964711d15a73bc57dc4346a1323d8d0f0ea..372ece308e4dccb679166b1048fcdc0ac22f79eb 100644
--- a/src/endWeek/reports/clinicReport.js
+++ b/src/endWeek/reports/clinicReport.js
@@ -4,7 +4,7 @@ App.EndWeek.clinicReport = function() {
 	const slaves = App.Utils.sortedEmployees(App.Entity.facilities.clinic);
 	let devBonus = (V.clinicDecoration !== "standard") ? 1 : 0;
 	let healthBonus = 0;
-	V.flSex = App.EndWeek.getFLSex(App.Entity.facilities.clinic); // FIXME: should be local, passed as a parameter to saRules
+	App.EndWeek.saVars.flSex = App.EndWeek.getFLSex(App.Entity.facilities.clinic);
 
 	function nurseText() {
 		let r = [];
diff --git a/src/endWeek/reports/nurseryReport.js b/src/endWeek/reports/nurseryReport.js
index 66bda83d85b7a45bdadfc4e5f65af73a82a21849..f97a9a47fcdd065ed615e83cdf67db6e19cf30e7 100644
--- a/src/endWeek/reports/nurseryReport.js
+++ b/src/endWeek/reports/nurseryReport.js
@@ -11,7 +11,7 @@ App.Facilities.Nursery.nurseryReport = function nurseryReport() {
 
 	let matronBonus = 0;
 
-	V.flSex = App.EndWeek.getFLSex(App.Entity.facilities.nursery); // FIXME: should be local, passed as a parameter to saRules
+	App.EndWeek.saVars.flSex = App.EndWeek.getFLSex(App.Entity.facilities.nursery);
 
 	function matronChanges() {
 		if (S.Matron) {
diff --git a/src/endWeek/reports/penthouseReport.js b/src/endWeek/reports/penthouseReport.js
index f2dc91c5624554929312c3bf835cb573c66e2dd7..33a654b76494797f3f4f1b537c3019f1387c3d0d 100644
--- a/src/endWeek/reports/penthouseReport.js
+++ b/src/endWeek/reports/penthouseReport.js
@@ -518,6 +518,19 @@ App.EndWeek.penthouseReport = function() {
 		App.Events.addNode(el, r, "div", "indent");
 		return el;
 
+		/**
+		 * Gives a back a clone with RA applied to it. The original is not modified.
+		 * Call and then check potential change against it to see if the RA would revert it.
+		 *
+		 * @param {FC.SlaveState} slave
+		 * @returns {FC.SlaveState}
+		 */
+		function slaveAfterRA(slave) {
+			const after = clone(slave);
+			DefaultRules(after);
+			return after;
+		}
+
 		function piercingCheck() {
 			let piercingForbidden = 0;
 			if (slave.piercing.ear.weight === 0 && slave.earShape !== "none") {
@@ -526,8 +539,7 @@ App.EndWeek.penthouseReport = function() {
 				} else {
 					slave.piercing.ear.weight = 1;
 				}
-				RulesDeconfliction(slave);
-				if (slave.piercing.ear.weight !== V.slaveAfterRA.piercing.ear.weight) {
+				if (slave.piercing.ear.weight !== slaveAfterRA(slave).piercing.ear.weight) {
 					piercingForbidden = 1;
 					slave.piercing.ear.weight = 0;
 				} else {
@@ -546,8 +558,7 @@ App.EndWeek.penthouseReport = function() {
 				} else {
 					slave.piercing.nose.weight = 1;
 				}
-				RulesDeconfliction(slave);
-				if (slave.piercing.nose.weight !== V.slaveAfterRA.piercing.nose.weight) {
+				if (slave.piercing.nose.weight !== slaveAfterRA(slave).piercing.nose.weight) {
 					piercingForbidden = 1;
 					slave.piercing.nose.weight = 0;
 				} else {
@@ -566,8 +577,7 @@ App.EndWeek.penthouseReport = function() {
 				} else {
 					slave.piercing.eyebrow.weight = 1;
 				}
-				RulesDeconfliction(slave);
-				if (slave.piercing.eyebrow.weight !== V.slaveAfterRA.piercing.eyebrow.weight) {
+				if (slave.piercing.eyebrow.weight !== slaveAfterRA(slave).piercing.eyebrow.weight) {
 					piercingForbidden = 1;
 					slave.piercing.eyebrow.weight = 0;
 				} else {
@@ -586,8 +596,7 @@ App.EndWeek.penthouseReport = function() {
 				} else {
 					slave.piercing.lips.weight = 1;
 				}
-				RulesDeconfliction(slave);
-				if (slave.piercing.lips.weight !== V.slaveAfterRA.piercing.lips.weight) {
+				if (slave.piercing.lips.weight !== slaveAfterRA(slave).piercing.lips.weight) {
 					piercingForbidden = 1;
 					slave.piercing.lips.weight = 0;
 				} else {
@@ -606,8 +615,7 @@ App.EndWeek.penthouseReport = function() {
 				} else {
 					slave.piercing.navel.weight = 1;
 				}
-				RulesDeconfliction(slave);
-				if (slave.piercing.navel.weight !== V.slaveAfterRA.piercing.navel.weight) {
+				if (slave.piercing.navel.weight !== slaveAfterRA(slave).piercing.navel.weight) {
 					piercingForbidden = 1;
 					slave.piercing.navel.weight = 0;
 				} else {
diff --git a/src/endWeek/reports/schoolroomReport.js b/src/endWeek/reports/schoolroomReport.js
index 72555c98105a927dab7f3492bccc81c7a9726283..5c64a0174d5d0b4cd95f2383d6651de44d45bd42 100644
--- a/src/endWeek/reports/schoolroomReport.js
+++ b/src/endWeek/reports/schoolroomReport.js
@@ -3,7 +3,7 @@ App.EndWeek.schoolroomReport = function() {
 
 	const slaves = App.Utils.sortedEmployees(App.Entity.facilities.schoolroom);
 	const devBonus = (V.schoolroomDecoration !== "standard") ? 1 : 0;
-	V.flSex = App.EndWeek.getFLSex(App.Entity.facilities.schoolroom); // FIXME: should be local, passed as a parameter to saRules
+	App.EndWeek.saVars.flSex = App.EndWeek.getFLSex(App.Entity.facilities.schoolroom);
 
 	function schoolteacherText() {
 		let r = [];
diff --git a/src/endWeek/reports/spaReport.js b/src/endWeek/reports/spaReport.js
index 0b1327e032675039452e45052b43355250f39ba1..bdb0208d91da6eca08a6d6108060bff044101fa2 100644
--- a/src/endWeek/reports/spaReport.js
+++ b/src/endWeek/reports/spaReport.js
@@ -11,7 +11,7 @@ App.EndWeek.spaReport = function() {
 	let restedSlave;
 	let devBonus = (V.spaDecoration !== "standard") ? 1 : 0;
 
-	V.flSex = App.EndWeek.getFLSex(App.Entity.facilities.spa); /* FIXME: should be local, passed as a parameter to saRules */
+	App.EndWeek.saVars.flSex = App.EndWeek.getFLSex(App.Entity.facilities.spa);
 
 	if (S.Attendant) {
 		let idleBonus = 0;
diff --git a/src/endWeek/saChoosesOwnClothes.js b/src/endWeek/saChoosesOwnClothes.js
index b0c97d25b35385a586eef2f278317403b5484391..55f8dc495f09e111b55d8b7f092ce704c50d9644 100644
--- a/src/endWeek/saChoosesOwnClothes.js
+++ b/src/endWeek/saChoosesOwnClothes.js
@@ -927,7 +927,7 @@ App.SlaveAssignment.choosesOwnClothes = function saChoosesOwnClothes(slave) {
 				/* pregnancy */
 				if (slave.belly >= 5000) {
 					wardrobeTastes.push({text: `and wears pretty lingerie to show off ${his} merchandise while giving ${his} protruding belly plenty of room to hang free.`, clothes: "attractive lingerie"});
-					wardrobeTastes.push({text: `and wears only panties. Something so easy to slip on is appreciable with such a big belly in the way.`, clothes: "panties"});
+					wardrobeTastes.push({text: `and wears only panties. ${He} appreciates something so easy to slip on, with such a big belly in the way.`, clothes: "panties"});
 					if (isItemAccessible.entry("kitty lingerie") === true) {
 						wardrobeTastes.push({text: `and wears cute lingerie to show off ${his} merchandise while giving ${his} protruding belly plenty of room to hang free.`, clothes: "kitty lingerie"});
 					}
diff --git a/src/endWeek/saClothes.js b/src/endWeek/saClothes.js
index edaca0092b6e0e7f584412eddd16181b1f7389d5..1a1c9d315334dad028f42102343a933c030f3235 100644
--- a/src/endWeek/saClothes.js
+++ b/src/endWeek/saClothes.js
@@ -670,7 +670,7 @@ App.SlaveAssignment.clothes = function saClothes(slave) {
 			} else if (slave.bellyPreg >= 1500) {
 				r.push(`The tight corseting has <span class="orange">caused ${him} to miscarry,</span> which <span class="health dec">damages ${his} health.</span>`);
 				healthDamage(slave, 20);
-				if (lastPregRule(slave, V.defaultRules)) {
+				if (rulesDemandContraceptives(slave, V.defaultRules)) {
 					slave.preg = -1;
 				} else {
 					slave.preg = 0;
@@ -1105,7 +1105,7 @@ App.SlaveAssignment.clothes = function saClothes(slave) {
 					if (jsRandom(1, 100) > 50) {
 						r.push(`The dildo penetrating ${his} womb <span class="orange">caused ${him} to miscarry,</span> which <span class="health dec">damages ${his} health.</span>`);
 						healthDamage(slave, 20);
-						if (lastPregRule(slave, V.defaultRules)) {
+						if (rulesDemandContraceptives(slave, V.defaultRules)) {
 							slave.preg = -1;
 						} else {
 							slave.preg = 0;
diff --git a/src/endWeek/saDrugs.js b/src/endWeek/saDrugs.js
index 4d318157ab68b5454414d7fafca66d7f7fa3d7be..6ff47053c6fd702764fbeed5c88a5fc7879ec440 100644
--- a/src/endWeek/saDrugs.js
+++ b/src/endWeek/saDrugs.js
@@ -1572,7 +1572,7 @@ App.SlaveAssignment.drugs = function saDrugs(slave) {
 					healthDamage(slave, (slave.preg + slave.pregType - slave.bellySag));
 					if (slave.health.condition < -90) {
 						r += ` ${His} critically poor health <span class="orange">caused ${him} to miscarry.</span>`;
-						if (lastPregRule(slave, V.defaultRules)) {
+						if (rulesDemandContraceptives(slave, V.defaultRules)) {
 							slave.preg = -1;
 						} else {
 							slave.preg = 0;
diff --git a/src/endWeek/saGetMilked.js b/src/endWeek/saGetMilked.js
index e6105425232ee1f0fdbad4a68ac5a32322a7ea72..2e0dac00c92f25c80a1071bbc4f57536a5e08111 100644
--- a/src/endWeek/saGetMilked.js
+++ b/src/endWeek/saGetMilked.js
@@ -92,7 +92,7 @@
 		}
 
 		/**
-		 * @param {object} incomeStats getSlaveStatisticData return value - FIXME should be a named type
+		 * @param {FC.SlaveStatisticData} incomeStats
 		 */
 		function recordFacilityStatistics(incomeStats) {
 			incomeStats.milk = r.milk;
diff --git a/src/endWeek/saLongTermEffects.js b/src/endWeek/saLongTermEffects.js
index 4887fe53fe2b55f68fa093d19e8f82724c6b7211..04b70afe184d7369f03873bc0bcf9eb52ac21b0a 100644
--- a/src/endWeek/saLongTermEffects.js
+++ b/src/endWeek/saLongTermEffects.js
@@ -2368,7 +2368,7 @@ App.SlaveAssignment.longTermEffects = function saLongTermEffects(slave) {
 						miscarriage = 1;
 					} else {
 						r.push(`${His} overwhelmed body has <span class="miscarriage">forced ${him} to miscarry,</span> possibly saving ${his} life.`);
-						slave.preg = (lastPregRule(slave, V.defaultRules)) ? -1 : 0;
+						slave.preg = rulesDemandContraceptives(slave, V.defaultRules) ? -1 : 0;
 						if (slave.fuckdoll === 0 && slave.fetish !== "mindbroken") {
 							if (slave.sexualFlaw === "breeder") {
 								r.push(`${He} is <span class="devotion dec">filled with violent, all-consuming hatred</span> at ${himself} for failing to carry to term and you for allowing this to happen.`);
diff --git a/src/endWeek/saLongTermMentalEffects.js b/src/endWeek/saLongTermMentalEffects.js
index 1eb131d8567a3990aef9c7c159f177aefdf5dee3..82923894e546a7d27f22c365550c38c00e7333de 100644
--- a/src/endWeek/saLongTermMentalEffects.js
+++ b/src/endWeek/saLongTermMentalEffects.js
@@ -1863,7 +1863,7 @@ App.SlaveAssignment.longTermMentalEffects = function saLongTermMentalEffects(sla
 				}
 			}
 		}
-		if (!slave.piercing.genitals.smart || slave.clitSetting === "off" || slave.clitSetting === "none" || slave.clitSetting === "all" || slave.clitSetting === "men" || slave.clitSetting === "women") {
+		if (!slave.piercing.genitals.smart || ["off", "none", "all", "men", "women", "anti-men", "anti-women"].includes(slave.clitSetting)) {
 			if (canDoAnal(slave)) {
 				if (slave.vagina > -1 && !canDoVaginal(slave)) {
 					if (slave.fetishStrength <= 95) {
diff --git a/src/endWeek/saPregnancy.js b/src/endWeek/saPregnancy.js
index 4806b557359fccaf894929315cb66e830cd91a60..27fb4cbede5f774f05e7e14dd15a3800758d6cc3 100644
--- a/src/endWeek/saPregnancy.js
+++ b/src/endWeek/saPregnancy.js
@@ -482,7 +482,7 @@ App.SlaveAssignment.pregnancy = function saPregnancy(slave) {
 				break;
 		}
 		if (isInduced(slave)) {
-			r.push(`${His} child${slave.pregType > 1 ? "ren visibly shift" : "visibly shifts"} within ${his} womb as ${slave.pregType > 1 ? "they prepare" : "it prepares"} to enter the world. ${He} experiences several`);
+			r.push(`${His} child${slave.pregType > 1 ? "ren visibly shift" : " visibly shifts"} within ${his} womb as ${slave.pregType > 1 ? "they prepare" : "it prepares"} to enter the world. ${He} experiences several`);
 			if (slave.geneticQuirks.uterineHypersensitivity === 2) {
 				r.push(`unexpected orgasms,`);
 			} else {
diff --git a/src/endWeek/saRules.js b/src/endWeek/saRules.js
index ca1ba60d12aec6feb0ac0fe401abfe94cf9b261b..f76ffc75a756a6dae7191a9f1ae1d13e9043099d 100644
--- a/src/endWeek/saRules.js
+++ b/src/endWeek/saRules.js
@@ -30,7 +30,7 @@ App.SlaveAssignment.rules = function(slave) {
 					} else if (slave.energy <= 20) {
 						r.push(`is frigid and has little interest in getting off, not that ${he} gets a choice.`);
 						slave.need = 0;
-					} else if (slave.need < slave.needCap * 0.5) {
+					} else if (slave.need < App.EndWeek.saVars.needCapPerSlave[slave.ID] * 0.5) {
 						if (slave.devotion <= 20) {
 							r.push(`gets off at work despite ${his} reluctance, <span class="hotpink">habituating ${him} to being a fuckhole.</span>`);
 							slave.devotion += 1;
@@ -63,7 +63,7 @@ App.SlaveAssignment.rules = function(slave) {
 					if (slave.energy <= 20) {
 						r.push(`is frigid and has little interest in getting off${(App.Utils.releaseRestricted(slave)) ? `, making the rule restricting ${his} sexual outlets superfluous` : ``}.`);
 						slave.need = 0;
-					} else if (slave.need < slave.needCap * 0.5) {
+					} else if (slave.need < App.EndWeek.saVars.needCapPerSlave[slave.ID] * 0.5) {
 						if (App.Utils.hasNonassignmentSex(slave)) {
 							r.push(`gets off at work as well as during ${his} rest time.`);
 						} else if (release.masturbation === 0) {
@@ -125,7 +125,7 @@ App.SlaveAssignment.rules = function(slave) {
 					} else if (slave.energy <= 20) {
 						r.push(`is frigid and has little interest in getting off, not that ${his} clients care.`);
 						slave.need = 0;
-					} else if (slave.need < slave.needCap * 0.5) {
+					} else if (slave.need < App.EndWeek.saVars.needCapPerSlave[slave.ID] * 0.5) {
 						if (slave.devotion <= 20) {
 							r.push(`gets off at work despite ${his} reluctance, <span class="hotpink">habituating ${him} to sexual slavery.</span>`);
 							slave.devotion += 1;
@@ -298,7 +298,7 @@ App.SlaveAssignment.rules = function(slave) {
 					if (slave.energy <= 20) {
 						r.push(`is frigid and has little interest in getting off${(App.Utils.releaseRestricted(slave)) ? `, making the rule restricting ${his} sexual outlets superfluous` : ``}.`);
 						slave.need = 0;
-					} else if (slave.need < slave.needCap * 0.5) {
+					} else if (slave.need < App.EndWeek.saVars.needCapPerSlave[slave.ID] * 0.5) {
 						if (App.Utils.hasNonassignmentSex(slave)) {
 							r.push(`gets off at work as well as during ${his} rest time.`);
 						} else if (release.masturbation === 0) {
@@ -360,7 +360,7 @@ App.SlaveAssignment.rules = function(slave) {
 					} else if (slave.energy <= 20) {
 						r.push(`is frigid and has little interest in getting off, not that ${his} spectators care.`);
 						slave.need = 0;
-					} else if (slave.need < slave.needCap * 0.5) {
+					} else if (slave.need < App.EndWeek.saVars.needCapPerSlave[slave.ID] * 0.5) {
 						if (slave.devotion <= 20) {
 							r.push(`gets off at work despite ${his} reluctance, <span class="hotpink">habituating ${him} to sexual slavery.</span>`);
 							slave.devotion += 1;
@@ -488,11 +488,11 @@ App.SlaveAssignment.rules = function(slave) {
 					r.push(App.SlaveAssignment.rewardAndPunishment(slave));
 					break;
 				case "be the Nurse":
-					slave.need -= (V.flSex.size * 3);
+					slave.need -= App.EndWeek.saVars.flSex.size * 3;
 					if (slave.energy <= 20) {
 						r.push(`is frigid and has little interest in getting off${(App.Utils.releaseRestricted(slave)) ? `, making the rule restricting ${his} sexual outlets superfluous` : ``}.`);
 						slave.need = 0;
-					} else if (slave.need < slave.needCap * 0.5) {
+					} else if (slave.need < App.EndWeek.saVars.needCapPerSlave[slave.ID] * 0.5) {
 						if (App.Utils.hasNonassignmentSex(slave)) {
 							r.push(`gets off at work as well as during ${his} rest time.`);
 						} else if (release.masturbation === 0) {
@@ -824,11 +824,11 @@ App.SlaveAssignment.rules = function(slave) {
 					r.push(App.SlaveAssignment.rewardAndPunishment(slave));
 					break;
 				case "be the Wardeness":
-					slave.need -= (V.flSex.size * 5);
+					slave.need -= App.EndWeek.saVars.flSex.size * 5;
 					if (slave.energy <= 20) {
 						r.push(`is frigid and has little interest in getting off${(App.Utils.releaseRestricted(slave)) ? `, making the rule restricting ${his} sexual outlets superfluous` : ``}.`);
 						slave.need = 0;
-					} else if (slave.need < slave.needCap * 0.5) {
+					} else if (slave.need < App.EndWeek.saVars.needCapPerSlave[slave.ID] * 0.5) {
 						r.push(`gets off at work, so ${he} doesn't feel the need for release that often.`);
 						slave.need -= 20;
 					} else {
@@ -877,7 +877,7 @@ App.SlaveAssignment.rules = function(slave) {
 					break;
 				case "be confined in the cellblock":
 					wardenFunTimes = 0;
-					if (V.flSex.has(slave.ID)) {
+					if (App.EndWeek.saVars.flSex.has(slave.ID)) {
 						wardenFunTimes = random(0, 5);
 						slave.need -= (10 * wardenFunTimes);
 					}
@@ -914,7 +914,7 @@ App.SlaveAssignment.rules = function(slave) {
 								}
 							}
 						}
-						if (slave.need < slave.needCap * 0.5) {
+						if (slave.need < App.EndWeek.saVars.needCapPerSlave[slave.ID] * 0.5) {
 							if (slave.devotion <= 20) {
 								r.push(`gets off despite ${his} reluctance, <span class="hotpink">habituating ${him} to sexual slavery.</span>`);
 								slave.devotion += 1;
@@ -1003,11 +1003,11 @@ App.SlaveAssignment.rules = function(slave) {
 					r.push(App.SlaveAssignment.rewardAndPunishment(slave));
 					break;
 				case "be the Attendant":
-					slave.need -= (V.flSex.size * 3);
+					slave.need -= App.EndWeek.saVars.flSex.size * 3;
 					if (slave.energy <= 20) {
 						r.push(`is frigid and has little interest in getting off${(App.Utils.releaseRestricted(slave)) ? `, making the rule restricting ${his} sexual outlets superfluous` : ``}.`);
 						slave.need = 0;
-					} else if (slave.need < slave.needCap * 0.5) {
+					} else if (slave.need < App.EndWeek.saVars.needCapPerSlave[slave.ID] * 0.5) {
 						if (App.Utils.hasNonassignmentSex(slave)) {
 							r.push(`gets off at work as well as during ${his} rest time.`);
 						} else if (release.masturbation === 0) {
@@ -1070,7 +1070,7 @@ App.SlaveAssignment.rules = function(slave) {
 					} else if (slave.energy <= 20) {
 						r.push(`is frigid and has little interest in getting off.`);
 						slave.need = 0;
-					} else if (V.flSex.has(slave.ID)) {
+					} else if (App.EndWeek.saVars.flSex.has(slave.ID)) {
 						r.push(`is routinely relieved of any built up tension by ${S.Attendant.slaveName} and ${his}`);
 						if (canPenetrate(slave) && S.Attendant.boobs >= 500) {
 							r.push(`luscious breasts.`);
@@ -1210,11 +1210,11 @@ App.SlaveAssignment.rules = function(slave) {
 					r.push(App.SlaveAssignment.rewardAndPunishment(slave));
 					break;
 				case "be the Matron":
-					slave.need -= (V.flSex.size * 3);
+					slave.need -= App.EndWeek.saVars.flSex.size * 3;
 					if (slave.energy <= 20) {
 						r.push(`is frigid and has little interest in getting off${(App.Utils.releaseRestricted(slave)) ? `, making the rule restricting ${his} sexual outlets superfluous` : ``}.`);
 						slave.need = 0;
-					} else if (slave.need < slave.needCap * 0.5) {
+					} else if (slave.need < App.EndWeek.saVars.needCapPerSlave[slave.ID] * 0.5) {
 						if (release.masturbation === 0) {
 							r.push(`gets off while relieving ${his} charges, so being forbidden from masturbation doesn't really bother ${him}.`);
 						} else {
@@ -1274,7 +1274,7 @@ App.SlaveAssignment.rules = function(slave) {
 					} else if (slave.energy <= 20) {
 						r.push(`is frigid and has little interest in getting off.`);
 						slave.need = 0;
-					} else if (V.flSex.has(slave.ID)) {
+					} else if (App.EndWeek.saVars.flSex.has(slave.ID)) {
 						r.push(`is routinely relieved of any built up tension by ${S.Matron.slaveName} and ${his}`);
 						if (canPenetrate(slave) && S.Matron.boobs >= 500) {
 							r.push(`luscious breasts.`);
@@ -1404,11 +1404,11 @@ App.SlaveAssignment.rules = function(slave) {
 					r.push(App.SlaveAssignment.rewardAndPunishment(slave));
 					break;
 				case "be the Schoolteacher":
-					slave.need -= (V.flSex.size * 10);
+					slave.need -= App.EndWeek.saVars.flSex.size * 10;
 					if (slave.energy <= 20) {
 						r.push(`is frigid and has little interest in getting off${(App.Utils.releaseRestricted(slave)) ? `, making the rule restricting ${his} sexual outlets superfluous` : ``}.`);
 						slave.need = 0;
-					} else if (slave.need < slave.needCap * 0.5) {
+					} else if (slave.need < App.EndWeek.saVars.needCapPerSlave[slave.ID] * 0.5) {
 						if (release.masturbation === 0) {
 							r.push(`gets off with ${his} students, so being forbidden from masturbation doesn't really bother ${him}.`);
 						} else {
@@ -1466,7 +1466,7 @@ App.SlaveAssignment.rules = function(slave) {
 					r.push(App.SlaveAssignment.rewardAndPunishment(slave));
 					break;
 				case "learn in the schoolroom":
-					if (V.flSex.has(slave.ID)) {
+					if (App.EndWeek.saVars.flSex.has(slave.ID)) {
 						slave.need -= 30;
 						seX(slave, "oral", S.Schoolteacher, "oral", 7);
 						if (canPenetrate(S.Schoolteacher) && slave.boobs > 500) {
@@ -1512,7 +1512,7 @@ App.SlaveAssignment.rules = function(slave) {
 					} else if (slave.energy <= 20) {
 						r.push(`is frigid and has little interest in getting off${(App.Utils.releaseRestricted(slave)) ? `, making the rule restricting ${his} sexual outlets superfluous` : ``}.`);
 						slave.need = 0;
-					} else if (slave.need < slave.needCap * 0.5) {
+					} else if (slave.need < App.EndWeek.saVars.needCapPerSlave[slave.ID] * 0.5) {
 						if (slave.devotion <= 20) {
 							r.push(`gets off during class despite ${his} reluctance, <span class="hotpink">habituating ${him} to sexual slavery.</span>`);
 							slave.devotion += 1;
@@ -1571,7 +1571,7 @@ App.SlaveAssignment.rules = function(slave) {
 					if (slave.energy <= 20) {
 						r.push(`is frigid and has little interest in getting off${(App.Utils.releaseRestricted(slave)) ? `, making the rule restricting ${his} sexual outlets superfluous` : ``}.`);
 						slave.need = 0;
-					} else if (slave.need < slave.needCap * 0.5) {
+					} else if (slave.need < App.EndWeek.saVars.needCapPerSlave[slave.ID] * 0.5) {
 						if (release.masturbation === 0) {
 							r.push(`gets off while performing ${his} duties, so being forbidden from masturbation doesn't really bother ${him}.`);
 							slave.need -= 20;
@@ -1633,7 +1633,7 @@ App.SlaveAssignment.rules = function(slave) {
 					} else if (slave.energy <= 20) {
 						r.push(`is frigid and has little interest in getting off.`);
 						slave.need = 0;
-					} else if (slave.need < slave.needCap * 0.5) {
+					} else if (slave.need < App.EndWeek.saVars.needCapPerSlave[slave.ID] * 0.5) {
 						if (slave.devotion <= 20) {
 							r.push(`gets off at work despite ${his} reluctance, <span class="hotpink">habituating ${him} to sexual slavery.</span>`);
 							slave.devotion += 1;
@@ -1830,7 +1830,7 @@ App.SlaveAssignment.rules = function(slave) {
 					if (slave.energy <= 20) {
 						r.push(`is frigid and has little interest in getting off${(App.Utils.releaseRestricted(slave)) ? `, making the rule restricting ${his} sexual outlets superfluous` : ``}.`);
 						slave.need = 0;
-					} else if (slave.need < slave.needCap * 0.5) {
+					} else if (slave.need < App.EndWeek.saVars.needCapPerSlave[slave.ID] * 0.5) {
 						if (release.masturbation === 0) {
 							r.push(`gets off while performing ${his} duties, so being forbidden from masturbation doesn't really bother ${him}.`);
 						} else {
@@ -1900,7 +1900,7 @@ App.SlaveAssignment.rules = function(slave) {
 						} else if (slave.energy <= 20) {
 							r.push(`is frigid and has little interest in getting off${(App.Utils.releaseRestricted(slave)) ? `, making the rule restricting ${his} sexual outlets superfluous` : ``}.`);
 							slave.need = 0;
-						} else if (slave.need < slave.needCap * 0.5) {
+						} else if (slave.need < App.EndWeek.saVars.needCapPerSlave[slave.ID] * 0.5) {
 							if (slave.devotion <= 20) {
 								r.push(`gets off from being milked despite ${his} reluctance, <span class="hotpink">habituating ${him} to sexual slavery.</span>`);
 								slave.devotion += 1;
@@ -2128,7 +2128,7 @@ App.SlaveAssignment.rules = function(slave) {
 					if (slave.energy <= 20) {
 						r.push(`is frigid and has little interest in getting off${(App.Utils.releaseRestricted(slave)) ? `, making the rule restricting ${his} sexual outlets superfluous` : ``}.`);
 						slave.need = 0;
-					} else if (slave.need < slave.needCap * 0.5) {
+					} else if (slave.need < App.EndWeek.saVars.needCapPerSlave[slave.ID] * 0.5) {
 						if (release.masturbation === 0) {
 							r.push(`gets off while performing ${his} duties, so being forbidden from masturbation doesn't really bother ${him}.`);
 							slave.need -= 20;
@@ -2189,7 +2189,7 @@ App.SlaveAssignment.rules = function(slave) {
 					} else if (slave.energy <= 20) {
 						r.push(`is frigid and has little interest in getting off${(App.Utils.releaseRestricted(slave)) ? `, making the rule restricting ${his} sexual outlets superfluous` : ``}.`);
 						slave.need = 0;
-					} else if (slave.need < slave.needCap * 0.5) {
+					} else if (slave.need < App.EndWeek.saVars.needCapPerSlave[slave.ID] * 0.5) {
 						if (slave.devotion <= 20) {
 							r.push(`gets off from working as a farmhand despite ${his} reluctance, <span class="hotpink">habituating ${him} to sexual slavery.</span>`);
 							slave.devotion += 1;
@@ -2395,7 +2395,7 @@ App.SlaveAssignment.rules = function(slave) {
 					} else if (V.masterSuiteUpgradeLuxury === 2 && L.masterSuite > 3) {
 						r.push(`never goes unsatisfied with all the action in the fuckpit.`);
 						slave.need -= 80;
-					} else if (slave.need < slave.needCap * 0.5) {
+					} else if (slave.need < App.EndWeek.saVars.needCapPerSlave[slave.ID] * 0.5) {
 						if (slave.devotion <= 20) {
 							r.push(`gets off regularly despite ${his} reluctance, <span class="hotpink">habituating ${him} to sexual slavery.</span>`);
 							slave.devotion += 1;
@@ -2472,7 +2472,7 @@ App.SlaveAssignment.rules = function(slave) {
 					} else if (slave.energy <= 20) {
 						r.push(`is frigid and has little interest in getting off, though it doesn't stop ${S.HeadGirl.slaveName}.`);
 						slave.need = 0;
-					} else if (slave.need < slave.needCap * 0.5) {
+					} else if (slave.need < App.EndWeek.saVars.needCapPerSlave[slave.ID] * 0.5) {
 						if (slave.devotion <= 20) {
 							r.push(`gets off with ${S.HeadGirl.slaveName} despite ${his} reluctance, <span class="hotpink">habituating ${him} to sexual slavery.</span>`);
 							slave.devotion += 1;
@@ -2538,7 +2538,7 @@ App.SlaveAssignment.rules = function(slave) {
 					} else if (slave.energy <= 20) {
 						r.push(`is frigid and has little interest in getting off${(App.Utils.releaseRestricted(slave)) ? `, making the rule restricting ${his} sexual outlets superfluous` : ``}.`);
 						slave.need = 0;
-					} else if (slave.need < slave.needCap * 0.5) {
+					} else if (slave.need < App.EndWeek.saVars.needCapPerSlave[slave.ID] * 0.5) {
 						if (slave.devotion <= 20) {
 							r.push(`gets off at work despite ${his} reluctance, <span class="hotpink">habituating ${him} to sexual slavery.</span>`);
 							slave.devotion += 1;
diff --git a/src/endWeek/saServeYourOtherSlaves.js b/src/endWeek/saServeYourOtherSlaves.js
index 42c2114cc4bfee059c3b27b56dde5d2109b9c4c8..c49c80cc2d5e8696205f3c967979305e3e347b98 100644
--- a/src/endWeek/saServeYourOtherSlaves.js
+++ b/src/endWeek/saServeYourOtherSlaves.js
@@ -1055,7 +1055,7 @@ App.SlaveAssignment.serveYourOtherSlaves = function saServeYourOtherSlaves(slave
 					}
 				} else {
 					if (domFetishKnown) {
-						r.push(`Since ${domName} loves pregnant ${girl}s,`);
+						r.push(`${domName} loves pregnant ${girl}s,`);
 					} else {
 						r.push(`${domName}, it turns out, <span class="lightcoral">really likes pregnant ${girl}s,</span>`);
 						domSlave.fetishKnown = 1;
diff --git a/src/endWeek/saSharedVariables.js b/src/endWeek/saSharedVariables.js
index d7c8156666437c5807e15d26be4805dbe3fc966b..3bd4691282a027cf9f6cf3ee829f53d6654e3e15 100644
--- a/src/endWeek/saSharedVariables.js
+++ b/src/endWeek/saSharedVariables.js
@@ -32,6 +32,12 @@ App.EndWeek.SASharedVariables = class {
 		this.subSlaveMap = new Map();
 		/** Slave art manager */
 		this.slaveArt = null;
+		/** Need cap per slave. Array indices are the slave IDs (resulting in a sparse array, but that's ok, because we never save this array) */
+		this.needCapPerSlave = [];
+		/** Which employees the leader of the currently processed facility is having sex with this week
+		 *  @see App.EndWeek.getFLSex
+		 */
+		this.flSex = 0;
 	}
 
 	/** Compute shared subslave ratio (subslaves per ordinary slave) */
diff --git a/src/endWeek/slaveAssignmentReport.js b/src/endWeek/slaveAssignmentReport.js
index f9d2f2c8fd7e469194f60c618d3adb3bb41a3471..4795a0df978902f2ac8566a77971bb77935ab8b4 100644
--- a/src/endWeek/slaveAssignmentReport.js
+++ b/src/endWeek/slaveAssignmentReport.js
@@ -147,7 +147,7 @@ App.EndWeek.slaveAssignmentReport = function() {
 				}
 				poorHealthNeedReduction(slave);
 				slave.need = Math.round(slave.need);
-				slave.needCap = slave.need;
+				App.EndWeek.saVars.needCapPerSlave[slave.ID] = slave.need;
 			}
 		}
 
@@ -345,7 +345,6 @@ App.EndWeek.slaveAssignmentReport = function() {
 
 	/* Clean up global SA variables */
 	App.EndWeek.saVars = null;
-	delete V.flSex; // FIXME: remove, once this is passed as a parameter to saRules
 
 	return res;
 
diff --git a/src/events/RE/reCitizenHookup.js b/src/events/RE/reCitizenHookup.js
index 611e5022458929177ff01743d1d03c8a8e25d549..c4c71ac0758e6a1e85ea856ff1899a828bbb976c 100644
--- a/src/events/RE/reCitizenHookup.js
+++ b/src/events/RE/reCitizenHookup.js
@@ -9,12 +9,7 @@ App.Events.RECitizenHookup = class RECitizenHookup extends App.Events.BaseEvent
 		let r = [];
 		let repopHookupPregnant;
 
-		const fsArray = [];
-		for (const FS of App.Data.FutureSociety.fsNames) {
-			if (V.arcologies[0][FS] !== "unset") {
-				fsArray.push(FS);
-			}
-		}
+		const fsArray = App.Data.FutureSociety.fsNames.filter(f => V.arcologies[0][f] !== "unset");
 		const FS = fsArray.random();
 		const fsAdj = (fsArray.length > 0) ? App.Data.FutureSociety.records[FS].adj : "none";
 
diff --git a/src/events/RE/reDevotedTwins.js b/src/events/RE/reDevotedTwins.js
index e98520a24ff61726e7b9744686bad9b6d2cf00b1..d438e3af668d95780b2a10317a68522d2b5aba26 100644
--- a/src/events/RE/reDevotedTwins.js
+++ b/src/events/RE/reDevotedTwins.js
@@ -78,9 +78,9 @@ App.Events.REDevotedTwins = class REDevotedTwins extends App.Events.BaseEvent {
 				t.push(`building your arousal,`);
 			}
 			if (canDoVaginal(alphaTwin) && canDoVaginal(betaTwin)) {
-				t.push(`and finger each others' holes`);
+				t.push(`and finger each other's holes`);
 			} else if (alphaTwin.dick > 0 && alphaTwin.chastityPenis === 0 && betaTwin.dick > 0 && betaTwin.chastityPenis === 0) {
-				t.push(`and fondle each others' dicks`);
+				t.push(`and fondle each other's dicks`);
 			} else {
 				t.push(`and fondle each other`);
 			}
diff --git a/src/events/RE/reMaleArcologyOwner.js b/src/events/RE/reMaleArcologyOwner.js
index 1ba9ddd3241e1b7a9cb803af72a350f91eac1d2d..d30b527a5ac085996a7cb9d5fe631e465ac22f02 100644
--- a/src/events/RE/reMaleArcologyOwner.js
+++ b/src/events/RE/reMaleArcologyOwner.js
@@ -66,7 +66,7 @@ App.Events.REMaleArcologyOwner = class REMaleArcologyOwner extends App.Events.Ba
 			if (V.PC.preg >= 28 && V.PC.pregMood === 2) {
 				r.push(`You move to waddle past him and purposefully stumble, prompting him to catch you. Feigning fatigue, you politely ask if he'd help you out. You aren't exactly subtle as he walks you back to your room, dropping hints at how difficult your pregnancy has been and just how good it feels to be with a man. ${capFirstChar(V.assistant.name)} cleared your suite long ago, so when you enter, disrobe and splay yourself across the bed, it's just you and him.`);
 				if (randomForeignFS > 90) {
-					r.push(`It's immediately clear by the look on his face that you made a mistake. The man clearly comes from a society that dislikes pregnant woman leading and your attempt to manipulate him into being your lover has pushed him past his level of tolerance. He storms out in anger and, upon returning to the party, makes your underhanded efforts known. <span class="reputation dec">Your reputation has taken a major hit.</span>`);
+					r.push(`It's immediately clear by the look on his face that you made a mistake. The man clearly comes from a society that dislikes pregnant women leading, and your attempt to manipulate him into being your lover has pushed him past his level of tolerance. He storms out in anger and, upon returning to the party, makes your underhanded efforts known. <span class="reputation dec">Your reputation has taken a major hit.</span>`);
 					repX(-1000, "event");
 				} else if (randomForeignFS > 70) { // repop
 					r.push(`It's immediately clear by the look on his face that you made a mistake. He wastes no time in leaping onto the bed, and in his haste, barely manages to disrobe. Before you can attempt to take control, he's hilted in your needy vagina and giving you the fucking your body has been craving. He leaves a few hours later with a satisfied expression and returns to the party, where he puts in a good word for just how good you were. <span class="reputation inc">Your reputation has slightly improved,</span> though nowhere near as much as his.`);
diff --git a/src/events/RE/reShelterInspection.js b/src/events/RE/reShelterInspection.js
index 414f45dc6b1acb6d33dc0e2998ce6767f88b21d3..7e05c7ea0c3fe02f784a0a0bff5f1a578f85fee9 100644
--- a/src/events/RE/reShelterInspection.js
+++ b/src/events/RE/reShelterInspection.js
@@ -113,23 +113,23 @@ App.Events.REShelterInspection = class REShelterInspection extends App.Events.Ba
 		if (S.Bodyguard || V.mercenaries > 1 || (V.SF.Toggle && V.SF.Active >= 1)) {
 			responses.push(new App.Events.Result(`Escort ${him2} off your property`, escortInspectorOut));
 		}
-		if ((V.dairy > 0) && (V.dairyRestraintsSetting > 1) && (inspectee.assignment === "work in the dairy")) {
+		if ((V.dairy > 0) && (V.dairyRestraintsSetting > 1) && (inspectee.assignment === Job.DAIRY)) {
 			responses.push(new App.Events.Result(
 				`The slave is attached to a milking machine in ${V.dairyName}, and so will be the inspector`,
 				dairyInspector,
 				`This will cost ${cashFormat(enslaveCost / 2)}`
 			));
-		} else if ((V.arcade > 0) && (inspectee.assignment === "be confined in the arcade")) {
+		} else if ((V.arcade > 0) && (inspectee.assignment === Job.ARCADE)) {
 			responses.push(new App.Events.Result(
 				`The slave is little more than a hole in ${V.arcadeName}, and so will be the inspector`,
 				arcadeInspector,
 				`This will cost ${cashFormat(enslaveCost / 2)}`
 			));
-		} else if ((inspectee.assignment === "be your agent") || (inspectee.assignment === "live with your agent")) {
+		} else if ((inspectee.assignment === Job.AGENT) || (inspectee.assignment === Job.AGENTPARTNER)) {
 			responses.push(new App.Events.Result(`Your slave is in another arcology`, agent));
 		} else if (inspectee.fuckdoll > 0) {
 			responses.push(new App.Events.Result(`The slave is little more than a latex wrapped doll, and so will be the inspector`, fuckdollInspector, `This will cost ${cashFormat(enslaveCost / 2)}`));
-		} else if ((V.dairyRestraintsSetting < 2) || (inspectee.assignment !== "work in the dairy")) {
+		} else {
 			responses.push(new App.Events.Result(`Allow the inspection`, allow));
 		}
 
@@ -139,7 +139,7 @@ App.Events.REShelterInspection = class REShelterInspection extends App.Events.Ba
 			const el = new DocumentFragment();
 			const r = [];
 			cashX(forceNeg(enslaveCost), "slaveTransfer", inspector);
-			r.push(`${V.assistant.name} ushers ${him2} into your penthouse and keeps ${him2} busy for the few minutes you need to circumvent the Shelter's various legal and contractual defenses to prevent slaveowners from doing exactly what you're doing. Fortunately, you're cleverer and richer than most, and you succeed. You have ${him2} brought into your office, and you are pleased to see ${him2}. ${He2}'s not young and ${he2} 's not pretty, but ${his2} suit cannot disguise ${his2} big bottom, and ${he2}'s using the tablet ${he2} has clasped protectively against ${his2} chest to conceal a huge pair of mature breasts. ${He2} knows exactly what's happened, and fixes you with a gimlet stare.`);
+			r.push(`${V.assistant.name} ushers ${him2} into your penthouse and keeps ${him2} busy for the few minutes you need to circumvent the Shelter's various legal and contractual defenses to prevent slaveowners from doing exactly what you're doing. Fortunately, you're cleverer and richer than most, and you succeed. You have ${him2} brought into your office, and you are pleased to see ${him2}. ${He2}'s not young and ${he2}'s not pretty, but ${his2} suit cannot disguise ${his2} big bottom, and ${he2}'s using the tablet ${he2} has clasped protectively against ${his2} chest to conceal a huge pair of mature breasts. ${He2} knows exactly what's happened, and fixes you with a gimlet stare.`);
 			r.push(Spoken(inspector, `"In a couple of months,"`));
 			r.push(`${he2} ${say2}s with venom,`);
 			r.push(Spoken(inspector, `"when you've filled me full of hormones and drugs and training, and I'm begging you to ${(V.PC.dick !== 0) ? `stick your tiny little dick up` : `fist`} my asshole, remember this. It'll be conditioning and self-preservation and Stockholm Syndrome talking, not me. I think you're a ${(V.PC.title === 1) ? `sad bastard` : `vile cunt`}, and I always will."`));
@@ -560,7 +560,7 @@ App.Events.REShelterInspection = class REShelterInspection extends App.Events.Ba
 						r.push(`The slave looks worried and replies,`);
 						r.push(Spoken(inspector, `"It's kinda big? And I wear it all the time. B-but I like it! It d-doesn't hurt. Well, anymore."`));
 						r.push(`The inspector is not amused. ${He2} offers you a few choice words, makes notes on ${his2} tablet, and storms out.`);
-					} else if ((inspectee.assignment === "whore") || (inspectee.assignment === "work in the brothel") || (inspectee.assignment === "serve the public") || (inspectee.assignment === "serve in the club")) {
+					} else if ([Job.WHORE, Job.BROTHEL, Job.PUBLIC, Job.CLUB].includes(inspectee.assignment)) {
 						r.push(Spoken(inspectee, `"W-well,"`));
 						r.push(`${he} ${say}s,`);
 						r.push(Spoken(inspectee, `"I get fucked there at work."`));
@@ -606,7 +606,7 @@ App.Events.REShelterInspection = class REShelterInspection extends App.Events.Ba
 						r.push(Spoken(inspectee, `It's really big, and I wear it all the time. I like it! It keeps my hole ready for anything."`));
 						r.push(`The inspector looks deflated, and reluctantly makes a positive note on ${his2} tablet.`);
 						V.shelterAbuse -= 1;
-					} else if ((inspectee.assignment === "whore") || (inspectee.assignment === "work in the brothel") || (inspectee.assignment === "serve the public") || (inspectee.assignment === "serve in the club")) {
+					} else if ([Job.WHORE, Job.BROTHEL, Job.PUBLIC, Job.CLUB].includes(inspectee.assignment)) {
 						r.push(Spoken(inspectee, `"I get fucked there at work!"`));
 						r.push(`${he} ${say}s confidently. The inspector frowns.`);
 						r.push(Spoken(inspector, `"How often!?"`));
diff --git a/src/events/RE/reSiblingPlease.js b/src/events/RE/reSiblingPlease.js
index 69250b756f8207303104a1d58c938c95d652b1f6..fa663935d92fcfe30c16ed8f9460299ef162fc0f 100644
--- a/src/events/RE/reSiblingPlease.js
+++ b/src/events/RE/reSiblingPlease.js
@@ -49,7 +49,7 @@ App.Events.RESiblingPlease = class RESiblingPlease extends App.Events.BaseEvent
 		t.push(`You take a moment to look at ${him}, ${canStand(dau) ? "standing" : "sitting"} there in front of your desk. ${He}'s devoted to you, willing to please you for the sake of pleasing you, rather than to avoid punishment or to make ${his} own life easier, and ${he} trusts you implicitly. With that in mind, you ask what's bothering ${him}.`);
 		t.toParagraph();
 
-		t.push(Spoken(dau, `"I...I was thinking about family, and mom. Can...can I have a little sister, ${Master}? We could do all sorts of things together!"`));
+		t.push(Spoken(dau, `"I... I was thinking about family, and mom. Can... can I have a little sister, ${Master}? We could do all sorts of things together!"`));
 		t.push(`${He} winks seductively at you, and suddenly you're thinking about all the fun things you could do with them. And with a bit of patience it's definitely workable;`, contextualIntro(dau, mom, true), `could bear ${mom.counter.births > 0 ? `another` : `a`} child for you, without a doubt.`);
 		t.toParagraph();
 
@@ -134,10 +134,10 @@ App.Events.RESiblingPlease = class RESiblingPlease extends App.Events.BaseEvent
 				t.push(`Even though ${mom.slaveName}'s sopping cunt is already dripping on the couch, you order ${dau.slaveName} to use ${his} mouth to get ${him2} ready for you, and ${he} dives in eagerly.`);
 				seX(dau, "oral", mom, "vaginal", 1);
 			} else if (mom.mpreg) {
-				t.push(`As you briefed ${him}, ${dau.slaveName} uses ${his} mouth and tongue to clean and lubricate the entrance to ${his} mother's anal womb as preparation.`);
+				t.push(`As you had directed, ${dau.slaveName} uses ${his} mouth and tongue to clean and lubricate the entrance to ${his} mother's anal womb as preparation.`);
 				seX(dau, "oral", mom, "anal", 1);
 			} else {
-				t.push(`As you briefed ${him}, ${dau.slaveName} eagerly goes down on ${his} mother's cunt, to get ${him2} good and ready for your cock.`);
+				t.push(`As you had directed, ${dau.slaveName} eagerly goes down on ${his} mother's cunt, to get ${him2} good and ready for your cock.`);
 				seX(dau, "oral", mom, "vaginal", 1);
 			}
 			if (mom.devotion > 50) {
@@ -194,7 +194,7 @@ App.Events.RESiblingPlease = class RESiblingPlease extends App.Events.BaseEvent
 				if (helping.length < 2) {
 					helping.push("so on");
 				}
-				t.push(`Meanwhile, ${dau.slaveName} "helps" any way ${he} can...running ${his} hands over both your bodies, ${toSentence(helping)}.`);
+				t.push(`Meanwhile, ${dau.slaveName} "helps" any way ${he} can... running ${his} hands over both your bodies, ${toSentence(helping)}.`);
 			}
 			t.push(`Eventually, you come deep inside ${him2}, filling ${his2}${mom.mpreg ? ` anal` : ``} womb with your seed.`);
 			t.toParagraph();
diff --git a/src/events/RESS/arcadeSadist.js b/src/events/RESS/arcadeSadist.js
index d589c641919aedd4769fff7e7859d5e9fd0b5877..d0fe05d609dfd495846a1aa95e1e12af00e872a7 100644
--- a/src/events/RESS/arcadeSadist.js
+++ b/src/events/RESS/arcadeSadist.js
@@ -29,6 +29,7 @@ App.Events.RESSArcadeSadist = class RESSArcadeSadist extends App.Events.BaseEven
 			He, he, his, him, himself, girl
 		} = getPronouns(eventSlave);
 		const {title: Master, say} = getEnunciation(eventSlave);
+		const hasSight = canSee(eventSlave);
 
 		let artDiv = document.createElement("div"); // named container so we can replace it later
 		App.Events.drawEventArt(artDiv, eventSlave);
@@ -104,12 +105,7 @@ App.Events.RESSArcadeSadist = class RESSArcadeSadist extends App.Events.BaseEven
 			} else {
 				r.push(`Once you've led ${him}`);
 			}
-			r.push(`in there, ${he} stops and`);
-			if (canSee(eventSlave)) {
-				r.push(`watches`);
-			} else {
-				r.push(`listens`);
-			}
+			r.push(`in there, ${he} stops and ${hasSight ? 'watches' : 'listens'}`);
 			if (V.PC.belly >= 100000 || V.PC.weight > 130) {
 				r.push(r.pop() + `as you struggle to join ${him} in the increasingly cramped space.`);
 			} else if (V.PC.belly >= 5000) {
@@ -198,12 +194,7 @@ App.Events.RESSArcadeSadist = class RESSArcadeSadist extends App.Events.BaseEven
 				} else {
 					r.push(`steadily pushing ${himself} along,`);
 				}
-				if (canSee(eventSlave)) {
-					r.push(`staring at`);
-				} else {
-					r.push(`facing`);
-				}
-				r.push(`you with a profound look of mixed <span class="trust inc">trust for your understanding of ${his} horrible sadism,</span> and deep unease that this is what truly gets ${him} off.`);
+				r.push(`${hasSight ? 'staring at' : 'facing'} you with a profound look of mixed <span class="trust inc">trust for your understanding of ${his} horrible sadism,</span> and deep unease that this is what truly gets ${him} off.`);
 				eventSlave.trust += 5;
 				return r;
 			}
@@ -217,19 +208,9 @@ App.Events.RESSArcadeSadist = class RESSArcadeSadist extends App.Events.BaseEven
 				App.Events.drawEventArt(artDiv, [eventSlave, randomArcadeSlave], [eventSlave.clothes, "no clothing"]);
 
 				r.push(`${He} seems to be focusing on the purely physical aspects of the degradation here. The true meaning of this place is so much more, and you decide to share it with ${him}. You call ${his} name, tearing ${his} attention away from the spectacle mere`);
-				if (V.showInches === 2) {
-					r.push(`inches`);
-				} else {
-					r.push(`centimeters`);
-				}
-				r.push(`over your heads, and`);
-				if (canSee(eventSlave)) {
-					r.push(`point`);
-				} else {
-					r.push(`direct ${him}`);
-				}
+				r.push(`${V.showInches === 2 ? 'inches' : 'centimeters'} over your heads, and ${hasSight ? 'point' : `direct ${him}`}`);
 				r.push(`to a particular slave. You tell ${eventSlave.slaveName} that this particular Arcade inmate's name is ${SlaveFullName(randomArcadeSlave)}.`);
-				if (randomArcadeSlave.career !== 0) {
+				if (randomArcadeSlave.career !== "a slave") {
 					r.push(`You tell ${him} that ${he2} is ${randomArcadeSlave.actualAge} years old, that ${he2} is ${randomArcadeSlave.nationality}, and that ${he2} was once ${convertCareer(randomArcadeSlave)}.`);
 				} else {
 					r.push(`You tell ${him} that ${he2} is ${randomArcadeSlave.actualAge} years old and ${randomArcadeSlave.nationality}.`);
@@ -251,12 +232,7 @@ App.Events.RESSArcadeSadist = class RESSArcadeSadist extends App.Events.BaseEven
 				} else {
 					r.push(`orgasming.`);
 				}
-				r.push(`${He} came without being touched. ${He}`);
-				if (canSee(eventSlave)) {
-					r.push(`stares at`);
-				} else {
-					r.push(`faces`);
-				}
+				r.push(`${He} came without being touched. ${He} ${hasSight ? 'stares at' : 'faces'}`);
 				r.push(`the mess ${he} made just by being in the presence of the arcology's <span class="devotion inc">undisputed preeminent sadist;</span> ${he} shudders at the sheer gothic glory of it. ${He} has a new moment to think of when ${he} feels like <span class="fetish inc">indulging ${his} own sadism.</span>`);
 				eventSlave.devotion += 5;
 				eventSlave.fetishStrength = Math.clamp(eventSlave.fetishStrength + 10, 0, 100);
diff --git a/src/events/RESS/injectionsPlease.js b/src/events/RESS/injectionsPlease.js
index 6a51805d262516031b53ead1078ea811b44b5462..3dd77c759fe8e787cded564e976f20ba6a6dbd5d 100644
--- a/src/events/RESS/injectionsPlease.js
+++ b/src/events/RESS/injectionsPlease.js
@@ -174,7 +174,7 @@ App.Events.RESSInjectionsPlease = class RESSInjectionsPlease extends App.Events.
 						possibleDrugs.push({type: "dickMinus", text: `hormones to shrink my dick? I don't need a big dick to get fucked, ${Master}. I don't want to intimidate anyone who might use me.`});
 					}
 					if (eventSlave.balls > 1 && V.arcologies[0].FSSlimnessEnthusiastResearch === 1) {
-						possibleDrugs.push({type: "ballsMinus", text: `hormones to shrink my balls? I don't need to cum buckets while getting fucked, ${Master}. I don't want to out do anyone who might use me.`});
+						possibleDrugs.push({type: "ballsMinus", text: `hormones to shrink my balls? I don't need to cum buckets while getting fucked, ${Master}. I don't want to outdo anyone who might use me.`});
 					}
 					if (eventSlave.butt < 9) {
 						possibleDrugs.push({type: "butt", text: `ass growth drugs? I want whoever's fucking me to have plenty of cushion to push into.`});
diff --git a/src/events/RESS/lazyEvening.js b/src/events/RESS/lazyEvening.js
index 26d7c308fcd8deb9d7116ecb746320264c822ad1..64703d90c07ce8fb25c1bd300be3e3199a8e58dc 100644
--- a/src/events/RESS/lazyEvening.js
+++ b/src/events/RESS/lazyEvening.js
@@ -94,7 +94,7 @@ App.Events.RESSLazyEvening = class RESSLazyEvening extends App.Events.BaseEvent
 			} else {
 				t.push(`from ${his} chest`);
 			}
-			t.push(`strains the soft pajama top to it's breaking point.`);
+			t.push(`strains the soft pajama top to its breaking point.`);
 		} else if (eventSlave.intelligence+eventSlave.intelligenceImplant > 50) {
 			t.push(`As a clever ${girl}, ${his} simple${eventSlave.belly >= 5000 ? `, yet tight around the middle,` : ""} summer dress evokes memories of bygone warm weather days at elite old world colleges — and the sexual conquest of their youthful residents.`);
 		} else if (eventSlave.muscles > 30) {
diff --git a/src/events/RESS/review/ageImplant.js b/src/events/RESS/review/ageImplant.js
index e7d87893f26b63960c909689df9cc1d4e2f8d467..d0a2d28846124ce7a9afce8e7ef0e41e95938165 100644
--- a/src/events/RESS/review/ageImplant.js
+++ b/src/events/RESS/review/ageImplant.js
@@ -31,7 +31,7 @@ App.Events.RESSAgeImplant = class RESSAgeImplant extends App.Events.BaseEvent {
 
 
 		App.Events.addParagraph(node, [
-			`In the morning the penthouse is a busy bustle of female energy. Slaves get up promptly, eat, shower, dress themselves, and head out to work. They chatter if able and allowed, and draw a good deal of strength from each other. As you pass by the kitchen, you are narrowly avoided by a rush of slaves heading to the showers. They're almost bouncing, feeding off each others' youthful energy. At the back of the pack is`,
+			`In the morning the penthouse is a busy bustle of female energy. Slaves get up promptly, eat, shower, dress themselves, and head out to work. They chatter if able and allowed, and draw a good deal of strength from each other. As you pass by the kitchen, you are narrowly avoided by a rush of slaves heading to the showers. They're almost bouncing, feeding off each other's youthful energy. At the back of the pack is`,
 			App.UI.DOM.combineNodes(contextualIntro(PC, eventSlave, true), `. ${He} looks as young as any of them, but after they're out, ${he} leans against the door frame for a moment and exhales slowly.`)
 		]);
 		let r = [];
@@ -257,7 +257,7 @@ App.Events.RESSAgeImplant = class RESSAgeImplant extends App.Events.BaseEvent {
 				}
 				r.push(r.pop() + `.`);
 			} else {
-				r.push(`The only slightly embarrassing incident is when ${he}'s standing up to rally the crowd behind ${him}, cheering while swinging ${his} absurd belly back and forth and accidentally smashes into a concession vendor sending them to the floor. ${His} efforts to help him up forces ${him} to stand in such a way that ${his}`);
+				r.push(`The only slightly embarrassing incident is when ${he}'s standing up to rally the crowd behind ${him}, cheering while swinging ${his} absurd belly back and forth and accidentally smashes into a concession vendor, sending them to the floor. ${His} efforts to help him up forces ${him} to stand in such a way that ${his}`);
 				if (eventSlave.butt > 5) {
 					r.push(`massive ass`);
 				} else if (eventSlave.butt > 2) {
diff --git a/src/events/RESS/review/bondedLove.js b/src/events/RESS/review/bondedLove.js
index 3f693373a51cbdde9b75d71ccc87022451570ff1..3b1f9553131385d1525a8c94d611cf05f26c097e 100644
--- a/src/events/RESS/review/bondedLove.js
+++ b/src/events/RESS/review/bondedLove.js
@@ -66,7 +66,7 @@ App.Events.RESSBondedLove = class RESSBondedLove extends App.Events.BaseEvent {
 			if (eventSlave.pregSource === -1) {
 				r.push(`completely filled with your ${children},`);
 			}
-			r.push(`and the slight movement within with each struggled step ${he} takes.`);
+			r.push(`and the slight movement within with each struggling step ${he} takes.`);
 		} else if (eventSlave.belly >= 150000) {
 			r.push(`${his} ${belly} belly and the vastly altered gait ${he} steps with to handle it.`);
 		} else if (eventSlave.bellyPreg >= 10000) {
diff --git a/src/events/RESS/review/masterfulEntertainer.js b/src/events/RESS/review/masterfulEntertainer.js
index ce6757057eb9f1907a5a0f55da8b79ffbdd4e4cc..9bcbbb48c5f218b4fab5d5cbcb86017f738186eb 100644
--- a/src/events/RESS/review/masterfulEntertainer.js
+++ b/src/events/RESS/review/masterfulEntertainer.js
@@ -29,7 +29,7 @@ App.Events.RESSMasterfulEntertainer = class RESSMasterfulEntertainer extends App
 		let r = [];
 		r.push(
 			`It's Friday evening, the most socially important part of the week in ${V.arcologies[0].name}.`,
-			contextualIntro(PC, eventSlave, true),
+			contextualIntro(PC, eventSlave, true, false, true),
 			`happens to be free this evening, and your schedule is open, too. Lately, ${he}'s been putting on a tour de force of seduction, erotic dance, and lewd entertainment whenever ${he} gets the chance to catch someone's eye`
 		);
 		if (eventSlave.belly >= 5000) {
diff --git a/src/events/RESS/review/usedWhore.js b/src/events/RESS/review/usedWhore.js
index dce4057092626a749df2ffb0ea7be44258a05c6f..90ef6cd54cb865f7b0d70d8a3a55d7edc078085e 100644
--- a/src/events/RESS/review/usedWhore.js
+++ b/src/events/RESS/review/usedWhore.js
@@ -416,7 +416,6 @@ App.Events.RESSUsedWhore = class RESSUsedWhore extends App.Events.BaseEvent {
 				case "a Futanari Sister":
 				case "a slave":
 				case "a slave since birth":
-				case 0:
 					r.push(`once upon a time.`);
 					break;
 				default:
diff --git a/src/events/assistant/assistantMarket.js b/src/events/assistant/assistantMarket.js
index bdbecb752feea8c1c7d2a02a8b48a092db21e08c..735ef655910e0eb5d4be760945fa2cc59a118514 100644
--- a/src/events/assistant/assistantMarket.js
+++ b/src/events/assistant/assistantMarket.js
@@ -202,7 +202,7 @@ App.Events.assistantMarket = class assistantMarket extends App.Events.BaseEvent
 					r.push(`is very pretty. ${V.assistant.name} turns to the market assistant's avatar, introducing ${himselfA}. The ${girlM} gapes at ${V.assistant.name}'s ivory skin, horns, and tentacle hair, and blushes when ${heM} sees ${hisM} cocks. "Look all you like," ${V.assistant.name}'s avatar says. "You can taste them later." The market assistant's avatar blushes harder, but doesn't look away.`);
 					break;
 				case "shemale":
-					r.push(`is an appropriate fuckbuddy. ${V.assistant.name} laughs throatily and turns to the market assistant's avatar. The younger dick${girlM} tries to introduce ${himselfM}, but is cut off by the senior assistant's lascivious kiss. They start rubbing their dicks against each over, giggling into each others' mouths.`);
+					r.push(`is an appropriate fuckbuddy. ${V.assistant.name} laughs throatily and turns to the market assistant's avatar. The younger dick${girlM} tries to introduce ${himselfM}, but is cut off by the senior assistant's lascivious kiss. They start rubbing their dicks against each over, giggling into each other's mouths.`);
 					break;
 				case "schoolgirl":
 					r.push(`looks pretty cute. ${V.assistant.name} giggles, and the new ${girlM} giggles too. "Hey," ${V.assistant.name} says to the market assistant's avatar, "Wanna be my ${girlM} friend?" The market assistant's avatar nods cutely and says "Sure!" ${V.assistant.name}'s avatar kisses ${himM} girlishly, and goes a whole two seconds before sliding a hand down the front of the market assistant's avatar's skirt.`);
@@ -356,7 +356,7 @@ App.Events.assistantMarket = class assistantMarket extends App.Events.BaseEvent
 					r.push(`becoming very similar to ${V.assistant.name}'s avatar, though ${heM} keeps a slightly chubbier appearance. ${V.assistant.name} introduces ${himselfA}. "Hi," ${heA} says, "I'm your twin ${sisterA}. Wanna fool around?" The market assistant's blushes and stutters, but says "Okay," and starts lifting ${hisM} dress. "Awesome!" says ${V.assistant.name}, watching raptly as ${hisA} sibling strips. "I gotta introduce you to my boyfriends sometime; they know how to make you feel amazing! If a little heavy..." ${heA} says while patting ${hisA} pregnant belly.`);
 					break;
 				case "businesswoman":
-					r.push(`becoming a much younger version of ${V.assistant.name}'s avatar. About a generation apart, in fact. ${V.assistant.name} turns to the market assistant's avatar to introduce ${himselfA}. "Come here, honey," ${heA} says, patting ${hisA} thighs. The new avatar sits on ${hisM} mother's lap, and they kiss lasciviously, stripping each others' jackets off.`);
+					r.push(`becoming a much younger version of ${V.assistant.name}'s avatar. About a generation apart, in fact. ${V.assistant.name} turns to the market assistant's avatar to introduce ${himselfA}. "Come here, honey," ${heA} says, patting ${hisA} thighs. The new avatar sits on ${hisM} mother's lap, and they kiss lasciviously, stripping each other's jackets off.`);
 					break;
 				case "fairy":
 				case "pregnant fairy":
diff --git a/src/events/intro/customizeSlaveTrade.js b/src/events/intro/customizeSlaveTrade.js
index 162c1cd4ba14080795040adb8efb55b6a44f4fb1..fc381fb63760d45e4447fc3fd8a32e2af8fac9de 100644
--- a/src/events/intro/customizeSlaveTrade.js
+++ b/src/events/intro/customizeSlaveTrade.js
@@ -209,7 +209,7 @@ App.Intro.CustomSlaveTrade = function() {
 			}
 		} else {
 			/* Filtered pop controls */
-			const controlsNationality = App.Data.misc.nationalitiesByRace[baseControlsFilter] || setup[baseControlsFilter + 'Nationalities'];
+			const controlsNationality = App.Data.misc.nationalitiesByRace[baseControlsFilter] || App.Data.misc[baseControlsFilter + 'Nationalities'];
 			const keys = Object.keys(controlsNationality);
 			for (const nation of keys) {
 				const li = document.createElement("LI");
diff --git a/src/events/intro/pcAppearance.js b/src/events/intro/pcAppearance.js
index 07206f540ecaa094af3377bc5e6e9acaa6f3e551..85d9523f96babf0c6f83fa72deebcd684ee8afd6 100644
--- a/src/events/intro/pcAppearance.js
+++ b/src/events/intro/pcAppearance.js
@@ -33,11 +33,11 @@ App.UI.Player.appearance = function(options, summary = false) {
 		);
 
 	options.addOption("Your skin tone is", "skin", V.PC).showTextBox()
-		.addValueList(makeAList(App.Medicine.Modification.naturalSkins));
+		.addValueList(makeAList(V.PC.race === "catgirl" ? App.Medicine.Modification.catgirlNaturalSkins : App.Medicine.Modification.naturalSkins));
 
 	if (V.cheatMode) {
 		options.addOption("Your genetic skin tone is", "origSkin", V.PC).showTextBox()
-			.addValueList(makeAList(App.Medicine.Modification.naturalSkins));
+			.addValueList(makeAList(V.PC.race === "catgirl" ? App.Medicine.Modification.catgirlNaturalSkins : App.Medicine.Modification.naturalSkins));
 	}
 
 	if (V.cheatMode) {
diff --git a/src/events/nonRandom/pAidInvitation.js b/src/events/nonRandom/pAidInvitation.js
index 362de9d163440d1c0660f3c101f5895dfd401690..2c58bbc85e88c2e2bf88a35b82d9ab89f1250d29 100644
--- a/src/events/nonRandom/pAidInvitation.js
+++ b/src/events/nonRandom/pAidInvitation.js
@@ -11,14 +11,14 @@ App.Events.pAidInvitation = class pAidInvitation extends App.Events.BaseEvent {
 		return [
 			() => V.plot === 1,
 			() => V.week >= 29,
-			() => !V.eventResults.hasOwnProperty("aid")
+			() => !V.eventResults.aid
 		];
 	}
 
 	execute(node) {
 		const trapped = [];
 		let r = [];
-		V.eventResults.aid = 0; // Mark event as seen.
+		V.eventResults.aid = -2; // Mark event as seen.
 		if (V.seeDicks <= 75) {
 			trapped.push("convent");
 			trapped.push("school");
diff --git a/src/events/recETS/newSlaveIncestSex.js b/src/events/recETS/newSlaveIncestSex.js
index 963a6e79e2eebf6ee735060c90c6302915f5949b..d8379ebfce309181edc2458a83f7b72fddbad02f 100644
--- a/src/events/recETS/newSlaveIncestSex.js
+++ b/src/events/recETS/newSlaveIncestSex.js
@@ -5,7 +5,29 @@
  * @returns {HTMLElement}
  */
 globalThis.newSlaveIncestSex = function(relative, relative2) {
-	const eventName = V.event ? V.event.eventName.replace("incest", "").replace(" ", "-").toLowerCase() : "";
+	function incestAction() {
+		if (V.event) {
+			switch (V.event.eventName) {
+				case "Identical Hermaphrodite Pair":
+					return "identical hermaphrodite twincest";
+				case "Identical Pair":
+					return "identical sister twincest";
+				case "Twin Brother Incest":
+					return "brother-brother twincest";
+				case "Twin Sister Incest":
+					return "sister-sister twincest";
+				case "Twins Mixed Incest":
+					return "brother-sister twincest";
+				case "Matched Pair":
+					return "'sister'-sister twincest";
+				default:
+					return V.event.eventName.toLowerCase().replace(" ", "-").replace("-incest", " incest");
+			}
+		} else {
+			return "";
+		}
+	}
+	const eventName = incestAction();
 	const div = document.createElement("div");
 	App.Events.drawEventArt(div, [relative, relative2], "no clothing");
 	const {
@@ -117,9 +139,9 @@ globalThis.newSlaveIncestSex = function(relative, relative2) {
 		}
 		App.Events.addParagraph(frag, r);
 
-		App.Events.addParagraph(frag, [`You can tell how uncomfortable they are with you watching them, but as they become increasingly worked up, they lose their inhibitions. Soon, you are watching some fairly enthralling ${eventName}-incest action at your office${(actions.length) ? `, including some enthusiastic ${actions.join(" and ")}`: ``}. Eventually, they bring each other to impressive mutual orgasms. Their lusty moans are muffled only by each others' crotches. Spent, exhausted, and with their faces covered in each others ${secretions.join(" and ")}, they untangle to rest comfortably on your couch.`]);
+		App.Events.addParagraph(frag, [`You can tell how uncomfortable they are with you watching them, but as they become increasingly worked up, they lose their inhibitions. Soon, you are watching some fairly enthralling ${eventName} action in your office${(actions.length) ? `, including some enthusiastic ${actions.join(" and ")}`: ``}. Eventually, they bring each other to impressive mutual orgasms. Their lusty moans are muffled only by each other's crotches. Spent, exhausted, and with their faces covered in each other's ${secretions.join(" and ")}, they untangle to rest comfortably on your couch.`]);
 
-		App.Events.addParagraph(frag, [`You indicate them to present themselves to you. Still shaking from the aftershocks of their orgasms, they stand side by side in front of you, panting, naked and with their ${genitals} dripping mixed juices. You simply nod, showing your approval. They are visibly relieved, and not only sexually. They are more confident of having made the right choice in enslaving themselves to you, since you seem <span class="mediumaquamarine">trustworthy</span> and <span class="hotpink">sympathetic.</span> They hug again, kissing and licking the sexual fluids off each others' stained faces.`]);
+		App.Events.addParagraph(frag, [`You indicate them to present themselves to you. Still shaking from the aftershocks of their orgasms, they stand side by side in front of you, panting, naked and with their ${genitals} dripping mixed juices. You simply nod, showing your approval. They are visibly relieved, and not only sexually. They are more confident of having made the right choice in enslaving themselves to you, since you seem <span class="mediumaquamarine">trustworthy</span> and <span class="hotpink">sympathetic.</span> They hug again, kissing and licking the sexual fluids off each other's stained faces.`]);
 
 		for (const rel of [relative, relative2]) {
 			rel.devotion += 4;
diff --git a/src/events/recETS/recetsIdenticalHermPair.js b/src/events/recETS/recetsIdenticalHermPair.js
index 92087891c5c4b4539b68d1980b86570718b989c4..dec8a9432dac3116e71bc2e5371a96b9d5435d6e 100644
--- a/src/events/recETS/recetsIdenticalHermPair.js
+++ b/src/events/recETS/recetsIdenticalHermPair.js
@@ -4,7 +4,6 @@ App.Events.recetsIdenticalHermPair = class recetsIdenticalHermPair extends App.E
 			() => V.seeDicks !== 0,
 			() => V.seeDicks !== 100,
 			() => V.seeIncest !== 0,
-			() => V.seePreg !== 0,
 			() => V.rep / 400 > random(1, 100) || (V.debugMode > 0 && V.debugModeEventSelection > 0)
 		];
 	}
@@ -16,7 +15,7 @@ App.Events.recetsIdenticalHermPair = class recetsIdenticalHermPair extends App.E
 	execute(node) {
 		V.encyclopedia = "Enslaving People";
 		const thing1 = GenerateNewSlave("XX", {
-			minAge: V.fertilityAge, maxAge: 21, ageOverridesPedoMode: 1, disableDisability: 1
+			minAge: Math.max(V.fertilityAge, V.potencyAge), maxAge: 21, ageOverridesPedoMode: 1, disableDisability: 1
 		});
 		thing1.origin = "$He was brought up in a radical slave school to match $his twin.";
 		thing1.career = "a slave";
@@ -26,9 +25,9 @@ App.Events.recetsIdenticalHermPair = class recetsIdenticalHermPair extends App.E
 		setHealth(thing1, jsRandom(0, 20), undefined, undefined, 0, 10);
 		thing1.anus = 1;
 		thing1.dick = 5;
-		thing1.foreskin = 4;
+		thing1.foreskin = thing1.dick += either(-1, 0);
 		thing1.balls = 6;
-		thing1.scrotum = 3;
+		thing1.scrotum = thing1.balls += either(-2, -1, -1, -1, 0, 0);
 		thing1.clit = 0;
 		thing1.vagina = 2;
 		thing1.weight = 0;
@@ -48,18 +47,13 @@ App.Events.recetsIdenticalHermPair = class recetsIdenticalHermPair extends App.E
 			thing1.buttImplant = 0;
 			thing1.buttImplantType = "none";
 		}
-		thing1.preg = 30;
-		thing1.pregType = 2;
-		thing1.pregKnown = 1;
-		thing1.pregWeek = thing1.preg;
-		thing1.belly = 14000;
-		thing1.bellyPreg = 14000;
 		thing1.pubertyXX = 1;
 		thing1.pubertyXY = 1;
 		thing1.hStyle = "tails";
 		thing1.pubicHStyle = "waxed";
-		thing1.fetish = "pregnancy";
-		thing1.fetishStrength = 100;
+		thing1.energy = Math.max(thing1.energy, 40);
+		thing1.attrXX = Math.max(thing1.attrXX, 70);
+		thing1.attrXY = Math.max(thing1.attrXY, 70);
 		thing1.sexualQuirk = "perverted";
 		thing1.sexualFlaw = "none";
 		thing1.behavioralFlaw = "none";
@@ -71,45 +65,62 @@ App.Events.recetsIdenticalHermPair = class recetsIdenticalHermPair extends App.E
 		let contractCost = cost;
 
 		const thing2 = generateRelatedSlave(thing1, "twin");
-		thing2.fetish = "pregnancy";
-		thing2.fetishStrength = 100;
+		thing2.energy = Math.max(thing2.energy, 40);
+		thing2.attrXX = Math.max(thing2.attrXX, 70);
+		thing2.attrXY = Math.max(thing2.attrXY, 70);
 		thing2.sexualQuirk = "perverted";
 		thing2.sexualFlaw = "none";
 		thing2.behavioralFlaw = "none";
-		WombFlush(thing2);
-		thing2.preg = 30;
-		thing2.pregType = 2;
-		thing2.pregSource = thing1.ID;
-		thing2.pregKnown = 1;
-		thing2.pregWeek = thing2.preg;
-		thing2.belly = 14000;
-		thing2.bellyPreg = 14000;
 		thing2.relationship = 4;
 		thing2.relationshipTarget = thing1.ID;
 
-		thing1.pregSource = thing2.ID;
-		thing1.relationshipTarget = thing2.ID;
+		if (V.seePreg) {
+			thing1.preg = 30;
+			thing1.pregType = 2;
+			thing1.pregKnown = 1;
+			thing1.pregWeek = thing1.preg;
+			thing1.belly = 14000;
+			thing1.bellyPreg = 14000;
+			thing1.fetish = "pregnancy";
+			thing1.fetishStrength = 100;
 
-		WombChangeGene(thing1, "fatherName", thing2.slaveName);
-		WombChangeGene(thing1, "motherName", thing1.slaveName);
+			WombFlush(thing2);
+			thing2.preg = 30;
+			thing2.pregType = 2;
+			thing2.pregSource = thing1.ID;
+			thing2.pregKnown = 1;
+			thing2.pregWeek = thing2.preg;
+			thing2.belly = 14000;
+			thing2.bellyPreg = 14000;
+			thing2.fetish = "pregnancy";
+			thing2.fetishStrength = 100;
+
+			thing1.pregSource = thing2.ID;
+			thing1.relationshipTarget = thing2.ID;
+
+			WombChangeGene(thing1, "fatherName", thing2.slaveName);
+			WombChangeGene(thing1, "motherName", thing1.slaveName);
+		}
 
 		const {His} = getPronouns(thing1);
 		const {He2, sister2} = getPronouns(thing2).appendSuffix("2");
 
 		App.Events.addParagraph(node, [
-			`A pair of young slaves is going door to door offering themselves for sale on behalf of their owner. It's rare to see a slave obedient enough to be entrusted with their own sale, and the price alone suggests there's something interesting, so you let them up. They stand in front of your desk, an arm around the other, and wait for instructions. They appear to be twins, and are dressed identically: they're wearing very skimpy miniskirts, which fail to conceal their semi erect cocks at all, bikini tops so brief that their areolae are clearly visible around the scrap of cloth over each nipple, and nothing over their huge pregnant bellies. You instruct them to tell you about themselves.`
+			`A pair of young slaves is going door to door offering themselves for sale on behalf of their owner. It's rare to see a slave obedient enough to be entrusted with their own sale, and the price alone suggests there's something interesting, so you let them up. They stand in front of your desk, an arm around the other, and wait for instructions. They appear to be twins, and are dressed identically: they're wearing very skimpy miniskirts, which fail to conceal their semi erect cocks at all, bikini tops so brief that their areolae are clearly visible around the scrap of cloth over each nipple, and nothing ${V.seePreg ? `over their huge pregnant bellies` : `else to speak of`}. You instruct them to tell you about themselves.`
 		]);
 
 		App.Events.addParagraph(node, [
-			`They pull themselves together, bringing their gravid middles in tight contact with one another. One of them speaks up.`,
+			`They pull themselves together, bringing their ${V.seePreg ? `gravid middles` : `unique bodies`} in tight contact with one another. One of them speaks up.`,
 			Spoken(thing1, `"We're twins, ${(V.PC.title !== 0) ? "sir" : "ma'am"}. Identical twins. We've also been trained ${(thing2.actualAge > V.minimumSlaveAge) ? `ever since we turned ${V.minimumSlaveAge}` : ``} to be completely obedient, ${(V.PC.title !== 0) ? "sir" : "ma'am"}, in everything, and sexually proficient."`)
 		]);
 
-		App.Events.addParagraph(node, [
-			`The other blurts out.`,
-			Spoken(thing2, `"But we kinda got too into each other, in more ways than one!"`),
-			`${He2} winks.`
-		]);
+		if (V.seePreg) {
+			App.Events.addParagraph(node, [
+				`The other blurts out.`,
+				Spoken(thing2, `"But we kinda got too into each other, in more ways than one!"`),
+				`${He2} winks.`
+			]);
+		}
 
 		App.Events.addParagraph(node, [Spoken(thing2, `"We cost ${cashFormat(contractCost)}, ${(V.PC.title !== 0) ? "sir" : "ma'am"}."`)]);
 		App.UI.DOM.appendNewElement("p", node, `${His} ${sister2} is identical.`, "detail");
@@ -132,8 +143,8 @@ App.Events.recetsIdenticalHermPair = class recetsIdenticalHermPair extends App.E
 			newSlave(thing2);
 			cashX(forceNeg(contractCost), "slaveTransfer", thing2);
 			return [
-				`They giggle and kiss each other rather passionately, their miniskirts becoming pinned against their pregnancies by their stiffening pricks. They're very well trained but not very disciplined, though their pervertedness will be fun.`,
-				Spoken(thing2, `"You know we each are carrying the other's twins, right?"`),
+				`They giggle and kiss each other rather passionately, their miniskirts ${V.seePreg ? `becoming pinned against their pregnancies` : `being lifted toward their navels`} by their stiffening pricks. They're very well trained but not very disciplined, though their pervertedness will be fun.`,
+				Spoken(thing2, `"You know we ${V.seePreg ? `each are carrying the other's twins` : `took each other's virginities`}, right?"`),
 				newSlaveIncestSex(thing1, thing2)
 			];
 		}
diff --git a/src/events/recETS/recetsIdenticalPair.js b/src/events/recETS/recetsIdenticalPair.js
index 6d1646986c66823ccc0a4d7ccf4107cd35b745ca..8467412741688b15be462974433f36a1dbc32fb0 100644
--- a/src/events/recETS/recetsIdenticalPair.js
+++ b/src/events/recETS/recetsIdenticalPair.js
@@ -29,7 +29,8 @@ App.Events.recetsIdenticalPair = class recetsIdenticalPair extends App.Events.Ba
 		thing1.weight = 0;
 		thing1.face = 15;
 		thing1.faceShape = "cute";
-		thing1.attrXX = 80;
+		thing1.energy = Math.max(thing1.energy, 40);
+		thing1.attrXX = Math.max(thing1.attrXX, 70);
 		if (thing1.physicalAge >= 12) {
 			thing1.teeth = "normal";
 		}
@@ -56,6 +57,8 @@ App.Events.recetsIdenticalPair = class recetsIdenticalPair extends App.Events.Ba
 		const contractCost = cost;
 
 		const thing2 = generateRelatedSlave(thing1, "twin");
+		thing2.energy = Math.max(thing2.energy, 40);
+		thing2.attrXX = Math.max(thing2.attrXX, 70);
 		thing2.relationship = 2;
 		thing2.relationshipTarget = thing1.ID;
 
diff --git a/src/events/recETS/recetsIncestBrotherBrother.js b/src/events/recETS/recetsIncestBrotherBrother.js
index d093dfec2af1184daa1de8b26e81c681e8619dad..6268372492c0142c2ff3b94233d7e9d66db9d7f5 100644
--- a/src/events/recETS/recetsIncestBrotherBrother.js
+++ b/src/events/recETS/recetsIncestBrotherBrother.js
@@ -25,8 +25,9 @@ App.Events.recetsIncestBrotherBrother = class recetsIncestBrotherBrother extends
 		brother1.face = random(15, 40);
 		brother1.clothes = "conservative clothing";
 		setHealth(brother1, jsRandom(20, 40), 0, 0, 0);
-		brother1.attrXY = 80;
 		brother1.pubicHStyle = "bushy";
+		brother1.energy = Math.max(brother1.energy, 40);
+		brother1.attrXY = Math.max(brother1.attrXY, 70);
 		if (brother1.behavioralFlaw === "hates men") {
 			brother1.behavioralFlaw = "none";
 		}
@@ -45,6 +46,11 @@ App.Events.recetsIncestBrotherBrother = class recetsIncestBrotherBrother extends
 		if (brother2.foreskin) {
 			brother2.foreskin = brother2.dick;
 		}
+		brother2.energy = Math.max(brother2.energy, 40);
+		brother2.attrXY = Math.max(brother2.attrXY, 70);
+		if (brother2.behavioralFlaw === "hates men") {
+			brother2.behavioralFlaw = "none";
+		}
 		brother2.relationship = 3;
 		brother2.relationshipTarget = brother1.ID;
 		brother1.relationshipTarget = brother2.ID;
diff --git a/src/events/recETS/recetsIncestBrotherSister.js b/src/events/recETS/recetsIncestBrotherSister.js
index d715ed783001ef5333bbde93680ea26cac1ba274..4ac96e91b55d4f673783576c10733906c2bb4e4c 100644
--- a/src/events/recETS/recetsIncestBrotherSister.js
+++ b/src/events/recETS/recetsIncestBrotherSister.js
@@ -25,12 +25,13 @@ App.Events.recetsIncestBrotherSister = class recetsIncestBrotherSister extends A
 		sis.oldDevotion = sis.devotion;
 		sis.ovaries = 1;
 		sis.face = random(15, 40);
-		sis.attrXX = 80;
 		sis.skill.vaginal = 15;
 		sis.clothes = "cutoffs and a t-shirt";
 		setHealth(sis, jsRandom(20, 40), 0, 0, 0);
 		sis.pubicHStyle = "in a strip";
-		if (sis.behavioralFlaw === "hates women") {
+		sis.energy = Math.max(sis.energy, 40);
+		sis.attrXY = Math.max(sis.attrXY, 70);
+		if (sis.behavioralFlaw === "hates men") {
 			sis.behavioralFlaw = "none";
 		}
 		sis.behavioralQuirk = "sinful";
@@ -41,12 +42,20 @@ App.Events.recetsIncestBrotherSister = class recetsIncestBrotherSister extends A
 
 		const bro = generateRelatedSlave(sis, "younger brother");
 		bro.pubicHStyle = "shaved";
+		bro.energy = Math.max(bro.energy, 40);
+		bro.attrXX = Math.max(bro.attrXX, 70);
+		if (bro.behavioralFlaw === "hates women") {
+			bro.behavioralFlaw = "none";
+		}
 		bro.relationship = 3;
 		bro.relationshipTarget = sis.ID;
 
 		sis.relationshipTarget = bro.ID;
 
 		if (V.seePreg) {
+			while (bro.actualAge < bro.pubertyAgeXY) {
+				ageSlave(bro, true);
+			}
 			sis.preg = random(20, 30);
 			sis.pregType = either(1, 1, 1, 1, 1, 2, 2, 3);
 			sis.pregKnown = 1;
diff --git a/src/events/recETS/recetsIncestFatherDaughter.js b/src/events/recETS/recetsIncestFatherDaughter.js
index c887076a4be0430713450c4d23e2c61b678da058..1feb9d539cbff392c4b18b889039eb97b4ae3616 100644
--- a/src/events/recETS/recetsIncestFatherDaughter.js
+++ b/src/events/recETS/recetsIncestFatherDaughter.js
@@ -30,6 +30,8 @@ App.Events.recetsIncestFatherDaughter = class recetsIncestFatherDaughter extends
 		father.faceShape = "masculine";
 		father.clothes = "conservative clothing";
 		setHealth(father, jsRandom(20, 40), 0, 0, 0);
+		father.energy = Math.max(father.energy, 40);
+		father.attrXX = Math.max(father.attrXX, 70);
 		if (father.behavioralFlaw === "hates women") {
 			father.behavioralFlaw = "none";
 		}
@@ -39,14 +41,15 @@ App.Events.recetsIncestFatherDaughter = class recetsIncestFatherDaughter extends
 		/* cost not needed, no option to sell */
 
 		const daughter = generateRelatedSlave(father, "daughter");
-		daughter.pubertyXX = 1;
 		daughter.vagina = 3;
 		daughter.clit = 1;
+		daughter.ovaries = 1;
 		daughter.voice = 2;
 		daughter.skill.vaginal = 50;
 		daughter.boobs = (random(3, 6) * 100);
-		daughter.ovaries = 1;
 		daughter.faceShape = "cute";
+		daughter.energy = Math.max(daughter.energy, 40);
+		daughter.attrXY = Math.max(daughter.attrXY, 70);
 		if (daughter.behavioralFlaw === "hates men") {
 			daughter.behavioralFlaw = "none";
 		}
@@ -58,6 +61,9 @@ App.Events.recetsIncestFatherDaughter = class recetsIncestFatherDaughter extends
 		father.relationshipTarget = daughter.ID;
 
 		if (V.seePreg) {
+			while (daughter.actualAge < daughter.pubertyAgeXX) {
+				ageSlave(daughter, true);
+			}
 			daughter.preg = random(20, 30);
 			daughter.pregType = either(1, 1, 1, 1, 1, 2, 2, 3);
 			daughter.pregKnown = 1;
diff --git a/src/events/recETS/recetsIncestFatherSon.js b/src/events/recETS/recetsIncestFatherSon.js
index 5c0d454ea32d5af6af0620ddf425d455b1de1752..44a969d7d21a960001f4d8422158357cbe9de019 100644
--- a/src/events/recETS/recetsIncestFatherSon.js
+++ b/src/events/recETS/recetsIncestFatherSon.js
@@ -28,6 +28,8 @@ App.Events.recetsIncestFatherSon = class recetsIncestFatherSon extends App.Event
 		father.faceShape = "masculine";
 		father.clothes = "conservative clothing";
 		setHealth(father, jsRandom(20, 40), 0, 0, 0);
+		father.energy = Math.max(father.energy, 40);
+		father.attrXY = Math.max(father.attrXY, 70);
 		if (father.behavioralFlaw === "hates men") {
 			father.behavioralFlaw = "none";
 		}
@@ -38,6 +40,11 @@ App.Events.recetsIncestFatherSon = class recetsIncestFatherSon extends App.Event
 
 		const son = generateRelatedSlave(father, "son");
 		son.career = "a student";
+		son.energy = Math.max(son.energy, 40);
+		son.attrXY = Math.max(son.attrXY, 70);
+		if (son.behavioralFlaw === "hates men") {
+			son.behavioralFlaw = "none";
+		}
 		son.relationship = 3;
 		son.relationshipTarget = father.ID;
 
diff --git a/src/events/recETS/recetsIncestMotherDaughter.js b/src/events/recETS/recetsIncestMotherDaughter.js
index f05d67d6f2571adfc07d03bae1e700ce3bedc414..9d24ea18678e4d4f738c17d998680d3a3cc85f13 100644
--- a/src/events/recETS/recetsIncestMotherDaughter.js
+++ b/src/events/recETS/recetsIncestMotherDaughter.js
@@ -39,6 +39,8 @@ App.Events.recetsIncestMotherDaughter = class recetsIncestMotherDaughter extends
 		mother.clothes = "conservative clothing";
 		setHealth(mother, jsRandom(20, 40), 0, 0, 0);
 		mother.pubicHStyle = "bushy";
+		mother.energy = Math.max(mother.energy, 40);
+		mother.attrXX = Math.max(mother.attrXX, 70);
 		if (mother.behavioralFlaw === "hates women") {
 			mother.behavioralFlaw = "none";
 		}
@@ -58,6 +60,11 @@ App.Events.recetsIncestMotherDaughter = class recetsIncestMotherDaughter extends
 		daughter.lactationDuration = 0;
 		daughter.boobsImplant = 0;
 		daughter.boobsImplantType = "none";
+		daughter.energy = Math.max(daughter.energy, 40);
+		daughter.attrXX = Math.max(daughter.attrXX, 70);
+		if (daughter.behavioralFlaw === "hates women") {
+			daughter.behavioralFlaw = "none";
+		}
 		daughter.relationship = 3;
 		daughter.relationshipTarget = mother.ID;
 
diff --git a/src/events/recETS/recetsIncestMotherSon.js b/src/events/recETS/recetsIncestMotherSon.js
index 3aba98324269d9995670e3537fe171c2f55d8ee5..86b9769a90170bbe420e229da29ba618cf18e777 100644
--- a/src/events/recETS/recetsIncestMotherSon.js
+++ b/src/events/recETS/recetsIncestMotherSon.js
@@ -39,6 +39,8 @@ App.Events.recetsIncestMotherSon = class recetsIncestMotherSon extends App.Event
 		mother.clothes = "conservative clothing";
 		setHealth(mother, jsRandom(20, 40), 0, 0, 0);
 		mother.pubicHStyle = "bushy";
+		mother.energy = Math.max(mother.energy, 40);
+		mother.attrXY = Math.max(mother.attrXY, 70);
 		if (mother.behavioralFlaw === "hates men") {
 			mother.behavioralFlaw = "none";
 		}
@@ -52,6 +54,8 @@ App.Events.recetsIncestMotherSon = class recetsIncestMotherSon extends App.Event
 		son.faceShape = "cute";
 		son.boobs = 0;
 		son.anus = 0;
+		son.energy = Math.max(son.energy, 40);
+		son.attrXX = Math.max(son.attrXX, 70);
 		if (son.behavioralFlaw === "hates women") {
 			son.behavioralFlaw = "none";
 		}
@@ -63,6 +67,9 @@ App.Events.recetsIncestMotherSon = class recetsIncestMotherSon extends App.Event
 		mother.relationshipTarget = son.ID;
 
 		if (V.seePreg) {
+			while (son.actualAge < son.pubertyAgeXY) {
+				ageSlave(son, true);
+			}
 			mother.lactation = 1;
 			mother.lactationDuration = 2;
 			mother.preg = random(20, 30);
@@ -85,10 +92,11 @@ App.Events.recetsIncestMotherSon = class recetsIncestMotherSon extends App.Event
 		const {
 			HeA
 		} = getPronouns(assistant.pronouns().main).appendSuffix("A");
+		const children = mother.pregType > 1 ? "children" : "child";
 
 		App.Events.addParagraph(node, [`You receive so many messages, as a noted titan of the new Free Cities world, that ${V.assistant.name} has to be quite draconian in culling them. ${HeA} lets only the most important through to you. One category of message that always gets through regardless of content, though, is requests for voluntary enslavement. As the new world takes shape, they've become less rare than they once were.`]);
 
-		App.Events.addParagraph(node, [`This call is coming from a public kiosk, which is usually an indication that the person on the other end is a transient individual who has decided to take slavery over homelessness. In this case, however, the story is more unusual — the callers seem stressed, but otherwise normal. They haltingly and quietly explain that they are a mother and ${daughter2} who had to flee their home after ${his} husband ${V.seePreg ? `found out the child in ${his} rounded middle was not his, but his ${daughter2}'s` : `caught them in the act of incest`}. They feel that life in an arcology together, even as slaves, would be better than their new life on the streets.`]);
+		App.Events.addParagraph(node, [`This call is coming from a public kiosk, which is usually an indication that the person on the other end is a transient individual who has decided to take slavery over homelessness. In this case, however, the story is more unusual — the callers seem stressed, but otherwise normal. They haltingly and quietly explain that they are a mother and ${daughter2} who had to flee their home after ${his} husband ${V.seePreg ? `found out ${his} rounded middle contained not his ${children}, but his ${daughter2}'s` : `caught them in the act of incest`}. They feel that life in an arcology together, even as slaves, would be better than their new life on the streets.`]);
 
 		App.Events.addParagraph(node, [`${capFirstChar(V.assistant.name)} assembles a dossier of information and photos from information they've sent describing their bodies and skills, to be used as a substitute for an in-person inspection.`]);
 
diff --git a/src/events/recETS/recetsIncestSisterSister.js b/src/events/recETS/recetsIncestSisterSister.js
index 0fc17394907056bd83fa11d0542f62e6dd48e5db..ff684f5355d1c16f100e5af37f1e844107e777b6 100644
--- a/src/events/recETS/recetsIncestSisterSister.js
+++ b/src/events/recETS/recetsIncestSisterSister.js
@@ -28,7 +28,9 @@ App.Events.recetsIncestSisterSister = class recetsIncestSisterSister extends App
 		sis1.clothes = "cutoffs and a t-shirt";
 		setHealth(sis1, jsRandom(20, 40), 0, 0, 0);
 		sis1.pubicHStyle = "in a strip";
-		if (sis1.behavioralFlaw === "hates men") {
+		sis1.energy = Math.max(sis1.energy, 40);
+		sis1.attrXX = Math.max(sis1.attrXX, 70);
+		if (sis1.behavioralFlaw === "hates women") {
 			sis1.behavioralFlaw = "none";
 		}
 		sis1.behavioralQuirk = "sinful";
@@ -39,6 +41,8 @@ App.Events.recetsIncestSisterSister = class recetsIncestSisterSister extends App
 
 		const sis2 = generateRelatedSlave(sis1, "younger sister");
 		sis2.pubicHStyle = "bushy";
+		sis2.energy = Math.max(sis2.energy, 40);
+		sis2.attrXX = Math.max(sis2.attrXX, 70);
 		if (sis2.behavioralFlaw === "hates women") {
 			sis2.behavioralFlaw = "none";
 		}
diff --git a/src/events/recETS/recetsIncestTwinBrother.js b/src/events/recETS/recetsIncestTwinBrother.js
index eeca44fd874c8939d2f5e3205b82350b375b7a0b..1606396cb110305e872d6d7bbb0c7d64d97f012a 100644
--- a/src/events/recETS/recetsIncestTwinBrother.js
+++ b/src/events/recETS/recetsIncestTwinBrother.js
@@ -25,8 +25,9 @@ App.Events.recetsIncestTwinBrother = class recetsIncestTwinBrother extends App.E
 		brother1.face = random(15, 40);
 		brother1.clothes = "conservative clothing";
 		setHealth(brother1, jsRandom(20, 40), 0, 0, 0);
-		brother1.attrXY = 80;
 		brother1.pubicHStyle = "bushy";
+		brother1.energy = Math.max(brother1.energy, 40);
+		brother1.attrXY = Math.max(brother1.attrXY, 70);
 		if (brother1.behavioralFlaw === "hates men") {
 			brother1.behavioralFlaw = "none";
 		}
@@ -37,6 +38,11 @@ App.Events.recetsIncestTwinBrother = class recetsIncestTwinBrother extends App.E
 
 		const brother2 = generateRelatedSlave(brother1, "twin");
 		brother2.height += random(-5, 5);
+		brother2.energy = Math.max(brother2.energy, 40);
+		brother2.attrXY = Math.max(brother2.attrXY, 70);
+		if (brother2.behavioralFlaw === "hates men") {
+			brother2.behavioralFlaw = "none";
+		}
 		brother2.relationship = 3;
 		brother2.relationshipTarget = brother1.ID;
 		brother1.relationshipTarget = brother2.ID;
diff --git a/src/events/recETS/recetsIncestTwinSister.js b/src/events/recETS/recetsIncestTwinSister.js
index fa4bc43b9898aeb196af325f0218689e59fed02f..86359872b6232ac9d254533d7227f666c5a69722 100644
--- a/src/events/recETS/recetsIncestTwinSister.js
+++ b/src/events/recETS/recetsIncestTwinSister.js
@@ -23,11 +23,12 @@ App.Events.recetsIncestTwinSister = class recetsIncestTwinSister extends App.Eve
 		sis1.oldDevotion = sis1.devotion;
 		sis1.oldTrust = sis1.trust;
 		sis1.face = random(15, 40);
-		sis1.attrXX = 80;
 		sis1.skill.vaginal = 15;
 		sis1.clothes = "cutoffs and a t-shirt";
 		setHealth(sis1, jsRandom(20, 40), 0, 0, 0);
 		sis1.pubicHStyle = "in a strip";
+		sis1.energy = Math.max(sis1.energy, 40);
+		sis1.attrXX = Math.max(sis1.attrXX, 70);
 		if (sis1.behavioralFlaw === "hates women") {
 			sis1.behavioralFlaw = "none";
 		}
@@ -40,6 +41,11 @@ App.Events.recetsIncestTwinSister = class recetsIncestTwinSister extends App.Eve
 		const sis2 = generateRelatedSlave(sis1, "twin");
 		sis2.slaveName = sis2.birthName;
 		sis2.height += random(-5, 5);
+		sis2.energy = Math.max(sis2.energy, 40);
+		sis2.attrXX = Math.max(sis2.attrXX, 70);
+		if (sis2.behavioralFlaw === "hates women") {
+			sis2.behavioralFlaw = "none";
+		}
 		sis2.relationship = 3;
 		sis2.relationshipTarget = sis1.ID;
 		sis1.relationshipTarget = sis2.ID;
diff --git a/src/events/recETS/recetsIncestTwinsMixed.js b/src/events/recETS/recetsIncestTwinsMixed.js
index d157d43197b093ea2e50268011d9672e5f702d3e..01294e5ff77fed7dbaf0cccd13d78e27bfbc32ba 100644
--- a/src/events/recETS/recetsIncestTwinsMixed.js
+++ b/src/events/recETS/recetsIncestTwinsMixed.js
@@ -15,80 +15,84 @@ App.Events.recetsIncestTwinsMixed = class recetsIncestTwinsMixed extends App.Eve
 	execute(node) {
 		V.encyclopedia = "Enslaving People";
 		const contractCost = 10000;
-		const sis1 = GenerateNewSlave("XX", {
+		const sis = GenerateNewSlave("XX", {
 			minAge: Math.max(V.fertilityAge, V.potencyAge, V.minimumSlaveAge), maxAge: 20, ageOverridesPedoMode: 1, disableDisability: 1
 		});
-		sis1.origin = "$He offered to become your slave to protect $his incestuous relationship.";
-		sis1.vagina = 1;
-		sis1.ovaries = 1;
-		sis1.devotion = random(-15, 15);
-		sis1.trust = random(-15, 15);
-		sis1.oldDevotion = sis1.devotion;
-		sis1.oldTrust = sis1.trust;
-		sis1.face = random(15, 40);
-		sis1.attrXX = 80;
-		sis1.skill.vaginal = 15;
-		sis1.clothes = "cutoffs and a t-shirt";
-		setHealth(sis1, jsRandom(20, 40), 0, 0, 0);
-		sis1.pubicHStyle = "in a strip";
-		if (sis1.behavioralFlaw === "hates men") {
-			sis1.behavioralFlaw = "none";
+		sis.origin = "$He offered to become your slave to protect $his incestuous relationship.";
+		sis.vagina = 1;
+		sis.devotion = random(-15, 15);
+		sis.trust = random(-15, 15);
+		sis.oldDevotion = sis.devotion;
+		sis.oldTrust = sis.trust;
+		sis.face = random(15, 40);
+		sis.skill.vaginal = 15;
+		sis.clothes = "cutoffs and a t-shirt";
+		setHealth(sis, jsRandom(20, 40), 0, 0, 0);
+		sis.pubicHStyle = "in a strip";
+		sis.energy = Math.max(sis.energy, 40);
+		sis.attrXY = Math.max(sis.attrXY, 70);
+		if (sis.behavioralFlaw === "hates men") {
+			sis.behavioralFlaw = "none";
 		}
-		sis1.behavioralQuirk = "sinful";
-		setMissingParents(sis1);
-		sis1.canRecruit = 0;
-		sis1.relationship = 3;
+		sis.behavioralQuirk = "sinful";
+		setMissingParents(sis);
+		sis.canRecruit = 0;
+		sis.relationship = 3;
 		/* cost not needed, no option to sell */
 
-		const sis2 = generateRelatedSlave(sis1, "twin", true);
-		sis2.height += random(-5, 5);
-		sis2.vagina = -1;
-		sis2.dick = 2;
-		sis2.foreskin = 2;
-		sis2.ovaries = 0;
-		sis2.balls = 2;
-		sis2.scrotum = 2;
-		sis2.skill.vaginal = 0;
-		sis2.faceShape = "masculine";
-		sis2.boobs = 0;
-		sis2.anus = 0;
-		sis2.pubicHStyle = "bushy";
-		if (sis2.behavioralFlaw === "hates women") {
-			sis2.behavioralFlaw = "none";
+		const bro = generateRelatedSlave(sis, "twin", true);
+		bro.height += random(-5, 5);
+		bro.vagina = -1;
+		bro.dick = 2;
+		bro.foreskin = 2;
+		bro.ovaries = 0;
+		bro.balls = 2;
+		bro.scrotum = 2;
+		bro.skill.vaginal = 0;
+		bro.faceShape = "masculine";
+		bro.boobs = 0;
+		bro.anus = 0;
+		bro.pubicHStyle = "bushy";
+		bro.energy = Math.max(bro.energy, 40);
+		bro.attrXX = Math.max(bro.attrXX, 70);
+		if (bro.behavioralFlaw === "hates women") {
+			bro.behavioralFlaw = "none";
 		}
-		sis2.behavioralQuirk = "none";
-		sis2.sexualQuirk = "perverted";
-		sis2.clothes = "conservative clothing";
-		sis2.relationship = 3;
-		sis2.relationshipTarget = sis1.ID;
+		bro.behavioralQuirk = "none";
+		bro.sexualQuirk = "perverted";
+		bro.clothes = "conservative clothing";
+		bro.relationship = 3;
+		bro.relationshipTarget = sis.ID;
 		
-		sis1.relationshipTarget = sis2.ID;
+		sis.relationshipTarget = bro.ID;
 
 		if (V.seePreg) {
-			sis1.preg = random(20, 30);
-			sis1.pregType = either(1, 1, 1, 1, 1, 2, 2, 3);
-			sis1.pregKnown = 1;
-			sis1.pregWeek = sis1.preg;
-			SetBellySize(sis1);
-			sis1.pregSource = sis2.ID;
-			WombChangeGene(sis1, "fatherName", sis2.slaveName);
-			WombChangeGene(sis1, "motherName", sis1.slaveName);
+			sis.ovaries = 1;
+			sis.preg = random(20, 30);
+			sis.pregType = either(1, 1, 1, 1, 1, 2, 2, 3);
+			sis.pregKnown = 1;
+			sis.pregWeek = sis.preg;
+			SetBellySize(sis);
+			sis.pregSource = bro.ID;
+			WombChangeGene(sis, "fatherName", bro.slaveName);
+			WombChangeGene(sis, "motherName", sis.slaveName);
 		}
 
 		const {HeA} = getPronouns(assistant.pronouns().main).appendSuffix("A");
 		const {
 			he, his
-		} = getPronouns(sis1);
+		} = getPronouns(sis);
+		const children = sis.pregType > 1 ? "children" : "child";
 
 		App.Events.addParagraph(node, [`You receive so many messages, as a noted titan of the new Free Cities world, that ${V.assistant.name} has to be quite draconian in culling them. ${HeA} lets only the most important through to you. One category of message that always gets through regardless of content, though, is requests for voluntary enslavement. As the new world takes shape, they've become less rare than they once were.`]);
 
-		App.Events.addParagraph(node, [`This call is coming from a public kiosk, which is usually an indication that the person on the other end is a transient individual who has decided to take slavery over homelessness. In this case, however, the story is more unusual — the callers seem stressed, but otherwise normal. They haltingly and quietly explain that they are twins who had to flee their home after their parents found out ${V.seePreg ? `${he} was bearing ${his} twin's child` : `they were having sex with each other`}. They feel that life in an arcology together, even as slaves, would be better than their current life on the streets.`]);
+		App.Events.addParagraph(node, [`This call is coming from a public kiosk, which is usually an indication that the person on the other end is a transient individual who has decided to take slavery over homelessness. In this case, however, the story is more unusual — the callers are a boy and a girl, each the spitting image of the other; they seem stressed, but otherwise normal. The girl haltingly and quietly explains that they are twins who had to flee their home after their parents found out ${V.seePreg ? `${he} was bearing ${his} twin brother's ${children}` : `they were having sex with each other`}. They feel that life in an arcology together, even as slaves, would be better than their current life on the streets.`]);
 
 		App.Events.addParagraph(node, [`${capFirstChar(V.assistant.name)} assembles a dossier of information and photos from information they've sent describing their bodies and skills, to be used as a substitute for an in-person inspection.`]);
 
 		App.UI.DOM.appendNewElement("p", node, `It would cost ${cashFormat(contractCost * 2)} to enslave the two of them.`, "detail");
 
-		const newSlaves = [sis1, sis2];
+		const newSlaves = [sis, bro];
 
 		node.append(App.UI.MultipleInspect(newSlaves, true, "generic"));
 		const choices = [];
@@ -101,13 +105,13 @@ App.Events.recetsIncestTwinsMixed = class recetsIncestTwinsMixed extends App.Eve
 		App.Events.addResponses(node, choices);
 
 		function both() {
-			newSlave(sis2);
-			newSlave(sis1);
-			cashX(forceNeg(contractCost), "slaveTransfer", sis1);
-			cashX(forceNeg(contractCost), "slaveTransfer", sis2);
+			newSlave(bro);
+			newSlave(sis);
+			cashX(forceNeg(contractCost), "slaveTransfer", sis);
+			cashX(forceNeg(contractCost), "slaveTransfer", bro);
 			return [
 				`They cheer happily and hug each other tightly. They ought to be an interesting addition to your penthouse.`,
-				newSlaveIncestSex(sis1, sis2)
+				newSlaveIncestSex(sis, bro)
 			];
 		}
 	}
diff --git a/src/events/recETS/recetsMatchedPair.js b/src/events/recETS/recetsMatchedPair.js
index 5dd86c8f1bcdce5635eba840c84fdeaf6a0fc89c..84dd394ec1bd703545464342ff51e48947520d6d 100644
--- a/src/events/recETS/recetsMatchedPair.js
+++ b/src/events/recETS/recetsMatchedPair.js
@@ -48,6 +48,8 @@ App.Events.recetsMatchedPair = class recetsMatchedPair extends App.Events.BaseEv
 		sis1.hStyle = "tails";
 		sis1.pubicHStyle = "waxed";
 		sis1.underArmHStyle = "waxed";
+		sis1.energy = Math.max(sis1.energy, 40);
+		sis1.attrXX = Math.max(sis1.attrXX, 70);
 		sis1.sexualFlaw = "none";
 		sis1.behavioralFlaw = "none";
 		setMissingParents(sis1);
@@ -69,6 +71,11 @@ App.Events.recetsMatchedPair = class recetsMatchedPair extends App.Events.BaseEv
 		sis2.boobsImplantType = "none";
 		sis2.buttImplant = 0;
 		sis2.buttImplantType = "none";
+		sis2.energy = Math.max(sis2.energy, 40);
+		sis2.attrXX = Math.max(sis2.attrXX, 70);
+		sis2.attrXY = Math.max(sis2.attrXY, 70);
+		sis2.sexualFlaw = "none";
+		sis2.behavioralFlaw = "none";
 		sis2.relationship = 2;
 
 		sis2.relationshipTarget = sis1.ID;
diff --git a/src/events/scheduled/seRaiding.js b/src/events/scheduled/seRaiding.js
index 8f7fb187ed9dd7404169f9aa9de1ce2ec25f3add..4b2165462534046f96ca68a8ec0defd85f8e6212 100644
--- a/src/events/scheduled/seRaiding.js
+++ b/src/events/scheduled/seRaiding.js
@@ -620,7 +620,7 @@ App.Events.SERaiding = class SERaiding extends App.Events.BaseEvent {
 					slave = GenerateNewSlave("XX", {maxAge: 20, disableDisability: 1});
 					slave.career = "a classical dancer";
 					slave.prestige = 1;
-					slave.prestigeDesc = "$He was the pride of renowned dance troupe.";
+					slave.prestigeDesc = "$He was the pride of a renowned dance troupe.";
 					slave.face = random(-20, 40);
 					slave.anus = 0;
 					slave.vagina = 1;
diff --git a/src/events/schools/resFailure.js b/src/events/schools/resFailure.js
index 34412e363393760425f92756b1957745d2c4e48c..1692dd4cfbbd4c32e50bdc4535f647ad0d6d01bc 100644
--- a/src/events/schools/resFailure.js
+++ b/src/events/schools/resFailure.js
@@ -25,117 +25,36 @@ App.Events.RESFailure = class RESFailure extends App.Events.BaseEvent {
 		/** @type {App.Entity.SlaveState[]} */
 		const slaveArray = [];
 		if (failedSchool === "TSS") {
-			for (let i = 0; i < slavesToAdd-1; i++) {
-				const slave = GenerateNewSlave("XX", {ageOverridesPedoMode: 1});
-				slave.career = "a slave";
-				slave.butt = either(1, 2, 2, 3);
-				slave.boobs = either(200, 300, 300, 400);
+			for (let i = 0; i < slavesToAdd; i++) {
+				const {slave} = generateMarketSlave("TSS");
 				if (V.TSS.schoolUpgrade === 1) {
 					slave.origin = "$He was given to you by a failed branch campus of the Slavegirl School after $he was retrained as a slave $girl.";
-					slave.butt++;
-					slave.boobs += 200;
 				} else {
 					slave.origin = "$He was given to you by a failed branch campus of the Slavegirl School right after $his majority.";
 				}
-				slave.actualAge = (V.TSS.schoolUpgrade === 1 ? random(36, 42) : 18);
-				slave.anus = (V.TSS.schoolUpgrade === 1 ? 1 : 0);
-				slave.vagina = (V.TSS.schoolUpgrade === 1 ? 1 : 0);
-				slave.visualAge = slave.actualAge;
-				slave.physicalAge = slave.actualAge;
-				slave.ovaryAge = slave.actualAge;
-				slave.intelligenceImplant = 15;
-				slave.teeth = "normal";
-				slave.intelligence = random(-50, 95);
-				slave.devotion = random(25, 45);
-				slave.trust = random(25, 45);
-				setHealth(slave, jsRandom(50, 60), 0, 0, 0);
-				slave.preg = 0;
-				slave.weight = 0;
-				slave.chem = 20;
-				slave.skill.vaginal = (V.TUO.schoolUpgrade !== 0 ? 15 : 0);
-				slave.skill.oral = (V.TUO.schoolUpgrade !== 0 ? 15 : 0);
-				slave.skill.anal = (V.TUO.schoolUpgrade !== 0 ? 15 : 0);
-				slave.skill.whoring = (V.TUO.schoolUpgrade !== 0 ? 15 : 0);
-				slave.skill.entertainment = 15;
-				slave.skill.combat = 0;
-				slave.pubicHStyle = "waxed";
-				slave.underArmHStyle = "waxed";
-				slave.sexualFlaw = either("none");
-				slave.behavioralFlaw = either("none");
-				slave.hStyle = "tails";
-				slave.custom.tattoo = "$He has the simple logo of the corporation that operates the Slavegirl School tattooed on $his left cheek.";
 				slaveArray.push(slave);
 			}
 		} else if (failedSchool === "TUO") {
 			for (let i = 0; i < slavesToAdd; i++) {
-				const slave = GenerateNewSlave(null, {minAge: V.minimumSlaveAge, maxAge: V.fertilityAge, disableDisability: 1});
+				const {slave} = generateMarketSlave("TUO");
 				slave.origin = "$He was given to you by a failed branch of The Utopian Orphanage right after $his graduation.";
-				slave.career = "a slave";
-				setHealth(slave, jsRandom(60, 80), 0, 0, 0);
-				slave.devotion = random(50, 75);
-				slave.trust = random(50, 75);
-				slave.face = (V.TUO.schoolUpgrade === 1 ? random(30, 100) : random(10, 65));
-				slave.intelligence = (V.TUO.schoolUpgrade === 1 ? random(55, 100) : random(35, 75));
-				slave.intelligenceImplant = (V.TUO.schoolUpgrade === 1 ? 30 : 15);
-				slave.accent = (V.TUO.schoolUpgrade === 1 ? 1 : either(0, 1));
-				slave.skill.entertainment = (V.TUO.schoolUpgrade === 1 ? 75 : 45);
-				slave.skill.combat = (V.TUO.schoolUpgrade === 1 ? 1 : 0);
-				slave.skill.vaginal = (V.TUO.schoolUpgrade === 2 ? 15 : 0);
-				slave.skill.oral = (V.TUO.schoolUpgrade === 2 ? 15 : 0);
-				slave.skill.anal = (V.TUO.schoolUpgrade === 2 ? 15 : 0);
-				slave.skill.whoring = (V.TUO.schoolUpgrade === 2 ? 15 : 0);
-				slave.energy = (V.TUO.schoolUpgrade === 2 ? random(40, 95) : random(15, 65));
-				slave.faceImplant = 0;
-				slave.weight = random(-17, 17);
-				slave.muscles = random(0, 20);
-				slave.lips = random(10, 40);
-				slave.lipsImplant = 0;
-				slave.boobs = 50;
-				slave.boobsImplant = 0;
-				slave.butt = random(0, 2);
-				slave.buttImplant = 0;
-				slave.vagina = 0;
-				slave.anus = 0;
 				slaveArray.push(slave);
 			}
 		} else if (failedSchool === "TCR") {
 			for (let i = 0; i < slavesToAdd; i++) {
-				const slave = GenerateNewSlave("XX", {
-					minAge: V.fertilityAge+6, maxAge: 32, disableDisability: 1, ageOverridesPedoMode: 1
-				});
-				slave.slaveName = setup.cowSlaveNames.random();
-				slave.slaveSurname = 0;
-				slave.career = "a dairy cow";
-				slave.butt = either(5, 6, 6, 7, 7, 8, 9);
-				slave.boobs = 30000;
-				slave.lactation = 1;
-				slave.lactationDuration = 2;
-				slave.lactationAdaptation = 100;
-				slave.origin = "$He is a prized dairy cow given to you by a failed local pasture of The Cattle Ranch.";
-				slave.anus = 1;
-				slave.vagina = 5;
-				slave.vaginaLube = 2;
-				setHealth(slave, jsRandom(50, 60), 0, 0, 0);
-				slave.preg = random(30, 39);
-				slave.pregType = random(3, 6);
-				slave.pregKnown = 1;
-				slave.pregWeek = slave.preg;
-				SetBellySize(slave);
-				slave.bellySag = 10;
-				slave.bellySagPreg = 10;
-				slave.hips = either(2, 2, 2, 2, 3);
-				slave.counter.birthsTotal = random(10, 15);
-				slave.geneticQuirks.hyperFertility = 2;
-				slave.weight = random(60, 160);
-				slave.muscles = random(60, 80);
-				slave.chem = 10;
-				slave.pubicHStyle = "waxed";
-				slave.underArmHStyle = "waxed";
-				slave.heels = 1;
-				applyMindbroken(slave, either(-100, -100, -100, -96, -80, -70, -50));
-				slave.hStyle = "neat";
-				slave.collar = "leather with cowbell";
-				App.Medicine.Modification.addBrand(slave, "right thigh", "the logo of the Cattle Ranch");
+				const {slave} = generateMarketSlave("TCR");
+				if (slave.career === "a breeding bull") {
+					slave.geneticQuirks.wellHung = 2;
+					slave.origin = "$He is a prized breeding bull given to you by a failed local pasture of The Cattle Ranch.";
+				} else {
+					slave.geneticQuirks.hyperFertility = 2;
+					slave.counter.birthsTotal *= 2;
+					slave.hips += 1;
+					slave.butt += 1;
+					slave.boobs += 200;
+					slave.vaginaLube = 2;
+					slave.origin = "$He is a prized dairy cow given to you by a failed local pasture of The Cattle Ranch.";
+				}
 				slave.boobsTat = "bovine patterns";
 				slave.buttTat = "bovine patterns";
 				slave.vaginaTat = "bovine patterns";
@@ -150,500 +69,53 @@ App.Events.RESFailure = class RESFailure extends App.Events.BaseEvent {
 			}
 		} else if (failedSchool === "GRI") {
 			for (let i = 0; i < slavesToAdd; i++) {
-				const slave = GenerateNewSlave("XX", {disableDisability: 1});
+				const {slave} = generateMarketSlave("GRI");
 				slave.origin = "$He was given to you by a failed subsidiary lab of the Growth Research Institute right after $his use as a test subject ended.";
-				slave.career = "a slave";
-				slave.intelligenceImplant = 0;
-				slave.devotion = random(-15, -5);
-				slave.trust = random(-25, -45);
-				slave.chem = 100;
-				if (V.GRI.schoolUpgrade === 1) {
-					setHealth(slave, 200, 0, 0, 0);
-				} else {
-					setHealth(slave, jsRandom(-70, 100), 0);
-				}
-				slave.height = random(150, 190);
-				slave.butt = random(4, 10);
-				slave.boobs = 200 * (V.GRI.schoolUpgrade === 2 ? random(15, 30) : random(4, 20));
-				if (V.GRI.schoolUpgrade === 2) {
-					slave.lactation = slave.lactationDuration = 2;
-				}
-				slave.nipples = either("huge", "inverted");
-				slave.areolae = either(0, 1, 2, 3, 4);
-				slave.clit = either(0, 1, 2, 3);
-				slave.lips = random(5, 85);
-				slave.anus = 0;
-				slave.vagina = 0;
-				slave.preg = 0;
-				slave.weight = 0;
-				slave.skill.vaginal = 0;
-				slave.skill.oral = 0;
-				slave.skill.anal = 0;
-				slave.skill.whoring = 0;
-				slave.skill.entertainment = 0;
-				slave.skill.combat = 0;
-				slave.pubicHStyle = "waxed";
-				slave.underArmHStyle = "waxed";
-				slave.actualAge = 19;
-				slave.visualAge = slave.actualAge;
-				slave.physicalAge = slave.actualAge;
-				slave.ovaryAge = slave.actualAge;
-				slave.behavioralFlaw = either("odd");
-				slave.hStyle = "shaved";
-				slave.hLength = 0;
-				slave.custom.tattoo = "$He has a barcode that identified $him when $he was a test subject at the Growth Research Institute tattooed on $his left cheek.";
 				slaveArray.push(slave);
 			}
 		} else if (failedSchool === "SCP") {
 			for (let i = 0; i < slavesToAdd; i++) {
-				const slave = GenerateNewSlave("XX", {disableDisability: 1});
+				const {slave} = generateMarketSlave("SCP");
 				slave.origin = "$He was given to you by a failed branch campus of St. Claver Preparatory after $he served as a plastic surgeon's passing final exam.";
-				slave.chem = 20;
-				slave.career = "a slave";
-				slave.intelligenceImplant = (V.SCP.schoolUpgrade === 1 ? 0 : 15);
-				slave.intelligence = (V.SCP.schoolUpgrade === 1 ? -70 : random(-50, 50));
-				slave.devotion = slave.trust = (V.SCP.schoolUpgrade === 1 ? 20 : random(25, 45));
-				if (V.SCP.schoolUpgrade !== 1) {
-					slave.teeth = "normal";
-				}
-				setHealth(slave, 100, 0, 0, 0);
-				slave.heightImplant = 1;
-				slave.height += 10;
-				slave.buttImplant = (4-slave.butt);
-				slave.butt += slave.buttImplant;
-				slave.boobsImplantType = "normal";
-				slave.boobsImplant = (2000-slave.boobs);
-				slave.boobs += slave.boobsImplant;
-				slave.boobsImplantType = "fillable";
-				slave.nipples = "tiny";
-				slave.areolae = 0;
-				slave.clit = 0;
-				slave.lipsImplant = (75-slave.lips);
-				slave.lips += slave.lipsImplant;
-				slave.faceImplant = 35;
-				slave.face = random(35, 80);
-				slave.anus = 0;
-				slave.vagina = 0;
-				slave.preg = 0;
-				slave.weight = -20;
-				slave.skill.vaginal = (V.SCP.schoolUpgrade === 2 ? 15 : 0);
-				slave.skill.oral = (V.SCP.schoolUpgrade === 2 ? 15 : 0);
-				slave.skill.anal = (V.SCP.schoolUpgrade === 2 ? 15 : 0);
-				slave.skill.whoring = (V.SCP.schoolUpgrade === 2 ? 15 : 0);
-				slave.skill.entertainment = (V.SCP.schoolUpgrade === 2 ? 15 : 0);
-				slave.skill.combat = 0;
-				slave.pubicHStyle = "waxed";
-				slave.underArmHStyle = "waxed";
-				slave.actualAge = 19;
-				slave.visualAge = slave.actualAge;
-				slave.physicalAge = slave.actualAge;
-				slave.ovaryAge = slave.actualAge;
-				slave.sexualFlaw = either("none");
-				slave.behavioralFlaw = either("none");
-				slave.hStyle = "tails";
-				slave.hColor = "blonde";
-				slave.pubicHColor = "blonde";
-				slave.underArmHColor = "blonde";
-				slave.race = "white";
-				slave.skin = "sun tanned";
-				slave.override_H_Color = 1;
-				slave.override_Arm_H_Color = 1;
-				slave.override_Pubic_H_Color = 1;
-				slave.override_Race = 1;
-				slave.override_Skin = 1;
-				slave.custom.tattoo = "$He has the coat of arms of St. Claver Preparatory tattooed on $his left cheek.";
 				slaveArray.push(slave);
 			}
 		} else if (failedSchool === "LDE") {
 			for (let i = 0; i < slavesToAdd; i++) {
-				const slave = GenerateNewSlave("XY", {disableDisability: 1});
+				const {slave} = generateMarketSlave("LDE");
 				slave.origin = "$He was given to you by a failed branch campus of the innovative École des Enculées right after $his graduation.";
-				slave.career = "a slave";
-				slave.intelligenceImplant = 0;
-				slave.chem = 100;
-				slave.devotion = (V.LDE.schoolUpgrade === 1 ? 20 : random(60, 70));
-				slave.trust = (V.LDE.schoolUpgrade === 1 ? 20 : random(55, 60));
-				setHealth(slave, jsRandom(60, 80), 0, 0, 0);
-				slave.muscles = 0;
 				if (random(1, 100) > 75) {
 					slave.geneticQuirks.rearLipedema = 2;
 					slave.butt = random(6, 16);
-				} else {
-					slave.butt = random(4, 5);
 				}
-				slave.face = random(20, 60);
-				slave.boobs = either(500, 650, 800);
-				slave.waist = -15;
-				slave.lips = 35;
-				slave.balls = (V.LDE.schoolUpgrade === 2 ? either(3, 4) : either(1, 1, 1, 2));
-				slave.dick = (V.LDE.schoolUpgrade === 2 ? either(3, 4) : either(1, 1, 1, 2));
-				if (slave.foreskin > 0) {
-					slave.foreskin = slave.dick;
-				}
-				if (slave.balls > 0) {
-					slave.scrotum = slave.balls;
-				}
-				slave.anus = 2;
-				slave.vagina = -1;
-				slave.preg = 0;
-				slave.weight = random(0, 20);
-				slave.skill.vaginal = 0;
-				slave.skill.oral = 15;
-				slave.skill.anal = 100;
-				slave.skill.whoring = 15;
-				slave.skill.entertainment = 15;
-				slave.skill.combat = 0;
-				slave.pubicHStyle = "waxed";
-				slave.underArmHStyle = "waxed";
-				slave.actualAge = 19;
-				slave.visualAge = slave.actualAge;
-				slave.physicalAge = slave.actualAge;
-				slave.ovaryAge = slave.actualAge;
-				slave.sexualFlaw = "none";
-				slave.behavioralFlaw = either("none", "odd");
-				slave.fetishStrength = either(1, 2);
-				slave.fetish = "buttslut";
-				slave.fetishKnown = 1;
-				slave.attrKnown = 1;
-				slave.hStyle = "tails";
-				slave.hLength = 100;
-				slave.custom.tattoo = "$He has the buttock-shaped symbol of the École des Enculées that created $his tattooed on $his left cheek.";
 				slaveArray.push(slave);
 			}
 		} else if (failedSchool === "NUL") {
 			for (let i = 0; i < slavesToAdd; i++) {
-				const slave = GenerateNewSlave(null, {minAge: 16, maxAge: 24, disableDisability: 1});
+				const {slave} = generateMarketSlave("NUL");
 				slave.origin = "$He was given to you by a failed branch campus of Nueva Universidad de Libertad right after $his graduation.";
-				slave.career = "a slave";
-				setHealth(slave, jsRandom(60, 80), 0, 0, 0);
-				slave.devotion = random(60, 75);
-				slave.trust = random(60, 75);
-				slave.intelligenceImplant = 30;
-				slave.intelligence = (V.NUL.schoolUpgrade === 1 ? random(50, 70) : random(20, 50));
-				slave.skill.whoring = (V.NUL.schoolUpgrade === 1 ? random(70, 80) : random(40, 50));
-				slave.skill.entertainment = (V.NUL.schoolUpgrade === 1 ? random(70, 80) : random(40, 50));
-				slave.skill.anal = (V.NUL.schoolUpgrade === 2 ? random(60, 80) : random(10, 30));
-				slave.skill.oral = (V.NUL.schoolUpgrade === 2 ? random(70, 90) : random(20, 40));
-				slave.anus = (V.NUL.schoolUpgrade === 2 ? random(1, 3) : either(0, 0, 0, 0, 1, 1, 1));
-				slave.muscles = 0;
-				slave.face = random(15, 55);
-				slave.faceShape = "androgynous";
-				slave.boobs = 50;
-				slave.butt = 0;
-				slave.vagina = -1;
-				slave.clit = 0;
-				slave.dick = 0;
-				slave.balls = 0;
-				slave.preg = 0;
-				slave.eyebrowHStyle = "bald";
-				slave.underArmHStyle = "bald";
-				slave.pubicHStyle = "bald";
-				slave.hStyle = "bald";
-				slave.custom.tattoo = "$He has the abstract symbol of Nueva Universidad de Libertad tattooed on $his left shoulder.";
 				slaveArray.push(slave);
 			}
 		} else if (failedSchool === "TGA") {
 			for (let i = 0; i < slavesToAdd; i++) {
-				const slave = GenerateNewSlave("XY", {disableDisability: 1});
+				const {slave} = generateMarketSlave("TGA");
 				slave.origin = "$He was given to you by a failed branch campus of the intense Gymnasium-Academy right after $his majority.";
-				slave.career = "a slave";
-				slave.intelligenceImplant = 15;
-				slave.teeth = "normal";
-				slave.intelligence = random(-50, 95);
-				slave.chem = 20;
-				slave.devotion = (V.TGA.schoolUpgrade === 1 ? 20 : random(25, 45));
-				slave.trust = (V.TGA.schoolUpgrade === 1 ? 20 : random(25, 45));
-				setHealth(slave, 100, 0, 0, 0);
-				slave.muscles = either(20, 50, 50);
-				slave.butt = either(2, 2, 3);
-				slave.boobs = either(100, 200);
-				slave.dick = random(3, 5);
-				if (slave.foreskin > 0) {
-					slave.foreskin = slave.dick;
-				}
-				if (slave.balls > 0) {
-					slave.scrotum = slave.balls;
-				}
-				slave.balls = random(3, 5);
-				slave.anus = 0;
-				slave.vagina = -1;
-				slave.preg = 0;
-				slave.weight = 0;
-				slave.skill.vaginal = 0;
-				slave.skill.oral = 0;
-				slave.skill.anal = 0;
-				slave.skill.whoring = 0;
-				slave.skill.entertainment = 0;
-				slave.skill.combat = (V.TGA.schoolUpgrade === 2 ? 1 : 0);
-				slave.pubicHStyle = "waxed";
-				slave.underArmHStyle = "waxed";
-				slave.actualAge = 18;
-				slave.visualAge = slave.actualAge;
-				slave.physicalAge = slave.actualAge;
-				slave.ovaryAge = slave.actualAge;
-				slave.sexualFlaw = either("apathetic", "none");
-				slave.behavioralFlaw = either("arrogant", "none", "odd");
-				slave.hStyle = "neat";
-				slave.hLength = 2;
-				App.Medicine.Modification.addBrand(slave,"left cheek", "the baroque crest of the Gymnasium-Academy that trained $him");
 				slaveArray.push(slave);
 			}
 		} else if (failedSchool === "HA") {
 			for (let i = 0; i < slavesToAdd; i++) {
-				const slave = GenerateNewSlave("XX", {disableDisability: 1});
-				slave.origin = "$He was given to you by a failed branch campus of the Hippolyta Academy right after $his majority.";
-				slave.career = "a slave";
-				slave.intelligenceImplant = 15;
-				slave.teeth = "normal";
-				slave.intelligence = random(0, 95);
-				slave.chem = 20;
-				slave.devotion = (V.HA.schoolUpgrade === 1 ? 20 : random(25, 45));
-				slave.trust = (V.HA.schoolUpgrade === 1 ? 20 : random(25, 45));
-				slave.faceShape = either("cute", "normal");
-				slave.face = either(20, 20, 35, 35, 35, 50, 75, 100);
-				slave.lips = either(0, 10, 25);
-				slave.weight = -10;
-				setHealth(slave, jsRandom(80, 100), 0, 0, 0);
-				slave.actualAge = 18;
-				slave.physicalAge = slave.actualAge;
-				slave.visualAge = slave.actualAge;
-				slave.ovaryAge = slave.actualAge;
-				slave.hips = 0;
-				slave.vagina = random(0, 1);
-				slave.anus = random(0, 1);
-				slave.butt = random(2, 4);
-				slave.boobs = (random(30, 60) * 10);
-				slave.preg = 0;
-				SetBellySize(slave);
-				setHealth(slave, jsRandom(60, 80), 0, 0, 0);
-				slave.muscles = random(40, 60);
-				const minHeight = random(170, 180);
-				slave.height = Math.clamp(Height.random(slave, {limitMult: [2, 15], spread: .1}), minHeight, 274);
-				slave.waist = -15;
-				slave.shoulders = 0;
-				slave.skill.vaginal = 10;
-				slave.skill.oral = 10;
-				slave.skill.anal = 10;
-				slave.skill.whoring = 10;
-				slave.skill.entertainment = either(10, 10, 30);
-				slave.skill.combat = 1;
-				slave.sexualFlaw = either("apathetic", "judgemental", "none", "none");
-				slave.behavioralFlaw = either("arrogant", "none");
-				slave.pubicHStyle = "waxed";
-				slave.underArmHStyle = "waxed";
-				slave.hStyle = either("braided", "bun", "neat", "ponytail", "tails");
-				slave.hLength = random(5, 50);
-				slave.custom.tattoo = "$He has the sword and eagle symbol of the Hippolyta Academy tattooed on $his left shoulder.";
+				const {slave} = generateMarketSlave("HA");
+				slave.origin = "$He was given to you by a failed branch campus of the Hippolyta Academy.";
 				slaveArray.push(slave);
 			}
 		} else if (failedSchool === "TFS") {
 			// normal slaves
 			for (let i = 0; i < slavesToAdd-1; i++) {
-				let SGProp = new GenerateNewSlavePram();
-				SGProp.disableDisability = 1;
-
-				let slaveGenRange = random(1, 4);
-				if (slaveGenRange === 4) {
-					if (V.retirementAge <= 40) {
-						slaveGenRange = random(1, 3);
-					} else {
-						SGProp.minAge = 40;
-						SGProp.maxAge = 42;
-					}
-				}
-				if (slaveGenRange === 3) {
-					if (V.retirementAge <= 35) {
-						slaveGenRange = random(1, 2);
-					} else {
-						SGProp.minAge = 35;
-						SGProp.maxAge = 39;
-					}
-				}
-				if (slaveGenRange === 2) {
-					if (V.retirementAge <= 30) {
-						slaveGenRange = 1;
-					} else {
-						SGProp.minAge = 30;
-						SGProp.maxAge = 34;
-					}
-				}
-				if (slaveGenRange === 1) {
-					SGProp.minAge = 25;
-					SGProp.maxAge = 29;
-				}
-
-				let slave;
-				if (V.TFS.schoolUpgrade === 3 && V.TFS.compromiseWeek + 15 <= V.week) {
-					slave = GenerateNewSlave(null, SGProp);
-				} else {
-					slave = GenerateNewSlave("XY", SGProp);
-				}
+				const {slave} = generateMarketSlave("TFS");
 				slave.origin = "$He was a Futanari Sister until you engineered $his early enslavement.";
-				slave.career = "a Futanari Sister";
-				slave.faceShape = either("exotic", "sensual");
-				if (slaveGenRange === 1) {
-					slave.intelligence = random(-50, -20);
-					slave.chem = 150;
-					slave.butt = either(5, 6);
-					slave.hips = 1;
-					slave.face = either(35, 35, 35, 75, 100);
-					slave.boobs = 100*random(12, 20);
-					slave.dick = random(2, 3);
-					if (V.TFS.schoolUpgrade === 3 && V.TFS.compromiseWeek+15 <= V.week) {
-						if (slave.genes === "XY") {
-							slave.balls = slave.scrotum = random(8, 9);
-						} else {
-							slave.balls = 1;
-							slave.scrotum = 0;
-						}
-					} else if (V.TFS.schoolUpgrade === 1) {
-						slave.balls = 1;
-						slave.scrotum = 0;
-					} else if (V.TFS.schoolUpgrade === 2) {
-						slave.balls = slave.scrotum = random(8, 9);
-					} else {
-						slave.balls = slave.scrotum = random(2, 3);
-					}
-					slave.lips = 0;
-					slave.weight = 0;
-					slave.vagina = 2;
-					slave.anus = 2;
-					slave.fetish = "submissive";
-				} else if (slaveGenRange === 2) {
-					slave.intelligence = random(-15, 15);
-					slave.chem = 200;
-					slave.butt = either(6, 7);
-					slave.hips = 2;
-					slave.face = either(35, 35, 75, 75, 100);
-					slave.boobs = 100*random(20, 32);
-					slave.dick = random(3, 4);
-					if (V.TFS.schoolUpgrade === 3 && V.TFS.compromiseWeek+15 <= V.week) {
-						if (slave.genes === "XY") {
-							slave.balls = slave.scrotum = random(9, 10);
-						} else {
-							slave.balls = 1;
-							slave.scrotum = 0;
-						}
-					} else if (V.TFS.schoolUpgrade === 1) {
-						slave.balls = 1;
-						slave.scrotum = 0;
-					} else {
-						slave.balls = slave.scrotum = (V.TFS.schoolUpgrade === 2 ? random(9, 10) : random(3, 4));
-					}
-					slave.lips = random(15, 25);
-					slave.weight = 20;
-					slave.vagina = 2;
-					slave.anus = 2;
-					slave.fetish = either("buttslut", "cumslut");
-				} else if (slaveGenRange === 3) {
-					slave.intelligence = random(16, 50);
-					slave.chem = 250;
-					slave.butt = either(7, 8);
-					slave.hips = 2;
-					slave.face = either(35, 75, 75, 100, 100);
-					slave.boobs = 100*random(32, 42);
-					slave.dick = random(4, 5);
-					if (V.TFS.schoolUpgrade === 3 && V.TFS.compromiseWeek+15 <= V.week) {
-						if (slave.genes === "XY") {
-							slave.balls = slave.scrotum = random(6, 7);
-						} else {
-							slave.balls = 1;
-							slave.scrotum = 0;
-						}
-					} else if (V.TFS.schoolUpgrade === 1) {
-						slave.balls = 1;
-						slave.scrotum = 0;
-					} else {
-						slave.balls = slave.scrotum = (V.TFS.schoolUpgrade === 2 ? random(6, 7) : random(4, 5));
-					}
-					slave.lips = random(25, 55);
-					slave.weight = 20;
-					slave.vagina = 3;
-					slave.anus = 3;
-					slave.fetish = either("buttslut", "cumslut");
-				} else {
-					slave.intelligence = random(51, 95);
-					slave.chem = 300;
-					slave.butt = either(8, 9);
-					slave.hips = 2;
-					slave.face = either(35, 75, 100, 100, 100);
-					slave.boobs = 100*random(44, 60);
-					slave.dick = random(5, 6);
-					slave.geneticQuirks.wellHung = 2;
-					if (V.TFS.schoolUpgrade === 3 && V.TFS.compromiseWeek+15 <= V.week) {
-						if (slave.genes === "XY") {
-							slave.balls = random(7, 8);
-							slave.scrotum = slave.balls;
-						} else {
-							slave.balls = 1;
-							slave.scrotum = 0;
-						}
-					} else if (V.TFS.schoolUpgrade === 1) {
-						slave.balls = 1;
-						slave.scrotum = 0;
-					} else {
-						slave.balls = slave.scrotum = (V.TFS.schoolUpgrade === 2 ? random(7, 8) : random(5, 6));
-					}
-					slave.lips = random(25, 55);
-					slave.weight = 50;
-					slave.vagina = 3;
-					slave.anus = 3;
-					slave.fetish = "dom";
-				}
-				if (slave.foreskin > 0) {
-					slave.foreskin = slave.dick;
-				}
-				slave.preg = -3;
-				if (V.TFS.farmUpgrade > 0) {
-					slave.ovaries = 1;
-					if (V.TFS.farmUpgrade >= 2) {
-						slave.preg = random(1, 41);
-						if (V.TFS.farmUpgrade === 3) {
-							slave.pregType = random(10, 30);
-							slave.pregAdaptation = 300;
-						} else {
-							slave.pregType = 1;
-						}
-						slave.pregSource = -9;
-						slave.pregKnown = 1;
-						slave.pregWeek = slave.preg;
-						SetBellySize(slave);
-					}
-				}
-				slave.intelligenceImplant = 30;
-				slave.teeth = "normal";
-				slave.energy = (V.TFS.schoolUpgrade === 2 ? 100 : slave.physicalAge + random(20, 30));
-				slave.devotion = random(30, 35);
-				slave.trust = random(-15, -5);
-				setHealth(slave, jsRandom(60, 80), 0, 0, 0);
-				slave.muscles = 20;
-				slave.waist = -15;
-				if (slave.genes === "XY") {
-					slave.shoulders = 1;
-				}
-				slave.skill.vaginal = 100;
-				slave.skill.oral = 100;
-				slave.skill.anal = 100;
-				slave.skill.whoring = 15;
-				slave.skill.entertainment = 100;
-				slave.skill.combat = 0;
-				slave.pubicHStyle = "waxed";
-				slave.underArmHStyle = "waxed";
-				if (V.TFS.schoolUpgrade === 1) {
-					slave.sexualQuirk = "caring";
-				}
-				slave.sexualFlaw = either("judgemental", "none");
-				slave.behavioralFlaw = either("arrogant", "none");
-				slave.fetishStrength = 100;
-				slave.fetishKnown = 0;
-				slave.attrKnown = 0;
-				slave.hStyle = "neat";
-				slave.hLength = 150;
-				slave.custom.tattoo = "$He has a simple pink heart tattooed on $his right temple.";
 				slaveArray.push(slave);
 			}
-			// Leader slave
-			let SGProp = new GenerateNewSlavePram();
+			// Matron
+			const SGProp = new GenerateNewSlavePram();
 			SGProp.disableDisability = 1;
 			SGProp.minAge = 40;
 			SGProp.maxAge = 42;
@@ -663,7 +135,6 @@ App.Events.RESFailure = class RESFailure extends App.Events.BaseEvent {
 			if (V.TFS.schoolUpgrade === 3 && V.TFS.compromiseWeek+15 <= V.week) {
 				slave.balls = slave.scrotum = 10;
 			} else {
-				slave.balls = slave.scrotum = (V.TFS.schoolUpgrade === 1 ? 1 :10);
 				slave.balls = slave.scrotum = (V.TFS.schoolUpgrade === 1 ? 0 : random(5, 6));
 			}
 			slave.lips = random(25, 55);
@@ -674,6 +145,7 @@ App.Events.RESFailure = class RESFailure extends App.Events.BaseEvent {
 			slave.preg = -3;
 			if (V.TFS.farmUpgrade > 0) {
 				slave.ovaries = 1;
+				slave.preg = -1
 				if (V.TFS.farmUpgrade >= 2) {
 					slave.preg = random(1, 41);
 					if (V.TFS.farmUpgrade === 3) {
@@ -747,7 +219,7 @@ App.Events.RESFailure = class RESFailure extends App.Events.BaseEvent {
 			App.Events.addParagraph(node, r);
 			r = [];
 			App.Events.addResponses(node, [
-				new App.Events.Result(`Rape ${him}`, TFSRape, `This will cost ${cashFormat(10000)}`)
+				new App.Events.Result(`Rape ${him}`, TFSRape)
 			]);
 		} else {
 			r.push(`You receive a personal call from a senior representative of ${SCH.title} as you've been expecting since their second missed rent payment. "I apologize," ${he2} says with some embarrassment, "but it seems our expansion into your arcology was a mistake. It's strange — the business climate seemed excellent, and other corporations are doing well."`);
@@ -794,13 +266,12 @@ App.Events.RESFailure = class RESFailure extends App.Events.BaseEvent {
 		function TFSRape() {
 			const frag = new DocumentFragment();
 			let r = [];
-			for (const slave of V.slaves) {
-				if (slave.origin === "$He was the leader of your arcology's Futanari Sisters until you engineered $his community's failure and enslavement.") {
-					slave.devotion += 10;
-					actX(slave, "anal");
-					actX(slave, "vaginal");
-				}
-			}
+			const isMatron = (s) => (s.origin === "$He was the leader of your arcology's Futanari Sisters until you engineered $his community's failure and enslavement.") && (s.newGamePlus !== 1);
+			// player can choose "Rape her" before or after the other choices...if she's not been enslaved yet, alter her template instead
+			const matron = V.slaves.find(isMatron) || slaveArray.find(isMatron);
+			matron.devotion += 10;
+			seX(matron, "anal", V.PC, "penetrative");
+			seX(matron, "vaginal", V.PC, "penetrative");
 			r.push(`You`);
 			if (V.PC.dick !== 0) {
 				r.push(`whip out your dick`);
diff --git a/src/facilities/dairy/dairy.js b/src/facilities/dairy/dairy.js
index ffbeccf79a5dd7d5a8111625fbb773d87b1c4b59..9e46f7d304fa087663a58c9858cb99f139173025 100644
--- a/src/facilities/dairy/dairy.js
+++ b/src/facilities/dairy/dairy.js
@@ -45,13 +45,7 @@ App.Facilities.Dairy.dairy = class Dairy extends App.Facilities.Facility {
 	get intro() {
 		const text = [];
 
-		if (V.dairyRestraintsSetting > 1) {
-			text.push(`${this.facility.nameCaps} is an industrial facility, but there's a viewing gallery for visitors.`);
-		} else {
-			text.push(this.facility.nameCaps);
-		}
-		text.push(this.decorations);
-
+		text.push(`${this.facility.nameCaps} ${V.dairyRestraintsSetting > 1 ? `is an industrial facility, but there's a viewing gallery for visitors.` : ``} ${this.decorations}`);
 		if (this.facility.hostedSlaves > 2) {
 			text.push(`${this.facility.nameCaps} is working steadily.`);
 
@@ -73,10 +67,10 @@ App.Facilities.Dairy.dairy = class Dairy extends App.Facilities.Facility {
 					}
 				}
 				if (V.dairyPregUpgrade === 1 && V.dairyPregSetting > 0) {
-					if (V.dairyPregSetting === 3) {
-						text.push(`Fertile cows' vaginas are constantly penetrated by huge dildos that ejaculate extra strong fertility drugs into their progressively distending wombs. The drugs induce intense arousal and excessive female lubrication, so the constant dildo rape fills ${this.facility.name} with occasional gushing noises and nonstop moaning.`);
-					} else if (V.dairyPregSetting === 2) {
-						text.push(`Fertile cows' vaginas are constantly penetrated by huge dildos that ejaculate fertility drugs. The drugs produce excessive female lubrication, so the constant dildo rape fills ${this.facility.name} with occasional gushing noises.`);
+					const DrugPlus = V.dairyPregSetting === 3;
+					if (V.dairyPregSetting >= 2) {
+						text.push(`Fertile cows' vaginas are constantly penetrated by huge dildos that ejaculate ${DrugPlus ? 'extra strong' : ''} fertility drugs${DrugPlus ? ' into their progressively distending wombs' : ''}.`);
+						text.push(`The drugs ${DrugPlus ? 'induce intense arousal and' : 'produce'} excessive female lubrication, so the constant dildo rape fills ${this.facility.name} with occasional gushing noises${DrugPlus ? ' and nonstop moaning' : ''}.`);
 					} else {
 						text.push(`The fertile cows are visibly pregnant.`);
 					}
@@ -184,11 +178,9 @@ App.Facilities.Dairy.dairy = class Dairy extends App.Facilities.Facility {
 		}
 
 		const res = FS[V.dairyDecoration];
-
 		if (!res) {
 			throw new Error(`Unknown V.dairyDecoration value of '${V.dairyDecoration}' found in decorations().`);
 		}
-
 		return res;
 	}
 
@@ -386,7 +378,6 @@ App.Facilities.Dairy.dairy = class Dairy extends App.Facilities.Facility {
 
 	/** @returns {FC.Facilities.Rule[]} */
 	get rules() {
-		const effectFunc = (arg) => this._getEffect(arg); // bind to this
 		return [
 			{
 				property: "dairyFeedersSetting",
@@ -398,25 +389,19 @@ App.Facilities.Dairy.dairy = class Dairy extends App.Facilities.Facility {
 						get text() { return `The feeders have been disabled.`; },
 						link: `Deactivate`,
 						value: 0,
-						dialog: {
-							get content() { return effectFunc('feeders'); },
-						},
+						handler: App.UI.DialogHandler(() => this._getEffect("feeders")),
 					},
 					{
 						get text() { return `The feeders are active.`; },
 						link: `Moderate`,
 						value: 1,
-						dialog: {
-							get content() { return effectFunc('feeders'); },
-						},
+						handler: App.UI.DialogHandler(() => this._getEffect("feeders")),
 					},
 					{
 						get text() { return `The feeders are industrial.`; },
 						link: `Industrial`,
 						value: 2,
-						dialog: {
-							get content() { return effectFunc('feeders'); },
-						},
+						handler: App.UI.DialogHandler(() => this._getEffect("feeders")),
 						prereqs: [
 							() => V.dairyRestraintsSetting > 1,
 						],
@@ -433,25 +418,19 @@ App.Facilities.Dairy.dairy = class Dairy extends App.Facilities.Facility {
 						get text() { return `Fertile cows' wombs are not for hire.`; },
 						link: `Not for hire`,
 						value: 0,
-						dialog: {
-							get content() { return effectFunc('preg'); },
-						},
+						handler: App.UI.DialogHandler(() => this._getEffect("preg")),
 					},
 					{
 						get text() { return `Fertile cows' wombs are for hire.`; },
 						link: `Moderate`,
 						value: 1,
-						dialog: {
-							get content() { return effectFunc('preg'); },
-						},
+						handler: App.UI.DialogHandler(() => this._getEffect("preg")),
 					},
 					{
 						get text() { return `Fertile cows' wombs are industrially employed.`; },
 						link: `Industrial`,
 						value: 2,
-						dialog: {
-							get content() { return effectFunc('preg'); },
-						},
+						handler: App.UI.DialogHandler(() => this._getEffect("preg")),
 						prereqs: [
 							() => V.dairyRestraintsSetting > 1,
 						],
@@ -460,9 +439,7 @@ App.Facilities.Dairy.dairy = class Dairy extends App.Facilities.Facility {
 						get text() { return `Fertile cows' wombs are worked to capacity.`; },
 						link: `Mass production`,
 						value: 3,
-						dialog: {
-							get content() { return effectFunc('preg'); },
-						},
+						handler: App.UI.DialogHandler(() => this._getEffect("preg")),
 						prereqs: [
 							() => V.seeExtreme > 0,
 							() => V.seeHyperPreg > 0,
@@ -482,25 +459,19 @@ App.Facilities.Dairy.dairy = class Dairy extends App.Facilities.Facility {
 						get text() { return `The sodomizers are inactive.`; },
 						link: `Deactivate`,
 						value: 0,
-						dialog: {
-							get content() { return effectFunc('stimulators'); },
-						},
+						handler: App.UI.DialogHandler(() => this._getEffect("stimulators")),
 					},
 					{
 						get text() { return `The sodomizers are active.`; },
 						link: `Moderate`,
 						value: 1,
-						dialog: {
-							get content() { return effectFunc('stimulators'); },
-						},
+						handler: App.UI.DialogHandler(() => this._getEffect("stimulators")),
 					},
 					{
 						get text() { return `The sodomizers are industrial, employing dildos the size of horse phalli.`; },
 						link: `Industrial`,
 						value: 2,
-						dialog: {
-							get content() { return effectFunc('stimulators'); },
-						},
+						handler: App.UI.DialogHandler(() => this._getEffect("stimulators")),
 						prereqs: [
 							() => !!V.seeExtreme,
 							() => V.dairyRestraintsSetting > 1,
@@ -518,25 +489,19 @@ App.Facilities.Dairy.dairy = class Dairy extends App.Facilities.Facility {
 						get text() { return `The cows are restrained only when necessary, allowing obedient cows freedom to range around.`; },
 						link: `Deactivate`,
 						value: 0,
-						dialog: {
-							get content() { return effectFunc('restraints'); },
-						},
+						handler: App.UI.DialogHandler(() => this._getEffect("restraints")),
 					},
 					{
 						get text() { return `The cows are restrained when being milked, giving the machines full play.`; },
 						link: `Free range`,
 						value: 1,
-						dialog: {
-							get content() { return effectFunc('restraints'); },
-						},
+						handler: App.UI.DialogHandler(() => this._getEffect("restraints")),
 					},
 					{
 						get text() { return `The cows are restrained permanently, allowing use of industrial techniques even devoted cows would flinch at.`; },
 						link: `Permanent machine milking`,
 						value: 2,
-						dialog: {
-							get content() { return effectFunc('restraints'); },
-						},
+						handler: App.UI.DialogHandler(() => this._getEffect("restraints")),
 					},
 				],
 			},
@@ -719,24 +684,20 @@ App.Facilities.Dairy.dairy = class Dairy extends App.Facilities.Facility {
 	/** @returns {HTMLDivElement} */
 	get warning() {
 		const div = document.createElement("div");
-
 		if (V.dairyPregSetting > 1 || V.dairyFeedersSetting > 1 || V.dairyStimulatorsSetting > 1) {
 			App.Events.addNode(div, [
 				`<span class="warning">Current milking machine settings will have dramatic and possibly irreversible effects on cow bodies and minds.</span>`
 			]);
 		}
-
 		return div;
 	}
 
 	/**
 	 * Returns the dialog text to display when setting a rule.
-	 *
 	 * @param {'feeders'|'stimulators'|'preg'|'restraints'} setting
 	 */
 	_getEffect(setting) {
 		const text = [];
-
 		const {He: HeU, he: heU, him: himU, his: hisU, himself: himselfU} = getNonlocalPronouns(V.seeDicks);
 
 		if (this.facility.hostedSlaves > 1) {
@@ -749,33 +710,23 @@ App.Facilities.Dairy.dairy = class Dairy extends App.Facilities.Facility {
 			} else if (setting === "preg") {
 				if (V.dairyPregSetting < 2) {
 					for (const slave of this.facility.employees()) {
-						const {him, his} = getPronouns(slave);
-
+						const {He, him, his} = getPronouns(slave);
 						if (slave.vagina.isBetween(-1, 3)) {
-							text.push(`${slave.slaveName}'s milking machine ejects ${him}, since it cannot fit the mandated dildo into ${his} tight cunt.`);
-
+							text.push(`${slave.slaveName}'s milking machine ejects ${him}, since it cannot fit the mandated dildo into ${his} tight cunt. <span class="yellow">${He} has been kicked out of ${this.facility.name}.</span>`);
 							removeJob(slave, Job.DAIRY);
 						}
 						if (V.dairyPregSetting > 0) {
 							WombCleanGenericReserve(slave, "incubator", 9999);
 							WombCleanGenericReserve(slave, "nursery", 9999);
-
 							if (slave.broodmother > 0 || slave.bellyImplant !== -1) {
-								text.push(`${slave.slaveName}'s milking machine ejects ${him}, since it detected a foreign body in ${his} womb blocking its required functions.`);
-
+								text.push(`${slave.slaveName}'s milking machine ejects ${him}, since it detected a foreign body in ${his} womb blocking its required functions. <span class="yellow">${He} has been kicked out of ${this.facility.name}.</span>`);
 								removeJob(slave, Job.DAIRY);
 							}
 						}
 					}
 
 					text.push(`In unison, the milking machines withdraw their dildos from the pregnant slaves' vaginas. The auxiliary drug injectors hiss as the slaves are filled with drugs that promote natural lubrication. The slaves begin to shift awkwardly as they feel their pussies begin to drool slick female fluids. Once a machine judges that its slave's cunt is sufficiently wet, it readies a gigantic dildo. The slaves cannot see their own groins, but as soon as the heads of the dildos touch their pussylips, they begin to`);
-
-					if (V.dairyFeedersSetting < 2) {
-						text.push(`struggle instinctively against their restraints, and the more energetic ones begin to weep.`);
-					} else {
-						text.push(`struggle instinctively against their restraints.`);
-					}
-
+					text.push(`struggle instinctively against their restraints${V.dairyFeedersSetting < 2 ? ', and the more energetic ones begin to weep': ''}.`);
 					text.push(`As the massive phalli begin to ejaculate fertility drugs and semen, they drive all resistance out of the poor girls.`);
 				} else {
 					text.push(`In unison, the milking machines withdraw their monstrous dildos from the pregnant slaves' stretched cunts. Their pussies' overcharged production of natural lubricant produces a gush of pent-up female fluids from each loose vagina as the phalli slide clear.`);
@@ -785,29 +736,18 @@ App.Facilities.Dairy.dairy = class Dairy extends App.Facilities.Facility {
 					} else {
 						text.push(`The slaves are silent, since their mouths and throats are being fucked by the feeders, but most of them relax a little in their restraints.`);
 					}
-
 					text.push(`The machines do replace the withdrawn dildos with more reasonably sized phalli and resume thrusting, but the slaves are relieved anyway.`);
 				}
 			} else if (setting === "stimulators") {
 				if (V.dairyStimulatorsSetting < 2) {
-					for (const slave of this.facility.employees()) {
-						const {him, his} = getPronouns(slave);
-
-						if (slave.vagina.isBetween(-1, 3)) {
-							text.push(`${slave.slaveName}'s milking machine ejects ${him}, since it cannot fit its massive anal dildo up ${his} tight asshole.`);
-
-							removeJob(slave, Job.DAIRY);
-						}
+					for (const slave of this.facility.employees().filter(s => s.vagina.isBetween(-1, 3))) {
+						const {He, him, his} = getPronouns(slave);
+						text.push(`${slave.slaveName}'s milking machine ejects ${him}, since it cannot fit its massive anal dildo up ${his} tight asshole. <span class="yellow">${He} has been kicked out of ${this.facility.name}.</span>`);
+						removeJob(slave, Job.DAIRY);
 					}
 
 					text.push(`In unison, the milking machines shove their dildos deep into slaves' anuses, ejaculating large quantities of lubricant deep inside their rectums. The slaves start in surprise at the sudden rush of warm slick fluid, and then relax as the phalli withdraw themselves from their butts. Their relief is short-lived, however, as their assholes are only empty for a moment. The reasonably sized dildos are replaced with dildos the size of horse cocks. As soon as the slaves feel the heads of these monstrous phalli press inexorably against their sphincters,`);
-
-					if (V.dairyStimulatorsSetting < 2) {
-						text.push(`they begin to scream and struggle instinctively. As the constant assrape that will define their existences for the foreseeable future begins in earnest, their whining`);
-					} else {
-						text.push(`they begin to struggle wildly. As the constant assrape that will define their existences for the foreseeable future begins in earnest, their wriggling`);
-					}
-
+					text.push(`they begin to ${V.dairyStimulatorsSetting < 2 ? 'scream and struggle instinctively' : 'struggle wildly'}. As the constant assrape that will define their existences for the foreseeable future begins in earnest, their ${V.dairyStimulatorsSetting < 2 ? 'whining' : 'wriggling'}`);
 					text.push(`gradually diminishes as each slave is exhausted and slumps within their restraints. The machines take no notice, and continue the relentless sodomy.`);
 				} else {
 					text.push(`In unison, the milking machines withdraw their gargantuan dildos from the slaves' loosened anuses.`);
@@ -817,20 +757,15 @@ App.Facilities.Dairy.dairy = class Dairy extends App.Facilities.Facility {
 					} else {
 						text.push(`The slaves are silent, since their mouths and throats are being fucked by the feeders, but most of them slump against their machines with relief`);
 					}
-
 					text.push(`as their sphincters gradually recover from wide open to merely gaping. The machines switch out the withdrawn dildos for phalli that are just large, but the slaves barely react at all as they are penetrated. After what their sphincters have been through, a merely big dick is nothing to them.`);
 				}
 			} else if (setting === "restraints") {
 				if (V.dairyRestraintsSetting < 2) {
 					if (V.dairyRestraintsSetting === 1) {
-						for (const slave of this.facility.employees()) {
-							if (slave.indentureRestrictions > 1) {
-								const {he, him} = getPronouns(slave);
-
-								text.push(`${slave.slaveName}'s milking machine declines to restrain ${him}, since ${he} is encoded as an indentured servant protected from restraint for milking.`);
-
-								removeJob(slave, Job.DAIRY);
-							}
+						for (const slave of this.facility.employees().filter(s => s.indentureRestrictions > 1)) {
+							const {He, he, him} = getPronouns(slave);
+							text.push(`${slave.slaveName}'s milking machine declines to restrain ${him}, since ${he} is encoded as an indentured servant protected from restraint for milking. <span class="yellow">${He} has been kicked out of ${this.facility.name}.</span>`);
+							removeJob(slave, Job.DAIRY);
 						}
 
 						text.push(`The next cow to stumble over to a milking machine to be drained is gently but firmly embraced by its restraints, allowing it to suck ${himU} dry and violate ${himU} without any regard for ${hisU} feelings. Most of the cows accept this new wrinkle in their lives, since the restraints let them go afterward, and the milking machines bring temporary relief. Some, however, begin to regard the machines with concern.`);
@@ -839,25 +774,18 @@ App.Facilities.Dairy.dairy = class Dairy extends App.Facilities.Facility {
 					}
 				} else {
 					if (V.dairyRestraintsSetting === 2) {
-						for (const slave of this.facility.employees()) {
-							const {he, him} = getPronouns(slave);
-
-							if (slave.indentureRestrictions > 1) {
-								text.push(`${slave.slaveName}'s milking machine declines to restrain ${him}, since ${he} is encoded as an indentured servant protected from restraint for milking.`);
-
-								removeJob(slave, Job.DAIRY);
-							}
+						for (const slave of this.facility.employees().filter(s => s.indentureRestrictions > 1)) {
+							const {He, he, him} = getPronouns(slave);
+							text.push(`${slave.slaveName}'s milking machine declines to restrain ${him}, since ${he} is encoded as an indentured servant protected from restraint for milking. <span class="yellow">${He} has been kicked out of ${this.facility.name}.</span>`);
+							removeJob(slave, Job.DAIRY);
 						}
-
-						text.push(`The next time a cow tries to get up after being milked, ${heU} finds to ${hisU} sudden terror that the machine will not let ${himU} go. It continues to add fluids to ${hisU} body, and remove them from ${hisU} nipples, ignoring ${hisU} mounting panic as ${heU} realizes that it's to be ${hisU} new partner and lover, on a level far more intimate than any possible human relationship. The other cows approach their machines with trepidation, but the mounting pressure in their udders forces them to embrace their immurement despite their terror.`);
-
 						if (S.Milkmaid) {
 							const {his} = getPronouns(S.Milkmaid);
-
 							text.push(`${S.Milkmaid.slaveName} has been removed from ${his} position as Milkmaid, since an industrialized dairy automates ${his} duties.`);
-
 							removeJob(S.Milkmaid, Job.MILKMAID);
 						}
+
+						text.push(`The next time a cow tries to get up after being milked, ${heU} finds to ${hisU} sudden terror that the machine will not let ${himU} go. It continues to add fluids to ${hisU} body, and remove them from ${hisU} nipples, ignoring ${hisU} mounting panic as ${heU} realizes that it's to be ${hisU} new partner and lover, on a level far more intimate than any possible human relationship. The other cows approach their machines with trepidation, but the mounting pressure in their udders forces them to embrace their immurement despite their terror.`);
 					} else {
 						text.push(`The next time a cow finishes an intensive milking period, ${hisU} restraints loosen. ${HeU} does not move for a long time, as though ${heU} is unable to believe that ${heU} is, at least in an immediate and local sense, free. Finally, ${heU} prises ${himselfU} out of ${hisU} milking machine's embrace, thick strings of fluid leading from it to ${hisU} orifices as ${heU} pulls each one off of its corresponding port.`);
 					}
diff --git a/src/facilities/nursery/utils/nurseryUtils.js b/src/facilities/nursery/utils/nurseryUtils.js
index e5672420220545207776ebaef45671bf0e8e2b62..dad2b09614e2568e4f43c62d3b4ff6bbef1f2eaf 100644
--- a/src/facilities/nursery/utils/nurseryUtils.js
+++ b/src/facilities/nursery/utils/nurseryUtils.js
@@ -426,7 +426,7 @@ App.Facilities.Nursery.nameChild = function nameChild(child) {
 		r += `<br>
 		<<link "Have your PA assign ${him} a random cat name">>
 		<<replace "#naming">>`;
-		child.slaveName = setup.catSlaveNames.random();
+		child.slaveName = App.Data.misc.catSlaveNames.random();
 		child.birthName = child.slaveName;
 		r += `${V.assistant.name} registers the new ${girl} as "${child.slaveName}" in your registry.
 		<</replace>>
diff --git a/src/facilities/salon/salonPassage.js b/src/facilities/salon/salonPassage.js
index ca637f7341ebd9e8df1f532b50824ebd8f131ba0..70974df9f6d32f10cbec07ec904bb287f42381ac 100644
--- a/src/facilities/salon/salonPassage.js
+++ b/src/facilities/salon/salonPassage.js
@@ -407,7 +407,8 @@ App.UI.salon = function(slave, cheat = false, startingGirls = false) {
 
 		if (cheat) {
 			option = options.addOption(`${His} natural skin color is`, "origSkin", slave).showTextBox().pulldown();
-			for (const skin of App.Medicine.Modification.naturalSkins) {
+			const naturalSkins = slave.race === "catgirl" ? App.Medicine.Modification.catgirlNaturalSkins : App.Medicine.Modification.naturalSkins;
+			for (const skin of naturalSkins) {
 				option.addValue(capFirstChar(skin), skin, () => slave.skin = slave.origSkin);
 			}
 		}
diff --git a/src/facilities/schoolroom/schoolroom.js b/src/facilities/schoolroom/schoolroom.js
index 02f9d3117cb705fc0cfba60e21c9c7f9aef82293..f095f86ecb3e856846841feba6b2af4897745496 100644
--- a/src/facilities/schoolroom/schoolroom.js
+++ b/src/facilities/schoolroom/schoolroom.js
@@ -54,7 +54,7 @@ App.Facilities.Schoolroom.schoolroom = class Schoolroom extends App.Facilities.F
 			"Degradationist": `displaying a rote recitation of a slave's proper acceptance of her subhuman status.`,
 			"Repopulationist": `reviewing a number of sexual positions to accommodate a heavily pregnant girl.`,
 			"Eugenics": `reviewing ways to better your owner's standing intermixed with exaggerated pregnancy horror stories focused on slave pregnancy.`,
-			"Asset Expansionist": `reviewing techniques that allow two slaves with huge breasts to inspect and moisturize each others' hard to reach areas.`,
+			"Asset Expansionist": `reviewing techniques that allow two slaves with huge breasts to inspect and moisturize each other's hard to reach areas.`,
 			"Transformation Fetishist": `offering a brief primer on surgical recovery, with practical techniques to make it quicker.`,
 			"Gender Radicalist": `going over how to keep one's asspussy ready for intercourse at any time, including how to schedule regular enemata and pre-lubrication.`,
 			"Gender Fundamentalist": `going over the trifecta that is the standard approach of sex slaves: a blowjob, followed by vaginal, finished with anal.`,
diff --git a/src/facilities/surgery/analyzePregnancy.js b/src/facilities/surgery/analyzePregnancy.js
index 0279720d92cfb4b99ca91043552ede6cc0322a25..26d217cb4ece8dc01152b916166802db56c90713 100644
--- a/src/facilities/surgery/analyzePregnancy.js
+++ b/src/facilities/surgery/analyzePregnancy.js
@@ -52,7 +52,7 @@ globalThis.analyzePregnancies = function(mother, cheat) {
 				}
 				option = options.addOption(`Skin tone: ${capFirstChar(genes.skin)}`, "skin", genes);
 				if (cheat) {
-					option.showTextBox().pulldown().addValueList(App.Medicine.Modification.naturalSkins);
+					option.showTextBox().pulldown().addValueList(genes.race === "catgirl" ? App.Medicine.Modification.catgirlNaturalSkins : App.Medicine.Modification.naturalSkins);
 				}
 				option = options.addOption(`Intelligence index: ${genes.intelligence} out of 100`, "intelligence", genes);
 				if (cheat) {
diff --git a/src/facilities/surgery/remoteSurgery.js b/src/facilities/surgery/remoteSurgery.js
index a6b6cde22d1fcb3eb289ec9468022a0d7ee39656..eff295b49c18c09f910cd3111e6f1ecb9905cf6d 100644
--- a/src/facilities/surgery/remoteSurgery.js
+++ b/src/facilities/surgery/remoteSurgery.js
@@ -29,20 +29,9 @@ App.UI.SlaveInteract.remoteSurgery = function(slave) {
 		App.UI.DOM.appendNewElement("p", el, `${His} indenture forbids elective surgery`, ["yellow", "note"]);
 	}
 
-	const tabBar = new App.UI.Tabs.TabBar("RemoteSurgery");
-	const f = new DocumentFragment();
-	App.Events.drawEventArt(f, slave);
-	tabBar.customNode = f;
-
-	tabBar.addTab("Hair and Face", "hairAndFace", App.UI.surgeryPassageHairAndFace(slave, refresh));
-	tabBar.addTab("Upper", "upper", App.UI.surgeryPassageUpper(slave, refresh));
-	tabBar.addTab("Lower", "lower", App.UI.surgeryPassageLower(slave, refresh));
-	tabBar.addTab("Structural", "structural", App.UI.surgeryPassageStructural(slave, refresh));
-	tabBar.addTab("Exotic", "exotic", App.UI.surgeryPassageExotic(slave, refresh));
-	if (V.seeExtreme) {
-		tabBar.addTab("Extreme", "extreme", App.UI.surgeryPassageExtreme(slave, refresh));
-	}
-	el.append(tabBar.render());
+	const tabsDiv = document.createElement("div");
+	tabsDiv.append(renderTabs());
+	el.append(tabsDiv);
 
 	return el;
 
@@ -69,8 +58,29 @@ App.UI.SlaveInteract.remoteSurgery = function(slave) {
 		return r;
 	}
 
+	/**
+	 * @returns {DocumentFragment}
+	 */
+	function renderTabs() {
+		const tabBar = new App.UI.Tabs.TabBar("RemoteSurgery");
+		const f = new DocumentFragment();
+		App.Events.drawEventArt(f, slave);
+		tabBar.customNode = f;
+
+		tabBar.addTab("Hair and Face", "hairAndFace", App.UI.surgeryPassageHairAndFace(slave, refresh));
+		tabBar.addTab("Upper", "upper", App.UI.surgeryPassageUpper(slave, refresh));
+		tabBar.addTab("Lower", "lower", App.UI.surgeryPassageLower(slave, refresh));
+		tabBar.addTab("Structural", "structural", App.UI.surgeryPassageStructural(slave, refresh));
+		tabBar.addTab("Exotic", "exotic", App.UI.surgeryPassageExotic(slave, refresh));
+		if (V.seeExtreme) {
+			tabBar.addTab("Extreme", "extreme", App.UI.surgeryPassageExtreme(slave, refresh));
+		}
+		return tabBar.render();
+	}
+
 	function refresh() {
 		$(introP).empty();
 		App.Events.addNode(introP, intro());
+		App.UI.DOM.replace(tabsDiv, renderTabs());
 	}
 };
diff --git a/src/facilities/surgery/surgeryPassageExotic.js b/src/facilities/surgery/surgeryPassageExotic.js
index 2d4f38eb683fbbbf7f46992cf66d3884366401f5..a2f853ad513d1ee847d05fc7f5f68477edcc51f7 100644
--- a/src/facilities/surgery/surgeryPassageExotic.js
+++ b/src/facilities/surgery/surgeryPassageExotic.js
@@ -1,12 +1,12 @@
 /**
  * UI for performing surgery. Refreshes without refreshing the passage.
  * @param {App.Entity.SlaveState} slave
- * @param {()=>void} refreshParent
+ * @param {()=>void} refresh
  * @param {boolean} [cheat=false]
  * @returns {HTMLElement}
  */
 
-App.UI.surgeryPassageExotic = function(slave, refreshParent, cheat = false) {
+App.UI.surgeryPassageExotic = function(slave, refresh, cheat = false) {
 	const container = document.createElement("span");
 	container.append(content());
 	return container;
@@ -253,11 +253,5 @@ App.UI.surgeryPassageExotic = function(slave, refreshParent, cheat = false) {
 			App.UI.DOM.appendNewElement("div", el, App.UI.DOM.generateLinksStrip(linkArray), "choices");
 			return el;
 		}
-
-		function refresh() {
-			jQuery(container).empty().append(content());
-			App.Events.refreshEventArt(slave);
-			refreshParent();
-		}
 	}
 };
diff --git a/src/facilities/surgery/surgeryPassageExtreme.js b/src/facilities/surgery/surgeryPassageExtreme.js
index 899c19869bf118d3c0f9ded6b0c4e74da6c53a79..fbee2aeb4083f48b16ebe8a500d5ef91f1d117e9 100644
--- a/src/facilities/surgery/surgeryPassageExtreme.js
+++ b/src/facilities/surgery/surgeryPassageExtreme.js
@@ -1,11 +1,11 @@
 /**
  * UI for performing surgery. Refreshes without refreshing the passage.
  * @param {App.Entity.SlaveState} slave
- * @param {()=>void} refreshParent
+ * @param {()=>void} refresh
  * @param {boolean} [cheat=false]
  * @returns {HTMLElement}
  */
-App.UI.surgeryPassageExtreme = function(slave, refreshParent, cheat = false) {
+App.UI.surgeryPassageExtreme = function(slave, refresh, cheat = false) {
 	const container = document.createElement("span");
 	container.append(content());
 	return container;
@@ -66,10 +66,4 @@ App.UI.surgeryPassageExtreme = function(slave, refreshParent, cheat = false) {
 			return el;
 		}
 	}
-
-	function refresh() {
-		jQuery(container).empty().append(content());
-		App.Events.refreshEventArt(slave);
-		refreshParent();
-	}
 };
diff --git a/src/facilities/surgery/surgeryPassageFaceAndHair.js b/src/facilities/surgery/surgeryPassageFaceAndHair.js
index ffe425dfae92be8ac31495c5557e25616daad15c..b5cd4822cb6808f556e277446c6b2fee1187f64e 100644
--- a/src/facilities/surgery/surgeryPassageFaceAndHair.js
+++ b/src/facilities/surgery/surgeryPassageFaceAndHair.js
@@ -1,12 +1,12 @@
 /**
  * UI for performing surgery. Refreshes without refreshing the passage.
  * @param {App.Entity.SlaveState} slave
- * @param {function():void} refreshParent
+ * @param {function():void} refresh
  * @param {boolean} [cheat=false]
  * @returns {HTMLElement}
  */
 
-App.UI.surgeryPassageHairAndFace = function(slave, refreshParent, cheat = false) {
+App.UI.surgeryPassageHairAndFace = function(slave, refresh, cheat = false) {
 	const container = document.createElement("span");
 	container.append(content());
 	return container;
@@ -846,10 +846,4 @@ App.UI.surgeryPassageHairAndFace = function(slave, refreshParent, cheat = false)
 			return el;
 		}
 	}
-
-	function refresh() {
-		jQuery(container).empty().append(content());
-		App.Events.refreshEventArt(slave);
-		refreshParent();
-	}
 };
diff --git a/src/facilities/surgery/surgeryPassageLower.js b/src/facilities/surgery/surgeryPassageLower.js
index 84266906226a03e80093651930429f702a7c710e..7250ecafaa210c6fc98b8588c94d753ecce2ad48 100644
--- a/src/facilities/surgery/surgeryPassageLower.js
+++ b/src/facilities/surgery/surgeryPassageLower.js
@@ -1,11 +1,11 @@
 /**
  * UI for performing surgery. Refreshes without refreshing the passage.
  * @param {App.Entity.SlaveState} slave
- * @param {()=>void} refreshParent
+ * @param {()=>void} refresh
  * @param {boolean} [cheat=false]
  * @returns {HTMLElement}
  */
-App.UI.surgeryPassageLower = function(slave, refreshParent, cheat = false) {
+App.UI.surgeryPassageLower = function(slave, refresh, cheat = false) {
 	const container = document.createElement("span");
 	container.append(content());
 	return container;
@@ -664,10 +664,4 @@ App.UI.surgeryPassageLower = function(slave, refreshParent, cheat = false) {
 			return el;
 		}
 	}
-
-	function refresh() {
-		jQuery(container).empty().append(content());
-		App.Events.refreshEventArt(slave);
-		refreshParent();
-	}
 };
diff --git a/src/facilities/surgery/surgeryPassageStructural.js b/src/facilities/surgery/surgeryPassageStructural.js
index 9abd966edd24f1441a85d66d0b0f660077a5f52b..18e97fdfbe237501650fc9af8b93623112ae049c 100644
--- a/src/facilities/surgery/surgeryPassageStructural.js
+++ b/src/facilities/surgery/surgeryPassageStructural.js
@@ -1,12 +1,12 @@
 /**
  * UI for performing surgery. Refreshes without refreshing the passage.
  * @param {App.Entity.SlaveState} slave
- * @param {function():void} refreshParent
+ * @param {function():void} refresh
  * @param {boolean} [cheat=false]
  * @returns {HTMLElement}
  */
 
-App.UI.surgeryPassageStructural = function(slave, refreshParent, cheat = false) {
+App.UI.surgeryPassageStructural = function(slave, refresh, cheat = false) {
 	const container = document.createElement("span");
 	container.append(content());
 	return container;
@@ -422,10 +422,4 @@ App.UI.surgeryPassageStructural = function(slave, refreshParent, cheat = false)
 			return el;
 		}
 	}
-
-	function refresh() {
-		jQuery(container).empty().append(content());
-		App.Events.refreshEventArt(slave);
-		refreshParent();
-	}
 };
diff --git a/src/facilities/surgery/surgeryPassageUpper.js b/src/facilities/surgery/surgeryPassageUpper.js
index 6f8bbec972dc53a5e821306d247661d8c6177033..811c0249da2e76a40fab53a3ada312314ba23a07 100644
--- a/src/facilities/surgery/surgeryPassageUpper.js
+++ b/src/facilities/surgery/surgeryPassageUpper.js
@@ -1,11 +1,11 @@
 /**
  * UI for performing surgery. Refreshes without refreshing the passage.
  * @param {App.Entity.SlaveState} slave
- * @param {()=>void} refreshParent
+ * @param {()=>void} refresh
  * @param {boolean} [cheat=false]
  * @returns {HTMLElement}
  */
-App.UI.surgeryPassageUpper = function(slave, refreshParent, cheat = false) {
+App.UI.surgeryPassageUpper = function(slave, refresh, cheat = false) {
 	const container = document.createElement("span");
 	container.append(content());
 	return container;
@@ -564,10 +564,4 @@ App.UI.surgeryPassageUpper = function(slave, refreshParent, cheat = false) {
 			return el;
 		}
 	}
-
-	function refresh() {
-		jQuery(container).empty().append(content());
-		App.Events.refreshEventArt(slave);
-		refreshParent();
-	}
 };
diff --git a/src/gui/Encyclopedia/encyclopedia.tw b/src/gui/Encyclopedia/encyclopedia.tw
index c13e2573c188e5f27cbb7370ffb59104a78c169f..56a5fa9531e460df47242071651f98e2415db5e7 100644
--- a/src/gui/Encyclopedia/encyclopedia.tw
+++ b/src/gui/Encyclopedia/encyclopedia.tw
@@ -622,7 +622,7 @@ SLAVE RELATIONSHIPS
 
 
 <<case "Romances">>
-	''Romances'' tend to arise naturally between slaves on the same assignment, and between slaves having a lot of sex with each other. Slaves will be saddened by their romantic partners' misfortunes, but will do better on public sexual assignments if assigned to work at the same job as a romantic partner. Slaves will also derive various mental effects from being in a relationship: they can rely on each other, take solace in each others' company, and even learn fetishes from each other. Romances can be suppressed by the rules, or fall apart naturally. On the other hand, romances can develop from friendships, to best friendships, to friendships with benefits, to loving relationships. Romances impede the formation of <<= App.Encyclopedia.Dialog.linkSC("rivalries", "Rivalries")>>, and can defuse them. Once a romance has advanced to where the slaves are lovers, its members are eligible for several events in which the couple can be <<= App.Encyclopedia.Dialog.linkSC("married", "Slave Marriages")>>.
+	''Romances'' tend to arise naturally between slaves on the same assignment, and between slaves having a lot of sex with each other. Slaves will be saddened by their romantic partners' misfortunes, but will do better on public sexual assignments if assigned to work at the same job as a romantic partner. Slaves will also derive various mental effects from being in a relationship: they can rely on each other, take solace in each other's company, and even learn fetishes from each other. Romances can be suppressed by the rules, or fall apart naturally. On the other hand, romances can develop from friendships, to best friendships, to friendships with benefits, to loving relationships. Romances impede the formation of <<= App.Encyclopedia.Dialog.linkSC("rivalries", "Rivalries")>>, and can defuse them. Once a romance has advanced to where the slaves are lovers, its members are eligible for several events in which the couple can be <<= App.Encyclopedia.Dialog.linkSC("married", "Slave Marriages")>>.
 
 
 <<case "Emotionally Bonded">>
diff --git a/src/gui/Encyclopedia/encyclopediaRAActivationEditor.js b/src/gui/Encyclopedia/encyclopediaRAActivationEditor.js
index 78e8cad4665e1c0bef587fb92d2cd60f2d9d01b3..5fa62bc7cac8f4e6b6381f63d5ccd45a69fa8c02 100644
--- a/src/gui/Encyclopedia/encyclopediaRAActivationEditor.js
+++ b/src/gui/Encyclopedia/encyclopediaRAActivationEditor.js
@@ -131,7 +131,7 @@ App.Encyclopedia.addArticle("RA Condition Editor", function() {
 		"the condition evaluation will fail!");
 	acc.toParagraph();
 	acc.push("For example to get the slave name you can use");
-	acc.push(App.UI.DOM.makeElement("span", "context => slave.slaveName", ["monospace"]));
+	acc.push(App.UI.DOM.makeElement("span", "context => context.slave.slaveName", ["monospace"]));
 	acc.push("and set the getter type to string.");
 	acc.toParagraph();
 	acc.push("Documentation for slave attributes can be found " +
diff --git a/src/gui/multipleInspect.js b/src/gui/multipleInspect.js
index faef31b80cf933d0e77ab64ecc825e74e3c59d25..c5843146501a222b81950d2a8d42c5fe44bceb39 100644
--- a/src/gui/multipleInspect.js
+++ b/src/gui/multipleInspect.js
@@ -2,14 +2,16 @@
  * Provide a mechanism to inspect multiple slaves at once (for example, for Household Liquidators and recETS).
  * @param {Array<App.Entity.SlaveState>} slaves
  * @param {boolean} showFamilyTree
- * @param {FC.SlaveMarketName} [market]
+ * @param {FC.SlaveMarketName | FC.SpecialMarketName} [market]
+ * @param {Map<number, string>} [marketText] map of Slave ID to text
  * @returns {DocumentFragment}
  */
-App.UI.MultipleInspect = function(slaves, showFamilyTree, market) {
+App.UI.MultipleInspect = function(slaves, showFamilyTree, market, marketText) {
 	const tabBar = new App.UI.Tabs.TabBar("MultipleInspect");
 
 	for (const slave of slaves) {
-		tabBar.addTab(slave.slaveName, `slave${slave.ID}`, App.Desc.longSlave(slave, {market: market}));
+		const text = marketText ? marketText.get(slave.ID) : null;
+		tabBar.addTab(slave.slaveName, `slave${slave.ID}`, App.Desc.longSlave(slave, {market: market, marketText: text}));
 	}
 
 	if (slaves.length > 1 && showFamilyTree) {
diff --git a/src/gui/options/options.js b/src/gui/options/options.js
index 38f0c8a7d5a50de9e5d74ac9342735b9b0ae3b34..95170f9e8cc052facbf22bf520fe12ca6d9712a4 100644
--- a/src/gui/options/options.js
+++ b/src/gui/options/options.js
@@ -1167,7 +1167,7 @@ App.UI.artOptions = function() {
 				options.addOption("Clothing erection bulges are", "showClothingErection")
 					.addValue("Enabled", true).on().addValue("Disabled", false).off();
 			} else if (V.imageChoice === 4) {
-				options.addComment(`This art is currently (12/12/21) the most actively developed. Real time 3D models. <a href='https://mega.nz/folder/m1JQ3JAQ#2GJsM7csBBvBu0DX8SB2kA' target='_blank'> Download the WebGL art assets</a> and place the 'webgl' folder into the resources/ folder where this HTML file is.
+				options.addComment(`This art is currently (05/19/22) the most actively developed. Real time 3D models. <a href='https://mega.nz/folder/DtoyiSwC#5FLXDYr1Uy90PZjxmfrKXA' target='_blank'> Download the WebGL art assets</a> and place the 'webgl' folder into the resources/ folder where this HTML file is.
 				Then <b>refresh</b> the page.
 				Create the resources folder if it does not exist. <span class="warning">(Android/MacOS not supported)</span>`);
 
@@ -1203,6 +1203,8 @@ App.UI.artOptions = function() {
 					.addComment("Recommended to be between 0.5 and 1.5.");
 				options.addOption("Ambient Occlusion", "setSSAO")
 					.addValue("Enabled", true).on().addValue("Disabled", false).off();
+				options.addOption("SubSurface Scattering", "setSSS")
+					.addValue("Enabled", true).on().addValue("Disabled", false).off();
 				options.addOption("Shadowmapping", "setShadowMapping")
 					.addValue("Enabled", true).on().addValue("Disabled", false).off();
 				options.addOption("Tonemapping", "setTonemapping")
diff --git a/src/gui/options/optionsGroup.js b/src/gui/options/optionsGroup.js
index b7403c7b291452cc87043757b2b1925de9405f4b..d51a9902e19397a6605f46e1d3a14c040bc32ce9 100644
--- a/src/gui/options/optionsGroup.js
+++ b/src/gui/options/optionsGroup.js
@@ -561,3 +561,25 @@ App.UI.OptionsGroup = (function() {
 		}
 	};
 })();
+
+/** A wrapper for option handlers that shows a dialog with the results of setting the option.
+ * @template T
+ * @param {function(T): string|HTMLElement|DocumentFragment} contentGenerator
+ * @param {string} [caption]
+ * @returns {function(T): void}
+ */
+App.UI.DialogHandler = function(contentGenerator, caption) {
+	return (arg) => {
+		const dialogContent = contentGenerator(arg);
+		if (dialogContent) {
+			if (Dialog.isOpen()) {
+				Dialog.close();
+			}
+			if (caption) {
+				Dialog.setup(caption);
+			}
+			$(Dialog.body()).empty().append(dialogContent);
+			Dialog.open();
+		}
+	};
+};
diff --git a/src/gui/sideBar.js b/src/gui/sideBar.js
index d9c086210a02d3fccc704407847fd3446fab3ab9..41914c8fb846785776dd6b67e9d2bb52d758eb3a 100644
--- a/src/gui/sideBar.js
+++ b/src/gui/sideBar.js
@@ -38,9 +38,6 @@ App.Utils.userButton = function(nextButton = V.nextButton, nextLink = V.nextLink
 			el.append(link);
 			el.append(" ");
 			App.UI.DOM.appendNewElement("span", el, App.UI.Hotkeys.hotkeys("endWeek"), "hotkey");
-			if (V.rulesAssistantAuto === 1 && DefaultRulesError()) {
-				App.UI.DOM.appendNewElement("div", el, `WARNING: Rules Assistant has rules with errors!`, "yellow");
-			}
 		} else {
 			if (nextButton !== " ") {
 				link = App.UI.DOM.passageLink(
diff --git a/src/gui/storyCaption.js b/src/gui/storyCaption.js
index 9498eb90d9f8cb94e07faee0fbcbf06938d3a201..2a71195f1cdea4453ad9fd47642c623f0e20643e 100644
--- a/src/gui/storyCaption.js
+++ b/src/gui/storyCaption.js
@@ -19,7 +19,7 @@ App.UI.storyCaption = function() {
 			fragment.append(cash());
 		}
 
-		if (V.mods.food.market) {
+		if (V.mods.food.enabled && V.mods.food.market) {
 			fragment.append(food());
 		}
 
diff --git a/src/interaction/sellSlave.js b/src/interaction/sellSlave.js
index 921528494d2b596e509a29edbfa6b3a9c0dc6734..06ed5b232bfc3793e8e9b665ff20b9d3bb9f027a 100644
--- a/src/interaction/sellSlave.js
+++ b/src/interaction/sellSlave.js
@@ -315,44 +315,42 @@ App.Interact.sellSlave = function(slave) {
 				t.push(`${He} appears to be neotonic, which will turn off some but attract others. Overall should prove positive though.`);
 			}
 
-			if (slave.career !== 0) {
-				if (App.Data.Careers.Leader.bodyguard.includes(slave.career)) {
-					t.push(`${His} background would help make ${him} a good Bodyguard; that's valuable.`);
-				} else if (App.Data.Careers.Leader.wardeness.includes(slave.career)) {
-					t.push(`${His} background would help make ${him} a good Wardeness; that's valuable.`);
-				} else if (App.Data.Careers.Leader.attendant.includes(slave.career)) {
-					t.push(`${His} background would help make ${him} a good Attendant; that's valuable.`);
-				} else if (App.Data.Careers.Leader.nurse.includes(slave.career)) {
-					t.push(`${His} background would help make ${him} a good Nurse; that's valuable.`);
-				} else if (App.Data.Careers.Leader.matron.includes(slave.career)) {
-					t.push(`${His} background would help make ${him} a good Matron; that's valuable.`);
-				} else if (App.Data.Careers.Leader.schoolteacher.includes(slave.career)) {
-					t.push(`${His} background would help make ${him} a good Schoolteacher; that's valuable.`);
-				} else if (App.Data.Careers.Leader.stewardess.includes(slave.career)) {
-					t.push(`${His} background would help make ${him} a good Stewardess; that's valuable.`);
-				} else if (App.Data.Careers.Leader.milkmaid.includes(slave.career)) {
-					t.push(`${His} background would help make ${him} a good Milkmaid; that's valuable.`);
-				} else if (App.Data.Careers.Leader.farmer.includes(slave.career)) {
-					t.push(`${His} background would help make ${him} a good Farmer; that's valuable.`);
-				} else if (App.Data.Careers.Leader.madam.includes(slave.career)) {
-					t.push(`${His} background would help make ${him} a good Madam; that's valuable.`);
-				} else if (App.Data.Careers.Leader.DJ.includes(slave.career)) {
-					t.push(`${His} background would help make ${him} a good DJ; that's valuable.`);
-				} else if (App.Data.Careers.Leader.HG.includes(slave.career)) {
-					t.push(`${His} background would help make ${him} a good Head Girl; that's valuable.`);
-				} else if (App.Data.Careers.Leader.recruiter.includes(slave.career)) {
-					t.push(`${His} background would help make ${him} a good Recruiter; that's valuable.`);
-				} else if (App.Data.Careers.General.entertainment.includes(slave.career)) {
-					t.push(`${His} background should help ${his} flirting a little.`);
-				} else if (App.Data.Careers.General.whore.includes(slave.career)) {
-					t.push(`${His} background should help ${his} fucking a little.`);
-				} else if (App.Data.Careers.General.grateful.includes(slave.career)) {
-					t.push(`${His} background should make ${him} a bit more trusting.`);
-				} else if (App.Data.Careers.General.menial.includes(slave.career)) {
-					t.push(`${His} background should make ${him} a bit more tractable.`);
-				} else if (App.Data.Careers.General.servant.includes(slave.career)) {
-					t.push(`${His} background should make ${him} a good servant.`);
-				}
+			if (App.Data.Careers.Leader.bodyguard.includes(slave.career)) {
+				t.push(`${His} background would help make ${him} a good Bodyguard; that's valuable.`);
+			} else if (App.Data.Careers.Leader.wardeness.includes(slave.career)) {
+				t.push(`${His} background would help make ${him} a good Wardeness; that's valuable.`);
+			} else if (App.Data.Careers.Leader.attendant.includes(slave.career)) {
+				t.push(`${His} background would help make ${him} a good Attendant; that's valuable.`);
+			} else if (App.Data.Careers.Leader.nurse.includes(slave.career)) {
+				t.push(`${His} background would help make ${him} a good Nurse; that's valuable.`);
+			} else if (App.Data.Careers.Leader.matron.includes(slave.career)) {
+				t.push(`${His} background would help make ${him} a good Matron; that's valuable.`);
+			} else if (App.Data.Careers.Leader.schoolteacher.includes(slave.career)) {
+				t.push(`${His} background would help make ${him} a good Schoolteacher; that's valuable.`);
+			} else if (App.Data.Careers.Leader.stewardess.includes(slave.career)) {
+				t.push(`${His} background would help make ${him} a good Stewardess; that's valuable.`);
+			} else if (App.Data.Careers.Leader.milkmaid.includes(slave.career)) {
+				t.push(`${His} background would help make ${him} a good Milkmaid; that's valuable.`);
+			} else if (App.Data.Careers.Leader.farmer.includes(slave.career)) {
+				t.push(`${His} background would help make ${him} a good Farmer; that's valuable.`);
+			} else if (App.Data.Careers.Leader.madam.includes(slave.career)) {
+				t.push(`${His} background would help make ${him} a good Madam; that's valuable.`);
+			} else if (App.Data.Careers.Leader.DJ.includes(slave.career)) {
+				t.push(`${His} background would help make ${him} a good DJ; that's valuable.`);
+			} else if (App.Data.Careers.Leader.HG.includes(slave.career)) {
+				t.push(`${His} background would help make ${him} a good Head Girl; that's valuable.`);
+			} else if (App.Data.Careers.Leader.recruiter.includes(slave.career)) {
+				t.push(`${His} background would help make ${him} a good Recruiter; that's valuable.`);
+			} else if (App.Data.Careers.General.entertainment.includes(slave.career)) {
+				t.push(`${His} background should help ${his} flirting a little.`);
+			} else if (App.Data.Careers.General.whore.includes(slave.career)) {
+				t.push(`${His} background should help ${his} fucking a little.`);
+			} else if (App.Data.Careers.General.grateful.includes(slave.career)) {
+				t.push(`${His} background should make ${him} a bit more trusting.`);
+			} else if (App.Data.Careers.General.menial.includes(slave.career)) {
+				t.push(`${His} background should make ${him} a bit more tractable.`);
+			} else if (App.Data.Careers.General.servant.includes(slave.career)) {
+				t.push(`${His} background should make ${him} a good servant.`);
 			}
 			if ((V.week - slave.weekAcquired >= 20) && (slave.skill.entertainment >= 100)) {
 				if (!App.Data.Careers.General.entertainment.includes(slave.career)) {
diff --git a/src/interaction/siRules.js b/src/interaction/siRules.js
index 6034405bf2dcc7d0a8609d2ff2cf7f15de64c494..2b05a1c0f80b98d186b21c889b4c2db1f065c9dd 100644
--- a/src/interaction/siRules.js
+++ b/src/interaction/siRules.js
@@ -413,6 +413,7 @@ App.UI.SlaveInteract.rules = function(slave, refresh) {
 			// Level
 			level.set(`No sex`, `none`);
 			level.set(`All sex`, `all`);
+			level.set(`Disabled`, `off`);
 
 			// Body part
 			bodyPart.set(`Vanilla`, `vanilla`);
diff --git a/src/interaction/siWardrobe.js b/src/interaction/siWardrobe.js
index 807d366336d54408bb3f000c79aa0cd8e077738b..49c5533360ee4824d881d2a95a77fab856204beb 100644
--- a/src/interaction/siWardrobe.js
+++ b/src/interaction/siWardrobe.js
@@ -138,7 +138,7 @@ App.UI.SlaveInteract.wardrobe = function(slave, contentRefresh) {
 		const label = document.createElement('div');
 		label.append(`Slave selects ${his} own outfits: `);
 
-		App.UI.DOM.appendNewElement("span", label, slave.choosesOwnClothes ? "Allowed" : "Forbidden", "bold");
+		App.UI.DOM.appendNewElement("span", label, slave.choosesOwnClothes ? "Allowed" : "Forbidden", ["bold"]);
 
 		el.appendChild(label);
 
@@ -175,7 +175,7 @@ App.UI.SlaveInteract.wardrobe = function(slave, contentRefresh) {
 		if (slave.fuckdoll === 0) {
 			// First Row
 			label = document.createElement('div');
-			label.append(`Clothes: `, App.UI.DOM.spanWithTooltip(slave.clothes, itemTooltip(slave.clothes, "clothes"), "bold"));
+			label.append(`Clothes: `, App.UI.DOM.spanWithTooltip(slave.clothes, itemTooltip(slave.clothes, "clothes"), ["bold"]));
 
 			if (slave.fuckdoll !== 0 || slave.clothes === "restrictive latex" || slave.clothes === "a latex catsuit" || slave.clothes === "a cybersuit" || slave.clothes === "a comfortable bodysuit") {
 				if (V.seeImages === 1 && V.imageChoice === 1) {
@@ -190,7 +190,7 @@ App.UI.SlaveInteract.wardrobe = function(slave, contentRefresh) {
 		}
 
 		label = document.createElement('div');
-		label.append(`Collar: `, App.UI.DOM.spanWithTooltip(slave.collar, itemTooltip(slave.collar, "collar"), "bold"));
+		label.append(`Collar: `, App.UI.DOM.spanWithTooltip(slave.collar, itemTooltip(slave.collar, "collar"), ["bold"]));
 		// Choose her own
 		if (slave.collar !== `none`) {
 			label.append(noneLink("collar"));
@@ -269,7 +269,7 @@ App.UI.SlaveInteract.wardrobe = function(slave, contentRefresh) {
 		const el = document.createElement('div');
 
 		const label = document.createElement('div');
-		label.append(`Mask: `, App.UI.DOM.spanWithTooltip(slave.faceAccessory, itemTooltip(slave.faceAccessory, "faceAccessory"), "bold"));
+		label.append(`Mask: `, App.UI.DOM.spanWithTooltip(slave.faceAccessory, itemTooltip(slave.faceAccessory, "faceAccessory"), ["bold"]));
 
 		// Choose her own
 		if (slave.faceAccessory !== `none`) {
@@ -312,7 +312,7 @@ App.UI.SlaveInteract.wardrobe = function(slave, contentRefresh) {
 		const el = document.createElement('div');
 
 		const label = document.createElement('div');
-		label.append(`Gag: `, App.UI.DOM.spanWithTooltip(slave.mouthAccessory, itemTooltip(slave.mouthAccessory, "mouthAccessory"), "bold"));
+		label.append(`Gag: `, App.UI.DOM.spanWithTooltip(slave.mouthAccessory, itemTooltip(slave.mouthAccessory, "mouthAccessory"), ["bold"]));
 
 		// Choose her own
 		if (slave.mouthAccessory !== `none`) {
@@ -343,7 +343,7 @@ App.UI.SlaveInteract.wardrobe = function(slave, contentRefresh) {
 		// App.Desc.armwear(slave)
 
 		const label = document.createElement('div');
-		label.append(`Arm accessory: `, App.UI.DOM.spanWithTooltip(slave.armAccessory, itemTooltip(slave.armAccessory, "armAccessory"), "bold"));
+		label.append(`Arm accessory: `, App.UI.DOM.spanWithTooltip(slave.armAccessory, itemTooltip(slave.armAccessory, "armAccessory"), ["bold"]));
 
 		// Choose her own
 		if (slave.armAccessory !== "none") {
@@ -364,7 +364,7 @@ App.UI.SlaveInteract.wardrobe = function(slave, contentRefresh) {
 		const el = document.createElement('div');
 		const label = App.UI.DOM.appendNewElement("div", el, `Shoes: `);
 
-		label.append(App.UI.DOM.spanWithTooltip(slave.shoes, itemTooltip(slave.shoes, "shoes"), "bold"));
+		label.append(App.UI.DOM.spanWithTooltip(slave.shoes, itemTooltip(slave.shoes, "shoes"), ["bold"]));
 
 		/* We have "barefoot" in App.Data.slaveWear.slaveWear to cover for this
 			// Choose her own
@@ -403,7 +403,7 @@ App.UI.SlaveInteract.wardrobe = function(slave, contentRefresh) {
 		const el = document.createElement('div');
 		const label = App.UI.DOM.appendNewElement("div", el, `Leg accessory: `);
 
-		label.append(App.UI.DOM.spanWithTooltip(slave.legAccessory, itemTooltip(slave.legAccessory, "legAccessory"), "bold"));
+		label.append(App.UI.DOM.spanWithTooltip(slave.legAccessory, itemTooltip(slave.legAccessory, "legAccessory"), ["bold"]));
 
 		// Choose her own
 		if (slave.legAccessory !== "none") {
@@ -416,7 +416,9 @@ App.UI.SlaveInteract.wardrobe = function(slave, contentRefresh) {
 	}
 
 	function bellyAccessory() {
+		/** @type {Array<FC.BellyAccessory>} */
 		let array = [];
+		/** @type {Array<FC.BellyAccessory>} */
 		let empathyArray = [];
 
 		for (const [key, object] of App.Data.bellyAccessory) {
@@ -438,7 +440,7 @@ App.UI.SlaveInteract.wardrobe = function(slave, contentRefresh) {
 
 		let label = App.UI.DOM.appendNewElement("div", el, `Belly accessory: `);
 
-		label.append(App.UI.DOM.spanWithTooltip(slave.bellyAccessory, itemTooltip(slave.bellyAccessory, "bellyAccessory"), "bold"));
+		label.append(App.UI.DOM.spanWithTooltip(slave.bellyAccessory, itemTooltip(slave.bellyAccessory, "bellyAccessory"), ["bold"]));
 
 		// Choose her own
 		if (slave.bellyAccessory !== `none`) {
@@ -470,7 +472,7 @@ App.UI.SlaveInteract.wardrobe = function(slave, contentRefresh) {
 		const el = document.createElement('div');
 
 		const label = document.createElement('div');
-		label.append(`Anal accessory: `, App.UI.DOM.spanWithTooltip(slave.buttplug, itemTooltip(slave.buttplug, "buttplug"), "bold"));
+		label.append(`Anal accessory: `, App.UI.DOM.spanWithTooltip(slave.buttplug, itemTooltip(slave.buttplug, "buttplug"), ["bold"]));
 
 		if (slave.buttplug !== `none`) {
 			label.append(noneLink("buttplug"));
@@ -522,7 +524,7 @@ App.UI.SlaveInteract.wardrobe = function(slave, contentRefresh) {
 		}
 
 		const label = document.createElement('div');
-		label.append(`Anal accessory attachment: `, App.UI.DOM.spanWithTooltip(slave.buttplugAttachment, itemTooltip(slave.buttplugAttachment, "buttplugAttachment"), "bold"));
+		label.append(`Anal accessory attachment: `, App.UI.DOM.spanWithTooltip(slave.buttplugAttachment, itemTooltip(slave.buttplugAttachment, "buttplugAttachment"), ["bold"]));
 
 		if (slave.buttplugAttachment !== `none`) {
 			label.append(noneLink("buttplugAttachment"));
@@ -559,7 +561,7 @@ App.UI.SlaveInteract.wardrobe = function(slave, contentRefresh) {
 		const el = document.createElement('div');
 
 		const label = document.createElement('div');
-		label.append(`Vaginal accessory: `, App.UI.DOM.spanWithTooltip(slave.vaginalAccessory, itemTooltip(slave.vaginalAccessory, "vaginalAccessory"), "bold"));
+		label.append(`Vaginal accessory: `, App.UI.DOM.spanWithTooltip(slave.vaginalAccessory, itemTooltip(slave.vaginalAccessory, "vaginalAccessory"), ["bold"]));
 
 		if (slave.vaginalAccessory !== `none`) {
 			label.append(noneLink("vaginalAccessory"));
@@ -620,7 +622,7 @@ App.UI.SlaveInteract.wardrobe = function(slave, contentRefresh) {
 
 		let label = App.UI.DOM.appendNewElement("div", el, `Vaginal accessory attachment: `);
 
-		label.append(App.UI.DOM.spanWithTooltip(slave.vaginalAttachment, itemTooltip(slave.vaginalAttachment, "vaginalAttachment"), "bold"));
+		label.append(App.UI.DOM.spanWithTooltip(slave.vaginalAttachment, itemTooltip(slave.vaginalAttachment, "vaginalAttachment"), ["bold"]));
 
 		if (slave.vaginalAttachment !== `none`) {
 			label.append(noneLink("vaginalAttachment"));
@@ -656,7 +658,7 @@ App.UI.SlaveInteract.wardrobe = function(slave, contentRefresh) {
 		const el = document.createElement('div');
 
 		const label = document.createElement('div');
-		label.append(`Dick accessory: `, App.UI.DOM.spanWithTooltip(slave.dickAccessory, itemTooltip(slave.dickAccessory, "dickAccessory"), "bold"));
+		label.append(`Dick accessory: `, App.UI.DOM.spanWithTooltip(slave.dickAccessory, itemTooltip(slave.dickAccessory, "dickAccessory"), ["bold"]));
 
 		if (slave.dickAccessory !== `none`) {
 			label.append(noneLink("dickAccessory"));
@@ -719,7 +721,7 @@ App.UI.SlaveInteract.wardrobe = function(slave, contentRefresh) {
 			chasCho = `THERE HAS BEEN AN ERROR `;
 		}
 
-		label.append(App.UI.DOM.spanWithTooltip(chasCho, itemTooltip(chasCho, "chastity"), "bold"));
+		label.append(App.UI.DOM.spanWithTooltip(chasCho, itemTooltip(chasCho, "chastity"), ["bold"]));
 
 		if (slave.chastityAnus !== 0 || slave.chastityPenis !== 0 || slave.chastityVagina !== 0) {
 			label.append(
@@ -1002,7 +1004,7 @@ App.UI.SlaveInteract.wardrobe = function(slave, contentRefresh) {
 					);
 
 					if (item.fs && item.fs.unlocks) {
-						link.append(App.UI.DOM.spanWithTooltip(`FS`, FutureSocieties.displayAdj(item.fs.unlocks), "note"));
+						link.append(App.UI.DOM.spanWithTooltip(`FS`, FutureSocieties.displayAdj(item.fs.unlocks), ["note"]));
 					}
 				}
 				linkArray.push(link);
diff --git a/src/js/DefaultRules.js b/src/js/DefaultRules.js
index 1deae90598aadc74b492d5fe298134b26974f8c2..5a6583e19a1b76a369589b2597495138eea550f9 100644
--- a/src/js/DefaultRules.js
+++ b/src/js/DefaultRules.js
@@ -1137,7 +1137,7 @@ globalThis.DefaultRules = (function() {
 						}
 					}
 
-					if (lastPregRule(slave, V.defaultRules)) {
+					if (rulesDemandContraceptives(slave, V.defaultRules)) {
 						slave.preg = -1;
 					} else {
 						slave.preg = 0;
@@ -1510,7 +1510,7 @@ globalThis.DefaultRules = (function() {
 					break;
 
 				case "appetite suppressors":
-					if (V.arcologies[0].FSSlimnessEnthusiastResearch !== 1 || slave.weight > -95) {
+					if (V.arcologies[0].FSSlimnessEnthusiastResearch !== 1 || slave.weight <= -95) {
 						flag = false;
 					}
 					break;
@@ -1621,6 +1621,7 @@ globalThis.DefaultRules = (function() {
 					}
 				} else if (["restricted", "fattening"].includes(slave.diet)) {
 					r += `<br>${slave.slaveName} is at the target weight, so ${his} diet has been normalized.`;
+					slave.diet = "healthy";
 					dietPills(slave);
 					muscleRule(slave, rule);
 				} else {
@@ -1641,6 +1642,7 @@ globalThis.DefaultRules = (function() {
 					}
 				} else if (["restricted", "fattening"].includes(slave.diet)) {
 					r += `<br>${slave.slaveName} is at the target weight, so ${his} diet has been normalized.`;
+					slave.diet = "healthy";
 					dietPills(slave);
 					muscleRule(slave, rule);
 				} else {
@@ -1760,7 +1762,7 @@ globalThis.DefaultRules = (function() {
 				r += `<br>${slave.slaveName} no longer needs to lose weight, so ${he}'s no longer being given appetite suppressors.`;
 			} else if (slave.diet === "restricted" && V.arcologies[0].FSSlimnessEnthusiastResearch === 1 && slave.drugs === "no drugs") {
 				slave.drugs = "appetite suppressors";
-				r += `<br>${slave.slaveName} needs to lose weight so ${he} will be given weight loss pills.`;
+				r += `<br>${slave.slaveName} needs to lose weight, so ${he} will be given weight loss pills.`;
 			}
 		}
 	}
@@ -3158,33 +3160,6 @@ globalThis.DefaultRules = (function() {
 	return DefaultRules;
 })();
 
-globalThis.RuleHasError = function() {
-	const rxCheckEqual = /[^!=<>]=[^=<>]/gi;
-	const compileCheck = function(code) {
-		try {
-			// TODO: This should use a cached Function, which should be the same as below.
-			new Function(`return ${code}`);
-		} catch (e) {
-			return false;
-		}
-		return true;
-	};
-
-	/**
-	 * @param {FC.RA.Rule} rule
-	 * @returns {boolean}
-	 */
-	function check(rule) {
-		return rule.condition.function === "custom" &&
-			(rule.condition.data.match(rxCheckEqual) ||
-				!compileCheck(rule.condition.data));
-	}
-
-	return check;
-}();
-
-globalThis.DefaultRulesError = () => V.defaultRules.some(r => RuleHasError(r));
-
 /**
  * @param {App.Entity.SlaveState} slave
  */
diff --git a/src/js/SlaveState.js b/src/js/SlaveState.js
index 2591ca65494a7c5ef3015363cdbaf826320d54f5..75095eb87f0af5da6859cd4a825ee9f882ea2805 100644
--- a/src/js/SlaveState.js
+++ b/src/js/SlaveState.js
@@ -613,8 +613,8 @@ App.Entity.SlaveState = class SlaveState {
 		 * @type {string} */
 		this.origin = "";
 		/** career prior to enslavement
-		 * @type {FC.Zeroable<string>} */
-		this.career = 0;
+		 * @type {string} */
+		this.career = "a slave";
 		/** slave's ID */
 		this.ID = 0;
 		/** slave's prestige */
@@ -1445,7 +1445,7 @@ App.Entity.SlaveState = class SlaveState {
 		 */
 		this.broodmotherFetuses = 0;
 		/**
-		 * If broodmother implant set to pause it 's work.
+		 * If broodmother implant set to pause its work.
 		 *
 		 * 1: implant on pause !1: working.
 		 *
diff --git a/src/js/SpacedTextAccumulator.js b/src/js/SpacedTextAccumulator.js
index c86bd406dea1d3fd3f4f8684722ad18d7a58bb37..d24ab68d2b5e628e0e689496f02d44be5259c0b5 100644
--- a/src/js/SpacedTextAccumulator.js
+++ b/src/js/SpacedTextAccumulator.js
@@ -1,8 +1,11 @@
-/** @template {HTMLElement|DocumentFragment} [ContainerT=DocumentFragment] */
+/**
+ * @typedef {HTMLElement|DocumentFragment} ContainerT
+ */
 globalThis.SpacedTextAccumulator = class SpacedTextAccumulator {
 	/** @param {ContainerT} [container] */
 	constructor(container) {
-		this._container = container || /** @type {ContainerT} */(new DocumentFragment());
+		/** @type {ContainerT} */
+		this._container = container || new DocumentFragment();
 		/** @type {(string|HTMLElement|DocumentFragment)[]} */
 		this._accumulator = [];
 		this._checked = false;
@@ -23,7 +26,6 @@ globalThis.SpacedTextAccumulator = class SpacedTextAccumulator {
 		}
 	}
 
-	/** get a reference the underlying container */
 	container() {
 		this._checkScope();
 		return this._container;
@@ -55,7 +57,7 @@ globalThis.SpacedTextAccumulator = class SpacedTextAccumulator {
 	/** assemble an element from the accumulated sentences or sentence fragments, separated by spaces
 	 * @template {keyof HTMLElementTagNameMap} K
 	 * @param {K} element
-	 * @param {string|Array<string>} [classNames]
+	 * @param {Array<string>} [classNames]
 	 */
 	toNode(element, classNames) {
 		const el = App.UI.DOM.makeElement(element, null, classNames);
diff --git a/src/js/birth/birth.js b/src/js/birth/birth.js
index 2843267c2dd1ebbe454c10ba895d2ee5ae2c8178..a72cb6f6c69ab1757c1505142ac97f65d3818bd4 100644
--- a/src/js/birth/birth.js
+++ b/src/js/birth/birth.js
@@ -941,7 +941,7 @@ globalThis.birth = function(slave, {birthStorm = false, cSection = false, artRen
 				}
 				healthDamage(slave, 80);
 			} else {
-				if (lastPregRule(slave, V.defaultRules)) {
+				if (rulesDemandContraceptives(slave, V.defaultRules)) {
 					slave.preg = -1;
 				} else {
 					slave.preg = 0;
@@ -2056,8 +2056,7 @@ globalThis.birth = function(slave, {birthStorm = false, cSection = false, artRen
 			slave.preg = WombMaxPreg(slave); // now we use most advanced remained fetus as base.
 			slave.pregSource = slave.womb[0].fatherID; // in such case it's good chance that there is different father also.
 		} else {
-			const tmp = lastPregRule(slave, V.defaultRules);
-			if ((!assignmentVisible(slave)) && (tmp !== null)) {
+			if (!assignmentVisible(slave) && rulesDemandContraceptives(slave, V.defaultRules)) {
 				slave.preg = -1;
 			} else {
 				slave.preg = 0;
diff --git a/src/js/economyJS.js b/src/js/economyJS.js
index 99fbadc1e13b4a7a444603dfdd9b132cea44aaaf..5cbe49ea29c91399fb6bb976f50faebab65d12b5 100644
--- a/src/js/economyJS.js
+++ b/src/js/economyJS.js
@@ -2171,7 +2171,7 @@ globalThis.endWeekSlaveMarket = function() {
 /**
  * @param {App.Entity.SlaveState} s
  * @param {object|undefined} facility
- * @returns {Object}
+ * @returns {FC.SlaveStatisticData}
  */
 globalThis.getSlaveStatisticData = function(s, facility) {
 	if (!facility) { // Base data, even without facility
diff --git a/src/js/ibcJS.js b/src/js/ibcJS.js
index 7d82694b237d6a2e26bc43acce03b58e1daec54b..7ac1750f6a171a038573048d41b27ae5ff98f75a 100644
--- a/src/js/ibcJS.js
+++ b/src/js/ibcJS.js
@@ -1,6 +1,3 @@
-const runningTestsWhenTheGameLoads = false;  // set this to true to run some tests when loading the page.  when they pass nothing happens, so to ensure it's working,
-		// you might tweak one of the expected values in the tests (as by changing 85/256 to 84/256, say) and then compile and reload the page.
-
 /** @typedef IBCRelative
  * An very simple object that represents a entity in a family tree.
  * Represents a group of common properties shared by SlaveState, InfantState, and PlayerState,
@@ -69,7 +66,7 @@ globalThis.ibc = (() => {
 
 	class CoancestryCache
 	{
-		#valuesByLeastParentId = {};
+		valuesByLeastParentId = {};
 
 		coefficientOfInbreeding(node) {
 			if (node._coeff === null)
@@ -85,7 +82,7 @@ globalThis.ibc = (() => {
 				// just two simple rules are needed to express it: self-with-self coancestry is (1 + coefficient of inbreeding of self) / 2, and...
 				return (1 + this.coefficientOfInbreeding(motherNode)) / 2;
 
-			return this.#kinship(motherNode.id, fatherNode.id,
+			return this.kinshipCache(motherNode.id, fatherNode.id,
 				() => {
 					let p1 = motherNode;
 					let p2 = fatherNode;
@@ -105,12 +102,12 @@ globalThis.ibc = (() => {
 				});
 		}
 
-		#kinship(motherId, fatherId, calculateValue) {  // this method exists solely for caching purposes
+		kinshipCache(motherId, fatherId, calculateValue) {  // this method exists solely for caching purposes
 			let id1 = motherId;
 			let id2 = fatherId;
 			if (id1 > id2) { let id_ = id1; id1 = id2; id2 = id_; }
 
-			let vs = this.#valuesByLeastParentId;
+			let vs = this.valuesByLeastParentId;
 
 			let vs1 = vs[id1];
 			if (vs1 === undefined)
@@ -118,10 +115,10 @@ globalThis.ibc = (() => {
 
 			// cache format, conceptually: a set of arrays of numbers.  the top-level set is indexed by id1.  a lower-level array has the format
 			// [id, kinship value, id, kinship value, ...] with the id in an entry corresponding to id2 here and with the pairs in the array being kept invariably sorted by id.
-			return this.#kinship_(vs1, id2, calculateValue);
+			return this.kinship_cache(vs1, id2, calculateValue);
 		}
 
-		#kinship_(vs, id, calculateValue) {  // this method exists solely for caching purposes
+		kinship_cache(vs, id, calculateValue) {  // this method exists solely for caching purposes
 			let entryCount = vs.length / 2;
 
 			// do a simple binary search
@@ -391,134 +388,10 @@ globalThis.ibc = (() => {
 		return recalculate_coeff_ids(world, [id]);
 	};
 
-	if (runningTestsWhenTheGameLoads) {
-		class MockSlave
-		{
-			ID;
-			mother;
-			father;
-
-			constructor(id) {
-				this.ID = id;
-			}
-		}
-
-		class MockMating
-		{
-			constructor(fatherId, motherId, ...childrenIds) {
-				for (let id of childrenIds)
-					if (id === fatherId || id === motherId)
-						throw new Error("cannot give birth to self");
-
-				this.fatherId = fatherId;
-				this.motherId = motherId;
-				this.childrenIds = childrenIds;
-			}
-		}
-
-		class MockWorld
-		{
-			#slaves;
-
-			constructor(matings) {
-				this.#slaves = [];
-
-				let slavesById = {};
-				let meetSlave = (id) => {
-					if (!slavesById[id]) {
-						let slave = new MockSlave(id);
-						this.#slaves.push(slave);
-						slavesById[id] = slave;
-					}
-
-					return slavesById[id];
-				};
-
-				for (let mating of matings) {
-					let father = meetSlave(mating.fatherId);
-					let mother = meetSlave(mating.motherId);
-					for (let id of mating.childrenIds) {
-						let child = meetSlave(id);
-						child.father = father.ID;
-						child.mother = mother.ID;
-					}
-				}
-			}
-
-			findSlaveState(id) {
-				return this.#slaves.find(slave => slave.ID === id) || null;
-			}
-		}
-
-		let testCoefficientForSlave = function(mockMatings, slaveId, expectedCoefficientOfInbreeding) {
-			let tolerance = .00000000001;
-
-			let world = new MockWorld(mockMatings);
-			let c = coeff_slave(world, world.findSlaveState(slaveId));
-			if (typeof c !== "number" || Number.isNaN(c) || Math.abs(c - expectedCoefficientOfInbreeding) > tolerance)
-				throw new Error("slave " + slaveId + " had wrong coefficient - expected " + expectedCoefficientOfInbreeding + ", was " + c);
-		}
-
-		// references:
-		// Introduction to Quantitative Genetics - Doulas S. Falconer, 1989
-		// Genetic and Quantitative Aspects of Genealogy - F.M. Lancaster, 2015
-		testCoefficientForSlave([new MockMating(1, 2, 3, 4)], 1, 0);  // basic outbred mating
-		testCoefficientForSlave([new MockMating(1, 2, 3, 4)], 4, 0);
-		testCoefficientForSlave([new MockMating(1, 1, 3, 4)], 4, 1/2);  // basic self-mating
-		testCoefficientForSlave([new MockMating(1, 1, 3, 4)], 1, 0);
-		testCoefficientForSlave([new MockMating(1, 2, 3), new MockMating(2, 3, 4)], 4, 1/4);  // basic child-parent mating
-		testCoefficientForSlave([new MockMating(1, 2, 3, 4), new MockMating(3, 4, 5)], 5, 1/4);  // basic sibling mating
-		testCoefficientForSlave([new MockMating(1, 2, 3), new MockMating(2, 4, 5), new MockMating(3, 5, 6)], 6, 1/8);  // basic half-sibling mating
-		testCoefficientForSlave([new MockMating(1, 2, 3, 4), new MockMating(5, 3, 6), new MockMating(4, 7, 8), new MockMating(6, 8, 9)], 9, 1/16);  // basic first cousin mating
-		testCoefficientForSlave([new MockMating(1, 2, 3, 4), new MockMating(5, 6, 7, 8),
-				new MockMating(3, 7, 9), new MockMating(4, 8, 10), new MockMating(9, 10, 11)], 11, 1/8);  // double first cousin mating
-		testCoefficientForSlave([new MockMating(1, 2, 3, 4), new MockMating(4, 5, 6), new MockMating(3, 6, 7)], 7, 1/8);  // aunt-niece mating
-		let scenario53 = [new MockMating(1, 2, 3, 4), new MockMating(5, 6, 7, 8), new MockMating(3, 7, 9, 10), new MockMating(4, 8, 11),
-				new MockMating(12, 9, 13), new MockMating(10, 11, 14), new MockMating(13, 14, 15)];
-		testCoefficientForSlave(scenario53, 14, 1/8);  // problem 5.3 from
-		testCoefficientForSlave(scenario53, 15, 3/32);  // Falconer
-		testCoefficientForSlave([new MockMating(1, 2, 4, 5), new MockMating(3, 4, 7), new MockMating(5, 6, 8), new MockMating(7, 8, 9), new MockMating(9, 10, 12, 13),
-				new MockMating(11, 12, 15), new MockMating(13, 14, 16), new MockMating(15, 16, 17, 18)], 17, 33/512);  // example in figure 64 from Lancaster
-		testCoefficientForSlave([new MockMating(1, 2, 4, 5), new MockMating(3, 4, 7), new MockMating(5, 6, 8), new MockMating(7, 8, 10, 11),
-				new MockMating(9, 10, 13), new MockMating(11, 12, 14), new MockMating(13, 14, 15)], 15, 9/128);  // example in figure 65 from Lancaster
-		testCoefficientForSlave([new MockMating(1, 2, 3, 4), new MockMating(3, 4, 5, 6), new MockMating(5, 6, 7, 8), new MockMating(7, 8, 9)], 9, 1/2);  // example in figure 66 from Lancaster
-		testCoefficientForSlave([new MockMating(1, 2, 3, 4), new MockMating(3, 4, 5, 6), new MockMating(5, 6, 7, 8), new MockMating(7, 8, 9, 10),
-				new MockMating(9, 10, 11)], 11, 19/32);  // extending the previous example by one more generation's worth of mating between siblings
-		// from here the preceding scenario of regular sibling-mating is extended by several more steps; by rules given in Falconer, the coefficient ck for step k, where step k follows
-		// step j which follows step i, should be ck = 1/4 + cj/2 + ci/4, and so that is how the expected values used in the next few tests were calculated
-		testCoefficientForSlave([new MockMating(1, 2, 3, 4), new MockMating(3, 4, 5, 6), new MockMating(5, 6, 7, 8), new MockMating(7, 8, 9, 10),
-				new MockMating(9, 10, 11, 12), new MockMating(11, 12, 13)], 13, 43/64);  // 43/64 = 1/4 + (19/32) / 2 + (1/2) / 4
-		testCoefficientForSlave([new MockMating(1, 2, 3, 4), new MockMating(3, 4, 5, 6), new MockMating(5, 6, 7, 8), new MockMating(7, 8, 9, 10),
-				new MockMating(9, 10, 11, 12), new MockMating(11, 12, 13, 14), new MockMating(13, 14, 15)], 15, 94/128);
-		testCoefficientForSlave([new MockMating(1, 2, 3, 4), new MockMating(3, 4, 5, 6), new MockMating(5, 6, 7, 8), new MockMating(7, 8, 9, 10),
-				new MockMating(9, 10, 11, 12), new MockMating(11, 12, 13, 14), new MockMating(13, 14, 15, 16), new MockMating(15, 16, 17)], 17, 201/256);
-		testCoefficientForSlave([new MockMating(1, 2, 3, 4), new MockMating(3, 4, 5, 6), new MockMating(5, 6, 7, 8), new MockMating(7, 8, 9, 10),
-				new MockMating(9, 10, 11, 12), new MockMating(11, 12, 13, 14), new MockMating(13, 14, 15, 16), new MockMating(15, 16, 17, 18), new MockMating(17, 18, 19)], 19, 423/512);
-		testCoefficientForSlave([new MockMating(1, 2, 3, 4), new MockMating(3, 4, 5, 6), new MockMating(5, 6, 7, 8), new MockMating(7, 8, 9, 10),
-				new MockMating(9, 10, 11, 12), new MockMating(11, 12, 13, 14), new MockMating(13, 14, 15, 16), new MockMating(15, 16, 17, 18), new MockMating(17, 18, 19, 20),
-				new MockMating(19, 20, 21)], 21, 880/1024);
-		// as of this writing, the ten-generations-of-sisterfucking test immediately above is enough to occupy the game's coefficient-of-inbreeding calculation algorithm for a few minutes
-		// on a ~4Ghz CPU.  this is very silly.  when you read this, that algorithm should have been replaced by a much faster one, allowing the above test to be comfortably run again.
-		let scenario70 = [new MockMating(1, 2, 6), new MockMating(2, 3, 7), new MockMating(3, 4, 8), new MockMating(5, 6, 9), new MockMating(7, 8, 10),
-				new MockMating(9, 10, 12), new MockMating(10, 11, 13), new MockMating(12, 13, 14), new MockMating(12, 14, 15)];  // example in figure 70 from Lancaster
-		testCoefficientForSlave(scenario70, 2, 0);
-		testCoefficientForSlave(scenario70, 10, 1/8);
-		testCoefficientForSlave(scenario70, 12, 1/32);
-		testCoefficientForSlave(scenario70, 15, 85/256);
-		let scenarioXXX = [new MockMating(1, 2, 5, 6), new MockMating(3, 4, 7, 8), new MockMating(5, 6, 9, 10), new MockMating(5, 7, 11), new MockMating(5, 8, 12),
-				new MockMating(5, 9, 13), new MockMating(9, 10, 14), new MockMating(5, 11, 15), new MockMating(10, 12, 16), new MockMating(13, 16, 17),
-				new MockMating(4, 16, 18), new MockMating(14, 15, 19), new MockMating(6, 17, 20), new MockMating(6, 18, 21), new MockMating(6, 19, 22),
-				new MockMating(20, 21, 23), new MockMating(22, 23, 24)];  // an invented example meant to represent a typical situation in a respectable arcology
-		testCoefficientForSlave(scenarioXXX, 24, 633/2048);  // here the expected coefficient was retrieved from the algorithm being tested and was not separately verified by hand,
-				// so this test could conceivably be wrong, but at least it can catch changes...
-		testCoefficientForSlave([new MockMating(1, 2, 3, 4), new MockMating(3, 4, 5, 6), new MockMating(5, 6, 7, 8), new MockMating(7, 8, 9, 10), new MockMating(9, 10, 11, 12),
-				new MockMating(11, 12, 13), new MockMating(13, 14, 15),		   // an example in which the old and new algorithms actually gave different results - the first one such
-				new MockMating(5, 5, 16), new MockMating(15, 16, 17)], 17, 1/4);  // to have been observed!  it seems that the old algorithm was wrong
-		testCoefficientForSlave([new MockMating(-120, -120, 87)], 87, 1/2);  // negative IDs need to be accepted (not bothering to test any weird special IDs like -1 here though)
-		testCoefficientForSlave([new MockMating(-999, -998, -300), new MockMating(-300, -998, 300)], 300, 1/4);
-	}
-
 	return {
+		_test: {
+			coeff_slave,
+		},
 		coeff: coeff_slave.bind(null, realWorld),
 		coeff_slaves: coeff_slaves.bind(null, realWorld),
 		kinship: kinship_slaves.bind(null, realWorld),
diff --git a/src/js/main.js b/src/js/main.js
index fb16243105f7d03fc3b0d29adcbd8af4fc76e5d9..e577fee479c272087ca1c528ccdbd84845c50a40 100644
--- a/src/js/main.js
+++ b/src/js/main.js
@@ -233,11 +233,6 @@ App.MainView.full = function() {
 
 				App.UI.reload();
 			}, null, '', `This will only check slaves in the Penthouse.`));
-
-			if (DefaultRulesError()) {
-				App.UI.DOM.appendNewElement("div", div, `One or more rules' custom conditions has errors!`,
-					['center', 'warning', 'notification']);
-			}
 		}
 
 		App.UI.DOM.appendNewElement("div", div, App.UI.DOM.generateLinksStrip(links), ['center', 'margin-bottom']);
diff --git a/src/js/rulesAssistant.js b/src/js/rulesAssistant.js
index 4c47a46bac4741ffd49d61ee0fd6892edcdd2434..66be45512f37b12852935668bed9da7d7b17b256 100644
--- a/src/js/rulesAssistant.js
+++ b/src/js/rulesAssistant.js
@@ -47,14 +47,21 @@ globalThis.hasEyeColorRule = function(slave, rules) {
 };
 
 /**
- * return if a rule is applied on a slave
+ * True, if the slave should be on contraceptives based on applied rules.
+ *
  * @param {App.Entity.SlaveState} slave
  * @param {FC.RA.Rule[]} rules
  * @returns {boolean}
  */
-globalThis.lastPregRule = function(slave, rules) {
-	return rules.some(rule =>
-		ruleApplied(slave, rule) && rule.set.preg === -1);
+globalThis.rulesDemandContraceptives = function(slave, rules) {
+	// iterate over rules backwards, as the last rule has the highest priority
+	for (let i = 0; i < rules.length; i++) {
+		const rule = rules[rules.length - i - 1];
+		if (ruleApplied(slave, rule) && rule.set.preg != null) {
+			return rule.set.preg;
+		}
+	}
+	return false;
 };
 
 /**
@@ -98,6 +105,7 @@ globalThis.RAFacilityRemove = function(slave, rule) {
 		r += `<br>${slave.slaveName} has been removed from ${facilityName} and has been assigned to ${rule.removalAssignment}.`;
 		assignJob(slave, rule.removalAssignment);
 	}
+	return r;
 };
 
 /**
@@ -162,7 +170,6 @@ App.RA.newRule = function() {
 			ID: id,
 			name: `Rule ${id}`,
 			condition: emptyConditions(),
-			// TODO: rename properties in snake_case to camelCase?
 			set: emptySetters()
 		};
 	}
@@ -170,7 +177,7 @@ App.RA.newRule = function() {
 	/** @returns {FC.RA.RuleConditions} */
 	function emptyConditions() {
 		return {
-			activation: [true, 1, "and"],
+			activation: ["devotion", 20, "gt", 1, "and"],
 			selectedSlaves: [],
 			excludedSlaves: [],
 			applyRuleOnce: false,
@@ -262,6 +269,7 @@ App.RA.newRule = function() {
 			preg: null,
 			abortion: null,
 			growth: emptyGrowth(),
+			// TODO: rename snake_case to camelCase?
 			// eslint-disable-next-line camelcase
 			hyper_drugs: 0,
 			aphrodisiacs: null,
@@ -357,21 +365,10 @@ App.RA.newRule = function() {
  */
 globalThis.emptyDefaultRule = App.RA.newRule.rule;
 
-/**
- * Saves the slave, silently fires the RA, saves the slave's after-RA state, and then reverts the slave.
- * Call and then check potential change against V.slaveAfterRA to see if the RA would revert it.
- * @param {App.Entity.SlaveState} slave
- */
-globalThis.RulesDeconfliction = function(slave) {
-	const before = clone(slave);
-	DefaultRules(slave);
-	V.slaveAfterRA = clone(slave);
-	slave = before;
-};
 
 /**
  * Creates RA target object used in rules for body properties
- * @param {string} condition comparison condition. One of '==', '>=', '<=', '>', '<'
+ * @param {"=="|">="|"<="|">"|"<"} condition comparison condition.
  * @param {number} val target value
  * @returns {FC.RA.NumericTarget}
  */
diff --git a/src/js/rulesAssistantOptions.js b/src/js/rulesAssistantOptions.js
index c41aa7ff14e7f7d890e1d9730bf22cb3aa28b109..d47e17530bd85da037165d1c16ba5bef4abd7997 100644
--- a/src/js/rulesAssistantOptions.js
+++ b/src/js/rulesAssistantOptions.js
@@ -1218,7 +1218,7 @@ App.RA.options = (function() {
 	// buttons for selecting the current rule
 	class RuleSelector extends List {
 		constructor() {
-			super("Current rule", V.defaultRules.map(i => [(i.name + (RuleHasError(i) ? " <span class='yellow'>[!]</span>" : "")), i]), false);
+			super("Current rule", V.defaultRules.map(i => [i.name, i]), false);
 			this.setValue(current_rule.name);
 			this.onchange = function(rule) {
 				V.currentRule = rule.ID;
diff --git a/src/js/slaveCostJS.js b/src/js/slaveCostJS.js
index 0553eceb2858c85044d6163d5a713af9fd9225ed..ff84712c598730bd117fd9092d4e1273ea527ac3 100644
--- a/src/js/slaveCostJS.js
+++ b/src/js/slaveCostJS.js
@@ -2321,7 +2321,7 @@ globalThis.FResultTooltip = function(slave, forSale = 0) {
  * @param {boolean} [isStartingSlave=false] is the slave a "starting slave"
  * @param {boolean} [followLaws=false] Apply cost variations from enacted Slave Market Regulations
  * @param {boolean} [isSpecial=false] is this slave a special/hero slave
-	* @param {object} [fromMarket=null] is this slave from the market
+ * @param {object} [fromMarket=null] is this slave from the market
  * @param {boolean} [returnDOM]
  * @returns {number|Object}
  */
@@ -2756,46 +2756,44 @@ globalThis.slaveCostBeauty = function(slave, isStartingSlave, followLaws, isSpec
 	 * @param {App.Entity.SlaveState} slave
 	 */
 	function calcCareersCost(slave) {
-		if (slave.career !== 0) {
-			if (slave.career === "a slave") {
-				updateMultiplier(`career`, 0.1);
-			} else if (App.Data.Careers.Leader.bodyguard.includes(slave.career)) {
-				updateMultiplier(`career`, 0.1);
-			} else if (App.Data.Careers.Leader.wardeness.includes(slave.career)) {
-				updateMultiplier(`career`, 0.1);
-			} else if (App.Data.Careers.Leader.attendant.includes(slave.career)) {
-				updateMultiplier(`career`, 0.1);
-			} else if (App.Data.Careers.Leader.nurse.includes(slave.career)) {
-				updateMultiplier(`career`, 0.1);
-			} else if (App.Data.Careers.Leader.matron.includes(slave.career)) {
-				updateMultiplier(`career`, 0.1);
-			} else if (App.Data.Careers.Leader.schoolteacher.includes(slave.career)) {
-				updateMultiplier(`career`, 0.1);
-			} else if (App.Data.Careers.Leader.stewardess.includes(slave.career)) {
-				updateMultiplier(`career`, 0.1);
-			} else if (App.Data.Careers.Leader.milkmaid.includes(slave.career)) {
-				updateMultiplier(`career`, 0.1);
-			} else if (App.Data.Careers.Leader.farmer.includes(slave.career)) {
-				updateMultiplier(`career`, 0.1);
-			} else if (App.Data.Careers.Leader.madam.includes(slave.career)) {
-				updateMultiplier(`career`, 0.1);
-			} else if (App.Data.Careers.Leader.DJ.includes(slave.career)) {
-				updateMultiplier(`career`, 0.1);
-			} else if (App.Data.Careers.Leader.HG.includes(slave.career)) {
-				updateMultiplier(`career`, 0.1);
-			} else if (App.Data.Careers.Leader.recruiter.includes(slave.career)) {
-				updateMultiplier(`career`, 0.1);
-			} else if (App.Data.Careers.General.entertainment.includes(slave.career)) {
-				updateMultiplier(`career`, 0.05);
-			} else if (App.Data.Careers.General.whore.includes(slave.career)) {
-				updateMultiplier(`career`, 0.05);
-			} else if (App.Data.Careers.General.grateful.includes(slave.career)) {
-				updateMultiplier(`career`, 0.05);
-			} else if (App.Data.Careers.General.menial.includes(slave.career)) {
-				updateMultiplier(`career`, 0.05);
-			} else if (App.Data.Careers.General.servant.includes(slave.career)) {
-				updateMultiplier(`career`, 0.05);
-			}
+		if (slave.career === "a slave") {
+			updateMultiplier(`career`, 0.1);
+		} else if (App.Data.Careers.Leader.bodyguard.includes(slave.career)) {
+			updateMultiplier(`career`, 0.1);
+		} else if (App.Data.Careers.Leader.wardeness.includes(slave.career)) {
+			updateMultiplier(`career`, 0.1);
+		} else if (App.Data.Careers.Leader.attendant.includes(slave.career)) {
+			updateMultiplier(`career`, 0.1);
+		} else if (App.Data.Careers.Leader.nurse.includes(slave.career)) {
+			updateMultiplier(`career`, 0.1);
+		} else if (App.Data.Careers.Leader.matron.includes(slave.career)) {
+			updateMultiplier(`career`, 0.1);
+		} else if (App.Data.Careers.Leader.schoolteacher.includes(slave.career)) {
+			updateMultiplier(`career`, 0.1);
+		} else if (App.Data.Careers.Leader.stewardess.includes(slave.career)) {
+			updateMultiplier(`career`, 0.1);
+		} else if (App.Data.Careers.Leader.milkmaid.includes(slave.career)) {
+			updateMultiplier(`career`, 0.1);
+		} else if (App.Data.Careers.Leader.farmer.includes(slave.career)) {
+			updateMultiplier(`career`, 0.1);
+		} else if (App.Data.Careers.Leader.madam.includes(slave.career)) {
+			updateMultiplier(`career`, 0.1);
+		} else if (App.Data.Careers.Leader.DJ.includes(slave.career)) {
+			updateMultiplier(`career`, 0.1);
+		} else if (App.Data.Careers.Leader.HG.includes(slave.career)) {
+			updateMultiplier(`career`, 0.1);
+		} else if (App.Data.Careers.Leader.recruiter.includes(slave.career)) {
+			updateMultiplier(`career`, 0.1);
+		} else if (App.Data.Careers.General.entertainment.includes(slave.career)) {
+			updateMultiplier(`career`, 0.05);
+		} else if (App.Data.Careers.General.whore.includes(slave.career)) {
+			updateMultiplier(`career`, 0.05);
+		} else if (App.Data.Careers.General.grateful.includes(slave.career)) {
+			updateMultiplier(`career`, 0.05);
+		} else if (App.Data.Careers.General.menial.includes(slave.career)) {
+			updateMultiplier(`career`, 0.05);
+		} else if (App.Data.Careers.General.servant.includes(slave.career)) {
+			updateMultiplier(`career`, 0.05);
 		}
 		if (V.week - slave.weekAcquired >= 20 && slave.skill.entertainment >= 100) {
 			if (!App.Data.Careers.General.entertainment.includes(slave.career)) {
diff --git a/src/js/utilsSlave.js b/src/js/utilsSlave.js
index 86e0deb8154e496787504d8db84ad0fa1f9c7289..1b13711a4f918af6d3f017b5fee947edacc6b559 100644
--- a/src/js/utilsSlave.js
+++ b/src/js/utilsSlave.js
@@ -1348,7 +1348,7 @@ globalThis.pronounReplacer = function(slavetext) {
 /**
  * Describes a slaves pre-slavery career in a gender sensitive way. If career is "a dominatrix" but the slave is male and the pronoun system is on, returns "a dominator"
  * @param {App.Entity.SlaveState} slave
- * @returns {FC.Zeroable<string>}
+ * @returns {string}
  */
 globalThis.convertCareer = function(slave) {
 	let job = slave.career;
@@ -1619,7 +1619,7 @@ globalThis.randomRaceSkin = function(raceName) {
 			skin = jsEither(["fair", "light", "pale"]);
 			break;
 		case "catgirl":
-			skin = jsEither(["black", "white", "brown", "red", "black and white striped", "yellow"]);
+			skin = jsEither(App.Medicine.Modification.catgirlNaturalSkins);
 			break;
 		default:
 			skin = jsEither(["dark", "light", "pale"]);
diff --git a/src/js/wombJS.js b/src/js/wombJS.js
index c2a86bec54093f7ecf80e882e4528f5815861999..ea100841dd207f7410043e6e26dfc4e33df304a6 100644
--- a/src/js/wombJS.js
+++ b/src/js/wombJS.js
@@ -47,7 +47,7 @@ globalThis.WombInit = function(actor) {
 	if (actor.pregData === undefined) {
 		actor.pregData = clone(App.Data.misc.pregData.human);
 		// Setup should be through deep copy, so in future, if we like, these values can be changed individually. Gameplay expansion possibilities. But for dev time to simplify debugging:
-		// actor.pregData = setup.pregData.human; // any changes in setup pregData template will be applied immediately to all. But can't be made separate changes.
+		// actor.pregData = App.Data.misc.pregData.human; // any changes in App.Data.misc pregData template will be applied immediately to all. But can't be made separate changes.
 	}
 
 	if (typeof actor.eggType !== 'string') {
diff --git a/src/markets/gingering.js b/src/markets/gingering.js
index cc25ffaed694f40b791681f214950fc897362b63..f0f9873e20d5911429bcc65b1a5ba44d87808436 100644
--- a/src/markets/gingering.js
+++ b/src/markets/gingering.js
@@ -1,7 +1,7 @@
 App.Entity.GingeringParameters = class {
 	/** Get gingering parameters for a particular slave and market.
 	 * @param {App.Entity.SlaveState} slave
-	 * @param {FC.Zeroable<FC.SlaveMarketName>} market
+	 * @param {FC.Zeroable<FC.SlaveMarketName | FC.SpecialMarketName>} market
 	 * @param {number} [arcIndex] - arcology index if market is "neighbor"
 	 */
 	constructor(slave, market, arcIndex) {
@@ -13,7 +13,7 @@ App.Entity.GingeringParameters = class {
 		this.detected = false;
 
 		// figure out what type of gingering applies, if any
-		if (applyLawCheck(market) === 1 && V.policies.SMR.honestySMR === 1) {
+		if (applyLawCheck(market) && V.policies.SMR.honestySMR === 1) {
 			/* SMR prohibits gingering and is enforced for this seller - do nothing */
 		} else if (App.Data.misc.schools.has(market)) {
 			/* slave schools have a reputation to maintain, and will never ginger their slaves */
@@ -95,7 +95,7 @@ globalThis._makeGingeredSlaveHandler = function(gParams, gKeys) {
 
 /** Get a gingered proxy for a slave.
  * @param {App.Entity.SlaveState} slave
- * @param {FC.Zeroable<FC.SlaveMarketName>} market
+ * @param {FC.Zeroable<FC.SlaveMarketName | FC.SpecialMarketName>} market
  * @param {number} arcIndex - arcology number if market is "neighbor"
  * @returns {FC.GingeredSlave}
  * */
diff --git a/src/markets/marketUI.js b/src/markets/marketUI.js
index be8770010fe5471e0dc092770768419d64500b18..05013f197e830479a0e01d98f8e8192946156ce2 100644
--- a/src/markets/marketUI.js
+++ b/src/markets/marketUI.js
@@ -9,14 +9,18 @@
 App.Markets.purchaseFramework = function(slaveMarket, {sTitleSingular = "slave", sTitlePlural = "slaves", costMod = 1} = {}) {
 	const el = new DocumentFragment();
 	const {slave, text} = generateMarketSlave(slaveMarket, (V.market.numArcology || 1));
-	const limitReached = V.slavesSeen > V.slaveMarketLimit;
-	const cost = getCost().cost;
+	const {He, him, his} = getPronouns(slave);
 	let prisonCrime = "";
 	if (slaveMarket === V.prisonCircuit[V.prisonCircuitIndex]) {
-		prisonCrime = pronounsForSlaveProp(slave, text);
+		prisonCrime = `${He} ${pronounsForSlaveProp(slave, text)}`;
 	} else {
 		$(el).append(` ${text}`);
 	}
+	
+	const applyLaw = applyLawCheck(slaveMarket);
+	const complianceResult = applyLaw ? App.Desc.lawCompliance(slave, slaveMarket) : ``;
+	const limitReached = V.slavesSeen > V.slaveMarketLimit;
+	const cost = getCost().cost;
 
 	App.Events.addParagraph(el, [
 		`The offered price is`, App.UI.DOM.combineNodes(getCost().report, "."),
@@ -27,12 +31,11 @@ App.Markets.purchaseFramework = function(slaveMarket, {sTitleSingular = "slave",
 	return el;
 
 	function getCost() {
-		const {cost, report} = slaveCost(slave, false, !App.Data.misc.lawlessMarkets.includes(slaveMarket), false, true, {limitReached, costMod});
+		const {cost, report} = slaveCost(slave, false, applyLaw, false, true, {limitReached, costMod});
 		return {cost, report};
 	}
 
 	function choices() {
-		const {him, his} = getPronouns(slave);
 		const el = document.createElement("p");
 		let title = {};
 		V.slavesSeen++;
@@ -133,7 +136,7 @@ App.Markets.purchaseFramework = function(slaveMarket, {sTitleSingular = "slave",
 			);
 		}
 
-		el.append(App.Desc.longSlave(slave, {market: slaveMarket, prisonCrime}));
+		el.append(App.Desc.longSlave(slave, {market: slaveMarket, marketText: prisonCrime || complianceResult}));
 		return el;
 
 		function student() {
@@ -146,10 +149,10 @@ App.Markets.purchaseFramework = function(slaveMarket, {sTitleSingular = "slave",
 };
 
 /** Construct the market global
- * @param {string} market
+ * @param {FC.SlaveMarketName | FC.SpecialMarketName} market
  */
 App.Markets.Global = function(market) {
-	/** @type {string} - overlaps with but is not contained by @see {FC.SlaveMarketName} */
+	/** @type {FC.SlaveMarketName | FC.SpecialMarketName} */
 	this.slaveMarket = market;
 	this.introType = "";
 	/** @type {Array<FC.GingeredSlave>} */
diff --git a/src/markets/specificMarkets/eliteSlave.js b/src/markets/specificMarkets/eliteSlave.js
index 9ce1dfee79920b6272dd63ae2b91c5146b954b6a..6aa670ec4e332abd00629a579a06f49410bfb0ce 100644
--- a/src/markets/specificMarkets/eliteSlave.js
+++ b/src/markets/specificMarkets/eliteSlave.js
@@ -20,11 +20,10 @@ App.Markets["Elite Slave"] = function() {
 			maxAge = 40;
 		}
 		let race;
-		let races;
 		if (V.arcologies[0].FSSupremacist !== "unset") {
 			race = V.arcologies[0].FSSupremacistRace;
 		} else if (V.arcologies[0].FSSubjugationist !== "unset") {
-			races = ["amerindian", "asian", "asian", "asian", "asian", "asian", "asian", "black", "black", "indo-aryan", "indo-aryan", "latina", "latina", "latina", "malay", "malay", "middle eastern", "middle eastern", "mixed race", "pacific islander", "semitic", "semitic", "southern european", "southern european", "white", "white", "white", "white", "white", "white", "white", "white", "white"];
+			let races = ["amerindian", "asian", "asian", "asian", "asian", "asian", "asian", "black", "black", "indo-aryan", "indo-aryan", "latina", "latina", "latina", "malay", "malay", "middle eastern", "middle eastern", "mixed race", "pacific islander", "semitic", "semitic", "southern european", "southern european", "white", "white", "white", "white", "white", "white", "white", "white", "white"];
 			races = races.delete(V.arcologies[0].FSSubjugationistRace);
 			race = races.random();
 		}
@@ -213,7 +212,7 @@ App.Markets["Elite Slave"] = function() {
 			slave.dick = random(2, 5);
 			slave.balls = random(2, 10);
 			slave.scrotum = slave.balls;
-			slave.prostate = random(1, 3);
+			slave.prostate = either(1, 2, 3);
 		}
 		slave.makeup = 2;
 		slave.nails = 1;
@@ -251,8 +250,8 @@ App.Markets["Elite Slave"] = function() {
 		slave.pubertyXX = 1;
 		slave.breedingMark = 1;
 
-		let cost = slaveCost(slave, false, true);
-
+		const complianceText = App.Desc.lawCompliance(slave, "Elite Slave")
+		const cost = slaveCost(slave, false, true);
 
 		App.UI.DOM.appendNewElement("p", el, `It will take ${cashFormat(cost)} to win the auction.`);
 
@@ -272,7 +271,7 @@ App.Markets["Elite Slave"] = function() {
 			App.UI.DOM.appendNewElement("p", el, `You lack the necessary funds to place a winning bid.`, "note");
 		}
 
-		el.append(App.Desc.longSlave(slave, {market: "Elite Slave"}));
+		el.append(App.Desc.longSlave(slave, {market: "Elite Slave", marketText: complianceText}));
 	}
 	return el;
 };
diff --git a/src/markets/specificMarkets/householdLiquidator.js b/src/markets/specificMarkets/householdLiquidator.js
index 2416fe2641293d5c8976a85ba40b8c796d5be3e8..4892ac3ee2e545be04b3edfc2f4194d8abcc22c8 100644
--- a/src/markets/specificMarkets/householdLiquidator.js
+++ b/src/markets/specificMarkets/householdLiquidator.js
@@ -3,16 +3,14 @@ App.Markets["Household Liquidator"] = function() {
 	V.market.newSlavesDone = 0;
 
 	const el = new DocumentFragment();
-	let slave;
-	let r = [];
-	const newSlaves = [];
-	let totalCost;
+	/** @type {ReturnType<prepSlaveSale>} */
+	let newSlaves = null;
 	if (jsRandom(1, 100) > 50) {
 		// Old enough to have a younger sibling who can be a slave.
-		slave = GenerateNewSlave(null, {
+		const slave = GenerateNewSlave(null, {
 			minAge: (V.minimumSlaveAge + 2), disableDisability: 1
 		});
-		finishSlave();
+		finishSlave(slave);
 		setMissingParents(slave);
 
 		// Create opposite sex chance of relative
@@ -20,30 +18,21 @@ App.Markets["Household Liquidator"] = function() {
 		App.UI.DOM.appendNewElement("p", el, `The household liquidator is offering a set of siblings for sale. You are permitted to inspect both slaves.`);
 
 		const relativeSlave = generateRelatedSlave(slave, "younger sibling", oppositeSex);
-		newSlaves.push(slave);
-		newSlaves.push(relativeSlave);
-
-		let cost = slaveCost(slave, false, true);
-		if (V.slavesSeen > V.slaveMarketLimit) {
-			cost += Math.trunc(cost * ((V.slavesSeen - V.slaveMarketLimit) * 0.1));
-		}
-		totalCost = cost * 3;
+		
+		newSlaves = prepSlaveSale(3.0, slave, relativeSlave);
 	} else if (jsRandom(1, 100) > 20) {
 		// Old enough to have a child who can be a slave.
-		slave = GenerateNewSlave(null, {
+		const slave = GenerateNewSlave(null, {
 			minAge: (V.fertilityAge + V.minimumSlaveAge), maxAge: 42, ageOverridesPedoMode: 1, disableDisability: 1
 		});
-		if (slave.vagina > -1) {
-			slave.boobs += 100;
-		}
 		slave.butt += 1;
 		if (slave.vagina > -1) {
+			slave.boobs += 100;
 			slave.vagina += 1;
-		}
-		if (slave.vagina > -1) {
 			slave.counter.birthsTotal = 1;
 		}
-		finishSlave();
+		finishSlave(slave);
+
 		// Create opposite sex chance of relative
 		const oppositeSex = (slave.genes !== GenerateChromosome());
 		const {his, mother} = getPronouns(slave);
@@ -51,50 +40,36 @@ App.Markets["Household Liquidator"] = function() {
 		const relativeSlave = generateRelatedSlave(slave, "child", oppositeSex);
 		const {daughter} = getPronouns(relativeSlave);
 
-		r.push(`The household liquidator is offering a ${mother} and ${his} ${daughter} for sale. You are permitted to inspect both slaves.`);
-		App.UI.DOM.appendNewElement("p", el, r.join(" "));
-
-		newSlaves.push(slave);
-		newSlaves.push(relativeSlave);
+		App.UI.DOM.appendNewElement("p", el, `The household liquidator is offering a ${mother} and ${his} ${daughter} for sale. You are permitted to inspect both slaves.`);
 
-		let cost = slaveCost(slave, false, true);
-		if (V.slavesSeen > V.slaveMarketLimit) {
-			cost += Math.trunc(cost * ((V.slavesSeen - V.slaveMarketLimit) * 0.1));
-		}
-		totalCost = cost * 3;
+		newSlaves = prepSlaveSale(3.0, slave, relativeSlave);
 	} else {
-		slave = GenerateNewSlave(null, {disableDisability: 1});
-		finishSlave();
+		const slave = GenerateNewSlave(null, {disableDisability: 1});
+		finishSlave(slave);
 		setMissingParents(slave);
 		App.UI.DOM.appendNewElement("p", el, `The household liquidator is offering something special: identical twins. The markup is huge, but the merchandise isn't something you see every day.`);
 
 		const relativeSlave = generateRelatedSlave(slave, "twin");
-		newSlaves.push(slave);
-		newSlaves.push(relativeSlave);
 
-		let cost = slaveCost(slave, false, true);
-		if (V.slavesSeen > V.slaveMarketLimit) {
-			cost += Math.trunc(cost * ((V.slavesSeen - V.slaveMarketLimit) * 0.1));
-		}
-		totalCost = cost * 4;
+		newSlaves = prepSlaveSale(4.0, slave, relativeSlave);
 	}
 
 	el.append(`The price is `);
-	el.append(App.UI.DOM.cashFormat(totalCost));
+	el.append(App.UI.DOM.cashFormat(newSlaves.cost));
 	el.append(`.`);
 	if (V.slavesSeen > V.slaveMarketLimit) {
 		el.append(` You have cast such a wide net for slaves this week that it is becoming more expensive to find more for sale. Your reputation helps determine your reach within the slave market.`);
 	}
 
-	if (V.cash >= totalCost) {
+	if (V.cash >= newSlaves.cost) {
 		App.UI.DOM.appendNewElement(
 			"div",
 			el,
 			App.UI.DOM.link(
 				`Buy their slave contracts`,
 				() => {
-					V.market.newSlaves = newSlaves;
-					V.market.newSlaves.forEach((s) => cashX(forceNeg(totalCost / V.market.newSlaves.length), "slaveTransfer", s));
+					V.market.newSlaves = newSlaves.slaves;
+					V.market.newSlaves.forEach((s) => cashX(forceNeg(newSlaves.cost / V.market.newSlaves.length), "slaveTransfer", s));
 					V.returnTo = "Buy Slaves";
 				},
 				[],
@@ -116,12 +91,38 @@ App.Markets["Household Liquidator"] = function() {
 			"Market"
 		)
 	);
-	App.UI.DOM.appendNewElement("p", el, App.UI.MultipleInspect(newSlaves, true, "Household Liquidators"));
+	App.UI.DOM.appendNewElement("p", el, App.UI.MultipleInspect(newSlaves.slaves, true, "Household Liquidator", newSlaves.text));
 
 	return el;
 
-	function finishSlave() {
-		slave = Object.assign(slave, {
+	/**
+	 * @param {number} costFactor
+	 * @param {Array<FC.GingeredSlave>} slaves
+	 */
+	function prepSlaveSale(costFactor, ...slaves) {
+		const bundle = {
+			/** @type {Array<FC.GingeredSlave>} */
+			slaves: [],
+			/** @type {Map<number, string>} */
+			text: new Map(),
+			cost: 0
+		};
+		for (const slave of slaves) {
+			const complianceText = App.Desc.lawCompliance(slave, "Household Liquidator")
+			bundle.slaves.push(slave);
+			bundle.text.set(slave.ID, complianceText);
+		}
+		let cost = slaveCost(slaves[0], false, true);
+		if (V.slavesSeen > V.slaveMarketLimit) {
+			cost += Math.trunc(cost * ((V.slavesSeen - V.slaveMarketLimit) * 0.1));
+		}
+		bundle.cost = cost * costFactor;
+		return bundle;
+	}
+
+	/** @param {FC.GingeredSlave} slave */
+	function finishSlave(slave) {
+		Object.assign(slave, {
 			origin: "You bought $him from the household liquidator.",
 			devotion: jsRandom(-75, -25),
 			trust: jsRandom(-45, -25),
diff --git a/src/markets/specificMarkets/schoolFutanari.js b/src/markets/specificMarkets/schoolFutanari.js
index f8e8ba9b80d4ce2cd23de548d8e655379c82449b..ba7a86e243b5e83aefed632e7eff4b980778f15c 100644
--- a/src/markets/specificMarkets/schoolFutanari.js
+++ b/src/markets/specificMarkets/schoolFutanari.js
@@ -21,7 +21,7 @@ App.Markets.TFS = function() {
 			if (V.TFS.farmUpgradeAsked < V.week - 10) {
 				App.UI.DOM.appendNewElement("p", el, `It's been long enough since you allowed them to use your organ farm to add ovaries to themselves and instructed them not to use contraceptives that most of them are visibly pregnant. This hasn't slowed their sexual congress at all, though. They've become much more focused on vaginal, and there's an obvious eagerness to cum inside Sisters who aren't obviously pregnant.`);
 			} else {
-				App.UI.DOM.appendNewElement("p", el, `It hasn't been long enough since you allowed them to use your organ farm to add ovaries to themselves for the effects to be obvious yet. Most of them are doubtless pregnant, however. There's been a subtle shift in their sexual behavior, too: they're much more likely to focus on vaginal sex than they were before, so much so that they often double penetrate each others' pussies. When there aren't any cunts available, they do their best to hold their orgasms until one opens up, so to speak.`);
+				App.UI.DOM.appendNewElement("p", el, `It hasn't been long enough since you allowed them to use your organ farm to add ovaries to themselves for the effects to be obvious yet. Most of them are doubtless pregnant, however. There's been a subtle shift in their sexual behavior, too: they're much more likely to focus on vaginal sex than they were before, so much so that they often double penetrate each other's pussies. When there aren't any cunts available, they do their best to hold their orgasms until one opens up, so to speak.`);
 			}
 		} else if (V.TFS.farmUpgrade === 3) {
 			if (V.TFS.farmUpgradeAsked < V.week - 20) {
@@ -29,7 +29,7 @@ App.Markets.TFS = function() {
 			} else if (V.TFS.farmUpgradeAsked < V.week - 10) {
 				App.UI.DOM.appendNewElement("p", el, `It's been long enough since you allowed them to use your organ farm to add ovaries to themselves and instructed them not to use contraceptives that most of them are visibly pregnant. This hasn't slowed their sexual congress at all, though. They've become much more focused on vaginal, and there's an obvious eagerness to cum inside Sisters who aren't obviously pregnant.`);
 			} else {
-				App.UI.DOM.appendNewElement("p", el, `It hasn't been long enough since you allowed them to use your organ farm to add ovaries to themselves for the effects to be obvious yet. Most of them are doubtless pregnant, however. There's been a subtle shift in their sexual behavior, too: they're much more likely to focus on vaginal sex than they were before, so much so that they often double penetrate each others' pussies. When there aren't any cunts available, they do their best to hold their orgasms until one opens up, so to speak.`);
+				App.UI.DOM.appendNewElement("p", el, `It hasn't been long enough since you allowed them to use your organ farm to add ovaries to themselves for the effects to be obvious yet. Most of them are doubtless pregnant, however. There's been a subtle shift in their sexual behavior, too: they're much more likely to focus on vaginal sex than they were before, so much so that they often double penetrate each other's pussies. When there aren't any cunts available, they do their best to hold their orgasms until one opens up, so to speak.`);
 			}
 		}
 		App.UI.DOM.appendNewElement("p", el, `Visitors are not common: in fact, visitors are only as frequent as you feel like visiting. It takes a while before they notice you. When a dreamy-eyed young futa finally does, she reaches a lazy hand over to alert the eldest one present by tugging on one of her nipples and pointing in your direction. The elder looks over at you and gives you a friendly wave followed by a wait-one-moment gesture. She's curled up on her back with her cockhead in her own mouth, using both hands to give her own shaft a boob job while a younger futa eats her ass and fingers her pussy. The futa matron orgasms promptly, sucking down her own cum. She gets up languidly, her plush body, softening forearm-sized dick, and enormous natural boobs making it a wonderful sight.`);
diff --git a/src/markets/theMarket/marketData.js b/src/markets/theMarket/marketData.js
index 72ee489efebbfe3ed0f8ba409754aebac7a542eb..4e2d1d9410aaced212c7fa8cb0ad4f61534ea1bd 100644
--- a/src/markets/theMarket/marketData.js
+++ b/src/markets/theMarket/marketData.js
@@ -5,7 +5,7 @@
 /**
  * @typedef {object} market
  * @property {string} title
- * @property {string} [marketType]
+ * @property {FC.SlaveMarketName | FC.SpecialMarketName} [marketType]
  * @property {string} [note]
  * @property {string} [encyclopedia]
  * @property {string} [sale]
diff --git a/src/npc/children/ChildState.js b/src/npc/children/ChildState.js
index 308179296ee75da573251b77c04f3ca31ca79860..67143dd75b9c2b2d8cf92c1a158e5a48068cc606 100644
--- a/src/npc/children/ChildState.js
+++ b/src/npc/children/ChildState.js
@@ -24,11 +24,11 @@ App.Facilities.Nursery.ChildState = class ChildState {
 		 * _0: Obtained prior to game start / at game start_ */
 		this.weekAcquired = 0;
 		/** Child's origin
-		 * @type {FC.Zeroable<string>} */
+		 * @type {string} */
 		this.origin = "$He was born and raised in your arcology.";
 		/** Career prior to enslavement
-		 * @type {FC.Zeroable<string>} */
-		this.career = 0;
+		 * @type {string} */
+		this.career = "a slave";
 		/** Child's ID */
 		this.ID = 0;
 		/**
@@ -809,7 +809,7 @@ App.Facilities.Nursery.ChildState = class ChildState {
 		 */
 		this.broodmotherFetuses = 0;
 		/**
-		 * If broodmother implant set to pause it 's work.
+		 * If broodmother implant set to pause its work.
 		 *
 		 * 1: implant on pause !1: working.
 		 *
diff --git a/src/npc/descriptions/boobs/boobs.js b/src/npc/descriptions/boobs/boobs.js
index a0949ea8e475522e12c035219dfe1cc906832e57..edaa2296cd827cc7fa45ca53bbba8c7d7235bfb0 100644
--- a/src/npc/descriptions/boobs/boobs.js
+++ b/src/npc/descriptions/boobs/boobs.js
@@ -601,7 +601,7 @@ App.Desc.boobs = function() {
 					} else if (slave.boobs > 2000) {
 						r += `${slave.slaveName}'s ${adjNoun} are bulging inside a beautiful halter top dress.`;
 					} else if (slave.boobs > 800) {
-						r += `${slave.slaveName}'s is draped inside a beautiful halter top dress, making ${his} ${adjNoun} the center of attention.`;
+						r += `${slave.slaveName}'s ${adjNoun} are draped inside a beautiful halter top dress, making them the center of attention.`;
 					} else if (slave.boobs < 300) {
 						r += `${slave.slaveName} is wearing a beautiful silky halter top dress, almost as if it was sculpted to hug ${his} flat chest.`;
 					} else {
diff --git a/src/npc/descriptions/career.js b/src/npc/descriptions/career.js
index 692cda989f1a91b994a58f2c85a0ca8c35bef8aa..10bad3191c3110ff96f8f46fd2de91b5c5409db4 100644
--- a/src/npc/descriptions/career.js
+++ b/src/npc/descriptions/career.js
@@ -10,117 +10,115 @@ App.Desc.career = function(slave) {
 	const career = convertCareer(slave);
 
 	if (slave.fuckdoll === 0) {
-		if (slave.career !== 0) {
-			if (slave.career === "a slave") {
-				r.push(`${He} was a slave long before you obtained ${him}.`);
-			} else if (slave.career === "a slave since birth") {
-				r.push(`${He}'s been your slave ${his} entire life.`);
-			} else if (slave.career === "a meat toilet" || slave.career === "a cum dump") {
-				r.push(`${He} sees ${himself} as a cum receptacle.`);
-			} else if (slave.career === "a dairy cow") {
-				r.push(`${He}'s been broken into the belief that ${he} is nothing more than a cow to be milked and bred.`);
-			} else if (slave.career === "a breeding bull") {
-				r.push(`${He}'s been broken into the belief that ${he} is nothing more than a bull destined to fill fertile wombs with calves.`);
-			} else if (slave.career === "a breeder") {
-				r.push(`Before you obtained ${him}, ${he} was a breeding slave.`);
-			} else if (slave.career === "a bioreactor") {
-				r.push(`${He} has spent time as a cow in an industrial dairy, an experience that marked ${him} deeply.`);
-			} else {
-				r.push(`Before ${he} was a slave, ${he} was`);
-				if (App.Data.Careers.Leader.bodyguard.includes(slave.career)) {
-					r.push(`${career}, giving ${him} potential as a Bodyguard.`);
-				} else if (App.Data.Careers.Leader.wardeness.includes(slave.career)) {
-					r.push(`${career}, giving ${him} potential as a Wardeness for`);
-					if (V.cellblock === 0) {
-						r.push(`a Cellblock.`);
-					} else {
-						r.push(`${V.cellblockName}.`);
-					}
-				} else if (App.Data.Careers.Leader.attendant.includes(slave.career)) {
-					r.push(`${career}, giving ${him} potential as an Attendant for`);
-					if (V.spa === 0) {
-						r.push(`a Spa.`);
-					} else {
-						r.push(`${V.spaName}.`);
-					}
-				} else if (App.Data.Careers.Leader.matron.includes(slave.career)) {
-					r.push(`${career}, giving ${him} potential as a Matron for`);
-					if (V.nursery === 0 && V.nurseryNannies === 0) {
-						r.push(`a Nursery.`);
-					} else {
-						r.push(`${V.nurseryName}.`);
-					}
-				} else if (App.Data.Careers.Leader.nurse.includes(slave.career)) {
-					r.push(`${career}, giving ${him} potential as a Nurse for`);
-					if (V.clinic === 0) {
-						r.push(`a Clinic.`);
-					} else {
-						r.push(`${V.clinicName}.`);
-					}
-				} else if (App.Data.Careers.Leader.schoolteacher.includes(slave.career)) {
-					r.push(`${career}, giving ${him} potential as a Schoolteacher for`);
-					if (V.schoolroom === 0) {
-						r.push(`a Schoolroom.`);
-					} else {
-						r.push(`${V.schoolroomName}.`);
-					}
-				} else if (App.Data.Careers.Leader.stewardess.includes(slave.career)) {
-					r.push(`${career}, giving ${him} potential as a Stewardess for`);
-					if (V.servantsQuarters === 0) {
-						r.push(`a Servant's Quarters.`);
-					} else {
-						r.push(`${V.servantsQuartersName}.`);
-					}
-				} else if (App.Data.Careers.Leader.milkmaid.includes(slave.career)) {
-					r.push(`${career}, giving ${him} potential as a Milkmaid for`);
-					if (V.dairy === 0) {
-						r.push(`a Dairy.`);
-					} else {
-						r.push(`${V.dairyName}.`);
-					}
-				} else if (App.Data.Careers.Leader.farmer.includes(slave.career)) {
-					r.push(`${career}, giving ${him} potential as a Farmer for`);
-					if (V.farmyard === 0) {
-						r.push(`a Farmyard.`);
-					} else {
-						r.push(`${V.farmyardName}.`);
-					}
-				} else if (App.Data.Careers.Leader.madam.includes(slave.career)) {
-					r.push(`${career}, giving ${him} potential as a Madam for`);
-					if (V.brothel === 0) {
-						r.push(`a Brothel.`);
-					} else {
-						r.push(`${V.brothelName}.`);
-					}
-				} else if (App.Data.Careers.Leader.DJ.includes(slave.career)) {
-					r.push(`${career}, giving ${him} potential as a DJ for`);
-					if (V.club === 0) {
-						r.push(`a Club.`);
-					} else {
-						r.push(`${V.clubName}.`);
-					}
-				} else if (App.Data.Careers.Leader.HG.includes(slave.career)) {
-					r.push(`${career}, giving ${him} potential as a Head Girl.`);
-				} else if (App.Data.Careers.Leader.recruiter.includes(slave.career)) {
-					r.push(`${career}, giving ${him} potential as a recruiter.`);
-				} else if (App.Data.Careers.General.entertainment.includes(slave.career)) {
-					r.push(`${career}, giving ${him} a slight edge at entertainment.`);
-				} else if (App.Data.Careers.General.whore.includes(slave.career)) {
-					r.push(`${career}, giving ${him} a slight edge at sexual commerce.`);
-				} else if (App.Data.Careers.General.grateful.includes(slave.career)) {
-					r.push(`${career}, so ${he} can remember what it's like`);
-					if (slave.career === "prisoner") {
-						r.push(`no one looking out for you.`);
-					} else {
-						r.push(`to have the freedom to starve.`);
-					}
-				} else if (App.Data.Careers.General.menial.includes(slave.career)) {
-					r.push(`${career}, giving ${him} experience following orders.`);
-				} else if (App.Data.Careers.General.servant.includes(slave.career)) {
-					r.push(`${career}, giving ${him} a slight edge in housekeeping.`);
+		if (slave.career === "a slave") {
+			r.push(`${He} was a slave long before you obtained ${him}.`);
+		} else if (slave.career === "a slave since birth") {
+			r.push(`${He}'s been your slave ${his} entire life.`);
+		} else if (slave.career === "a meat toilet" || slave.career === "a cum dump") {
+			r.push(`${He} sees ${himself} as a cum receptacle.`);
+		} else if (slave.career === "a dairy cow") {
+			r.push(`${He}'s been broken into the belief that ${he} is nothing more than a cow to be milked and bred.`);
+		} else if (slave.career === "a breeding bull") {
+			r.push(`${He}'s been broken into the belief that ${he} is nothing more than a bull destined to fill fertile wombs with calves.`);
+		} else if (slave.career === "a breeder") {
+			r.push(`Before you obtained ${him}, ${he} was a breeding slave.`);
+		} else if (slave.career === "a bioreactor") {
+			r.push(`${He} has spent time as a cow in an industrial dairy, an experience that marked ${him} deeply.`);
+		} else {
+			r.push(`Before ${he} was a slave, ${he} was`);
+			if (App.Data.Careers.Leader.bodyguard.includes(slave.career)) {
+				r.push(`${career}, giving ${him} potential as a Bodyguard.`);
+			} else if (App.Data.Careers.Leader.wardeness.includes(slave.career)) {
+				r.push(`${career}, giving ${him} potential as a Wardeness for`);
+				if (V.cellblock === 0) {
+					r.push(`a Cellblock.`);
+				} else {
+					r.push(`${V.cellblockName}.`);
+				}
+			} else if (App.Data.Careers.Leader.attendant.includes(slave.career)) {
+				r.push(`${career}, giving ${him} potential as an Attendant for`);
+				if (V.spa === 0) {
+					r.push(`a Spa.`);
+				} else {
+					r.push(`${V.spaName}.`);
+				}
+			} else if (App.Data.Careers.Leader.matron.includes(slave.career)) {
+				r.push(`${career}, giving ${him} potential as a Matron for`);
+				if (V.nursery === 0 && V.nurseryNannies === 0) {
+					r.push(`a Nursery.`);
+				} else {
+					r.push(`${V.nurseryName}.`);
+				}
+			} else if (App.Data.Careers.Leader.nurse.includes(slave.career)) {
+				r.push(`${career}, giving ${him} potential as a Nurse for`);
+				if (V.clinic === 0) {
+					r.push(`a Clinic.`);
+				} else {
+					r.push(`${V.clinicName}.`);
+				}
+			} else if (App.Data.Careers.Leader.schoolteacher.includes(slave.career)) {
+				r.push(`${career}, giving ${him} potential as a Schoolteacher for`);
+				if (V.schoolroom === 0) {
+					r.push(`a Schoolroom.`);
+				} else {
+					r.push(`${V.schoolroomName}.`);
+				}
+			} else if (App.Data.Careers.Leader.stewardess.includes(slave.career)) {
+				r.push(`${career}, giving ${him} potential as a Stewardess for`);
+				if (V.servantsQuarters === 0) {
+					r.push(`a Servant's Quarters.`);
 				} else {
-					r.push(`${career}.`);
+					r.push(`${V.servantsQuartersName}.`);
 				}
+			} else if (App.Data.Careers.Leader.milkmaid.includes(slave.career)) {
+				r.push(`${career}, giving ${him} potential as a Milkmaid for`);
+				if (V.dairy === 0) {
+					r.push(`a Dairy.`);
+				} else {
+					r.push(`${V.dairyName}.`);
+				}
+			} else if (App.Data.Careers.Leader.farmer.includes(slave.career)) {
+				r.push(`${career}, giving ${him} potential as a Farmer for`);
+				if (V.farmyard === 0) {
+					r.push(`a Farmyard.`);
+				} else {
+					r.push(`${V.farmyardName}.`);
+				}
+			} else if (App.Data.Careers.Leader.madam.includes(slave.career)) {
+				r.push(`${career}, giving ${him} potential as a Madam for`);
+				if (V.brothel === 0) {
+					r.push(`a Brothel.`);
+				} else {
+					r.push(`${V.brothelName}.`);
+				}
+			} else if (App.Data.Careers.Leader.DJ.includes(slave.career)) {
+				r.push(`${career}, giving ${him} potential as a DJ for`);
+				if (V.club === 0) {
+					r.push(`a Club.`);
+				} else {
+					r.push(`${V.clubName}.`);
+				}
+			} else if (App.Data.Careers.Leader.HG.includes(slave.career)) {
+				r.push(`${career}, giving ${him} potential as a Head Girl.`);
+			} else if (App.Data.Careers.Leader.recruiter.includes(slave.career)) {
+				r.push(`${career}, giving ${him} potential as a recruiter.`);
+			} else if (App.Data.Careers.General.entertainment.includes(slave.career)) {
+				r.push(`${career}, giving ${him} a slight edge at entertainment.`);
+			} else if (App.Data.Careers.General.whore.includes(slave.career)) {
+				r.push(`${career}, giving ${him} a slight edge at sexual commerce.`);
+			} else if (App.Data.Careers.General.grateful.includes(slave.career)) {
+				r.push(`${career}, so ${he} can remember what it's like`);
+				if (slave.career === "prisoner") {
+					r.push(`no one looking out for you.`);
+				} else {
+					r.push(`to have the freedom to starve.`);
+				}
+			} else if (App.Data.Careers.General.menial.includes(slave.career)) {
+				r.push(`${career}, giving ${him} experience following orders.`);
+			} else if (App.Data.Careers.General.servant.includes(slave.career)) {
+				r.push(`${career}, giving ${him} a slight edge in housekeeping.`);
+			} else {
+				r.push(`${career}.`);
 			}
 		}
 		if (V.week - slave.weekAcquired >= 20 && slave.skill.entertainment >= 100) {
diff --git a/src/npc/descriptions/longSlave.js b/src/npc/descriptions/longSlave.js
index 5d55dfc4c661ad6edd17fec48e1c84099a99ea8d..095cbddbeda4543323c0baf59dd86bf6e6efc701 100644
--- a/src/npc/descriptions/longSlave.js
+++ b/src/npc/descriptions/longSlave.js
@@ -3,7 +3,7 @@
  * @param {FC.Desc.LongSlaveOptions} params
  * @returns {DocumentFragment}
  */
-App.Desc.longSlave = function(slave, {descType, market = 0, prisonCrime, noArt} = {}) {
+App.Desc.longSlave = function(slave, {descType, market = 0, marketText, noArt} = {}) {
 	const {
 		He, His, him, he, his
 	} = getPronouns(slave);
@@ -12,7 +12,6 @@ App.Desc.longSlave = function(slave, {descType, market = 0, prisonCrime, noArt}
 	let frag;
 	let p;
 	let r;
-	const applyLaw = (market !== "starting") && applyLawCheck(market);
 	SlaveStatClamp(slave);
 
 	descType = descType || (market ? DescType.MARKET : DescType.NORMAL);
@@ -37,17 +36,14 @@ App.Desc.longSlave = function(slave, {descType, market = 0, prisonCrime, noArt}
 		p.append("(", App.UI.DOM.makeElement('span', slave.custom.label, "custom-label"), ") ");
 	}
 
-	if (market && V.ui !== "start") {
-		if (applyLaw === 1) {
+	if (market && market !== "starting") {
+		if (applyLawCheck(market)) {
 			p.append(`has passed inspection to be sold in your arcology. `);
-			$(p).append(App.Desc.lawCompliance(slave, market));
-			p.append(` `);
 		} else {
 			p.append(`is for sale and is available to inspect. `);
-			if (prisonCrime) {
-				// reports a slave's crime in the criminal market
-				p.append(`${He} ${prisonCrime} `);
-			}
+		}
+		if (marketText) {
+			$(p).append(marketText, ` `);
 		}
 		$(p).append(reportGingering(slave));
 		el.appendChild(p);
diff --git a/src/npc/generate/generateGenetics.js b/src/npc/generate/generateGenetics.js
index 3d0ec6b553f8c56f85c2702e77a48c4f95882a80..1b5ffaeeaff97f3673e1baac4b46fb2268a2867f 100644
--- a/src/npc/generate/generateGenetics.js
+++ b/src/npc/generate/generateGenetics.js
@@ -104,8 +104,8 @@ globalThis.generateGenetics = (function() {
 		genes.inbreedingCoeff = ibc.kinship(mother, father);
 		genes.nationality = setNationality(father, mother);
 		genes.geneticQuirks = setGeneticQuirks(activeFather, activeMother, genes.gender);
-		genes.skin = setSkin(father, mother);
 		genes.race = setRace(father, mother);
+		genes.skin = setSkin(father, mother, genes.race);
 		genes.intelligence = setIntelligence(father, mother, activeMother, actor2, genes.inbreedingCoeff);
 		genes.face = setFace(father, mother, activeMother, actor2, genes.geneticQuirks, genes.inbreedingCoeff);
 		genes.faceShape = setFaceShape(father, mother, genes.geneticQuirks);
@@ -284,8 +284,12 @@ globalThis.generateGenetics = (function() {
 		return race;
 	}
 
-	// skin
-	function setSkin(father, mother) {
+	/**
+	 * @param {FC.Zeroable<FC.HumanState>} father
+	 * @param {FC.HumanState} mother
+	 * @param {FC.Race} race
+	 */
+	function setSkin(father, mother, race) {
 		/** @type {FC.Zeroable<string>} */
 		let fatherSkin = 0;
 		let dadSkinIndex;
@@ -316,21 +320,54 @@ globalThis.generateGenetics = (function() {
 			"ivory": 2,
 			"pure white": 1
 		};
-		const momSkinIndex = mother ? (skinToMelanin[mother.origSkin] || 13) : 8;
-		if (father !== 0) {
-			fatherSkin = father.origSkin;
-		} else if (fatherRace !== 0) {
-			fatherSkin = randomRaceSkin(fatherRace);
-		}
-		dadSkinIndex = fatherSkin !== 0 ? (skinToMelanin[fatherSkin] || 13) : 8;
-		const skinIndex = Math.round(Math.random() * (dadSkinIndex - momSkinIndex) + momSkinIndex);
+		const racialSkinToneBounds = {
+			"black": [21, 25],
+			"white": [4, 12],
+			"latina": [10, 22],
+			"indo-aryan": [9, 24],
+			"malay": [9, 24],
+			"pacific islander": [11, 24],
+			"amerindian": [11, 22],
+			"asian": [4, 15],
+			"middle eastern": [10, 21],
+			"semitic": [10, 21],
+			"southern european": [9, 15]
+		};
 
 		let prop = "";
-		for (prop in skinToMelanin) {
-			if (!skinToMelanin.hasOwnProperty(prop)) { continue; }
-			if (skinIndex >= skinToMelanin[prop]) { return prop; }
+		if (race === "catgirl") {
+			// pick a random catgirl color, slightly preferring the parents' coloration if applicable
+			const catgirlColors = [...App.Medicine.Modification.catgirlNaturalSkins];
+			if (mother.origSkin in catgirlColors) {
+				catgirlColors.push(mother.origSkin, mother.origSkin);
+			}
+			if (father && father.origSkin in catgirlColors) {
+				catgirlColors.push(father.origSkin, father.origSkin);
+			}
+			return catgirlColors.random();
+		} else {
+			// blend the father's and mother's skintones
+			const momSkinIndex = mother ? (skinToMelanin[mother.origSkin] || 13) : 8;
+			if (father !== 0) {
+				fatherSkin = father.origSkin;
+			} else if (fatherRace !== 0) {
+				fatherSkin = randomRaceSkin(fatherRace);
+			}
+			dadSkinIndex = fatherSkin !== 0 ? (skinToMelanin[fatherSkin] || 13) : 8;
+
+			let skinIndex = Math.round(Math.random() * (dadSkinIndex - momSkinIndex) + momSkinIndex);
+			if (race in racialSkinToneBounds) {
+				// don't exceed the skintone bounds of the already-selected race (note that "mixed race" does not have bounds)
+				skinIndex = Math.clamp(skinIndex, racialSkinToneBounds[race][0], racialSkinToneBounds[race][1]);
+			}
+
+			// find the skin name associated with the blended skintone
+			for (prop in skinToMelanin) {
+				if (!skinToMelanin.hasOwnProperty(prop)) { continue; }
+				if (skinIndex >= skinToMelanin[prop]) { return prop; }
+			}
+			return prop;
 		}
-		return prop; // skinIndex can be zero - now false?
 	}
 
 	/** Make sure a given eye color is a valid genetic eye color and not the result of some modification.
diff --git a/src/npc/generate/generateMarketSlave.js b/src/npc/generate/generateMarketSlave.js
index 577405342ca499b03d7c03f5cced989bc3e58ad8..4e298cb935d5d07ff5e1fa403f59d65a6e6b0271 100644
--- a/src/npc/generate/generateMarketSlave.js
+++ b/src/npc/generate/generateMarketSlave.js
@@ -1807,11 +1807,12 @@ globalThis.generateMarketSlave = function(market = "kidnappers", numArcology = 1
 			slave.butt = jsEither([2, 2, 3]);
 			slave.boobs = jsEither([100, 200]);
 			slave.dick = jsRandom(3, 5);
+			if (slave.foreskin > 0) {
+				slave.foreskin = slave.dick;
+			}
 			slave.balls = jsRandom(3, 5);
+			slave.scrotum = slave.balls;
 			slave.anus = 0;
-			slave.vagina = -1;
-			slave.preg = 0;
-			SetBellySize(slave);
 			slave.weight = 0;
 			slave.waist = jsRandom(-10, 30);
 			slave.skill.vaginal = 0;
@@ -1998,7 +1999,7 @@ globalThis.generateMarketSlave = function(market = "kidnappers", numArcology = 1
 			slave.faceShape = jsEither(["exotic", "sensual"]);
 			slave.pubertyXY = 1;
 			if (sisterAge === 1) {
-				slave.intelligence = -60;
+				slave.intelligence = random(-60, -30);
 				slave.hips = 0;
 				slave.face = jsEither([35, 35, 35, 75, 100]);
 				if (V.TFS.schoolUpgrade === 3 && V.TFS.compromiseWeek + 15 <= V.week) {
@@ -2026,7 +2027,7 @@ globalThis.generateMarketSlave = function(market = "kidnappers", numArcology = 1
 				slave.anus = 2;
 				slave.fetish = "submissive";
 			} else if (sisterAge === 2) {
-				slave.intelligence = -30;
+				slave.intelligence = random(-50, -20);
 				slave.hips = 1;
 				slave.face = jsEither([35, 35, 35, 75, 100]);
 				if (V.TFS.schoolUpgrade === 3 && V.TFS.compromiseWeek + 15 <= V.week) {
@@ -2054,7 +2055,7 @@ globalThis.generateMarketSlave = function(market = "kidnappers", numArcology = 1
 				slave.anus = 2;
 				slave.fetish = jsEither(["buttslut", "cumslut", "submissive"]);
 			} else if (sisterAge === 3) {
-				slave.intelligence = 0;
+				slave.intelligence = random(-15, 15);
 				slave.hips = 2;
 				slave.face = jsEither([35, 35, 75, 75, 100]);
 				if (V.TFS.schoolUpgrade === 3 && V.TFS.compromiseWeek + 15 <= V.week) {
@@ -2082,7 +2083,7 @@ globalThis.generateMarketSlave = function(market = "kidnappers", numArcology = 1
 				slave.anus = 2;
 				slave.fetish = jsEither(["buttslut", "cumslut"]);
 			} else if (sisterAge === 4) {
-				slave.intelligence = 30;
+				slave.intelligence = random(16, 50);
 				slave.hips = 2;
 				slave.face = jsEither([35, 75, 75, 100, 100]);
 				if (V.TFS.schoolUpgrade === 3 && V.TFS.compromiseWeek + 15 <= V.week) {
@@ -2110,7 +2111,7 @@ globalThis.generateMarketSlave = function(market = "kidnappers", numArcology = 1
 				slave.anus = 3;
 				slave.fetish = jsEither(["buttslut", "cumslut", "dom"]);
 			} else {
-				slave.intelligence = 60;
+				slave.intelligence = random(51, 95);
 				slave.hips = 2;
 				slave.face = jsEither([35, 75, 100, 100, 100]);
 				if (V.TFS.schoolUpgrade === 3 && V.TFS.compromiseWeek + 15 <= V.week) {
diff --git a/src/npc/generate/generateNewSlaveJS.js b/src/npc/generate/generateNewSlaveJS.js
index 7ce7bc3833a6df2ffacaceabba832784122418bc..691e0b754e72ccbf4a0d7039f7288f0488129ec1 100644
--- a/src/npc/generate/generateNewSlaveJS.js
+++ b/src/npc/generate/generateNewSlaveJS.js
@@ -1484,7 +1484,7 @@ globalThis.GenerateNewSlave = (function() {
 				slave.lips = jsRandom(5, 30);
 				slave.origSkin = jsEither(["pure black", "ebony", "black", "dark brown", "brown"]);
 				slave.origHColor = jsEither(["jet black", "black", "black", "black", "dark brown"]);
-				slave.hStyle = jsEither(["crinkled", "neat"]);
+				slave.hStyle = jsEither(["afro", "neat"]);
 				eyeColor(["brown"], true);
 				break;
 			case "white":
@@ -1553,7 +1553,7 @@ globalThis.GenerateNewSlave = (function() {
 				break;
 			case "catgirl":
 				slave.lips = jsRandom(5, 25);
-				slave.origSkin = jsEither(["white", "brown", "black", "red", "yellow", "black and white striped"]);
+				slave.origSkin = jsEither(App.Medicine.Modification.catgirlNaturalSkins);
 				slave.origHColor = jsEither(["black", "white", "golden", "red", "brown"]);
 				slave.hStyle = jsEither(["undercut", "neat"]);
 				slave.faceShape = "feline";
diff --git a/src/npc/generate/lawCompliance.js b/src/npc/generate/lawCompliance.js
index c4018d3685061a6a854f125a0c8a67649e4a5e17..ee337eda42df03c75828b834d6d68a09b46cb055 100644
--- a/src/npc/generate/lawCompliance.js
+++ b/src/npc/generate/lawCompliance.js
@@ -1,6 +1,6 @@
 /**
  * @param {App.Entity.SlaveState} slave
- * @param {number | string} [market=0]
+ * @param {FC.Zeroable<FC.SlaveMarketName | FC.SpecialMarketName>} [market=0]
  * @returns {string}
  */
 App.Desc.lawCompliance = function(slave, market = 0) {
@@ -146,6 +146,8 @@ App.Desc.lawCompliance = function(slave, market = 0) {
 	if (market !== "Elite Slave" && policies.countEugenicsSMRs() > 0) {
 		r.push(eugenicsSMRsCount());
 	}
+	
+	SlaveStatClamp(slave);
 
 	return r.join(" ");
 
diff --git a/src/npc/generate/newChildIntro.js b/src/npc/generate/newChildIntro.js
index b537b8e214fcd29fa4aa8920c97378ba0a94f58d..04f0ce1d96d0fa413c65c41a029d92b67b8891e9 100644
--- a/src/npc/generate/newChildIntro.js
+++ b/src/npc/generate/newChildIntro.js
@@ -104,7 +104,7 @@ App.UI.newChildIntro = function(slave) {
 			App.UI.DOM.link(
 				`Have your PA assign ${him} a random cat name`,
 				() => {
-					slave.slaveName = setup.catSlaveNames.random();
+					slave.slaveName = App.Data.misc.catSlaveNames.random();
 					slave.birthName = slave.slaveName;
 					jQuery(naming).empty().append(`${V.assistant.name} registers the new ${girl} as "${slave.slaveName}" in your registry.`);
 					jQuery(newName).empty().append(SlaveFullName(slave));
diff --git a/src/npc/generate/slaveGenerationJS.js b/src/npc/generate/slaveGenerationJS.js
index dba524cf81ef38b9813cf28cfc1f47dc16ddc669..aea42dcc3584ad1718f4eb8d694c52b8b8abf461 100644
--- a/src/npc/generate/slaveGenerationJS.js
+++ b/src/npc/generate/slaveGenerationJS.js
@@ -1447,13 +1447,9 @@ globalThis.applyAgeImplantOlder = function(slave) {
 
 /**
  * Determine whether a given market should apply SMR laws or not.
- * @param {FC.Zeroable<FC.SlaveMarketName>} [market]
- * @returns {number} [1|0]
+ * @param {FC.Zeroable<FC.SlaveMarketName | FC.SpecialMarketName>} [market]
+ * @returns {boolean}
  */
 globalThis.applyLawCheck = function(market) {
-	if (typeof market !== "string" || App.Data.misc.lawlessMarkets.includes(market)) {
-		return 0;
-	} else {
-		return 1;
-	}
+	return (typeof market === "string" && !App.Data.misc.lawlessMarkets.includes(market))
 };
diff --git a/src/npc/interaction/fDance.js b/src/npc/interaction/fDance.js
index d2f37221dc86f83e3d9b19f10c75e52438d4c9aa..7557c9e1ff7dece1df7953e11fbc2e119dd7f2e4 100644
--- a/src/npc/interaction/fDance.js
+++ b/src/npc/interaction/fDance.js
@@ -1136,7 +1136,7 @@ App.Interact.fDance = function(slave) {
 			} else if (slave.boobs > 2000) {
 				text.push(`${slave.slaveName}'s enormous breasts are bulging inside a beautiful halter top dress.`);
 			} else if (slave.boobs > 800) {
-				text.push(`${slave.slaveName}'s is draped inside a beautiful halter top dress, making ${his} big breasts the center of attention.`);
+				text.push(`${slave.slaveName}'s ${adjNoun} are draped inside a beautiful halter top dress, making them the center of attention.`);
 			} else if (slave.boobs < 300) {
 				text.push(`${slave.slaveName}`);
 				text.push(`is wearing a beautiful silky halter top dress, almost as if it was sculpted to hug ${his} flat chest.`);
diff --git a/src/npc/interaction/fFeelings.js b/src/npc/interaction/fFeelings.js
index b66c8267573edaa541834376624b4f7b08b5deda..134e5c6e6a8f4df940d39b1d578c8b6f305a88d6 100644
--- a/src/npc/interaction/fFeelings.js
+++ b/src/npc/interaction/fFeelings.js
@@ -1768,7 +1768,7 @@ App.Interact.fFeelings = function(slave) {
 							}
 						} else if (slave.toyHole !== "dick") {
 							if (slave.energy > 95 && V.PC.dick !== 0) {
-								text.push(`${Spoken(slave, `I love how taking your cock is my only job$${fuckSlavesLength() > 0 ? `, and I love having your other toys to have sex with too` : ``}.`)}`);
+								text.push(`${Spoken(slave, `I love how taking your cock is my only job${fuckSlavesLength() > 0 ? `, and I love having your other toys to have sex with too` : ``}.`)}`);
 							} else {
 								text.push(`${Spoken(slave, `It's nice being your ${girl}.`)}`);
 							}
diff --git a/src/npc/interaction/fRelation.js b/src/npc/interaction/fRelation.js
index 246c6d3123f0573f0a5a30a9b955772ee7df2537..52162e42907e5f7cb5354819ca392863b0a4d1d7 100644
--- a/src/npc/interaction/fRelation.js
+++ b/src/npc/interaction/fRelation.js
@@ -266,7 +266,7 @@ App.Interact.fRelation = function(slave, partner) {
 		} else {
 			r.push(`fuck`);
 		}
-		r.push(`whatever hole catches your eye next. They rarely break their intimate kissing, forming between the two of them a loving entity on the couch with all sorts of interesting parts to experience. They're sex slaves, and you're fucking them, but they're also lovers who are very comfortable in each others'`);
+		r.push(`whatever hole catches your eye next. They rarely break their intimate kissing, forming between the two of them a loving entity on the couch with all sorts of interesting parts to experience. They're sex slaves, and you're fucking them, but they're also lovers who are very comfortable in each other's`);
 		if (hasBothArms(slave) && hasBothArms(partner)) {
 			r.push(`arms,`);
 		} else if ((hasAnyArms(slave) && hasAnyArms(partner))) {
diff --git a/src/npc/interaction/passage/abort.js b/src/npc/interaction/passage/abort.js
index 9e47f9de3650530fa297ce3d4973e1bd1668c961..11727043ce376ba38594f95d3ae0c05eabc6d211 100644
--- a/src/npc/interaction/passage/abort.js
+++ b/src/npc/interaction/passage/abort.js
@@ -55,7 +55,7 @@ App.Interact.abort = function(slave) {
 		cashX(forceNeg(V.modCost), "slaveMod", slave);
 	}
 
-	slave.preg = (lastPregRule(slave, V.defaultRules)) ? -1 : 0;
+	slave.preg = rulesDemandContraceptives(slave, V.defaultRules) ? -1 : 0;
 
 	TerminatePregnancy(slave);
 	actX(slave, "abortions");
diff --git a/src/npc/interaction/passage/fSlaveImpreg.js b/src/npc/interaction/passage/fSlaveImpreg.js
index ca912b74ee684874bb5970a0b80110f10c1ca26b..a475a8b0e764dc5e2315e2af00d942cd6b08a128 100644
--- a/src/npc/interaction/passage/fSlaveImpreg.js
+++ b/src/npc/interaction/passage/fSlaveImpreg.js
@@ -234,9 +234,9 @@ App.Interact.fSlaveImpreg = function(slave, impregnatrix) {
 		slave.trust -= 5;
 		impregnatrix.devotion += 4;
 	} else if (slave.devotion <= 20 || impregnatrix.devotion <= 20) {
-		r.push(`You order ${slave.slaveName} onto the couch and tell ${impregnatrix.slaveName} to get on with it. They fuck mechanically, gazing with roiling emotions into each others' eyes. They do seem to come to some sort of a nonverbal understanding on the necessity of getting it done, and there is no real unhappiness in either of them when they finish and disentangle themselves, with ${impregnatrix.slaveName}'s rapidly softening dick slipping easily out of ${slave.slaveName}'s cum-filled ${assPussy}.`);
+		r.push(`You order ${slave.slaveName} onto the couch and tell ${impregnatrix.slaveName} to get on with it. They fuck mechanically, gazing with roiling emotions into each other's eyes. They do seem to come to some sort of a nonverbal understanding on the necessity of getting it done, and there is no real unhappiness in either of them when they finish and disentangle themselves, with ${impregnatrix.slaveName}'s rapidly softening dick slipping easily out of ${slave.slaveName}'s cum-filled ${assPussy}.`);
 	} else if (slave.devotion <= 50 || impregnatrix.devotion <= 50) {
-		r.push(`You order ${slave.slaveName} and ${impregnatrix.slaveName} to get on with it. They fuck mechanically at first, gazing with roiling emotions into each others' eyes. Eventually, they begin to enjoy the extreme intimacy of the act, finding between themselves a hint of a life before slavery, when men and women had sex within the bonds of marriage for the purpose of procreation${(superfetation === 1) ? `, even though one of them is already heavy with child` : ``}. They finish and resume life as slaves, the light of this intimacy diminishing, softening with ${impregnatrix.slaveName}'s dick and dripping away with the contents of ${slave.slaveName}'s cum-filled ${assPussy}.`);
+		r.push(`You order ${slave.slaveName} and ${impregnatrix.slaveName} to get on with it. They fuck mechanically at first, gazing with roiling emotions into each other's eyes. Eventually, they begin to enjoy the extreme intimacy of the act, finding between themselves a hint of a life before slavery, when men and women had sex within the bonds of marriage for the purpose of procreation${(superfetation === 1) ? `, even though one of them is already heavy with child` : ``}. They finish and resume life as slaves, the light of this intimacy diminishing, softening with ${impregnatrix.slaveName}'s dick and dripping away with the contents of ${slave.slaveName}'s cum-filled ${assPussy}.`);
 	} else if (slave.mpreg === 1) {
 		r.push(`The parents-to-be need little encouragement. They embrace happily and turn eagerly to the business of anal sex in`);
 		if (slave.belly + impregnatrix.belly >= 5000) {
@@ -244,7 +244,7 @@ App.Interact.fSlaveImpreg = function(slave, impregnatrix) {
 		} else {
 			r.push(`the`);
 		}
-		r.push(`cowgirl position. They take their time, humping slowly and gazing into each others' eyes. After a little while, though, ${slave.slaveName} looks over to where you're sitting, the invitation clear in ${his} eyes. As soon as you stand to come over, ${slave.slaveName} turns around on ${impregnatrix.slaveName}'s cock and opens wide for you. You and ${impregnatrix.slaveName} enjoy the`);
+		r.push(`cowgirl position. They take their time, humping slowly and gazing into each other's eyes. After a little while, though, ${slave.slaveName} looks over to where you're sitting, the invitation clear in ${his} eyes. As soon as you stand to come over, ${slave.slaveName} turns around on ${impregnatrix.slaveName}'s cock and opens wide for you. You and ${impregnatrix.slaveName} enjoy the`);
 		if (superfetation === 1) {
 			r.push(`gravid ${girl}`);
 		} else {
@@ -309,7 +309,7 @@ App.Interact.fSlaveImpreg = function(slave, impregnatrix) {
 		} else {
 			r.push(`the`);
 		}
-		r.push(`missionary position. They take their time, humping slowly and gazing into each others' eyes. After a little while, though, ${slave.slaveName} looks over ${impregnatrix.slaveName}'s shoulder to where you're sitting, the invitation clear in ${his} eyes. As soon as you stand to come over, they roll over without being ordered to`);
+		r.push(`missionary position. They take their time, humping slowly and gazing into each other's eyes. After a little while, though, ${slave.slaveName} looks over ${impregnatrix.slaveName}'s shoulder to where you're sitting, the invitation clear in ${his} eyes. As soon as you stand to come over, they roll over without being ordered to`);
 		if (canDoAnal(slave)) {
 			r.push(`present ${slave.slaveName}'s butthole.`);
 
diff --git a/src/npc/interaction/passage/fSlaveSlaveAss.js b/src/npc/interaction/passage/fSlaveSlaveAss.js
index a9a66a751b21dd8b60e48cd374e6e42fe39edf41..691fe98701eccc415b684dbf68ebb9ae3aca055e 100644
--- a/src/npc/interaction/passage/fSlaveSlaveAss.js
+++ b/src/npc/interaction/passage/fSlaveSlaveAss.js
@@ -406,11 +406,11 @@ App.Interact.fSlaveSlaveAss = function(slave, rapist) {
 		}
 		r.push(`to ${his} horror and resentment, while ${rapist.slaveName} is sleeping next to ${him} in a state of obvious satiation and bliss.`);
 	} else if (slave.devotion <= 20 || rapist.devotion <= 20) {
-		r.push(`You order ${slave.slaveName} onto the couch and tell ${rapist.slaveName} to get on with it. They fuck mechanically, gazing with roiling emotions into each others' eyes. They do seem to come to some sort of a non-verbal understanding on the necessity of getting it done, and there is no real unhappiness in either of them when they finish and disentangle themselves. As they clean themselves and exit, you notice ${rapist.slaveName} is looking a little more longingly at ${slave.slaveName}.`);
+		r.push(`You order ${slave.slaveName} onto the couch and tell ${rapist.slaveName} to get on with it. They fuck mechanically, gazing with roiling emotions into each other's eyes. They do seem to come to some sort of a non-verbal understanding on the necessity of getting it done, and there is no real unhappiness in either of them when they finish and disentangle themselves. As they clean themselves and exit, you notice ${rapist.slaveName} is looking a little more longingly at ${slave.slaveName}.`);
 	} else if (slave.devotion <= 50 || rapist.devotion <= 50) {
-		r.push(`You order ${slave.slaveName} and ${rapist.slaveName} to get on with it. They fuck mechanically at first, gazing with roiling emotions into each others' eyes. Eventually, they begin to enjoy the intimacy of the act, finding the shared pleasure between them comforting. They finish and resume life as slaves, the light of this intimacy diminishing, softening with rapist.slaveName's dick and dripping away with the contents of ${slave.slaveName}'s cum-filled asshole.`);
+		r.push(`You order ${slave.slaveName} and ${rapist.slaveName} to get on with it. They fuck mechanically at first, gazing with roiling emotions into each other's eyes. Eventually, they begin to enjoy the intimacy of the act, finding the shared pleasure between them comforting. They finish and resume life as slaves, the light of this intimacy diminishing, softening with rapist.slaveName's dick and dripping away with the contents of ${slave.slaveName}'s cum-filled asshole.`);
 	} else {
-		r.push(`The two slaves happily and eagerly get down to business. They take their time with foreplay, humping slowly and gazing into each others' eyes, exchanging kisses almost constantly. After a little while, ${slave.slaveName} looks over ${rapist.slaveName}'s shoulder to where you're sitting, the invitation clear in ${his} eyes. As soon as you stand to come over, they roll over without being ordered to`);
+		r.push(`The two slaves happily and eagerly get down to business. They take their time with foreplay, humping slowly and gazing into each other's eyes, exchanging kisses almost constantly. After a little while, ${slave.slaveName} looks over ${rapist.slaveName}'s shoulder to where you're sitting, the invitation clear in ${his} eyes. As soon as you stand to come over, they roll over without being ordered to`);
 		if (canDoAnal(slave) && slave.anus > 0) {
 			r.push(`present ${slave.slaveName}'s butthole.`);
 		} else {
diff --git a/src/npc/interaction/passage/fSlaveSlaveDick.js b/src/npc/interaction/passage/fSlaveSlaveDick.js
index 347894c22c7aa1bbf2b58d9d697463b717a923df..c133de0af1ced5a5421507bf89814205b7ee5c16 100644
--- a/src/npc/interaction/passage/fSlaveSlaveDick.js
+++ b/src/npc/interaction/passage/fSlaveSlaveDick.js
@@ -1062,7 +1062,7 @@ App.Interact.fSlaveSlaveDick = function(slave, rapist) {
 				}
 			}
 		}/* closes losing virginity */
-		r.push(`They fuck mechanically, gazing with roiling emotions into each others' eyes. They do seem to come to some sort of a nonverbal understanding on the necessity of getting it done, and there is no real unhappiness in either of them when they finish and disentangle themselves. As they clean themselves and exit, you notice ${rapist.slaveName} is looking a little more longingly at ${slave.slaveName}.`);
+		r.push(`They fuck mechanically, gazing with roiling emotions into each other's eyes. They do seem to come to some sort of a nonverbal understanding on the necessity of getting it done, and there is no real unhappiness in either of them when they finish and disentangle themselves. As they clean themselves and exit, you notice ${rapist.slaveName} is looking a little more longingly at ${slave.slaveName}.`);
 	} else if (slave.devotion <= 50 || rapist.devotion <= 50) {
 		r.push(`You order ${slave.slaveName} and ${rapist.slaveName} to get on with it.`);
 		if (rapist.vagina === 0) { /* losing virginity */
@@ -1126,7 +1126,7 @@ App.Interact.fSlaveSlaveDick = function(slave, rapist) {
 				}
 			}
 		}/* closes losing virginity */
-		r.push(`They fuck mechanically at first, gazing with roiling emotions into each others' eyes. Eventually, they begin to enjoy the intimacy of the act, finding the shared pleasure between them comforting. They finish and resume life as slaves, the light of this intimacy diminishing, softening with ${slave.slaveName}'s`);
+		r.push(`They fuck mechanically at first, gazing with roiling emotions into each other's eyes. Eventually, they begin to enjoy the intimacy of the act, finding the shared pleasure between them comforting. They finish and resume life as slaves, the light of this intimacy diminishing, softening with ${slave.slaveName}'s`);
 		if (slave.clit >= 4) {
 			r.push(`massive clit`);
 		} else {
diff --git a/src/npc/interaction/passage/fSlaveSlaveVag.js b/src/npc/interaction/passage/fSlaveSlaveVag.js
index 0203e8790bb50be5b255c2f9f7d0c2c43e46e051..89eefc31c85b10e6c1c2dd902dc928dd82b1aa84 100644
--- a/src/npc/interaction/passage/fSlaveSlaveVag.js
+++ b/src/npc/interaction/passage/fSlaveSlaveVag.js
@@ -417,11 +417,11 @@ App.Interact.fSlaveSlaveVag = function(slave, rapist) {
 		}
 		r.push(`to ${his} horror and resentment, while ${rapist.slaveName} is sleeping next to ${him} in a state of obvious satiation and bliss.`);
 	} else if (slave.devotion <= 20 || rapist.devotion <= 20) {
-		r.push(`You order ${slave.slaveName} onto the couch and tell ${rapist.slaveName} to get on with it. They fuck mechanically, gazing with roiling emotions into each others' eyes. They do seem to come to some sort of a non-verbal understanding on the necessity of getting it done, and there is no real unhappiness in either of them when they finish and disentangle themselves. As they clean themselves and exit, you notice ${rapist.slaveName} is looking a little more longingly at ${slave.slaveName}.`);
+		r.push(`You order ${slave.slaveName} onto the couch and tell ${rapist.slaveName} to get on with it. They fuck mechanically, gazing with roiling emotions into each other's eyes. They do seem to come to some sort of a non-verbal understanding on the necessity of getting it done, and there is no real unhappiness in either of them when they finish and disentangle themselves. As they clean themselves and exit, you notice ${rapist.slaveName} is looking a little more longingly at ${slave.slaveName}.`);
 	} else if (slave.devotion <= 50 || rapist.devotion <= 50) {
-		r.push(`You order ${slave.slaveName} and ${rapist.slaveName} to get on with it. They fuck mechanically at first, gazing with roiling emotions into each others' eyes. Eventually, they begin to enjoy the intimacy of the act, finding the shared pleasure between them comforting. They finish and resume life as slaves, the light of this intimacy diminishing, softening with ${rapist.slaveName}'s dick and dripping away with the contents of ${slave.slaveName}'s cum-filled pussy.`);
+		r.push(`You order ${slave.slaveName} and ${rapist.slaveName} to get on with it. They fuck mechanically at first, gazing with roiling emotions into each other's eyes. Eventually, they begin to enjoy the intimacy of the act, finding the shared pleasure between them comforting. They finish and resume life as slaves, the light of this intimacy diminishing, softening with ${rapist.slaveName}'s dick and dripping away with the contents of ${slave.slaveName}'s cum-filled pussy.`);
 	} else {
-		r.push(`The two slaves happily and eagerly get down to business. They take their time with foreplay, humping slowly and gazing into each others' eyes, exchanging kisses almost constantly. After a little while, ${slave.slaveName} looks over ${rapist.slaveName}'s shoulder to where you're sitting, the invitation clear in ${his} eyes. As soon as you stand to come over, they roll over without being ordered to`);
+		r.push(`The two slaves happily and eagerly get down to business. They take their time with foreplay, humping slowly and gazing into each other's eyes, exchanging kisses almost constantly. After a little while, ${slave.slaveName} looks over ${rapist.slaveName}'s shoulder to where you're sitting, the invitation clear in ${his} eyes. As soon as you stand to come over, they roll over without being ordered to`);
 		if (canDoAnal(slave) && slave.anus > 0) {
 			r.push(`present ${slave.slaveName}'s butthole.`);
 
diff --git a/src/npc/interaction/slaveOnSlaveFeeding/fSlaveFeed.js b/src/npc/interaction/slaveOnSlaveFeeding/fSlaveFeed.js
index c90db6763b6cc8c466688e5839804252ba9abc20..7f6d4b3ddf81e1fed545568dac6973fe4bf596fe 100644
--- a/src/npc/interaction/slaveOnSlaveFeeding/fSlaveFeed.js
+++ b/src/npc/interaction/slaveOnSlaveFeeding/fSlaveFeed.js
@@ -63,7 +63,7 @@ globalThis.FSlaveFeed = function(slave, milkTap) {
 				r.push(`It takes minimal effort to get ${his2} milk flowing.`);
 			}
 		} else if ((milkTap.fetish === "boobs") && (milkTap.fetishKnown === 1) && (milkTap.fetishStrength > 60) && (milkTap.devotion >= -20)) {
-			r.push(`This is very easy, since ${milkTap.slaveName} loves ${his2} tits played with and can't wait to get suckled.`);
+			r.push(`This is very easy, since ${milkTap.slaveName} loves having ${his2} tits played with and can't wait to get suckled.`);
 			if (milkTap.lactation > 1) {
 				r.push(`${He2} is practically gushing milk with excitement.`);
 			} else {
diff --git a/src/npc/startingGirls/startingGirlsPassage.js b/src/npc/startingGirls/startingGirlsPassage.js
index 9bed3894a16d6b2b59f4631aa21e1da4329ec789..1d2414a5c038cc2fd89e88c9b1e98a44d615b301 100644
--- a/src/npc/startingGirls/startingGirlsPassage.js
+++ b/src/npc/startingGirls/startingGirlsPassage.js
@@ -115,16 +115,13 @@ App.StartingGirls.passage = function() {
 				App.UI.DOM.appendNewElement("div", el, App.UI.DOM.link(
 					"Novice",
 					() => {
-						V.activeSlave = App.StartingGirls.generate();
+						V.activeSlave = App.StartingGirls.generate({minAge: 18, maxAge: 18});
 						V.activeSlave.skill.anal = 0;
 						V.activeSlave.skill.oral = 0;
 						V.activeSlave.skill.vaginal = 0;
 						V.activeSlave.skill.whoring = 0;
 						V.activeSlave.skill.entertainment = 0;
 						V.activeSlave.skill.combat = 0;
-						V.activeSlave.actualAge = 18;
-						V.activeSlave.visualAge = 18;
-						V.activeSlave.physicalAge = 18;
 						V.activeSlave.fetishKnown = 0;
 						V.activeSlave.attrKnown = 0;
 					},
@@ -354,7 +351,7 @@ App.StartingGirls.passage = function() {
 					App.UI.DOM.appendNewElement("div", el, App.UI.DOM.combineNodes(`Relative of slave: `, select));
 
 					const linkDiv = App.UI.DOM.appendNewElement("div", el, ``);
-					App.UI.DOM.appendNewElement("div", el, "Warning: related slaves will influence each others' opinion of you, and may become difficult to control if not properly broken.", "note");
+					App.UI.DOM.appendNewElement("div", el, "Warning: related slaves will influence each other's opinion of you, and may become difficult to control if not properly broken.", "note");
 					App.UI.DOM.appendNewElement("div", el, App.UI.DOM.passageLink("Back", "Starting Girls"));
 					jQuery(headerLinks).empty().append(el);
 				}
diff --git a/src/personalAssistant/assistantAppearance.js b/src/personalAssistant/assistantAppearance.js
index 27c9f3d2e606b204bc98d0dd94023a6a3bb61af2..735b3b718c43c146b40bed75e4ada21a3e7d88b9 100644
--- a/src/personalAssistant/assistantAppearance.js
+++ b/src/personalAssistant/assistantAppearance.js
@@ -208,7 +208,7 @@ globalThis.PersonalAssistantAppearance = function() {
 					}
 					r.push(`heat against their own.`);
 				} else {
-					r.push(`They're making heavenly love, kissing deeply and fingering each other voluptuously. They somehow make mutual masturbation look like a deeply sacred act, occasionally breaking their kiss to look into each others' eyes.`);
+					r.push(`They're making heavenly love, kissing deeply and fingering each other voluptuously. They somehow make mutual masturbation look like a deeply sacred act, occasionally breaking their kiss to look into each other's eyes.`);
 				}
 			} else {
 				r.push(`${HeA}'s reclined on one arm, idly stroking ${hisA} heavy abdomen with the other, a contemplative look on ${hisA} face. When ${heA} sees your glance ${heA} smiles placidly and sits upright, ${hisA} hands resting atop ${hisA} dripping breasts.`);
@@ -240,7 +240,7 @@ globalThis.PersonalAssistantAppearance = function() {
 				} else if (V.assistant.market.relationship === "nonconsensual") {
 					r.push(`${V.assistant.name} is wearing a strap-on, and is bullying ${hisA} school ${girlM} conquest's pussy. The market assistant's avatar orgasms loudly as you watch, and then claps both hands over ${hisM} mouth, crying a little, unwilling to give ${V.assistant.name} the satisfaction.`);
 				} else if (V.assistant.market.relationship === "incestuous") {
-					r.push(`They're making faces at each other and giggling, but as you watch them, this degenerates rapidly into clumsy kissing, groping of each others' breasts, and finally some enthusiastic tribbing.`);
+					r.push(`They're making faces at each other and giggling, but as you watch them, this degenerates rapidly into clumsy kissing, groping of each other's breasts, and finally some enthusiastic tribbing.`);
 				} else {
 					r.push(`${V.assistant.name} is giving the market assistant's avatar oral, and to go by the bespectacled ${girlM}'s gasping, is doing a good job. ${V.assistant.name} finishes and leans back, wiping ${hisA} mouth and grinning as ${hisA} lover bends over to return the favor.`);
 				}
@@ -282,7 +282,7 @@ globalThis.PersonalAssistantAppearance = function() {
 					}
 					r.push(`aids ${hisA} fecund sibling to ${hisM} feet and into ${hisA} waiting arms.`);
 				} else {
-					r.push(`They're making heavenly love, kissing deeply and fingering each other voluptuously. They somehow make mutual masturbation look like a deeply sacred act, occasionally breaking their kiss to look into each others' eyes.`);
+					r.push(`They're making heavenly love, kissing deeply and fingering each other voluptuously. They somehow make mutual masturbation look like a deeply sacred act, occasionally breaking their kiss to look into each other's eyes.`);
 				}
 			} else {
 				r.push(`${HeA}'s reclined on one arm, idly stroking ${hisA} huge abdomen with the other, a contemplative look on ${hisA} face. When ${heA} sees your glance ${heA} smiles placidly and sits upright, ${hisA} hands resting atop ${hisA} dripping breasts.`);
diff --git a/src/player/electiveSurgery.js b/src/player/electiveSurgery.js
index 55fd1e625c7099ed75a3063c5d7ed2106982131d..b78f705dac0f6716aad0e81e99a503f17b265af3 100644
--- a/src/player/electiveSurgery.js
+++ b/src/player/electiveSurgery.js
@@ -29,118 +29,33 @@ App.UI.electiveSurgery = function() {
 			`"You sure you want to mess with that lovely face?" ${heU} teases, caressing your cheek. "<span class="cash">${cashFormat(5000)}.</span> Also wouldn't recommend changing your eyes, face shape or skin color; some security systems get real uppity over things like that. Though I s'pose race and hair can fall under that as well, but hey, we don't handle racial surgery and this isn't a hair salon, so nothing to worry about, right? Yes, I'm certain your systems will recognize you after we finish working on you — give us some credit."`
 		], "div");
 		r.push(`You're <span class="intro question">${V.PC.actualAge} years old.</span>`);
-		if (V.PC.actualAge >= 65) {
+		if (V.PC.faceImplant) {
 			if (V.PC.visualAge > V.PC.actualAge) {
-				r.push(`You've had surgery to make yourself <span class="lime">look older.</span>`);
+				r.push(`You've had surgery to make yourself <span class="lime">look ${V.PC.visualAge > V.PC.actualAge ? 'older' : 'younger'}.</span>`);
 				linkArray.push(surgeryLink("Undo Facial surgery", "restoreFace", () => {
 					V.PC.faceImplant = 0;
 					V.PC.visualAge = V.PC.physicalAge;
 					cashX(forceNeg(5000), "PCmedical");
 				}));
-			} else if (V.PC.visualAge < V.PC.actualAge) {
-				r.push(`You've had surgery to make yourself <span class="lime">look younger.</span>`);
-				linkArray.push(surgeryLink("Undo Facial surgery", "restoreFace", () => {
-					V.PC.faceImplant = 0;
-					V.PC.visualAge = V.PC.physicalAge;
-					cashX(forceNeg(5000), "PCmedical");
-				}));
-			} else {
-				r.push(`You could benefit from a face lift.`);
-				if (V.PC.visualAge >= 25) {
-					linkArray.push(surgeryLink("Get a face lift", "ageDown", () => {
-						V.PC.faceImplant = 1;
-						cashX(forceNeg(5000), "PCmedical");
-					}));
-				}
-				linkArray.push(surgeryLink("Remodel your face to appear older", "ageUp", () => {
-					V.PC.faceImplant = 1;
-					cashX(forceNeg(5000), "PCmedical");
-				}));
 			}
-		} else if (V.PC.actualAge >= 50) {
-			if (V.PC.visualAge > V.PC.actualAge) {
-				r.push(`You've had surgery to make yourself <span class="lime">look older.</span>`);
-				linkArray.push(surgeryLink("Undo Facial surgery", "restoreFace", () => {
-					V.PC.faceImplant = 0;
-					V.PC.visualAge = V.PC.physicalAge;
-					cashX(forceNeg(5000), "PCmedical");
-				}));
-			} else if (V.PC.visualAge < V.PC.actualAge) {
-				r.push(`You've had surgery to make yourself <span class="lime">look younger.</span>`);
-				linkArray.push(surgeryLink("Undo Facial surgery", "restoreFace", () => {
-					V.PC.faceImplant = 0;
-					V.PC.visualAge = V.PC.physicalAge;
-					cashX(forceNeg(5000), "PCmedical");
-				}));
-			} else {
+		} else {
+			if (V.PC.actualAge >= 65 || V.PC.actualAge >= 50) {
 				r.push(`You could benefit from a face lift.`);
-				if (V.PC.visualAge >= 25) {
-					linkArray.push(surgeryLink("Get a face lift", "ageDown", () => {
-						V.PC.faceImplant = 1;
-						cashX(forceNeg(5000), "PCmedical");
-					}));
-				}
-				linkArray.push(surgeryLink("Remodel your face to appear older", "ageUp", () => {
-					V.PC.faceImplant = 1;
-					cashX(forceNeg(5000), "PCmedical");
-				}));
-			}
-		} else if (V.PC.actualAge >= 35) {
-			if (V.PC.visualAge > V.PC.actualAge) {
-				r.push(`You've had surgery to make yourself <span class="lime">look older.</span>`);
-				linkArray.push(surgeryLink("Undo Facial surgery", "restoreFace", () => {
-					V.PC.faceImplant = 0;
-					V.PC.visualAge = V.PC.physicalAge;
-					cashX(forceNeg(5000), "PCmedical");
-				}));
-			} else if (V.PC.visualAge < V.PC.actualAge) {
-				r.push(`You've had surgery to make yourself <span class="lime">look younger.</span>`);
-				linkArray.push(surgeryLink("Undo Facial surgery", "restoreFace", () => {
-					V.PC.faceImplant = 0;
-					V.PC.visualAge = V.PC.physicalAge;
-					cashX(forceNeg(5000), "PCmedical");
-				}));
-			} else {
+			} else if (V.PC.actualAge >= 35) {
 				r.push(`You could go for a face lift, though making yourself look older could be useful.`);
-				if (V.PC.visualAge >= 25) {
-					linkArray.push(surgeryLink("Get a face lift", "ageDown", () => {
-						V.PC.faceImplant = 1;
-						cashX(forceNeg(5000), "PCmedical");
-					}));
-				}
-				linkArray.push(surgeryLink("Remodel your face to appear older", "ageUp", () => {
-					V.PC.faceImplant = 1;
-					cashX(forceNeg(5000), "PCmedical");
-				}));
-			}
-		} else {
-			if (V.PC.visualAge > V.PC.actualAge) {
-				r.push(`You've had surgery to make yourself <span class="lime">look older.</span>`);
-				linkArray.push(surgeryLink("Undo Facial surgery", "restoreFace", () => {
-					V.PC.faceImplant = 0;
-					V.PC.visualAge = V.PC.physicalAge;
-					cashX(forceNeg(5000), "PCmedical");
-				}));
-			} else if (V.PC.visualAge < V.PC.actualAge) {
-				r.push(`You've had surgery to make yourself <span class="lime">look younger.</span>`);
-				linkArray.push(surgeryLink("Undo Facial surgery", "restoreFace", () => {
-					V.PC.faceImplant = 0;
-					V.PC.visualAge = V.PC.physicalAge;
-					cashX(forceNeg(5000), "PCmedical");
-				}));
 			} else {
 				r.push(`You could undergo facial surgery to make yourself look older${(V.PC.visualAge >= 25) ? ", though you could also make yourself look even younger" : ""}.`);
-				if (V.PC.visualAge >= 25) {
-					linkArray.push(surgeryLink("Remodel your face to appear younger", "ageDown", () => {
-						V.PC.faceImplant = 1;
-						cashX(forceNeg(5000), "PCmedical");
-					}));
-				}
-				linkArray.push(surgeryLink("Remodel your face to appear older", "ageUp", () => {
+			}
+			if (V.PC.visualAge >= 25) {
+					linkArray.push(surgeryLink(`${V.PC.actualAge < 35 ? 'Remodel your face to appear younger' : 'Get a face lift'}`, "ageDown", () => {
 					V.PC.faceImplant = 1;
 					cashX(forceNeg(5000), "PCmedical");
 				}));
 			}
+			linkArray.push(surgeryLink("Remodel your face to appear older", "ageUp", () => {
+				V.PC.faceImplant = 1;
+				cashX(forceNeg(5000), "PCmedical");
+			}));
 		}
 		App.Events.addNode(p, r, "div");
 		p.append(App.UI.DOM.makeElement("div", App.UI.DOM.generateLinksStrip(linkArray)));
@@ -168,7 +83,7 @@ App.UI.electiveSurgery = function() {
 		if (V.PC.skin !== V.PC.origSkin) {
 			choices.push({key: V.PC.origSkin, name: capFirstChar(`Restore natural ${V.PC.origSkin}`)});
 		}
-		for (const skin of App.Medicine.Modification.naturalSkins) {
+		for (const skin of [...App.Medicine.Modification.naturalSkins, ...App.Medicine.Modification.dyedSkins]) {
 			choices.push({key: skin, name: capFirstChar(skin)});
 		}
 
diff --git a/src/player/js/PlayerState.js b/src/player/js/PlayerState.js
index 473896486426854ee720a4a22c19451aa37ff69f..a62186dcdf3c14b92e965b1d826d98e05477b88f 100644
--- a/src/player/js/PlayerState.js
+++ b/src/player/js/PlayerState.js
@@ -995,7 +995,7 @@ App.Entity.PlayerState = class PlayerState {
 		 */
 		this.broodmotherFetuses = 0;
 		/**
-		 * If broodmother implant set to pause it 's work.
+		 * If broodmother implant set to pause its work.
 		 *
 		 * 1: implant on pause !1: working.
 		 *
diff --git a/src/pregmod/FCTV/FCTVshows.js b/src/pregmod/FCTV/FCTVshows.js
index 3fed92388bc648ed31c802b18b9cdf01cbc0ff7b..2cd771996fe479a6d871d4bea2853a11c208fbdc 100644
--- a/src/pregmod/FCTV/FCTVshows.js
+++ b/src/pregmod/FCTV/FCTVshows.js
@@ -1634,24 +1634,24 @@ App.Data.FCTV.channels = {
 					r.push(`<p>The woman posed in the mirror. She was tall for a woman, fair skinned, and wore a keyhole sweater dress. Her scarlet hair was done in a braid down her back and her plump lips were covered in ruby red lipstick. She was slender, but not intolerably so; at the very least, she filled out her dress enough to avoid being arrested for indecency. All in all, the woman's reflection made for a pleasant picture. The only thing detracting from this scene was her glare.</p>`);
 					r.push(`<p>"Hey Scott, do you have anything a bit more conservative?" The woman asked. "We've been over this." Scott said. "Not showing off your breasts is seen as very rude here." Scott frowned, "Well, that's not entirely correct, but it is seen as distinctly unfriendly; the only girls who don't show some cleavage are frigid cunts and old-worlders fresh off the boat."</p>`);
 					r.push(`<p>The woman kept glaring in the mirror. "I understand that, but what is the point of this?" she said, pulling at the slits in the fabric at her sides. "Oh, those are for girls who want get milked on-the-go and not disrupt the view of their cleavage. Also, for this." The man said as he reached through the slits to give her breasts a polite squeeze.</p>`);
-					r.push(`<p>The woman wriggled out of the man's grasp and turned to face him. She attempted to cross her arms across her chest in a protective fashion, but all she managed to do was make her breasts bulge enticingly. Scott sighed and pinched the bridge of his nose, "You really need to work on that." he said. She just glared at him "I'll never be able to take you out in public, much less find you a job, if you keep acting like that when people try to greet you."</p>`);
+					r.push(`<p>The woman wriggled out of the man's grasp and turned to face him. She attempted to cross her arms across her chest in a protective fashion, but all she managed to do was make her breasts bulge enticingly. Scott sighed and pinched the bridge of his nose, "You really need to work on that." he said. She just glared at him. "I'll never be able to take you out in public, much less find you a job, if you keep acting like that when people try to greet you."</p>`);
 					r.push(`<p>The woman huffed and turned back to continue glaring at the mirror. "Don't you huff at me young lady." Scott said, his face a stern mask. "If someone doesn't give you a squeeze or press breasts with you the first time you meet, it doesn't mean that they hold your beloved old-world values, it means they're snubbing you." The woman wilted at his words and turned around, opening her mouth to speak.</p>`);
 					r.push(`<p>Any response died on her lips as a little golden haired girl bounced into the room. She wore a thong printed with cartoon cows and nothing else. An old-worlder would say the girl looked 'absurd' or 'cartoonishly proportioned', an arcology citizen would say the old-worlder looked like they needed some cheap tooth removal. In the girl's arms she carried a bundle of clothing. "Daddy, I got more clothes for Cathy." The little girl said, presenting the bundle.</p>`);
-					r.push(`<p>Scott's face softened and gave the little girl's breasts a gentle squeeze before taking the bundle. "Thank you, sweetheart." He said before presenting the bundle to Cathy. His daughter beamed and then jiggled over to sit on the love seat across from the mirror. He followed her to the love seat, sat down, and lifted her on to his lap. She squealed and giggled before wiggling her bottom on his crotch. The girl grabbed her father's hands and guided them to her breasts. Scott obligingly started groping her. Cathy just stared at the bundle with such intensity one might think she was trying to force the clothes to change into jeans and a hoodie with force of will alone. "Go try it on." Scott said. Cathy sighed, walked into the adjacent bathroom and closed the door.</p>`);
+					r.push(`<p>Scott's face softened and gave the little girl's breasts a gentle squeeze before taking the bundle. "Thank you, sweetheart." He said before presenting the bundle to Cathy. His daughter beamed and then jiggled over to sit on the love seat across from the mirror. He followed her to the love seat, sat down, and lifted her on to his lap. She squealed and giggled before wiggling her bottom on his crotch. The girl grabbed her father's hands and guided them to her breasts. Scott obligingly started groping her. Cathy just stared at the bundle with such intensity one might think she was trying to force the clothes to change into jeans and a hoodie with force of will alone. "Go try it on," Scott said. Cathy sighed, walked into the adjacent bathroom, and closed the door.</p>`);
 					r.push(`<p>"Daddy, why does Cathy have to leave when she changes clothes?" the little girl questioned. Scott continued groping his daughter and thought for a moment before replying. "Sarah, Cathy comes from a very different place and they do things differently there. From her perspective, she is a stranger in an even stranger land." He gave Sarah's breasts a firm squeeze before continuing. "She'll do things that will confuse and irritate you, but I want you to keep being patient with her, okay? "</p>`);
 					r.push(`<p>After a moment, she nodded her head and begrudgingly said, "Okay". Scott smiled at his daughter and kissed the crown of her head before giving her another firm squeeze. "Thank you, sweetheart." Scott grinned and his voice took on a tone that only a parent can manage. "Hey, who's my cuddly moo cow?" Sarah blushed, but said nothing. "Who's my cuddly moo cow?" he asked again, his tone reaching diabetes inducing levels. "I am," she said softly. "You are!" he declared before giving her breasts a jiggle and another kiss on her crown.</p>`);
 					r.push(`<p>A moment past in companionable silence before Scott remembered something. "Hey sweetheart, why didn't Sadie come back with you?" Sarah made a sound of surprise and said, "Oh, mommy got a bit drippy getting into her exo and needed Sadie for licky. Mommy also wants you to pound her boobies after you're done with Cathy."</p>`);
-					r.push(`<p>"Alright, June will help you take care of your morning milkies while Sadie and I tend to your mother," he said. "But I wanna watch!" she pleaded. He shook his head and said, "You need to have your milkies and mommy's going to use the bedroom milker, so you're going to use the one in the kitchen." "I can use my backpack milker" she said quickly. He gave her a look and his voice took on a wry tone. "Weren't you just telling me that your milker is old and we need to get you a new one? I guess you'll have to wait until then." Sarah looked up at her father, unshed tears in her eyes. "Please?" she pleaded. Scott sighed deeply and said, "Alright, go get your milker after we're done here." Sarah gave her father a sunny smile and cheered.</p>`);
+					r.push(`<p>"Alright, June will help you take care of your morning milkies while Sadie and I tend to your mother," he said. "But I wanna watch!" she pleaded. He shook his head and said, "You need to have your milkies and mommy's going to use the bedroom milker, so you're going to use the one in the kitchen." "I can use my backpack milker," she said quickly. He gave her a look and his voice took on a wry tone. "Weren't you just telling me that your milker is old and we need to get you a new one? I guess you'll have to wait until then." Sarah looked up at her father, unshed tears in her eyes. "Please?" she pleaded. Scott sighed deeply and said, "Alright, go get your milker after we're done here." Sarah gave her father a sunny smile and cheered.</p>`);
 					r.push(`<p>Scott called out to Cathy, "Come on out and give us a look!" The door to the bathroom opened and Cathy stepped out, tugging at her skirt. The clothing was a simple blouse and skirt affair with a Holstein pattern. In truth, it didn't look like something that would offend old world sensibilities, except for the fact the skirt was so short that her frilly panties were in plain view.</p>`);
 					r.push(`<p>"Do you have a skirt that's a little longer?" Cathy said and tugged at her skirt again. "It's supposed to look like that." Scott explained. Cathy looked skeptical. "Really?" she asked. Sarah gave Cathy a look that asked if she really was that stupid. "Why would you wear pretty panties and never show anyone?" She asked rhetorically. Cathy started on a hot retort, but was interrupted by Scott. "We'll stop for now and pick this up later. You can put your new clothes away." Cathy sighed, picked up her clothes, and walked out the door.</p>`);
-					r.push(`<p>Scott stood up and set his daughter on her feet. She turned her back to her father, bent over slightly, and wiggled her bottom meaningfully. Scott smacked her right butt cheek and said, "Off you go." Sarah didn't move. "Daddy" she said pleadingly and wiggled her bottom again. He smacked her left butt cheek. She giggled happily and jiggled out the door. Scott smiled at his daughter's antics, shook his head, and made his way to his bedroom.</p>`);
+					r.push(`<p>Scott stood up and set his daughter on her feet. She turned her back to her father, bent over slightly, and wiggled her bottom meaningfully. Scott smacked her right butt cheek and said, "Off you go." Sarah didn't move. "Daddy," she said pleadingly and wiggled her bottom again. He smacked her left butt cheek. She giggled happily and jiggled out the door. Scott smiled at his daughter's antics, shook his head, and made his way to his bedroom.</p>`);
 					return r.join(" ");
 				}
 			},
 			{
 				get text() {
 					const r = [];
-					r.push(`<p>On his way to the bedroom he passed through the kitchen and caught sight of June preparing lunch. She was tall for a woman. Her hair was a golden blonde and her figure spoke of her ongoing romance with growth drugs. She wore a black thong and an apron embroidered with the words 'Milk the cook'. Which was rather strange considering she normally wore her 'Rape the cook' apron on Saturdays. But he guessed she was still raw from last night.</p>`);
+					r.push(`<p>On his way to the bedroom he passed through the kitchen and caught sight of June preparing lunch. She was tall for a woman. Her hair was a golden blonde and her figure spoke of her ongoing romance with growth drugs. She wore a black thong and an apron embroidered with the words 'Milk the cook', which was rather strange, considering she normally wore her 'Rape the cook' apron on Saturdays. But he guessed she was still raw from last night.</p>`);
 					r.push(`<p>He came up behind her and reached into her apron to give her breasts a squeeze. June made a sound of pleasure in response. "Hello master, are you finished with Cathy for today?" He set his chin on the top of her head. "For the moment. After I've tended to Annie and had lunch, I think I'll take Cathy out for a bit. Maybe take the whole family out for ice cream."</p>`);
 					r.push(`<p>June paused for a moment. "You think she's ready? She still seems rather... willful." She said uncertainly. He gave her breasts a comforting squeeze. "Cathy may be a bit hardheaded, but I don't think she'll make too much of a scene with me there. And besides, I think it's time she had a taste of the local culture. There's not much of a point in teaching her etiquette if she freaks out at every Lactation Station."</p>`);
 					r.push(`<p>She still looked somewhat uncertain, but nodded her head. "Whatever you think is best, master." Scott kissed the crown of her head and said, "Don't worry about it. It'll be fine. I'm not throwing her in the deep end yet." He gave her breasts a reassuring squeeze, gave both of her ass cheeks a quick slap and made his way to the bedroom.</p>`);
@@ -1661,12 +1661,12 @@ App.Data.FCTV.channels = {
 					r.push(`<p>For a moment, the only sounds in the room were the soft 'whir' of a milker and the muffled hum of Sadie's vibrator. Annie's breath hitched from Sadie's ministrations and responded, "That's good sweetie, bring it here." The door to the bathroom swung open and Sarah walked into the room. She had lost her thong and was wearing a backpack printed with cartoon farm animals. Flexible tubing snaked out of the pack and attached two cups on her breasts. In her hands was a bottle of edible lube and a bright pink rounded cylinder with the words 'Her First Vibrator' printed in a saccharine font on the base.</p>`);
 					r.push(`<p>Sarah looked at both her parents, then tilted her head and frowned. "You and daddy were playing that weird game again, weren't you?" Scott and Annie looked at each other; silently communicating in a way only parents can. Annie looked at her daughter and said, "When you're a bit older you're going to want to play those games too." Sarah looked unconvinced. "Anyway, you got the lube, are both your milker and vibrator charged?" Annie asked. Sarah bobbed her head. "Then why don't you lube up daddy?" Sarah bobbed her head again and knelt at the side of the bed.</p>`);
 					r.push(`<p>Scott quickly undressed and sat at the edge of the bed. Sarah knelt between her father's legs. She begins softly licking her father's cock, her tongue sliding along its length and gently swirling around its head, her mouth making lewd noises. She runs her tongue on the underside of her dad's cock, taking him into her mouth and beginning to give him a slow blowjob.</p>`);
-					r.push(`<p>Scott felt himself slowly harden to full mast. He resisted the urge to pull her down till her nose touched his crotch and said, "That's good sweetheart, now use the lube." Sarah pulled herself off his cock with a lewd pop and picked up the bottle of lube. She squirted a generous amount of it into one hand, rubbed both hands together, and began to stroke his cock. "You're doing a good job sweetie, did you help mommy too?" he asked. Annie nodded "She did a very good job and was very thorough." Sarah preened at her parents praise and said, "It wasn't easy. I had to use a whole bottle to do mommy." Scott turned his head to his wife and raised an eyebrow. Annie gave him a lewd grin and her blush slowly spread down her chest, but said nothing.</p>`);
+					r.push(`<p>Scott felt himself slowly harden to full mast. He resisted the urge to pull her down till her nose touched his crotch and said, "That's good sweetheart, now use the lube." Sarah pulled herself off his cock with a lewd pop and picked up the bottle of lube. She squirted a generous amount of it into one hand, rubbed both hands together, and began to stroke his cock. "You're doing a good job sweetie, did you help mommy too?" he asked. Annie nodded "She did a very good job and was very thorough." Sarah preened at her parents' praise and said, "It wasn't easy. I had to use a whole bottle to do mommy." Scott turned his head to his wife and raised an eyebrow. Annie gave him a lewd grin and her blush slowly spread down her chest, but said nothing.</p>`);
 					r.push(`<p>Scott patted his daughter's head. "Okay sweetie, take a seat." Sarah gave her dad's cock a kiss on the head, grabbed her vibrator and sat down on the couch across from the bed. She rubbed the lube on her hands over her crotch and her vibrator before licking off what remained. Scott began to stand, but paused; an impish smile spread across his face. "Before we begin, I have a question for mommy." He reached into a night table and pulled out an odd remote and a Wartenberg pinwheel.</p>`);
 					r.push(`<p>"Oh, and what would that be?" Annie said in a knowing tone, her eyes twinkling. Scott just grinned and pressed a button on the remote. Annie squealed as her exosuit shifted her forward onto her breasts. Scott craned his head to look behind wife. "You alright down there, Sadie?" He cocked his head a bit more and barely made out a thumbs up beyond the horizon of his wife's ass. "Good. Now-" He rolled the pinwheel across her arm to collar bone and Annie gasped. "Why did you ask Sadie to stay behind when I sent her off for clothes?"</p>`);
 					r.push(`<p>Annie inhaled sharply and said, "I needed to cum." He ran the pinwheel slowly down her collarbone to her breast. "And what had you so worked up you needed Sadie?" Her breathing began to speed up. "I nearly threw my back out getting into my exo." He raised an eyebrow and ran the pinwheel in winding loops across her breasts, goose bumps forming in its wake. "Oh? And why would that get you so hot and bothered?" he asked in a knowing tone.</p>`);
-					r.push(`<p>Annie's breath became more ragged and began to babble. "I'm-I'm so big, so big. I'm a breast obsessed cowslut." She held the smart material of her exo in a white knuckle grip. "I came when I couldn't see my feet anymore. I masturbated seven times when I first got stuck in a door. I once wrapped my tits around a guard rail and humped it for three hours. I came buckets to the look on Cathy's face when she met me." Annie gave him a look of pure want. "I need to cum. Mommy needs her boobs pounded." She pleaded. Scott smiled and pressed a button on the remote that caused the smart material to press Annie's breasts into a fuckable channel. He positioned himself and said, "Honesty is to be rewarded", then thrust himself into her.</p>`);
-					r.push(`<p>She gasped and moaned loudly. Her mewling was almost loud enough to match the lewd noises of flesh against flesh. Scott began to increase his speed. With each thrust sending ripples through her body. With how worked up she was, it wasn't long before her moaning increased in volume until she suddenly gasped, her body tensed up, shuddered and then relaxed. Annie's tongue lolled out and her eyes fluttered.</p>`);
+					r.push(`<p>Annie's breath became more ragged and began to babble. "I'm-I'm so big, so big. I'm a breast obsessed cowslut." She held the smart material of her exo in a white knuckle grip. "I came when I couldn't see my feet anymore. I masturbated seven times when I first got stuck in a door. I once wrapped my tits around a guard rail and humped it for three hours. I came buckets to the look on Cathy's face when she met me." Annie gave him a look of pure want. "I need to cum. Mommy needs her boobs pounded," she pleaded. Scott smiled and pressed a button on the remote that caused the smart material to press Annie's breasts into a fuckable channel. He positioned himself and said, "Honesty is to be rewarded," then thrust himself into her.</p>`);
+					r.push(`<p>She gasped and moaned loudly. Her mewling was almost loud enough to match the lewd noises of flesh against flesh. Scott began to increase his speed, with each thrust sending ripples through her body. With how worked up she was, it wasn't long before her moaning increased in volume until she suddenly gasped, her body tensed up, shuddered and then relaxed. Annie's tongue lolled out and her eyes fluttered.</p>`);
 					r.push(`<p>Scott snorted in amusement and was about to continue when he felt a tongue licking him. He looked down to see Sarah. She looked at him with pleading eyes "Daddy, I need it." Scott sighed, but smiled softly at his daughter and said, "Okay, how do you want it?" She thought for a second before saying, "I want puss-puss." He nodded, picked her up and laid his daughter atop his wife's vast breasts.</p>`);
 					r.push(`<p>He teased himself against her cunny and then began to ease himself into her. She gasped at the intrusion and began to tease her own nipples, milk slowly leaking out of them. As he eased into her tight cunny, he could feel his own orgasm building. He paused for a moment and then began to move. It didn't take long for his orgasm to build again. He increased the speed of his thrusts until he came inside his daughter. He continued until she tensed, her breasts spraying milk violently, and fell limp.</p>`);
 					r.push(`<p>Scott leaned into his wife's breasts to bask in the afterglow. While Annie had coaxed Sarah to turn around, pulled Sarah's cunny to her face and began to slowly eat her daughter out. Sarah just lay bonelessly atop her mother's breasts.</p>`);
@@ -1679,7 +1679,7 @@ App.Data.FCTV.channels = {
 			{
 				get text() {
 					const r = [];
-					r.push(`<p>After a light lunch, the family assembled in the entry hall. Sarah rode in her mother's cleavage, playing with a tablet as they waited on Cathy. When Cathy made her way into the hall Scott frowned at her. "You're not going out dressed like that." Cathy looked down at herself. "What's wrong with this? This shows of my body well enough, right?"</p>`);
+					r.push(`<p>After a light lunch, the family assembled in the entry hall. Sarah rode in her mother's cleavage, playing with a tablet as they waited on Cathy. When Cathy made her way into the hall Scott frowned at her. "You're not going out dressed like that." Cathy looked down at herself. "What's wrong with this? This shows off my body well enough, right?"</p>`);
 					r.push(`<p>She wore jeans and a T-shirt made a few sizes too small by her regimen of growth drug. "Why don't you put on that bikini I gave you?" Cathy just furrowed her brow and said nothing. Scott sighed and said, "Just don't wear pants and show some cleavage. For god's sake, people show more skin at funerals than you do." Cathy frowned, but went back to change. When she came back she was wearing the Holstein pattern skirt and blouse he had given her earlier. "You're going to wear that?" he asked. "What's wrong now?" He raised his hands in a placating gesture. "Nothing, nothing. Let's go."</p>`);
 					r.push(`<p>The family left, made their way to an elevator and rode it down a few floors before exiting. As they turned the corner onto the street, the creamery came into sight. The family continued onward until Sarah spoke up. "Daddy, where's Cathy?" Scott looked back and saw that Cathy had stopped walking a few`);
 					if (V.showInches === 2) {
@@ -1688,16 +1688,16 @@ App.Data.FCTV.channels = {
 						r.push(`meters`);
 					}
 					r.push(`back. She stood staring wide eyed at the creamery.</p>`);
-					r.push(`<p>The LCD screen above the entrance wasn't anything too eye catching. It proudly displayed the words Blue Barn Creamery & Grocery in bright letters. An ever changing list of advertisements and new products on sale scrolled along the bottom. Truly, it could have fit-in with old world signs if no one looked too closely at the words on screen. What was truly eye catching is what surrounded it.</p>`);
+					r.push(`<p>The LCD screen above the entrance wasn't anything too eye catching. It proudly displayed the words Blue Barn Creamery & Grocery in bright letters. An ever changing list of advertisements and new products on sale scrolled along the bottom. Truly, it could have fit in with old world signs if no one looked too closely at the words on screen. What was truly eye-catching is what surrounded it.</p>`);
 					r.push(`<p>Dozens of cowslaves mounted in milking frames surrounded the screen up and down the building. The building curved out to dangle slaves into the street. Whoever created the display had been thoughtful enough to arrange the cowslaves in order of pregnancy, from flat bellies at the top to the monstrously pregnant at the bottom.</p>`);
-					r.push(`<p>Scott walked back to Cathy grabbed her hand. "Come along." He managed to get a dozen paces before she pulled her hand out of his grasp. "Those are people" she hissed under her breath. "Of course they're people. What did you think they were, animatronics?" he scoffed. "This isn't Yellow Farmhouse." He shook his head. "Honestly, what were they thinking?"</p>`);
+					r.push(`<p>Scott walked back to Cathy grabbed her hand. "Come along." He managed to get a dozen paces before she pulled her hand out of his grasp. "Those are people," she hissed under her breath. "Of course they're people. What did you think they were, animatronics?" he scoffed. "This isn't Yellow Farmhouse." He shook his head. "Honestly, what were they thinking?"</p>`);
 					r.push(`<p>She sputtered and fumed at him for a moment before she found her voice again. "That isn't what I meant and you know it." She turned away from him to take in the scene. A dark haired beauty with smoky eyes amongst the cowslaves above caught her eye and gave her a lewd grin or, rather, attempted to do so around the feeding dildo in her mouth. Cathy wrinkled her nose. "Why would people do this?"</p>`);
 					r.push(`<p>"Well, at Blue Barn Creamery they believe customers have the right to see the cow before they drink from it." He answered in a wry tone. "But never mind that, let's go." He gave her butt a quick slap and continued walking. She winced at the impact, but followed him, her eyes still on the display.</p>`);
 					r.push(`<p>After a moment, her features slowly shifted from disgust to morbid curiosity. "What happens when they need to give birth?" Scott beamed at her. "You can't see it, but those platforms have systems that allow them to give birth without removing them and after giving birth they can be easily moved back to the top." He said gesturing to the slaves. "Blue Barn also offers an app that allows you to follow a slave's pregnancy and insemination."</p>`);
 					r.push(`<p>As they approached the entrance to the creamery, a crowd began to form around it. Slaves with the Blue Barn logo tattooed on their cheek passed out cups to the crowd. Scott and Cathy rejoined the family as June was gathering cups for everyone. "Everything alright?" Annie asked, an odd twinkle in her eye. "Nothing to worry about." Scott said as June gave him and Cathy their cups.</p>`);
 					r.push(`<p>Cathy looked at the cup in confusion. "Shouldn't we head inside?" Scott just gave her a small smile. "Not just yet." An aura of excitement began to build in the crowd. She noticed that most of the crowd wore very little. Most of the women wore underwear or jewelry and nothing else. Cathy just stared at her cup and then looked up and noticed something she'd missed before. A countdown timer in the corner of the creamery's screen. And it had just hit zero.</p>`);
 					r.push(`<p>The cowslaves mounted above began to moan and a deluge of milk flowed onto the crowd. The crowd cheered and laughed raising their cups high. Some of the women just basked in the spray, rubbing the milk into their skin. Some of the younger children danced and jumped in the puddles that formed.</p>`);
-					r.push(`<p>While others were reveling in the downpour, Cathy just stood still, exuding the aura of a wet cat. Scott laughed at Cathy's put out expression and took another swig from his cup. "Now we can go in."</p>`);
+					r.push(`<p>While others were reveling in the downpour, Cathy just stood still, exuding the aura of a wet cat. Scott laughed at Cathy's put-out expression and took another swig from his cup. "Now we can go in."</p>`);
 
 					return r.join(" ");
 				}
@@ -1706,23 +1706,23 @@ App.Data.FCTV.channels = {
 				get text() {
 					const r = [];
 					r.push(`<p>A soft gust of air flowed over the family as they passed through the doors to the creamery. Compared to its outward appearance, the creamery's interior was rather rustic. The warmly colored wood and soft amber lighting gave it a close and homey feel, like walking into an old ranch home at sunset.</p>`);
-					r.push(`<p>The family passed wooden stalls containing a bevy of cowslaves. Each stall had a plaque with the slave's name on it, milking lines snaked down from the ceiling, and above each stall was a screen displaying the cow's sexual exploits. Some of them were chatting with customers, taking selfies with them and recommending products to try. Others were providing more intimate services to their clientele or simply milked themselves and stared at passersby, their smoldering eyes and flushed faces promising every earthly delight one could imagine. Cathy did her best to ignore the goings on around her and focus her attention forward, but couldn't help herself from taking short peeks from the corner of her eye.</p>`);
+					r.push(`<p>The family passed wooden stalls containing a bevy of cowslaves. Each stall had a plaque with the slave's name on it, milking lines snaked down from the ceiling, and above each stall was a screen displaying the cow's sexual exploits. Some of them were chatting with customers, taking selfies with them and recommending products to try. Others were providing more intimate services to their clientele or simply milking themselves and staring at passersby, their smoldering eyes and flushed faces promising every earthly delight one could imagine. Cathy did her best to ignore the goings on around her and focus her attention forward, but couldn't help herself from taking short peeks from the corner of her eye.</p>`);
 					r.push(`<p>Eventually, the family came to a stop at a dessert counter. Various cakes, pastries, and other treats tempted customers from behind the glass. Each dessert had stylized pictures of the cows that provided the ingredients. One particularly large cake had a picture of adorably deformed and scantily clad construction crew building the cake in a clumsy, but earnest fashion. Above the counter was a series of chalk boards that listed products on the menu, as well as boasting of the day's specials. On top of the counter was a single silver bell. Annie sidled up to the counter, her breasts pressing into the glass. Her daughter reached over from her perch and rung the bell. They didn't have to wait for long before a voice called out to them. "Oh, master! I didn't know you were coming in today."</p>`);
 					r.push(`<p>She was relatively short for a woman, with dark hair and warm brown eyes. Her figure spoke of a long romance with growth drugs and an ongoing affair with the contents of the dessert counter. She wore a slutty maid uniform, tubes snaking out the slits at her breasts to her backpack, and what looked like a modified soda gun was holstered at her hip. The upper slope of her right breast was tattooed with loopy script that said Martha.</p>`);
 					r.push(`<p>Scott smiled and gave her a polite grope. "Just a little family outing. Could you get us a table?" He glanced at Cathy for a moment. "And maybe a few towels?" Martha grinned and said, "Sure thing." She disappeared behind the counter and returned with fluffy towels and menus in hand. After handing out the towels, she said, "Alright, follow me" and lead the family away.</p>`);
 					r.push(`<p>Martha led them to table that, despite being designed with more generous figures in mind, groaned when Annie sat down. The waitress passed out menus as the family busied themselves with drying off. After passing out the menus, Martha gave a quick, "Be back in a sec." and walked away. As the family finished up drying off, Martha returned with a tray of empty glasses. She set out the glasses on the table, unholstered the soda gun at her hip, and began to fill them up.</p>`);
 					r.push(`<p>Cathy just stared at the glass of milk in front of her. "It's pink." Annie handed her daughter a glass before taking a sip from her own. "And quite delicious, you should give it a try, Kitten." Sarah licked off her milk mustache and nodded, "Uh-huh, you should give it a try before you say you don't like it." Cathy took a tentative sip and then looked at her glass with new eyes. "It's strawberry flavored, how does that work?"</p>`);
 					r.push(`<p>Scott patted Martha's breasts. "A bit of minor surgery and a regimen of various supplements. If you go upstairs you can buy drugs that do the much the same thing, if less effectively." Scott took drink from his glass before continuing. "A side benefit of research performed on behalf of the vampires of Sanguine." Cathy looked skeptical. "Vampires?" she asked. Scott waved his hand. "I'll tell you about them another time."</p>`);
-					r.push(`<p>Martha pulled a small notepad out of her cleavage. "Do you folks know what you want or do you need some time?" Scott thought for a second before responding. "Why don't you give us a moment, It's Cathy's first time here." She beamed at Cathy. "Oh, glad to have you here! I hope you enjoy yourself darlin'" She took a slender remote out of her cleavage and handed it to Cathy. "Give me a buzz when you've made up your mind." With a small wave, Martha turned sashayed away.</p>`);
-					r.push(`<p>Cathy turned her attention to the menu in front of her. At a distance it didn't appear to be any different from a normal paper menu, but on closer inspection one could see it was a flexible touch screen. Scrolling down each page revealed a variety of intriguing dishes as cartoon cowslaves danced in the margins. Tapping on a dish opened a window that showed a video of the item, beside the window was a tab that enticed customers to see the cows in action. A significant part of the dessert page was dedicated extolling the virtues of growth drug laced ice cream produced by Blue Barn's partnership with Bloom Pharma. Below this were animations of cows eating ice cream and suddenly growing assets of immense size.</p>`);
+					r.push(`<p>Martha pulled a small notepad out of her cleavage. "Do you folks know what you want or do you need some time?" Scott thought for a second before responding. "Why don't you give us a moment, It's Cathy's first time here." She beamed at Cathy. "Oh, glad to have you here! I hope you enjoy yourself, darlin'." She took a slender remote out of her cleavage and handed it to Cathy. "Give me a buzz when you've made up your mind." With a small wave, Martha turned sashayed away.</p>`);
+					r.push(`<p>Cathy turned her attention to the menu in front of her. At a distance it didn't appear to be any different from a normal paper menu, but on closer inspection one could see it was a flexible touch screen. Scrolling down each page revealed a variety of intriguing dishes as cartoon cowslaves danced in the margins. Tapping on a dish opened a window that showed a video of the item; beside the window was a tab that enticed customers to see the cows in action. A significant part of the dessert page was dedicated extolling the virtues of growth drug laced ice cream produced by Blue Barn's partnership with Bloom Pharma. Below this were animations of cows eating ice cream and suddenly growing assets of immense size.</p>`);
 					r.push(`<p>"Hmm, anyone know what they want?" Scott asked. June looked up from the menu. "I think I'll get my usual." Sadie yawned and said, "Same." Sarah held up her menu, jabbing finger at a picture of a black forest cake and said, "I want this." While the cake quite impressive, to the right of it was something far more attention grabbing.</p>`);
 					r.push(`<p>A window had opened and was playing a video of the cows that helped produce the cake. A heavily pregnant cowslave was railing a far younger, but equally as pregnant cow with a strap-on. The younger cow's eyes were glassy and unfocused. The older slave let out a growl of need and began to pick up speed, their considerable breasts jiggling with each thrust. The menu was polite enough to have a blurb informing them that the cows are actresses on The Young and the Fecund. If one was feeling uncharitable, they could say that the sole video tag of "lactating lolis" was technically correct, but a woefully inadequate description.</p>`);
 					r.push(`<p>He raised an eyebrow. "I thought you wanted a bloom berry sundae?" She gave him puppy dog eyes. "I want cake too," she whined. He narrowed his eyes at her. The puppy dog eyes increased in intensity. A moment passed before he caved. "You can have a small slice." The puppy dog eyes vanished and she let out a small cheer. Annie set down her menu. "I think I'll have rum raisin —" She smiled at him, her eyes crinkling. "— and a slice of cake." He huffed at her, but smiled anyway. Then he turned to Cathy. "And you?" he asked. "I think I'll have a vanilla sundae." He cocked his head at her and raised an eyebrow. "What?" she said defensively. He held up his hands in a placating gesture. "Nothing, nothing. If you've made up your mind, just use the remote." She picked up the remote and pressed the call button.</p>`);
 					r.push(`<p>A few moments later, Martha returned, her face flushed. "Everyone all set?" She briskly took down their orders and set off for the kitchens. After a few minutes, she returned with a full tray. With an agility that only comes from years of being a fighter pilot or working in the food industry, she passed out their orders and topped off every glass. With a quick, "Buzz me if you need me," Martha returned to the counter.</p>`);
 					r.push(`<p>June demurely ate her ice cream while Sadie seemed intent on eating her banana split in as lewd a fashion possible. In stark contrast, Sarah was savaging her cake and ice cream, icing smearing on her face and chest. As Annie was eating her ice cream, she 'accidentally' started dribbling onto her cleavage. "Oh my!" she said in a tone of faux concern. "Sweetie, could you help mommy out?" Sarah wiggled around in her mother's cleavage and began to lap up the drips of ice cream, leaving smears of icing in her wake. "Oh, you've such a messy eater. Come here and let mommy clean you up." Annie pulled her close and began to lick the remnants of cake and ice cream off her face. Her licks slowly morphed into a deep kiss. Their tongues danced and faces flushed. Annie pulled away from her, trailing a line of kisses down her chest and began to suckle from her. Sarah bit her lip, closed her eyes, and began to moan, her fingers teasing her clit.</p>`);
-					r.push(`<p>Cathy looked upon this scene with an expression that could only be charitably described as slack jawed. Scott caught her eye and gave her an amused grin. She flushed with embarrassment and cleared her throat before asking, "So, you own this place, don't you?" He took a lick of his ice cream. "Indeed I do. Something you want to ask?"</p>`);
+					r.push(`<p>Cathy looked upon this scene with an expression that could only be charitably described as slack-jawed. Scott caught her eye and gave her an amused grin. She flushed with embarrassment and cleared her throat before asking, "So, you own this place, don't you?" He took a lick of his ice cream. "Indeed I do. Something you want to ask?"</p>`);
 					r.push(`<p>Her features became troubled and she shifted in her seat. "Yeah, why do you have all those girls mounted out front?" He shrugged his shoulders. "Like I said, we at Blue Barn Creamery believe the customer has a right to see the cow before they drink from it." She nodded "Yeah you said that, but why? Wouldn't putting up a bunch of screens be as effective?"</p>`);
-					r.push(`<p>Scott pondered her question for a moment, before saying, "Shortly after starting up here, there was a big scandal over slave milk. Apparently, some moron thought adulterating slave milk with actual cow milk was a good idea. As you might guess, it didn't turn out well for him." He took a bite of his ice cream before continuing. "After that, customer trust was at an all-time low. So, I decided to make sure customers could see the whole process right outside the door." He jabbed his cone at her. "That level of transparency made me quite rich and my cows famous. Upstairs, you can buy all sorts of merchandise based on them: clothes, dolls, you name it." He smiled. "There's even a cartoon in the works." Cathy looked at him with a thoughtful expression. "So, that's why?" He gave her a lewd grin. "That, and it's quite sexy. Now, eat your ice cream before it melts." He turned to his wife and daughter. "That goes for you too. Remember, clean your plate, then masturbate." Sarah pulled away from Annie, gobbled down what remained of food, and then pressed her breasts into her mother's face.</p>`);
+					r.push(`<p>Scott pondered her question for a moment, before saying, "Shortly after starting up here, there was a big scandal over slave milk. Apparently, some moron thought adulterating slave milk with actual cow milk was a good idea. As you might guess, it didn't turn out well for him." He took a bite of his ice cream before continuing. "After that, customer trust was at an all-time low. So, I decided to make sure customers could see the whole process right outside the door." He jabbed his cone at her. "That level of transparency made me quite rich and my cows famous. Upstairs, you can buy all sorts of merchandise based on them: clothes, dolls, you name it." He smiled. "There's even a cartoon in the works." Cathy looked at him with a thoughtful expression. "So, that's why?" He gave her a lewd grin. "That, and it's quite sexy. Now, eat your ice cream before it melts." He turned to his wife and daughter. "That goes for you too. Remember, clean your plate, then masturbate." Sarah pulled away from Annie, gobbled down what remained of her food, and then pressed her breasts into her mother's face.</p>`);
 					r.push(`<p>A few minutes later, the family had finished their ice cream and were taking a moment to relax. Sarah rested languidly in her mother's cleavage, basking in the afterglow. Annie shifted uncomfortably. "Feeling pretty sticky. I think mommy is going to go home and take a shower." Scott patted her breast. "Sounds good, I think June and I'll get some shopping done and head back." At the word 'shopping,' Sarah immediately said, "No spinach." Scott looked at his daughter. "Yes spinach. You're not going to grow up to be big and milky like mommy if you don't eat your greens." Sarah pouted and mumbled into her mother's breast, "I want lolimommy cheese curds and a new plushy." Scott ruffled her hair. "We'll see."</p>`);
 					r.push(`<p>As the family gathered themselves, Cathy held up the remote. "Should we just leave this at the counter?" Annie smiled at her. "Why don't you give her a buzz? I'm sure she'd like it." Cathy gave her a confused frown. Annie sighed and asked, "Kitten, how do you think that gets her attention?" Cathy turned the remote over in her hands. "She would have something that blinked and vibrated when someone used the remote." Annie just looked at her with a serene smile, her eyes twinkling mischievously. With a sudden gasp, Cathy dropped the remote like it had shocked her. A second later, there was a distant squeal and the clatter of a tray hitting the floor.</p>`);
 
@@ -1734,13 +1734,13 @@ App.Data.FCTV.channels = {
 					const r = [];
 					r.push(`<p>A moment later, a beet red Cathy was stuttering an apology to an even redder Martha. "Don't worry about it, it was just a pleasant surprise." She smiled and pressed her breasts against Cathy's. "Hope to see you 'round soon darlin'." Martha pulled her into a hug and whispered softly into her ear. "I know it's hard to adjust to, but I think you'll do just fine here." She pressed a piece of paper into Cathy's cleavage and sashayed away.</p>`);
 					r.push(`<p>Cathy fished it out to see it was an email and phone number with a lip print in bright red lipstick. "Oh my," Annie said, her eyes dancing with amusement. "It looks like you've made a friend." She turned to her husband and said, "I think they'd make a cute couple, wouldn't you?" Scott examined Cathy for a second before nodding. "So long as she makes an honest woman out of her, she has my blessing." Cathy just slowly fumed, her face scarlet in embarrassment. A moment passed before Annie couldn't take it anymore and let out loud, breast quaking, laughter.</p>`);
-					r.push(`<p>Cathy glared at Annie "She was just being friendly." This just sent Annie into another fit of bosom shaking hysterics. Scott attempted to steady his wife. "Cathy, do you remember the remote she gave you?" She nodded warily. "You don't need one of those to call a waitress. The menus have a button that calls the nearest one to the table," he explained. "Remotes like that are typically reserved for VIPs or favored customers." Her brow furrowed. "But you're the owner! Why wouldn't she leave one with you?" He gave her a small grin and said, "Indeed I am, but she gave the remote to you, not me." Cathy thought that over for a second before putting her head in her hands and sulked.</p>`);
+					r.push(`<p>Cathy glared at Annie. "She was just being friendly." This just sent Annie into another fit of bosom shaking hysterics. Scott attempted to steady his wife. "Cathy, do you remember the remote she gave you?" She nodded warily. "You don't need one of those to call a waitress. The menus have a button that calls the nearest one to the table," he explained. "Remotes like that are typically reserved for VIPs or favored customers." Her brow furrowed. "But you're the owner! Why wouldn't she leave one with you?" He gave her a small grin and said, "Indeed I am, but she gave the remote to you, not me." Cathy thought that over for a second before putting her head in her hands and sulked.</p>`);
 					r.push(`<p>After a few seconds, Annie's laughter had died down to jiggling chortles. She wiped a tear from her eye, smiled and said, "Oh dear sweet kitten, never change." The chortling slowed and so did the wobbling of her bosom.</p>`);
 					r.push(`<p>Finally, she let out a long relaxed sigh before saying, "On that note, I think it's time to head back. Coming, Sadie?" Sadie nodded and took up a position beside Annie. They linked arms and presented their bottoms to Scott. He gave both their asses a quick smack and said, "Off you go." Annie wiggled her bottom. "Daddy," she whined plaintively, her eyes bright with amusement. He sighed good-naturedly and gave both her cheeks a solid smack. She squealed and tittered, her eyes twinkling, then wobbled to the door and out of sight.</p>`);
-					r.push(`<p>The party made their way to an elevator past the dessert counter and went up a floor. The grocery portion of Blue Barn had much the same aesthetic as downstairs: all Cedar and Oak construction, and pendant lamps hanging above. The hardwood floor was polished in the way only an obsessive-compulsive could manage. Immediately out of the elevator were lines of wooden shelves and tables bearing Blue Barn merchandise.</p>`);
+					r.push(`<p>The party made their way to an elevator past the dessert counter and went up a floor. The grocery portion of Blue Barn had much the same aesthetic as downstairs: all cedar and oak construction, and pendant lamps hanging above. The hardwood floor was polished in the way only an obsessive-compulsive could manage. Immediately out of the elevator were lines of wooden shelves and tables bearing Blue Barn merchandise.</p>`);
 					r.push(`<p>There were posters, coasters, clothing and all manner of little knickknacks, but the true star of the show were the plushies. Rows upon rows of them covered the shelves and tables arranged in little displays, all of them made in the image of cows working at the creamery. One table had the plushies in a mini concert hall, the ones on stage wielding toy instruments that had 'Press me!' stickers on them. Another table had them arranged in what looked to be a garden party. Spread across two tables was a diorama of the creamery with plushies placed throughout it. One plushie that looked distinctly like Martha was plopped behind the dessert counter. Another was placed near the elevators and if one looked closely they could see a matching cowslave sat drowsing amongst the merchandise.</p>`);
 					r.push(`<p>The cow was young, busty, even for the arcology, and heavily pregnant. She wore what looked to be Holstein print pajamas with a hood made to look like a stylized cow. Her strawberry blonde hair was mussed with sleep and she cradled a plushie in one arm. Truly, she looked like a daughter waiting for her daddy to come home. As the party approached, she began to stir.</p>`);
-					r.push(`<p>Scott reached out and began to gently pat her head. "How are you doing, Tabby?" Tabby just made a sound of contentment and pressed into his hand, luxuriating in his touch. After a few moments, she yawned and blinked, looking up at him. For a beat she just stared at him, her sleep addled brain struggling to process the sight in front of her. Finally, the penny dropped.</p>`);
+					r.push(`<p>Scott reached out and began to gently pat her head. "How are you doing, Tabby?" Tabby just made a sound of contentment and pressed into his hand, luxuriating in his touch. After a few moments, she yawned and blinked, looking up at him. For a beat she just stared at him, her sleep-addled brain struggling to process the sight in front of her. Finally, the penny dropped.</p>`);
 					r.push(`<p>She squeaked and sat up so quick one would think she had been hit with a cattle prod. With a panicked expression, she began to babble a fervent apology. "Master, I'm sorry I fell asleep." She hiccupped and pleaded with him, on the verge of tears. "Please don't tell Gabe I fell asleep again. She'll yell at me for sure." Scott just continued patting her head, knelt beside her chair, and spoke in a calm tone, "Hey, hey, no need for tears. Just take a deep breath and calm down Tabby cat. I won't tell Gabe."</p>`);
 					r.push(`<p>Tabby sniffed, took a deep breath, and hiccupped. For a moment, she just relaxed into Scott's ministrations before she frowned and said, "I thought you weren't coming in today master." He moved her hair out her eyes. "I figured I'd get some shopping done and show the newbie around," he said with a nod to Cathy. Tabby gave her a bright smile. "Oh, nice to meet you. Would you like a free sample?" she asked gesturing to table next to her.</p>`);
 					r.push(`<p>The table next to her held a platter of cheese curds. A sign with the words 'Free Samples' printed on it stood to the left of table. Someone apparently thought that was an insufficient description and had taken a pen to add the words 'I made them myself!' in bright pink letters. Next to the platter, was an empty package that said 'Lolimommy Cheese Curds' in a cheery font. On the package, was an adorably deformed picture of Tabby that said 'The fresh ones squeak'.</p>`);
@@ -1777,18 +1777,18 @@ App.Data.FCTV.channels = {
 				get text() {
 					const r = [];
 					r.push(`<p>The offending bottle was of a higher quality than most of its neighbors, offering a gallery of artists' renditions of the cow in addition to the video. The video on the bottle showed Scott fucking Annie from behind, a slightly tinny slap followed by a squeal from the tiny speakers on the bottle. However, this was not the source of Cathy's distress, as surprising as that may be. Rather, it was the words, Top shelf: All in the Family, printed in red lettering across the bottle.</p>`);
-					r.push(`<p>Scott cocked an eyebrow, utterly unperturbed. "Yes? And?" Cathy sputtered, but quickly regained her momentum. "You don't do that! Why would do that!?" she all but shrieked, her face blotchy.</p>`);
+					r.push(`<p>Scott cocked an eyebrow, utterly unperturbed. "Yes? And?" Cathy sputtered, but quickly regained her momentum. "You don't do that! Why would you do that!?" she all but shrieked, her face blotchy.</p>`);
 					r.push(`<p>"For two reasons," he said, and held up a finger. "One, have you seen her?" A lewd grin spread across his face, "Rawr." Cathy's blush deepened as her outrage engine built up steam. The grin slid off of Scott's face and his voice grew solemn. "Second, parents can't sell their children if they're already married."</p>`);
 					r.push(`<p>She froze, the blood draining from her face. "What... why?" Scott sighed and looked her in the eye. "I would think you of all people would understand why." Cathy flinched and looked down, clutching her plushie tighter. His stare never wavered. "Didn't you sign up with me to help your mother? Family is important after all." For a while she just stared down at her own cleavage, fiddling with her plushie. She nodded, "Yeah... but not just for her." She looked up, her eyes shining with unshed tears. "I did it for me too." A shudder ran through her and tears began to roll down her cheeks. "I didn't know what to do without her."</p>`);
 					r.push(`<p>Scott patted her head and pulled her into a hug. "Honesty is to be rewarded." Her body shuddered with suppressed sobs. June caught his eye over Cathy's shoulder and gave him an uncertain look. He shook his head slightly and gave her a thumbs up. She nodded slightly and quietly left the aisle. Cathy sniffed and mumbled into his chest, "I miss my mom." He rubbed slow circles in back and said softly, "I know, I know."</p>`);
 					r.push(`<p>After her sobs subsided, the pair continued hugging in silence for a moment before separating. Cathy rubbed her eyes and sniffed, attempting to regain her composure. "So what happens now?" Scott quirked an eyebrow, "For now, we finish shopping and head on home." He looked up and down the aisle. "Provided that we can find where June wandered off to." A soft sigh escaped her throat. "That's not what I meant." She gave him a steady look. "What's the next step to getting my mom back?"</p>`);
 					r.push(`<p>Scott considered her for a moment before responding. "For now, you'll continue your drug regimen and etiquette lessons." He gave her breasts a poke. "Your milk hasn't come in so we can't start that training or begin selling your milk, but there are things you can do." He drummed out a little beat on her breasts. "Now that you're big enough to not get stopped when you walk down the street, you could start working here." An impish grin spread over his face. "I'm sure Martha would enjoy having you around." Cathy flushed and let out a small huff. His grin widened.</p>`);
 					r.push(`<p>He groped her slowly as he thought. "As for getting your mom, considering who your mother was, her price is bound to be exorbitant. Even if I got a sweetheart rate." He brushed a lock of her hair behind her ear. "But we're in no rush. Her current owner is treating her rather well and seems fond of her, so he probably won't randomly sell her off." A mild frown spread across his face. "The downside of that is it will take even more money or favors to get him to part with her, but we'll cross that bridge when we get to it."</p>`);
-					r.push(`<p>As he spoke, Cathy's expression became more and more disheartened. Scott smiled reassuringly, "Don't worry about it. I have a plan to make you so profitable you could buy your mom a dozen times over." She nodded, but didn't look entirely reassured. "You've already made a fair bit of progress today" he said, his smile bright. "What do you mean?" her face had the expression of a wary kitten expecting the roar of vacuum cleaner. "You didn't freak out when I touched your breasts." He gave them a squeeze for emphasis. She looked conflicted and seemed on the verge of saying something, but decided against it. "Anyway, we should find June and head on out." He patted her butt, saying "Come along" and set out to find June with Cathy in tow.</p>`);
+					r.push(`<p>As he spoke, Cathy's expression became more and more disheartened. Scott smiled reassuringly, "Don't worry about it. I have a plan to make you so profitable you could buy your mom a dozen times over." She nodded, but didn't look entirely reassured. "You've already made a fair bit of progress today," he said, his smile bright. "What do you mean?" Her face had the expression of a wary kitten expecting the roar of vacuum cleaner. "You didn't freak out when I touched your breasts." He gave them a squeeze for emphasis. She looked conflicted and seemed on the verge of saying something, but decided against it. "Anyway, we should find June and head on out." He patted her butt, saying "Come along," and set out to find June with Cathy in tow.</p>`);
 					r.push(`<p>It didn't take too long to find her. After passing a display of plushie cowslaves playing in a pool, they walked into an aisle devoted to greeting cards and other printed media. The various cards bore animated scenes catering to various situations. One card depicted a busty woman losing power to her exosuit, being pulled to the ground by her own breasts and a small group comes by to help her up. The words flowing across the card said 'We know you've fallen on hard times, but we'll always be there to support you' in a wavy font. The other side of the aisle bore a number of coloring books plastered with pictures of famous cows, magazines devoted to various tastes, and paperback erotica, some written by store employees. And hunched over by the magazines was June.</p>`);
 					r.push(`<p>She was engrossed in reading an issue of Cow's Life. The cover had a looping video of a blonde cow spraying milk and making bedroom eyes at the viewer. The headline of the issue was an interview with Alexis Cream, the first cow in space. In smaller text were headlines like 'Five nipple stimulation methods guaranteed to increase lactation!', 'Finding the right cleavage vibrator for you', 'The new frontier of breast sex'. June seemed to have ignored the magazine's other offerings in favor of a review of a new model of mobile milker that promised to provide endless breastgasms.</p>`);
 					r.push(`<p>Scott walked up behind her and slipped his arms around her to grab her breasts. "Doing some early Christmas shopping?" he asked looking over her shoulder. She leaned into him, "Just looking at the new milker you were going to buy for Sarah." He gave her breasts a squeeze as he read the article over her shoulder. "It's a high end model. I'm a very thoughtful father, aren't I?" he said in faux pompous tone, nodding to himself. A small smile spread across June's face. "Of course you are, master." They enjoyed each other's company for a moment before she spoke in a low tone, "Everything alright?" He teased her nipples and spoke in the same tone, "I think I've made some progress with her." He leaned closer to whisper in her ear, "I told you it was a good idea."</p>`);
-					r.push(`<p>They separated and returned to the cart. While they were having their little chat, Cathy had decided to brave the contents of the erotica section. The look on her face was difficult to describe, but she was reading the book very intently. Scott caught her eye and said, "you can have it, if you want it..." She snapped the book shut, placed it on the shelf, and tried to look as small as possible. He just shrugged in response and looked through the cart before nodding to himself. "I think we're ready to go. Any objections?" June shook her head, but Cathy looked at the shelf for a long second before shaking her head.</p>`);
+					r.push(`<p>They separated and returned to the cart. While they were having their little chat, Cathy had decided to brave the contents of the erotica section. The look on her face was difficult to describe, but she was reading the book very intently. Scott caught her eye and said, "You can have it, if you want it..." She snapped the book shut, placed it on the shelf, and tried to look as small as possible. He just shrugged in response and looked through the cart before nodding to himself. "I think we're ready to go. Any objections?" June shook her head, but Cathy looked at the shelf for a long second before shaking her head.</p>`);
 					r.push(`<p>The three gathered up their purchases and headed to the exit. As they were passing through the checkout, Scott noticed a certain book pass amongst their purchases. He turned to give Cathy a look. She was pointedly not looking in his direction when she snatched up the book and made a beeline for the door. When she stepped outside, she noticed something was off about the building, but she couldn't put her finger on it. Scott and June made their way outside to see her gaze flickering over the building. Scott simply smiled at Cathy. Finally, the penny dropped, Cathy glared at him like she was trying to set him on fire with her mind. The upper level of Blue Barn didn't have cows mounted on the wall. Cackling laughter echoed in the street.</p>`);
 
 					return r.join(" ");
@@ -1798,7 +1798,7 @@ App.Data.FCTV.channels = {
 				get text() {
 					const r = [];
 					r.push(`<p>Cathy's ire had faded somewhat by the time they made it home. A soft chime rang out as they walked in the door, followed shortly by a faint voice calling out, "Welcome home," from further in the house. The trio put the groceries on the kitchen island and began to put them away. The background murmur of a TV and the faint roar of a hair dryer were coming from the next room. Scott was about to walk into the room, but he paused and looked at the plushie in his hand. He turned and pressed it into June's hands, giving her a meaningful look. She looked at the doll for a moment before returning his look with a smile. Message received. He gave her a quick nod before walking through the door.</p>`);
-					r.push(`<p>At first glance, the living room didn't look too dissimilar from something of an old-world home decorating magazine. The furniture was well made and comfortable, but was noticeably designed with more robust figures in mind. Books and magazines laid scattered across a number of coffee and end tables. An old copy of <i>Milkers Monthly</i> was opened to a video of a cowslave demonstrating the use of a cleavage vibrator. The shelves on the far wall held a number of statues, their brass figures bearing immobilizing breasts. And on the end was a 1st place ribbon from a school milking competition.</p>`);
+					r.push(`<p>At first glance, the living room didn't look too dissimilar from something out of an old-world home decorating magazine. The furniture was well made and comfortable, but was noticeably designed with more robust figures in mind. Books and magazines laid scattered across a number of coffee and end tables. An old copy of <i>Milkers Monthly</i> was opened to a video of a cowslave demonstrating the use of a cleavage vibrator. The shelves on the far wall held a number of statues, their brass figures bearing immobilizing breasts. And on the end was a 1st place ribbon from a school milking competition.</p>`);
 					r.push(`<p>Annie sat on the couch with a towel around her shoulders, only wearing her exosuit. She relaxed as Sadie went over her vast cleavage with a hair dryer and a towel, letting out noises of contentment as she luxuriated in Sadie's ministrations. Sarah was cuddled up to her mother, her hair still damp from the shower. Scott sat down on the couch and pulled Sarah onto his lap before moving closer to his wife. Sarah just made herself comfortable while Annie leaned over to give him a kiss. "Everything go alright?" He patted his wife's breast reassuringly. "There was a little bump or two, but it went alright. I'll tell you about it later."</p>`);
 					r.push(`<p>Sarah turned around to face her father. "Did you get it?" she asked expectantly. He tilted his head in faux confusion, "Get what?" She frowned at him and said in an annoyed tone, "Daddy." A plushie peeked over from behind the couch for a moment before dipping back down. Sarah focused on the spot the plushie had just inhabited with laser intensity before turning to give him an accusing look. Scott did his best to look confused. "What is it sweetheart?"</p>`);
 					r.push(`<p>The plushie popped back up and began to move along the back of the couch in a parody of walking. The doll stopped at Scott's shoulder and prodded it with a tiny hand. "Oh, hi Tabby. What brings you here?" The plushie moved closer to his ear. "You're here to keep Sarah company? That's very sweet of you." Sarah let out a piteous whine, "Daddy." Scott appeared to be too enthralled with his diminutive conversation partner to hear her. "What's that? You're also here to make sure she eats her greens, cleans her toys, and does her homework?" He gave Sarah a dubious glance and turned to whisper to the plushie, sotto voce, "I think that's bit of a tall order." Sarah pouted at her father, but grudgingly said, "Okay."</p>`);
@@ -1811,8 +1811,8 @@ App.Data.FCTV.channels = {
 					r.push(`<p>All throughout the show, Cathy was mesmerized by the show in the same way one might be by a train wreck. When the credits rolled, she broke out of the spell and noticed that another episode was about to play. "Uh, could we watch something else?" Cathy asked tentatively. Sarah looked like she going to object, but Scott gave her a quick look. She pouted at her father, but began to flip through the channels. As the channels went by, Cathy's eyes grew wider. "Why don't we go back to the cartoon channel? "</p>`);
 					r.push(`<p>After a few more episodes, Scott noticed the time. "I think it's bedtime for cuddly cows." Sarah grumbled, but didn't resist when he lifted her out of Annie's cleavage. She received a hug and a kiss from both her parents before June took her off to prepare for bed. Scott turned to Cathy and said, "Why don't you head off too? You'll be coming into work with me in the morning so you should get some rest." Cathy nodded and headed off to her room, but returned a moment later to grab her plushie. Annie caught sight of the doll and gave Cathy a lascivious grin. Cathy did her best to avoid eye contact and all but ran out of the room.</p>`);
 					r.push(`<p>The moment Cathy was out the door, Annie began giggling which shortly transformed into breast quaking laughter. She wiped at her eyes, her smile bright as she looked at her husband. "Have I told you lately that I love you?" He wrapped an arm around her waist, his eyes bright with amusement, "I don't know, but it couldn't hurt to say it again." She pulled him closer, kissed him on the cheek and whispered, "I love you." He returned her kiss, rubbed her back gently, and whispered back, "I know."</p>`);
-					r.push(`<p>For a while, the couple just relaxed and enjoyed each other's company. Annie let out a sigh of pleasure and asked, "You said there were a few bumps?" Scott let out a sound of acknowledgment. "Gabe has been riding the girls rather hard." Almost immediately, she said, "You should try getting her knocked up." Laughter burst of his chest, at her questioning look he said, "Beth said much the same thing." She nodded approvingly. "Beth is a smart girl. There can always be more babies." She pressed herself into her husband and said, "Gabe isn't the only one who needs knocking up." She nibbled on his earlobe and whispered, "Babies, babies, babies, babies, babies." He turned to look at her and she caught his lips in a searing kiss. When they parted she seemed short of breath, her eyes filled with need. "Babies."</p>`);
-					r.push(`<p>Scott took a moment to steady his breath. "You know we have to wait for the treatment to take." Annie let out a sound of displeasure, but Scott didn't waver. "Do you want to go through all the trouble we had with Sarah again?" She deflated a bit. He leaned over and whispered in her ear, "That doesn't mean we can't get some practice in." Immediately, Annie captured his mouth again and began tearing at his clothes. It took them a few hours to make it back to bedroom.</p>`);
+					r.push(`<p>For a while, the couple just relaxed and enjoyed each other's company. Annie let out a sigh of pleasure and asked, "You said there were a few bumps?" Scott let out a sound of acknowledgment. "Gabe has been riding the girls rather hard." Almost immediately, she said, "You should try getting her knocked up." Laughter burst from his chest; at her questioning look he said, "Beth said much the same thing." She nodded approvingly. "Beth is a smart girl. There can always be more babies." She pressed herself into her husband and said, "Gabe isn't the only one who needs knocking up." She nibbled on his earlobe and whispered, "Babies, babies, babies, babies, babies." He turned to look at her and she caught his lips in a searing kiss. When they parted she seemed short of breath, her eyes filled with need. "Babies."</p>`);
+					r.push(`<p>Scott took a moment to steady his breath. "You know we have to wait for the treatment to take." Annie let out a sound of displeasure, but Scott didn't waver. "Do you want to go through all the trouble we had with Sarah again?" She deflated a bit. He leaned over and whispered in her ear, "That doesn't mean we can't get some practice in." Immediately, Annie captured his mouth again and began tearing at his clothes. It took them a few hours to make it back to the bedroom.</p>`);
 
 					return r.join(" ");
 				}
diff --git a/tests/ibc.js b/tests/ibc.js
new file mode 100644
index 0000000000000000000000000000000000000000..0199220e0897a61714af05573a7856e37c55bb77
--- /dev/null
+++ b/tests/ibc.js
@@ -0,0 +1,131 @@
+{
+	class MockSlave {
+		ID;
+		mother;
+		father;
+
+		constructor(id) {
+			this.ID = id;
+		}
+	}
+
+	class MockMating {
+		constructor(fatherId, motherId, ...childrenIds) {
+			for (let id of childrenIds) {
+				if (id === fatherId || id === motherId) {
+					throw new Error("cannot give birth to self");
+				}
+			}
+
+			this.fatherId = fatherId;
+			this.motherId = motherId;
+			this.childrenIds = childrenIds;
+		}
+	}
+
+	class MockWorld {
+		slavesArray;
+
+		constructor(matings) {
+			this.slavesArray = [];
+
+			let slavesById = {};
+			let meetSlave = (id) => {
+				if (!slavesById[id]) {
+					let slave = new MockSlave(id);
+					this.slavesArray.push(slave);
+					slavesById[id] = slave;
+				}
+
+				return slavesById[id];
+			};
+
+			for (let mating of matings) {
+				let father = meetSlave(mating.fatherId);
+				let mother = meetSlave(mating.motherId);
+				for (let id of mating.childrenIds) {
+					let child = meetSlave(id);
+					child.father = father.ID;
+					child.mother = mother.ID;
+				}
+			}
+		}
+
+		findSlaveState(id) {
+			return this.slavesArray.find(slave => slave.ID === id) || null;
+		}
+	}
+
+	let testCoefficientForSlave = function(name, mockMatings, slaveId, expectedCoefficientOfInbreeding) {
+		App.Testing.executeTest(name, () => {}, () => {
+			let tolerance = .00000000001;
+
+			let world = new MockWorld(mockMatings);
+			let c = ibc._test.coeff_slave(world, world.findSlaveState(slaveId));
+			App.Testing.isType(c, "number");
+			App.Testing.notNaN(c);
+			App.Testing.inRange(c, expectedCoefficientOfInbreeding, tolerance);
+		}, () => {});
+	};
+
+	// references:
+	// Introduction to Quantitative Genetics - Doulas S. Falconer, 1989
+	// Genetic and Quantitative Aspects of Genealogy - F.M. Lancaster, 2015
+	testCoefficientForSlave("basic outbred mating, 1",[new MockMating(1, 2, 3, 4)], 1, 0);
+	testCoefficientForSlave("basic outbred mating, 2",[new MockMating(1, 2, 3, 4)], 4, 0);
+	testCoefficientForSlave("basic self-mating, 1", [new MockMating(1, 1, 3, 4)], 4, 1 / 2);
+	testCoefficientForSlave("basic self-mating, 2", [new MockMating(1, 1, 3, 4)], 1, 0);
+	testCoefficientForSlave("basic child-parent mating",[new MockMating(1, 2, 3), new MockMating(2, 3, 4)], 4, 1 / 4);
+	testCoefficientForSlave("basic sibling mating",[new MockMating(1, 2, 3, 4), new MockMating(3, 4, 5)], 5, 1 / 4);
+	testCoefficientForSlave("basic half-sibling mating",[new MockMating(1, 2, 3), new MockMating(2, 4, 5), new MockMating(3, 5, 6)], 6, 1 / 8);
+	testCoefficientForSlave("basic first cousin mating",[new MockMating(1, 2, 3, 4), new MockMating(5, 3, 6), new MockMating(4, 7, 8), new MockMating(6, 8, 9)], 9, 1 / 16);
+	testCoefficientForSlave("double first cousin mating",[new MockMating(1, 2, 3, 4), new MockMating(5, 6, 7, 8),
+		new MockMating(3, 7, 9), new MockMating(4, 8, 10), new MockMating(9, 10, 11)], 11, 1 / 8);
+	testCoefficientForSlave("aunt-niece mating",[new MockMating(1, 2, 3, 4), new MockMating(4, 5, 6), new MockMating(3, 6, 7)], 7, 1 / 8);
+	let scenario53 = [new MockMating(1, 2, 3, 4), new MockMating(5, 6, 7, 8), new MockMating(3, 7, 9, 10), new MockMating(4, 8, 11),
+		new MockMating(12, 9, 13), new MockMating(10, 11, 14), new MockMating(13, 14, 15)];
+	testCoefficientForSlave("problem 5.3 from Falconer, 1",scenario53, 14, 1 / 8);  //
+	testCoefficientForSlave("problem 5.3 from Falconer, 2",scenario53, 15, 3 / 32);
+	testCoefficientForSlave("example in figure 64 from Lancaster",[new MockMating(1, 2, 4, 5), new MockMating(3, 4, 7), new MockMating(5, 6, 8), new MockMating(7, 8, 9), new MockMating(9, 10, 12, 13),
+		new MockMating(11, 12, 15), new MockMating(13, 14, 16), new MockMating(15, 16, 17, 18)], 17, 33 / 512);
+	testCoefficientForSlave("example in figure 65 from Lancaster",[new MockMating(1, 2, 4, 5), new MockMating(3, 4, 7), new MockMating(5, 6, 8), new MockMating(7, 8, 10, 11),
+		new MockMating(9, 10, 13), new MockMating(11, 12, 14), new MockMating(13, 14, 15)], 15, 9 / 128);
+	testCoefficientForSlave("example in figure 66 from Lancaster",[new MockMating(1, 2, 3, 4), new MockMating(3, 4, 5, 6), new MockMating(5, 6, 7, 8), new MockMating(7, 8, 9)], 9, 1 / 2);
+	testCoefficientForSlave("example in figure 66 from Lancaster, extended by one more generation's worth of mating between siblings",[new MockMating(1, 2, 3, 4), new MockMating(3, 4, 5, 6), new MockMating(5, 6, 7, 8), new MockMating(7, 8, 9, 10),
+		new MockMating(9, 10, 11)], 11, 19 / 32);
+	// from here the preceding scenario of regular sibling-mating is extended by several more steps; by rules given in Falconer, the coefficient ck for step k, where step k follows
+	// step j which follows step i, should be ck = 1/4 + cj/2 + ci/4, and so that is how the expected values used in the next few tests were calculated
+	testCoefficientForSlave("extended, 1: 43/64 = 1/4 + (19/32) / 2 + (1/2) / 4",[new MockMating(1, 2, 3, 4), new MockMating(3, 4, 5, 6), new MockMating(5, 6, 7, 8), new MockMating(7, 8, 9, 10),
+		new MockMating(9, 10, 11, 12), new MockMating(11, 12, 13)], 13, 43 / 64);
+	testCoefficientForSlave("extended, 2",[new MockMating(1, 2, 3, 4), new MockMating(3, 4, 5, 6), new MockMating(5, 6, 7, 8), new MockMating(7, 8, 9, 10),
+		new MockMating(9, 10, 11, 12), new MockMating(11, 12, 13, 14), new MockMating(13, 14, 15)], 15, 94 / 128);
+	testCoefficientForSlave("extended, 3", [new MockMating(1, 2, 3, 4), new MockMating(3, 4, 5, 6), new MockMating(5, 6, 7, 8), new MockMating(7, 8, 9, 10),
+		new MockMating(9, 10, 11, 12), new MockMating(11, 12, 13, 14), new MockMating(13, 14, 15, 16), new MockMating(15, 16, 17)], 17, 201 / 256);
+	testCoefficientForSlave("extended, 4", [new MockMating(1, 2, 3, 4), new MockMating(3, 4, 5, 6), new MockMating(5, 6, 7, 8), new MockMating(7, 8, 9, 10),
+		new MockMating(9, 10, 11, 12), new MockMating(11, 12, 13, 14), new MockMating(13, 14, 15, 16), new MockMating(15, 16, 17, 18), new MockMating(17, 18, 19)], 19, 423 / 512);
+	testCoefficientForSlave("extended, 5", [new MockMating(1, 2, 3, 4), new MockMating(3, 4, 5, 6), new MockMating(5, 6, 7, 8), new MockMating(7, 8, 9, 10),
+		new MockMating(9, 10, 11, 12), new MockMating(11, 12, 13, 14), new MockMating(13, 14, 15, 16), new MockMating(15, 16, 17, 18), new MockMating(17, 18, 19, 20),
+		new MockMating(19, 20, 21)], 21, 880 / 1024);
+	// Does not apply to current algorithm: as of this writing, the ten-generations-of-sisterfucking test immediately above is enough to occupy the game's coefficient-of-inbreeding calculation algorithm for a few minutes
+	// on a ~4Ghz CPU.  this is very silly.  when you read this, that algorithm should have been replaced by a much faster one, allowing the above test to be comfortably run again.
+	let scenario70 = [new MockMating(1, 2, 6), new MockMating(2, 3, 7), new MockMating(3, 4, 8), new MockMating(5, 6, 9), new MockMating(7, 8, 10),
+		new MockMating(9, 10, 12), new MockMating(10, 11, 13), new MockMating(12, 13, 14), new MockMating(12, 14, 15)];  // example in figure 70 from Lancaster
+	testCoefficientForSlave("example in figure 70 from Lancaster, 1", scenario70, 2, 0);
+	testCoefficientForSlave("example in figure 70 from Lancaster, 2", scenario70, 10, 1 / 8);
+	testCoefficientForSlave("example in figure 70 from Lancaster, 3", scenario70, 12, 1 / 32);
+	testCoefficientForSlave("example in figure 70 from Lancaster, 4", scenario70, 15, 85 / 256);
+	let scenarioXXX = [new MockMating(1, 2, 5, 6), new MockMating(3, 4, 7, 8), new MockMating(5, 6, 9, 10), new MockMating(5, 7, 11), new MockMating(5, 8, 12),
+		new MockMating(5, 9, 13), new MockMating(9, 10, 14), new MockMating(5, 11, 15), new MockMating(10, 12, 16), new MockMating(13, 16, 17),
+		new MockMating(4, 16, 18), new MockMating(14, 15, 19), new MockMating(6, 17, 20), new MockMating(6, 18, 21), new MockMating(6, 19, 22),
+		new MockMating(20, 21, 23), new MockMating(22, 23, 24)];  // an invented example meant to represent a typical situation in a respectable arcology
+	testCoefficientForSlave("change, 1",scenarioXXX, 24, 633 / 2048);  // here the expected coefficient was retrieved from the algorithm being tested and was not separately verified by hand,
+	// so this test could conceivably be wrong, but at least it can catch changes...
+	testCoefficientForSlave("change, 2",[new MockMating(1, 2, 3, 4), new MockMating(3, 4, 5, 6), new MockMating(5, 6, 7, 8), new MockMating(7, 8, 9, 10), new MockMating(9, 10, 11, 12),
+		new MockMating(11, 12, 13), new MockMating(13, 14, 15),
+		new MockMating(5, 5, 16), new MockMating(15, 16, 17)], 17, 1 / 4);  // an example in which the old and new algorithms actually gave different results - the first one such to have been observed! It seems that the old algorithm was wrong
+	testCoefficientForSlave("negative IDs, 1",[new MockMating(-120, -120, 87)], 87, 1 / 2);  // negative IDs need to be accepted (not bothering to test any weird special IDs like -1 here though)
+	testCoefficientForSlave("negative IDs, 2",[new MockMating(-999, -998, -300), new MockMating(-300, -998, 300)], 300, 1 / 4);
+
+	// Do this last
+	App.Testing.unitDone();
+}
diff --git a/tests/index.js b/tests/index.js
index 873c8564cc64305b4dc5a873b2c085054a6705ea..91d932d6e1689fabe0e5a365a22a747bb68eb477 100644
--- a/tests/index.js
+++ b/tests/index.js
@@ -51,6 +51,8 @@ App.Testing = (function() {
 			test();
 		} catch (e) {
 			console.log("TEST_FAILED", name, e);
+			tryCleanup(name, cleanup);
+			return;
 		}
 		if (tryCleanup(name, cleanup)) {
 			unitSuccesses++;
@@ -105,14 +107,27 @@ App.Testing = (function() {
 		}
 	}
 
+	function notNaN(value) {
+		if (Number.isNaN(value)) {
+			throw new TestError("Expected valid number, got NaN.");
+		}
+	}
+
+	function inRange(value, target, tolerance) {
+		if (Math.abs(value - target) > tolerance) {
+			throw new TestError(`Value ${value} outside range. Expected ${target} with tolerance ${tolerance}.`);
+		}
+	}
+
 	return {
 		addTestUnit, start, unitDone, executeTest,
-		equals, hasProperty, hasNoProperty: hasNoProperty, isType
+		equals, hasProperty, hasNoProperty: hasNoProperty, isType, notNaN, inRange,
 	};
 })();
 
 // Now load all tests
 App.Testing.addTestUnit("diffProxy");
+App.Testing.addTestUnit("ibc");
 
 // Finally, execute the tests
 App.Testing.start();