diff --git a/src/js/utilsAssaySlave.js b/src/js/utilsAssaySlave.js
new file mode 100644
index 0000000000000000000000000000000000000000..d9a4ff083bacaefda9c947af7ed9600396bcd902
--- /dev/null
+++ b/src/js/utilsAssaySlave.js
@@ -0,0 +1,410 @@
+/**
+ * @param {App.Entity.SlaveState} slave
+ * @returns {string}
+ */
+globalThis.getSlaveDevotionClass = function(slave) {
+	if ((!slave) || (!State)) {
+		return undefined;
+	}
+	if (slave.fetish === "mindbroken") {
+		return "mindbroken";
+	}
+	if (slave.devotion < -95) {
+		return "very-hateful";
+	} else if (slave.devotion < -50) {
+		return "hateful";
+	} else if (slave.devotion < -20) {
+		return "resistant";
+	} else if (slave.devotion <= 20) {
+		return "ambivalent";
+	} else if (slave.devotion <= 50) {
+		return "accepting";
+	} else if (slave.devotion <= 95) {
+		return "devoted";
+	} else {
+		return "worshipful";
+	}
+};
+
+/**
+ * @param {App.Entity.SlaveState} slave
+ * @returns {string}
+ */
+globalThis.getSlaveTrustClass = function(slave) {
+	if ((!slave) || (!State)) {
+		return undefined;
+	}
+
+	if (slave.fetish === "mindbroken") {
+		return "";
+	}
+
+	if (slave.trust < -95) {
+		return "extremely-terrified";
+	} else if (slave.trust < -50) {
+		return "terrified";
+	} else if (slave.trust < -20) {
+		return "frightened";
+	} else if (slave.trust <= 20) {
+		return "fearful";
+	} else if (slave.trust <= 50) {
+		if (slave.devotion < -20) {
+			return "hate-careful";
+		} else {
+			return "careful";
+		}
+	} else if (slave.trust <= 95) {
+		if (slave.devotion < -20) {
+			return "bold";
+		} else {
+			return "trusting";
+		}
+	} else if (slave.devotion < -20) {
+		return "defiant";
+	} else {
+		return "profoundly-trusting";
+	}
+};
+
+/**
+ * @param {App.Entity.SlaveState} slave
+ * @param {number} [induce]
+ * @returns {string}
+ */
+globalThis.induceLactation = function(slave, induce = 0) {
+	const {His} = getPronouns(slave);
+	let r = "";
+	let lactationStartChance = jsRandom(10, 100);
+	slave.induceLactation += induce;
+	if (slave.boobs < 300) {
+		lactationStartChance *= 1.5;
+	} else if (slave.boobs < 400 || slave.boobs >= 5000) {
+		lactationStartChance *= 1.2;
+	}
+	if (slave.pubertyXX === 0) {
+		lactationStartChance *= 1.5;
+	}
+	if (slave.preg > (slave.pregData.normalBirth / 1.33)) {
+		lactationStartChance *= .5;
+	}
+	if (slave.health.condition < -20) {
+		lactationStartChance *= 2;
+	}
+	if (slave.weight <= -30) {
+		lactationStartChance *= 1.5;
+	}
+	if (slave.boobsImplant > 0) {
+		lactationStartChance *= (1 + (slave.boobsImplant / slave.boobs));
+	}
+	if (slave.lactationAdaptation > 0) {
+		lactationStartChance = (lactationStartChance / (slave.lactationAdaptation / 10));
+	}
+	if (slave.geneticQuirks.galactorrhea === 2) {
+		lactationStartChance *= .5;
+	}
+	lactationStartChance = Math.floor(lactationStartChance);
+	if (slave.induceLactation >= lactationStartChance) {
+		r += `${His} breasts have been stimulated often enough to <span class="lime">induce lactation.</span>`;
+		slave.induceLactation = 0;
+		slave.lactationDuration = 2;
+		slave.lactation = 1;
+	}
+	return r;
+};
+
+/**
+ * @param {string} targetSkill - Skill to be checked.
+ * @param {Object} slave - Slave to be checked.
+ * @param {number} [skillIncrease=1]
+ * @returns {string}
+ */
+globalThis.slaveSkillIncrease = function(targetSkill, slave, skillIncrease = 1) {
+	let r = "", skillDec;
+	const {He, his, him} = getPronouns(slave);
+	const isleadershipRole = function() {
+		if (['headGirl', 'recruiter', 'bodyguard', 'madam', 'DJ', 'nurse', 'teacher', 'attendant', 'matron', 'stewardess', 'milkmaid', 'farmer', 'wardeness'].includes(targetSkill)) {
+			return true;
+		}
+		return false;
+	};
+
+	if (slave.skill[targetSkill] <= 10) {
+		switch(targetSkill) {
+			case 'oral':
+			case 'vaginal':
+			case 'anal':
+				skillDec = `knowledge about ${targetSkill} sex,`; break;
+			case 'whoring':
+				skillDec = `knowledge about how to whore,`; break;
+			case 'entertainment':
+				skillDec = `knowledge about how to be entertaining,`; break;
+		}
+		if (isleadershipRole()) {
+			skillDec = `${capFirstChar(targetSkill)} skills.`;
+		}
+
+		if (slave.skill[targetSkill] + skillIncrease > 10) {
+			r = `<span class="green">${He} now has basic ${skillDec}</span>`;
+			switch(targetSkill) {
+				case 'oral':
+					r += ` and at least suck a dick without constant gagging.`; break;
+				case 'vaginal':
+					r += ` and can avoid some of the common pitfalls and turnoffs.`; break;
+				case 'anal':
+					r += ` and can accept penetration of ${his} anus without danger.`; break;
+				case 'whoring':
+					r += ` and can avoid some potentially dangerous situations.`; break;
+				case 'entertainment':
+					r += ` and can usually avoid serious faux pas.`; break;
+			}
+		}
+	} else if (slave.skill[targetSkill] <= 30) {
+		switch(targetSkill) {
+			case 'oral':
+			case 'vaginal':
+			case 'anal':
+				skillDec = `${targetSkill} skills,`; break;
+			case 'whoring':
+				skillDec = `skill as a whore,`; break;
+			case 'entertainment':
+				skillDec = `skill as an entertainer,`; break;
+		}
+		if (isleadershipRole()) {
+			skillDec = `skill as a ${capFirstChar(targetSkill)}.`;
+		}
+
+		if (slave.skill.oral + skillIncrease > 30) {
+			r = `<span class="green">${He} now has some ${skillDec}</span>`;
+			switch(targetSkill) {
+				case 'oral':
+					r += ` and can reliably bring dicks and pussies to climax with ${his} mouth.`; break;
+				case 'vaginal':
+					r += ` and can do more than just lie there and take it.`; break;
+				case 'anal':
+					r += ` and needs less preparation before taking rough penetration.`; break;
+				case 'whoring':
+					r += ` and knows how to sell ${his} body at a good price.`; break;
+				case 'entertainment':
+					r += ` and can flirt, dance, and strip acceptably.`; break;
+			}
+		}
+	} else if (slave.skill[targetSkill] <= 60) {
+		switch(targetSkill) {
+			case 'oral':
+			case 'vaginal':
+			case 'anal':
+				skillDec = `${targetSkill} sex expert,`; break;
+			case 'whoring':
+				skillDec = `expert whore,`; break;
+			case 'entertainment':
+				skillDec = `expert entertainer,`; break;
+		}
+		if (isleadershipRole()) {
+			skillDec = `expert ${capFirstChar(targetSkill)}.`;
+		}
+
+		if (slave.skill[targetSkill] + skillIncrease > 60) {
+			r = `<span class="green">${He} is now an ${skillDec}</span>`;
+			switch(targetSkill) {
+				case 'oral':
+					r += ` and has a delightfully experienced tongue.`; break;
+				case 'vaginal':
+					r += ` and has the muscular control to massage anything that's inside ${him}.`; break;
+				case 'anal':
+					r += ` and knows how to use ${his} sphincter to please.`; break;
+				case 'whoring':
+					r += ` and can often make clients forget that $he's a prostitute they're paying for.`; break;
+				case 'entertainment':
+					r += ` and can flirt engagingly, dance alluringly, and strip arousingly.`; break;
+			}
+		}
+	} else if (slave.skill[targetSkill] < 100) {
+		switch(targetSkill) {
+			case 'oral':
+			case 'vaginal':
+			case 'anal':
+				skillDec = `has mastered ${targetSkill} sex,`; break;
+			case 'whoring':
+				skillDec = `is now a masterful whore,`; break;
+			case 'entertainment':
+				skillDec = `is now a masterful entertainer,`; break;
+		}
+		if (isleadershipRole()) {
+			skillDec = `is now a masterful ${capFirstChar(targetSkill)}.`;
+		}
+
+		if (slave.skill[targetSkill] + skillIncrease >= 100) {
+			r = `<span class="green">${He} ${skillDec}</span>`;
+			switch(targetSkill) {
+				case 'oral':
+					r += ` and can learn nothing more about sucking dick or eating pussy.`; break;
+				case 'vaginal':
+					r += ` and can learn nothing more about tribbing or taking dick.`; break;
+				case 'anal':
+					r += ` and can learn nothing more about taking it up the ass.`; break;
+				case 'whoring':
+					r += ` and can learn nothing more about prostitution.`; break;
+				case 'entertainment':
+					r += ` and can learn nothing more about flirting, dancing, or stripping.`; break;
+			}
+		}
+	}
+
+	if (isleadershipRole() && slave.skill[targetSkill] + skillIncrease >= 100) {
+		V.tutorGraduate.push(slave.ID);
+		V.slaveTutor[capFirstChar(targetSkill)].delete(slave.ID);
+	}
+	slave.skill[targetSkill] += skillIncrease;
+	return r;
+};
+
+/**
+ * Returns a "disobedience factor" between 0 (perfectly obedient) and 100 (completely defiant)
+ * @param {App.Entity.SlaveState} slave
+ * @returns {number}
+ */
+globalThis.disobedience = function(slave) {
+	const devotionBaseline = 20; // with devotion above this number slaves will obey completely
+	const trustBaseline = -20; // with trust below this number slaves will obey completely
+
+	if (slave.devotion > devotionBaseline || slave.trust < trustBaseline) {
+		return 0; // no chance of disobedience
+	}
+
+	// factors are between 0 (right on the boundary of perfectly obedient) and 10 (completely disobedient)
+	let devotionFactor = 10 - ((10 * (slave.devotion + 100)) / (devotionBaseline + 100));
+	let trustFactor = (10 * (slave.trust - trustBaseline)) / (100 - trustBaseline);
+	return Math.round(devotionFactor * trustFactor);
+};
+
+/**
+ * Returns how exposing a slave's outfit is, after taking into consideration a topless outfit is more revealing for beboobed slaves or female ones.
+ * @param {App.Entity.SlaveState} slave
+ * @returns {0|1|2|3|4}
+ */
+globalThis.getExposure = function(slave) {
+	const clothes = App.Data.clothes.get(slave.clothes);
+	return (clothes.topless && clothes.exposure < 3 && (slave.boobs > 299 || (slave.genes === 'XX' && slave.vagina >= 0))) ? 3 : clothes.exposure;
+};
+
+/**
+ * @param {App.Entity.SlaveState} slave
+ * @returns {boolean}
+ */
+globalThis.canImproveIntelligence = function(slave) {
+	let origIntel = V.genePool.find(function(s) { return s.ID === slave.ID; }).intelligence;
+	return (slave.intelligence < origIntel + 15) && (slave.intelligence < 100);
+};
+
+/**
+ * @param {App.Entity.SlaveState} slave
+ * @returns {number}
+ */
+globalThis.maxHeight = function(slave) {
+	let max = Math.trunc(Math.clamp((Height.mean(slave) * 1.25), 0, 274)); /* max achievable height is expected height plus 25% */
+
+	if (slave.geneticQuirks.dwarfism === 2 && slave.geneticQuirks.gigantism !== 2) {
+		max = Math.min(max, 160);
+	}
+
+	return max;
+};
+
+/**
+ * @param {App.Entity.SlaveState} slave
+ * @returns {boolean}
+ */
+globalThis.canImproveHeight = function(slave) {
+	return slave.height < maxHeight(slave);
+};
+
+/**
+ * @param {App.Entity.SlaveState} slave
+ * @param {FC.HumanState} target
+ * @returns {boolean}
+ */
+globalThis.haveRelationshipP = function(slave, target) {
+	return slave.relationshipTarget === target.ID;
+};
+
+/**
+ * @param {App.Entity.SlaveState} slave
+ * @param {App.Entity.SlaveState} target
+ * @returns {boolean}
+ */
+globalThis.isRivalP = function(slave, target) {
+	return slave.rivalryTarget === target.ID;
+};
+
+/**
+ * @param {FC.HumanState} slave
+ * @returns {boolean}
+ */
+globalThis.supremeRaceP = function(slave) {
+	return V.arcologies[0].FSSupremacistRace === slave.race;
+};
+
+/**
+ * @param {FC.HumanState} slave
+ * @returns {boolean}
+ */
+globalThis.inferiorRaceP = function(slave) {
+	return V.arcologies[0].FSSubjugationistRace === slave.race;
+};
+
+/**
+ * @param {App.Entity.SlaveState} slave
+ * @returns {boolean}
+ */
+globalThis.isLeaderP = function(slave) {
+	const leaders = [S.HeadGirl, S.Bodyguard, S.Recruiter, S.Concubine, S.Nurse, S.Attendant, S.Matron, S.Madam, S.DJ, S.Milkmaid, S.Farmer, S.Stewardess, S.Schoolteacher, S.Wardeness];
+
+	return leaders.some(leader => leader && leader.ID === slave.ID);
+};
+
+/** Get the written title for a given slave, without messing with global state.
+ * @param {App.Entity.SlaveState} [slave]
+ * @returns {string}
+ */
+globalThis.getWrittenTitle = function(slave) {
+	if (slave && slave.custom.title !== undefined && slave.custom.title !== "" && slave.rudeTitle === 0) {
+		return slave.custom.title;
+	}
+	if (V.PC.customTitle !== undefined) {
+		return V.PC.customTitle;
+	} else if (V.PC.title !== 0) {
+		return "Master";
+	} else {
+		return "Mistress";
+	}
+};
+
+/**
+ * @param {App.Entity.SlaveState} slave
+ * @returns {string}
+ */
+globalThis.SlaveFullName = function(slave) {
+	const pair = slave.slaveSurname ? [slave.slaveName, slave.slaveSurname] : [slave.slaveName];
+	if ((V.surnameOrder !== 1 && ["Cambodian", "Chinese", "Hungarian", "Japanese", "Korean", "Mongolian", "Taiwanese", "Vietnamese"].includes(slave.nationality)) || (V.surnameOrder === 2)) {
+		pair.reverse();
+	}
+	return pair.join(" ");
+};
+
+/** Is the slave a shelter slave?
+ * @param {App.Entity.SlaveState} slave
+ * @returns {boolean}
+ */
+globalThis.isShelterSlave = function(slave) {
+	return (typeof slave.origin === "string" && slave.origin.includes("Slave Shelter"));
+};
+
+/**
+ * Returns if a slave appears male, female, or androgynous.
+ *
+ * @param {App.Entity.SlaveState} slave
+ * @returns {number}
+ */
+globalThis.perceivedGender = function(slave) {
+	return -1;
+};
diff --git a/src/js/utilsFC.js b/src/js/utilsFC.js
index 59ed3ac912bb4d544253d6a78bf53cbed248abdb..9196be050320aac0c5aa41d3fa6c4a1b576d1b26 100644
--- a/src/js/utilsFC.js
+++ b/src/js/utilsFC.js
@@ -512,3 +512,112 @@ globalThis.initRules = function() {
 	V.defaultRules = [rule];
 	V.rulesToApplyOnce = {};
 };
+
+/** @typedef {object} getBestSlavesParams
+ * @property {string|function(App.Entity.SlaveState): number} part slave object property or custom function
+ * @property {number} [count] number of slaves to return
+ * @property {boolean} [largest] should it search for the biggest or smallest value
+ * @property {function(App.Entity.SlaveState): boolean} [filter] filter out undesired slaves
+ */
+
+/**
+ * @param {getBestSlavesParams} params
+ * @returns {App.Entity.SlaveState[]} sorted from best to worst
+ */
+globalThis.getBestSlaves = function({part, count = 3, largest = true, filter = (() => true)}) {
+	const partCB = _.isFunction(part) ? part :  (slave) => slave[part];
+
+	const sortMethod = largest ? (left, right) => right.value - left.value : (left, right) => left.value - right.value;
+	return V.slaves.filter(slave => filter(slave))
+		.map(slave => ({slave, value: partCB(slave)}))
+		.sort(sortMethod)
+		.slice(0, count)
+		.map(slaveInfo => slaveInfo.slave);
+};
+
+/**
+ * Returns a valid rape target for a slave who is going to rape one of his peers into rivalry with him.
+ * @param {App.Entity.SlaveState} slave
+ * @param {function(App.Entity.SlaveState): boolean} predicate
+ * @returns {App.Entity.SlaveState | undefined}
+ */
+globalThis.randomRapeRivalryTarget = function(slave, predicate) {
+	const willIgnoreRules = disobedience(slave) > jsRandom(0, 100);
+
+	function canBeARapeRival(s) {
+		return (s.devotion <= 95 && s.energy <= 95 && !s.rivalry && !s.fuckdoll && s.fetish !== "mindbroken");
+	}
+
+	function canRape(rapist, rapee) {
+		const opportunity = (assignmentVisible(rapist) && assignmentVisible(rapee)) || rapist.assignment === rapee.assignment;
+		const taboo = V.seeIncest === 0 && areRelated(rapist, rapee);
+		const desire = !(rapist.relationship >= 3 && rapist.relationshipTarget === rapee.id) && !taboo;
+		const permission = willIgnoreRules || App.Utils.sexAllowed(rapist, rapee);
+		return opportunity && desire && permission;
+	}
+
+	if (typeof predicate !== 'function') {
+		predicate = (() => true);
+	}
+
+	const arr = V.slaves.filter((s) => { return canBeARapeRival(s) && canRape(slave, s); }).shuffle();
+	return arr.find(predicate);
+};
+
+/**
+ * @param {getBestSlavesParams} info
+ * @returns {number[]}
+ */
+globalThis.getBestSlavesIDs = function(info) {
+	return getBestSlaves(info).map(slave => slave.ID);
+};
+
+/*
+//Example
+getBestSlaves({part:"butt", count: 5});
+getBestSlaves({part:"boobs"});//defaults to top 3
+getBestSlaves({part:"dick", smallest:true, filter:(slave)=>slave.dick > 0});//defaults to top 3
+getBestSlaves({part:slave=>slave.intelligence+slave.intelligenceImplant});
+*/
+
+/**
+ * Generates a new slave ID that is guaranteed to be unused
+ * @returns {number} slave ID
+ */
+globalThis.generateSlaveID = function() {
+	// household liquidators and recETS generate slaves at an offset of 1000 (and many such slaves already exist)
+	// if you go through enough slaves you WILL generate collisions, so make sure we haven't just done that.
+	let allSlaveIDs = [...V.slaves.map((s) => s.ID), ...V.tanks.map((s) => s.ID), ...V.cribs.map((s) => s.ID)];
+	while (allSlaveIDs.includes(V.IDNumber)) {
+		V.IDNumber++;
+	}
+	return V.IDNumber++;
+};
+
+globalThis.ASDump = function() {
+	if ((typeof V.activeSlave === undefined) || (V.activeSlave === 0)) {
+		return `<span class="red">ERROR:</span> AS Dump, activeSlave invalid, returnTo is 'V.returnTo', previous passage was '${previous()}'. Please report this. `;
+	} else {
+		let SL = V.slaves.length;
+		let ID = V.activeSlave.ID;
+		if (V.i >= 0 && V.i < SL && V.slaves[V.i].ID === ID) { /* shortcut if V.i is already pointing to this slave */
+			V.slaves[V.i] = V.activeSlave;
+		} else {
+			V.i = V.slaveIndices[ID]; // find V.i if exists
+			if (typeof V.i === undefined) { /* not found, so new slave */
+				newSlave(V.activeSlave);
+			} else {
+				V.slaves[V.i] = V.activeSlave;
+			}
+		}
+	}
+};
+
+/**
+ * @param {App.Entity.SlaveState} A
+ * @param {App.Entity.SlaveState} B
+ * @returns {boolean}
+ */
+globalThis.sameAssignmentP = function(A, B) {
+	return A.assignment === B.assignment;
+};
diff --git a/src/js/utilsSlave.js b/src/js/utilsSlave.js
index 34fbfd2d6b5a576ee2ed668724c51dc9305fb653..02f8e60df6fb86c4ca77de3a71bab6278a839d50 100644
--- a/src/js/utilsSlave.js
+++ b/src/js/utilsSlave.js
@@ -909,119 +909,6 @@ As a categorizer
 <</if>>
 <<print $cats.muscleCat.cat(_Slave.muscles)>>
 */
-/**
- * @param {App.Entity.SlaveState} slave
- * @returns {string}
- */
-globalThis.getSlaveDevotionClass = function(slave) {
-	if ((!slave) || (!State)) {
-		return undefined;
-	}
-	if (slave.fetish === "mindbroken") {
-		return "mindbroken";
-	}
-	if (slave.devotion < -95) {
-		return "very-hateful";
-	} else if (slave.devotion < -50) {
-		return "hateful";
-	} else if (slave.devotion < -20) {
-		return "resistant";
-	} else if (slave.devotion <= 20) {
-		return "ambivalent";
-	} else if (slave.devotion <= 50) {
-		return "accepting";
-	} else if (slave.devotion <= 95) {
-		return "devoted";
-	} else {
-		return "worshipful";
-	}
-};
-
-/**
- * @param {App.Entity.SlaveState} slave
- * @returns {string}
- */
-globalThis.getSlaveTrustClass = function(slave) {
-	if ((!slave) || (!State)) {
-		return undefined;
-	}
-
-	if (slave.fetish === "mindbroken") {
-		return "";
-	}
-
-	if (slave.trust < -95) {
-		return "extremely-terrified";
-	} else if (slave.trust < -50) {
-		return "terrified";
-	} else if (slave.trust < -20) {
-		return "frightened";
-	} else if (slave.trust <= 20) {
-		return "fearful";
-	} else if (slave.trust <= 50) {
-		if (slave.devotion < -20) {
-			return "hate-careful";
-		} else {
-			return "careful";
-		}
-	} else if (slave.trust <= 95) {
-		if (slave.devotion < -20) {
-			return "bold";
-		} else {
-			return "trusting";
-		}
-	} else if (slave.devotion < -20) {
-		return "defiant";
-	} else {
-		return "profoundly-trusting";
-	}
-};
-
-/**
- * @param {App.Entity.SlaveState} slave
- * @param {number} [induce]
- * @returns {string}
- */
-globalThis.induceLactation = function(slave, induce = 0) {
-	const {His} = getPronouns(slave);
-	let r = "";
-	let lactationStartChance = jsRandom(10, 100);
-	slave.induceLactation += induce;
-	if (slave.boobs < 300) {
-		lactationStartChance *= 1.5;
-	} else if (slave.boobs < 400 || slave.boobs >= 5000) {
-		lactationStartChance *= 1.2;
-	}
-	if (slave.pubertyXX === 0) {
-		lactationStartChance *= 1.5;
-	}
-	if (slave.preg > (slave.pregData.normalBirth / 1.33)) {
-		lactationStartChance *= .5;
-	}
-	if (slave.health.condition < -20) {
-		lactationStartChance *= 2;
-	}
-	if (slave.weight <= -30) {
-		lactationStartChance *= 1.5;
-	}
-	if (slave.boobsImplant > 0) {
-		lactationStartChance *= (1 + (slave.boobsImplant / slave.boobs));
-	}
-	if (slave.lactationAdaptation > 0) {
-		lactationStartChance = (lactationStartChance / (slave.lactationAdaptation / 10));
-	}
-	if (slave.geneticQuirks.galactorrhea === 2) {
-		lactationStartChance *= .5;
-	}
-	lactationStartChance = Math.floor(lactationStartChance);
-	if (slave.induceLactation >= lactationStartChance) {
-		r += `${His} breasts have been stimulated often enough to <span class="lime">induce lactation.</span>`;
-		slave.induceLactation = 0;
-		slave.lactationDuration = 2;
-		slave.lactation = 1;
-	}
-	return r;
-};
 
 globalThis.pronounReplacer = function(slavetext) {
 	switch (slavetext) {
@@ -1484,6 +1371,7 @@ globalThis.pronounReplacer = function(slavetext) {
 	}
 	return slavetext;
 };
+
 globalThis.convertCareer = function(slave) {
 	let job = slave.career;
 	if ((V.diversePronouns === 1) && (slave.pronoun === App.Data.Pronouns.Kind.male)) {
@@ -1587,151 +1475,10 @@ globalThis.convertCareer = function(slave) {
 };
 
 /**
- * @param {string} targetSkill - Skill to be checked.
- * @param {Object} slave - Slave to be checked.
- * @param {number} [skillIncrease=1]
- * @returns {string}
+ * 
+ * @param {App.Entity.SlaveState} slave
+ * @returns {string|null}
  */
-globalThis.slaveSkillIncrease = function(targetSkill, slave, skillIncrease = 1) {
-	let r = "", skillDec;
-	const {He, his, him} = getPronouns(slave);
-	const isleadershipRole = function() {
-		if (['headGirl', 'recruiter', 'bodyguard', 'madam', 'DJ', 'nurse', 'teacher', 'attendant', 'matron', 'stewardess', 'milkmaid', 'farmer', 'wardeness'].includes(targetSkill)) {
-			return true;
-		}
-		return false;
-	};
-
-	if (slave.skill[targetSkill] <= 10) {
-		switch(targetSkill) {
-			case 'oral':
-			case 'vaginal':
-			case 'anal':
-				skillDec = `knowledge about ${targetSkill} sex,`; break;
-			case 'whoring':
-				skillDec = `knowledge about how to whore,`; break;
-			case 'entertainment':
-				skillDec = `knowledge about how to be entertaining,`; break;
-		}
-		if (isleadershipRole()) {
-			skillDec = `${capFirstChar(targetSkill)} skills.`;
-		}
-
-		if (slave.skill[targetSkill] + skillIncrease > 10) {
-			r = `<span class="green">${He} now has basic ${skillDec}</span>`;
-			switch(targetSkill) {
-				case 'oral':
-					r += ` and at least suck a dick without constant gagging.`; break;
-				case 'vaginal':
-					r += ` and can avoid some of the common pitfalls and turnoffs.`; break;
-				case 'anal':
-					r += ` and can accept penetration of ${his} anus without danger.`; break;
-				case 'whoring':
-					r += ` and can avoid some potentially dangerous situations.`; break;
-				case 'entertainment':
-					r += ` and can usually avoid serious faux pas.`; break;
-			}
-		}
-	} else if (slave.skill[targetSkill] <= 30) {
-		switch(targetSkill) {
-			case 'oral':
-			case 'vaginal':
-			case 'anal':
-				skillDec = `${targetSkill} skills,`; break;
-			case 'whoring':
-				skillDec = `skill as a whore,`; break;
-			case 'entertainment':
-				skillDec = `skill as an entertainer,`; break;
-		}
-		if (isleadershipRole()) {
-			skillDec = `skill as a ${capFirstChar(targetSkill)}.`;
-		}
-
-		if (slave.skill.oral + skillIncrease > 30) {
-			r = `<span class="green">${He} now has some ${skillDec}</span>`;
-			switch(targetSkill) {
-				case 'oral':
-					r += ` and can reliably bring dicks and pussies to climax with ${his} mouth.`; break;
-				case 'vaginal':
-					r += ` and can do more than just lie there and take it.`; break;
-				case 'anal':
-					r += ` and needs less preparation before taking rough penetration.`; break;
-				case 'whoring':
-					r += ` and knows how to sell ${his} body at a good price.`; break;
-				case 'entertainment':
-					r += ` and can flirt, dance, and strip acceptably.`; break;
-			}
-		}
-	} else if (slave.skill[targetSkill] <= 60) {
-		switch(targetSkill) {
-			case 'oral':
-			case 'vaginal':
-			case 'anal':
-				skillDec = `${targetSkill} sex expert,`; break;
-			case 'whoring':
-				skillDec = `expert whore,`; break;
-			case 'entertainment':
-				skillDec = `expert entertainer,`; break;
-		}
-		if (isleadershipRole()) {
-			skillDec = `expert ${capFirstChar(targetSkill)}.`;
-		}
-
-		if (slave.skill[targetSkill] + skillIncrease > 60) {
-			r = `<span class="green">${He} is now an ${skillDec}</span>`;
-			switch(targetSkill) {
-				case 'oral':
-					r += ` and has a delightfully experienced tongue.`; break;
-				case 'vaginal':
-					r += ` and has the muscular control to massage anything that's inside ${him}.`; break;
-				case 'anal':
-					r += ` and knows how to use ${his} sphincter to please.`; break;
-				case 'whoring':
-					r += ` and can often make clients forget that $he's a prostitute they're paying for.`; break;
-				case 'entertainment':
-					r += ` and can flirt engagingly, dance alluringly, and strip arousingly.`; break;
-			}
-		}
-	} else if (slave.skill[targetSkill] < 100) {
-		switch(targetSkill) {
-			case 'oral':
-			case 'vaginal':
-			case 'anal':
-				skillDec = `has mastered ${targetSkill} sex,`; break;
-			case 'whoring':
-				skillDec = `is now a masterful whore,`; break;
-			case 'entertainment':
-				skillDec = `is now a masterful entertainer,`; break;
-		}
-		if (isleadershipRole()) {
-			skillDec = `is now a masterful ${capFirstChar(targetSkill)}.`;
-		}
-
-		if (slave.skill[targetSkill] + skillIncrease >= 100) {
-			r = `<span class="green">${He} ${skillDec}</span>`;
-			switch(targetSkill) {
-				case 'oral':
-					r += ` and can learn nothing more about sucking dick or eating pussy.`; break;
-				case 'vaginal':
-					r += ` and can learn nothing more about tribbing or taking dick.`; break;
-				case 'anal':
-					r += ` and can learn nothing more about taking it up the ass.`; break;
-				case 'whoring':
-					r += ` and can learn nothing more about prostitution.`; break;
-				case 'entertainment':
-					r += ` and can learn nothing more about flirting, dancing, or stripping.`; break;
-			}
-		}
-	}
-
-	if (isleadershipRole() && slave.skill[targetSkill] + skillIncrease >= 100) {
-		V.tutorGraduate.push(slave.ID);
-		V.slaveTutor[capFirstChar(targetSkill)].delete(slave.ID);
-	}
-	slave.skill[targetSkill] += skillIncrease;
-	return r;
-};
-
 globalThis.tutorForSlave = function(slave) {
 	for (const tutor of Object.keys(V.slaveTutor)) {
 		const pupils = V.slaveTutor[tutor];
@@ -1742,6 +1489,11 @@ globalThis.tutorForSlave = function(slave) {
 	return null;
 };
 
+/**
+ * 
+ * @param {string} skill
+ * @returns {number}
+ */
 globalThis.upgradeMultiplier = function(skill) {
 	if (skill === 'medicine' && V.PC.career === "medicine" || skill === 'engineering' && V.PC.career === "engineer"
 		|| ((skill === 'medicine' || skill === 'engineering') && V.arcologies[0].FSRestartDecoration >= 100 && V.eugenicsFullControl === 0)) {
@@ -2136,125 +1888,6 @@ globalThis.moreNational = function(nation) {
 	return country;
 };
 
-/**
- * Returns a "disobedience factor" between 0 (perfectly obedient) and 100 (completely defiant)
- * @param {App.Entity.SlaveState} slave
- * @returns {number}
- */
-globalThis.disobedience = function(slave) {
-	const devotionBaseline = 20; // with devotion above this number slaves will obey completely
-	const trustBaseline = -20; // with trust below this number slaves will obey completely
-
-	if (slave.devotion > devotionBaseline || slave.trust < trustBaseline) {
-		return 0; // no chance of disobedience
-	}
-
-	// factors are between 0 (right on the boundary of perfectly obedient) and 10 (completely disobedient)
-	let devotionFactor = 10 - ((10 * (slave.devotion + 100)) / (devotionBaseline + 100));
-	let trustFactor = (10 * (slave.trust - trustBaseline)) / (100 - trustBaseline);
-	return Math.round(devotionFactor * trustFactor);
-};
-
-/**
- * Returns a valid rape target for a slave who is going to rape one of his peers into rivalry with him.
- * @param {App.Entity.SlaveState} slave
- * @param {function(App.Entity.SlaveState): boolean} predicate
- * @returns {App.Entity.SlaveState | undefined}
- */
-globalThis.randomRapeRivalryTarget = function(slave, predicate) {
-	const willIgnoreRules = disobedience(slave) > jsRandom(0, 100);
-
-	function canBeARapeRival(s) {
-		return (s.devotion <= 95 && s.energy <= 95 && !s.rivalry && !s.fuckdoll && s.fetish !== "mindbroken");
-	}
-
-	function canRape(rapist, rapee) {
-		const opportunity = (assignmentVisible(rapist) && assignmentVisible(rapee)) || rapist.assignment === rapee.assignment;
-		const taboo = V.seeIncest === 0 && areRelated(rapist, rapee);
-		const desire = !(rapist.relationship >= 3 && rapist.relationshipTarget === rapee.id) && !taboo;
-		const permission = willIgnoreRules || App.Utils.sexAllowed(rapist, rapee);
-		return opportunity && desire && permission;
-	}
-
-	if (typeof predicate !== 'function') {
-		predicate = (() => true);
-	}
-
-	const arr = V.slaves.filter((s) => { return canBeARapeRival(s) && canRape(slave, s); }).shuffle();
-	return arr.find(predicate);
-};
-
-
-/** @typedef {object} getBestSlavesParams
- * @property {string|function(App.Entity.SlaveState): number} part slave object property or custom function
- * @property {number} [count] number of slaves to return
- * @property {boolean} [largest] should it search for the biggest or smallest value
- * @property {function(App.Entity.SlaveState): boolean} [filter] filter out undesired slaves
- */
-
-/**
- * @param {getBestSlavesParams} params
- * @returns {App.Entity.SlaveState[]} sorted from best to worst
- */
-globalThis.getBestSlaves = function({part, count = 3, largest = true, filter = (() => true)}) {
-	const partCB = _.isFunction(part) ? part :  (slave) => slave[part];
-
-	const sortMethod = largest ? (left, right) => right.value - left.value : (left, right) => left.value - right.value;
-	return V.slaves.filter(slave => filter(slave))
-		.map(slave => ({slave, value: partCB(slave)}))
-		.sort(sortMethod)
-		.slice(0, count)
-		.map(slaveInfo => slaveInfo.slave);
-};
-/**
- * @param {getBestSlavesParams} info
- * @returns {number[]}
- */
-globalThis.getBestSlavesIDs = function(info) {
-	return getBestSlaves(info).map(slave => slave.ID);
-};
-
-/*
-//Example
-getBestSlaves({part:"butt", count: 5});
-getBestSlaves({part:"boobs"});//defaults to top 3
-getBestSlaves({part:"dick", smallest:true, filter:(slave)=>slave.dick > 0});//defaults to top 3
-getBestSlaves({part:slave=>slave.intelligence+slave.intelligenceImplant});
-*/
-
-/**
- * Generates a new slave ID that is guaranteed to be unused
- * @returns {number} slave ID
- */
-globalThis.generateSlaveID = function() {
-	// household liquidators and recETS generate slaves at an offset of 1000 (and many such slaves already exist)
-	// if you go through enough slaves you WILL generate collisions, so make sure we haven't just done that.
-	let allSlaveIDs = [...V.slaves.map((s) => s.ID), ...V.tanks.map((s) => s.ID), ...V.cribs.map((s) => s.ID)];
-	while (allSlaveIDs.includes(V.IDNumber)) {
-		V.IDNumber++;
-	}
-	return V.IDNumber++;
-};
-
-globalThis.ASDump = function() {
-	if ((typeof V.activeSlave === undefined) || (V.activeSlave === 0)) {
-		return `<span class="red">ERROR:</span> AS Dump, activeSlave invalid, returnTo is 'V.returnTo', previous passage was '${previous()}'. Please report this. `;
-	} else {
-		let SL = V.slaves.length;
-		let ID = V.activeSlave.ID;
-		if (V.i >= 0 && V.i < SL && V.slaves[V.i].ID === ID) { /* shortcut if V.i is already pointing to this slave */
-			V.slaves[V.i] = V.activeSlave;
-		} else {
-			V.i = V.slaveIndices[ID]; // find V.i if exists
-			if (typeof V.i === undefined) { /* not found, so new slave */
-				newSlave(V.activeSlave);
-			} else {
-				V.slaves[V.i] = V.activeSlave;
-			}
-		}
-	}
-};
-
 /** Deflate a slave (reset inflation to none)
  * @param {App.Entity.SlaveState} slave
  */
@@ -2267,100 +1900,6 @@ globalThis.deflate = function(slave) {
 	SetBellySize(slave);
 };
 
-/**
- * Returns how exposing a slave's outfit is, after taking into consideration a topless outfit is more revealing for beboobed slaves or female ones.
- * @param {App.Entity.SlaveState} slave
- * @returns {0|1|2|3|4}
- */
-globalThis.getExposure = function(slave) {
-	const clothes = App.Data.clothes.get(slave.clothes);
-	return (clothes.topless && clothes.exposure < 3 && (slave.boobs > 299 || (slave.genes === 'XX' && slave.vagina >= 0))) ? 3 : clothes.exposure;
-};
-
-/**
- * @param {App.Entity.SlaveState} A
- * @param {App.Entity.SlaveState} B
- * @returns {boolean}
- */
-globalThis.sameAssignmentP = function(A, B) {
-	return A.assignment === B.assignment;
-};
-
-/**
- * @param {App.Entity.SlaveState} slave
- * @returns {boolean}
- */
-globalThis.canImproveIntelligence = function(slave) {
-	let origIntel = V.genePool.find(function(s) { return s.ID === slave.ID; }).intelligence;
-	return (slave.intelligence < origIntel + 15) && (slave.intelligence < 100);
-};
-
-/**
- * @param {App.Entity.SlaveState} slave
- * @returns {number}
- */
-globalThis.maxHeight = function(slave) {
-	let max = Math.trunc(Math.clamp((Height.mean(slave) * 1.25), 0, 274)); /* max achievable height is expected height plus 25% */
-
-	if (slave.geneticQuirks.dwarfism === 2 && slave.geneticQuirks.gigantism !== 2) {
-		max = Math.min(max, 160);
-	}
-
-	return max;
-};
-
-/**
- * @param {App.Entity.SlaveState} slave
- * @returns {boolean}
- */
-globalThis.canImproveHeight = function(slave) {
-	return slave.height < maxHeight(slave);
-};
-
-/**
- * @param {App.Entity.SlaveState} slave
- * @param {FC.HumanState} target
- * @returns {boolean}
- */
-globalThis.haveRelationshipP = function(slave, target) {
-	return slave.relationshipTarget === target.ID;
-};
-
-/**
- * @param {App.Entity.SlaveState} slave
- * @param {App.Entity.SlaveState} target
- * @returns {boolean}
- */
-globalThis.isRivalP = function(slave, target) {
-	return slave.rivalryTarget === target.ID;
-};
-
-/**
- * @param {FC.HumanState} slave
- * @returns {boolean}
- */
-globalThis.supremeRaceP = function(slave) {
-	return V.arcologies[0].FSSupremacistRace === slave.race;
-};
-
-/**
- * @param {FC.HumanState} slave
- * @returns {boolean}
- */
-globalThis.inferiorRaceP = function(slave) {
-	return V.arcologies[0].FSSubjugationistRace === slave.race;
-};
-
-/**
- * @param {App.Entity.SlaveState} slave
- * @returns {boolean}
- */
-globalThis.isLeaderP = function(slave) {
-	const leaders = [S.HeadGirl, S.Bodyguard, S.Recruiter, S.Concubine, S.Nurse, S.Attendant, S.Matron, S.Madam, S.DJ, S.Milkmaid, S.Farmer, S.Stewardess, S.Schoolteacher, S.Wardeness];
-
-	return leaders.some(leader => leader && leader.ID === slave.ID);
-};
-
 /**
  * colors skin, eyes and hair based on genetic Color.
  * Takes .override_*_Color into account.
@@ -2546,23 +2085,6 @@ globalThis.newSlave = function(slave) {
 	}
 };
 
-/** Get the written title for a given slave, without messing with global state.
- * @param {App.Entity.SlaveState} [slave]
- * @returns {string}
- */
-globalThis.getWrittenTitle = function(slave) {
-	if (slave && slave.custom.title !== undefined && slave.custom.title !== "" && slave.rudeTitle === 0) {
-		return slave.custom.title;
-	}
-	if (V.PC.customTitle !== undefined) {
-		return V.PC.customTitle;
-	} else if (V.PC.title !== 0) {
-		return "Master";
-	} else {
-		return "Mistress";
-	}
-};
-
 /**
  * @param {App.Entity.SlaveState} slave
  * @returns {number}
@@ -2595,18 +2117,6 @@ globalThis.fetishChangeChance = function(slave) {
 	return chance;
 };
 
-/**
- * @param {App.Entity.SlaveState} slave
- * @returns {string}
- */
-globalThis.SlaveFullName = function(slave) {
-	const pair = slave.slaveSurname ? [slave.slaveName, slave.slaveSurname] : [slave.slaveName];
-	if ((V.surnameOrder !== 1 && ["Cambodian", "Chinese", "Hungarian", "Japanese", "Korean", "Mongolian", "Taiwanese", "Vietnamese"].includes(slave.nationality)) || (V.surnameOrder === 2)) {
-		pair.reverse();
-	}
-	return pair.join(" ");
-};
-
 /**
  * @param {App.Entity.SlaveState} slave
  * @returns {string}
@@ -3428,6 +2938,9 @@ globalThis.DegradingName = function(slave) {
 	slave.slaveSurname = surname;
 };
 
+/**
+ * @param {App.Entity.SlaveState} slave
+ */
 globalThis.PaternalistName = function(slave) {
 	if (slave.slaveName.search("Miss") === -1) {
 		if (slave.slaveName.search("Ms.") === -1) {
@@ -3444,6 +2957,11 @@ globalThis.PaternalistName = function(slave) {
 	}
 };
 
+/**
+ * 
+ * @param {App.Entity.SlaveState} parent
+ * @param {App.Entity.SlaveState} child
+ */
 globalThis.parentNames = function(parent, child) {
 	const slaves = V.slaves;
 
@@ -3736,21 +3254,3 @@ globalThis.ageSlave = function(slave, forceDevelopment = false) {
 		physicalDevelopment(slave);
 	}
 };
-
-/** Is the slave a shelter slave?
- * @param {App.Entity.SlaveState} slave
- * @returns {boolean}
- */
-globalThis.isShelterSlave = function(slave) {
-	return (typeof slave.origin === "string" && slave.origin.includes("Slave Shelter"));
-};
-
-/**
- * Returns if a slave appears male, female, or androgynous.
- *
- * @param {App.Entity.SlaveState} slave
- * @returns {number}
- */
-globalThis.perceivedGender = function(slave) {
-	return -1;
-};