code:
// cSpell:ignore identicality

globalThis.generateRelatedSlave = (function() {
	let sourceID;

	/**
	 * Generate a very similar relative for an existing slave (for use in Household Liquidators, for example).
	 * @param {FC.SlaveState} slave - the source relative. Note: this slave is NOT changed, calling code is responsible for setting up the source end of the relationship!
	 * @param {string} relationship - the relationship that the new relative has with the source. Currently supports "parent", "child", "older sibling", "younger sibling", "twin", and applicable gender-specific variants of those (i.e. mother/father, daughter/son, older/younger brother/sister).
	 * @param {boolean} oppositeSex - set to true if the new relative should be the opposite sex of the old one (otherwise it will be the same sex). Will be ignored if gender is implied by relationship.
	 * @param {number} ageOverridesPedoMode - set to 1 to allow generation of slaves over 18 in pedo mode. Regardless of this value, pedo mode will be ignored if a slave 18 or under isn't possible (e.g., generating the parent of an 18-year-old), but pedo mode may still produce extreme results (e.g., siblings aged 44 and 16).
	 * @param {number} minAgeDifference - (integer) minimum age difference between existing slave and new relative; if undefined, min difference depends on the type of relationship. Does not override V.minimumSlaveAge, V.retirementAge, the pedo mode limit (if applicable), or the biological limits of fertility, but improper values can produce absurd results.
	 * @param {number} maxAgeDifference - (integer) maximum age difference between existing slave and new relative; if undefined, max difference depends on the type of relationship. Does not override V.minimumSlaveAge, V.retirementAge, the pedo mode limit (if applicable), or the biological limits of fertility.
	 * In pedo mode, the age cap is "soft" for younger siblings, that is, a random value from 16 to 18, to avoid all younger siblings of older slaves ending up exactly 18.
	 * Pedo mode parents are possible for very young slaves, but in cases where they're not possible, the function reverts to normal behavior, meaning it could produce dramatic parent age differences with small differences in child age; use this option with caution.
	 * @returns {FC.SlaveState} - new relative
	 */
	function generateRelative(slave, relationship, oppositeSex=false, ageOverridesPedoMode=0, minAgeDifference=undefined, maxAgeDifference=undefined) {
		let relative = prepareClone(slave);

		const usePedoMode = V.pedoMode==1 && ageOverridesPedoMode==0;
		({relationship, oppositeSex} = interpretGenderSpecificRelationship(slave, relationship, oppositeSex));
		if (relationship === "twin") {
			makeTwin(relative);
		} else if (relationship === "child") {
			makeChild(relative, slave.genes, minAgeDifference, maxAgeDifference, usePedoMode);
		} else if (relationship === "parent") {
			const father = (slave.genes === "XX" && oppositeSex) || (slave.genes === "XY" && !oppositeSex);
			makeParent(relative, father, minAgeDifference, maxAgeDifference, usePedoMode);
		} else if (relationship === "younger sibling") {
			makeYoungerSibling(relative, minAgeDifference, maxAgeDifference, usePedoMode);
		} else if (relationship === "older sibling") {
			makeOlderSibling(relative, minAgeDifference, maxAgeDifference, usePedoMode);
		} else {
			throw Error(`Unknown relationship: ${relationship}`);
		}
		if (oppositeSex) {
			if (slave.genes === "XX") {
				changeSexToXY(relative);
			} else if (slave.genes === "XY") {
				changeSexToXX(relative);
			} else {
				// we'll assume futa are their own opposites and don't need tweaking
			}
		}
		generateGivenName(relative); // must happen *after* any possible sex change
		// perform age-related adjustment for all relatives *except* same-sex twins (preserve identicality)
		if (relative.actualAge !== slave.actualAge || oppositeSex) {
			ageFixup(relative);
		}
		resetStatus(relative);

		return relative;
	}

	/**
	 * Split a gender-specific relationship into a gender-neutral relationship and a base-slave-relative gender toggle
	 * @param {FC.SlaveState} slave
	 * @param {string} relationship
	 * @param {boolean} oppositeSex - original value of oppositeSex (will be copied unchanged if not overridden)
	 * @returns {{relationship: string, oppositeSex: boolean}}
	 */
	function interpretGenderSpecificRelationship(slave, relationship, oppositeSex) {
		/** @param {string} genes - expected genes for this case
		 * @returns {boolean} - whether genes match slave or not */
		function isOppositeSex(genes) { return slave.genes !== genes; }

		if (relationship === "daughter") {
			return {relationship: "child", oppositeSex: isOppositeSex("XX")};
		} else if (relationship === "son") {
			return {relationship: "child", oppositeSex: isOppositeSex("XY")};
		} else if (relationship === "mother") {
			return {relationship: "parent", oppositeSex: isOppositeSex("XX")};
		} else if (relationship === "father") {
			return {relationship: "parent", oppositeSex: isOppositeSex("XY")};
		} else {
			const bits = relationship.split(" ");
			if (bits.length === 2) {
				if (bits[0] === "younger" || bits[0] === "older") {
					if (bits[1] === "sister") {
						return {relationship: `${bits[0]} sibling`, oppositeSex: isOppositeSex("XX")};
					} else if (bits[1] === "brother") {
						return {relationship: `${bits[0]} sibling`, oppositeSex: isOppositeSex("XY")};
					}
				}
			}
		}
		return {relationship: relationship, oppositeSex: oppositeSex};
	}

	/**
	 * Clone the original slave and do some common preparations to it.
	 * @param {FC.SlaveState} slave - the source relative
	 * @returns {FC.SlaveState} - the new relative
	 */
	function prepareClone(slave) {
		let relative = clone(slave);

		// regenerate accent
		nationalityToAccent(relative);

		// fuzz trust/devotion
		relative.devotion += random(-5, 5);
		relative.oldDevotion = relative.devotion;
		relative.trust += random(-5, 5);
		relative.oldTrust = relative.trust;

		// fuzz attraction and energy
		relative.attrXX += random(-20, 20);
		relative.attrXX = Math.clamp(relative.attrXX, 0, 100);
		relative.attrXY += random(-20, 20);
		relative.attrXY = Math.clamp(relative.attrXX, 0, 100);
		relative.energy += random(-20, 20);

		// store old ID and set the new ID
		sourceID = slave.ID;
		relative.ID = generateSlaveID();

		return relative;
	}

	/**
	 * Generate a new given name for the slave (keeping the surname).
	 * @param {FC.SlaveState} slave - the new relative to be renamed
	 */
	function generateGivenName(slave) {
		const surname = slave.slaveSurname;
		const birthSurname = slave.birthSurname;
		nationalityToName(slave);
		slave.slaveSurname = surname;
		slave.birthSurname = birthSurname;
	}

	/**
	 * Finish configuring an identical twin
	 * @param {FC.SlaveState} slave - the new twin
	 */
	function makeTwin(slave) {
		/* twins are physically identical, change only mental traits. */
		randomizeFetishFlaws(slave);
	}

	/**
	 * Finish configuring a sibling
	 * @param {FC.SlaveState} slave - the new sibling
	 * @param {number} minAgeDifference - integer or undefined
	 * @param {number} maxAgeDifference - integer or undefined
	 * @param {boolean} usePedoMode - Caps younger sibling's age at 16-18, and and therefore can produce very large age gaps between older existing slaves and younger siblings.
	 */
	function makeYoungerSibling(slave, minAgeDifference=2, maxAgeDifference=6, usePedoMode) {
		// reduce age
		const olderAge = slave.actualAge;
		const olderBirthWeek = slave.birthWeek;
		const absoluteMaxDifference = slave.actualAge - V.minimumSlaveAge;
		maxAgeDifference = Math.min(maxAgeDifference, absoluteMaxDifference);
		slave.actualAge -= random(minAgeDifference, maxAgeDifference);
		slave.actualAge = Math.max(slave.actualAge, V.minimumSlaveAge);
		if (usePedoMode) {
			slave.actualAge = Math.min(slave.actualAge, random(16, 18));
		}

		// Generate birth week. If siblings are born too close together for the second child to come to term, make them fraternal twins.
		const olderAgeInWeeks = olderAge * 52 + olderBirthWeek;
		slave.birthWeek = random(0, 51);
		const youngerAgeInWeeks = slave.actualAge * 52 + slave.birthWeek;
		if (olderAgeInWeeks - youngerAgeInWeeks < 42) {
			slave.actualAge = olderAge;
			slave.birthWeek = olderBirthWeek;
		}

		slave.visualAge = slave.actualAge;
		slave.physicalAge = slave.actualAge;
		slave.ovaryAge = slave.actualAge;

		fuzzPhysicalTraits(slave);

		randomizeFetishFlaws(slave);
	}

	/**
	 * Finish configuring a sibling
	 * @param {FC.SlaveState} slave - the new sibling
	 * @param {number} minAgeDifference - integer or undefined
	 * @param {number} maxAgeDifference - integer or undefined
	 * @param {boolean} usePedoMode - caps older sibling's age at 18, and therefore used only if the younger sibling's age <= 18. Can compress the desired age difference, making it especially likely for the siblings to be twins.
	 */
	function makeOlderSibling(slave, minAgeDifference=2, maxAgeDifference=6, usePedoMode) {
		// increase age
		let absoluteMaxDifference = (V.retirementAge - 1) - slave.actualAge;
		if (usePedoMode && slave.actualAge <= 18) {
			absoluteMaxDifference = 18 - slave.actualAge;
		}
		maxAgeDifference = Math.min(maxAgeDifference, absoluteMaxDifference);
		// If retirement age or pedo mode pushes the max difference below the min difference, lower the min to the max.
		if (maxAgeDifference < minAgeDifference) {
			minAgeDifference = maxAgeDifference;
		}
		const ageDifference = random(minAgeDifference, maxAgeDifference);

		// Generate birth week and age the older sibling. If siblings are born too close together for the second child to come to term, make them fraternal twins.
		const youngerBirthWeek = slave.birthWeek;
		slave.birthWeek = random(0, 51);
		if ((ageDifference == 0 && (slave.birthWeek - youngerBirthWeek) < 42) || (ageDifference == 1 && (52 - youngerBirthWeek + slave.birthWeek) < 42)) {
			slave.birthWeek = youngerBirthWeek;
		} else {
			fastForward(slave, ageDifference);
		}

		fuzzPhysicalTraits(slave);

		randomizeFetishFlaws(slave);
	}

	/**
	 * Finish configuring a child
	 * @param {FC.SlaveState} slave - the new child
	 * @param {string} parentSex - the sex of the parent
	 * @param {number} minAgeDifference - integer or undefined
	 * @param {number} maxAgeDifference - integer or undefined
	 * @param {boolean} usePedoMode - caps child age at 18.
	 * Note that even if ageOverridesPedoMode is 1 (true) when calling the parent function, pedo mode will still affect the minimum age if V.slaveMinimumAge < 8.
	 */
	function makeChild(slave, parentSex, minAgeDifference, maxAgeDifference=999, usePedoMode) {
		slave.mother = parentSex === "XX" ? sourceID : SID.GENERIC;
		slave.father = parentSex !== "XX" ? sourceID : SID.GENERIC;

		// select age
		const parentAge = slave.actualAge;
		// Add an extra year for gestation.
		let absoluteMaxAge = parentAge - ((parentSex === "XX" ? slave.pubertyAgeXX : slave.pubertyAgeXY) + 1);
		if (absoluteMaxAge < V.minimumSlaveAge) {
			throw Error("Cannot generate child (parent not old enough).");
		}
		if (usePedoMode) {
			absoluteMaxAge = Math.min(absoluteMaxAge, 18);
		}
		const prefMaxAge = Math.max(minAgeDifference != undefined ? parentAge - minAgeDifference : 22);
		const maxAge = Math.min(absoluteMaxAge, prefMaxAge);
		const absoluteMinAge = (V.pedoMode === 1 ? V.minimumSlaveAge : Math.max(8, V.minimumSlaveAge));
		let minAge = Math.max(absoluteMinAge, parentAge - maxAgeDifference);
		// In the unlikely event that the min age gets pushed higher than the max age, use the max age for both, since it's a biological limit or determined by pedo mode.
		if (minAge > maxAge) {
			minAge = maxAge;
		}
		slave.actualAge = random(minAge, maxAge);
		slave.visualAge = slave.actualAge;
		slave.physicalAge = slave.actualAge;
		slave.ovaryAge = slave.actualAge;
		slave.birthWeek = random(0, 51);

		// child always has less devotion/trust
		slave.devotion -= 10;
		slave.trust -= 10;

		// child always has less boobs/butt than mother
		if (parentSex === "XX") {
			slave.boobs -= 100;
			slave.butt -= 1;
		}

		fuzzPhysicalTraits(slave);

		// daughter has never had children and is likely a virgin
		if (slave.genes === "XX") {
			slave.vagina = either(0, 0, 0, 1);
			slave.counter.birthsTotal = 0;
		}

		randomizeFetishFlaws(slave);
	}

	/**
	 * Finish configuring a parent
	 * @param {FC.SlaveState} slave - the new parent
	 * @param {boolean} father - is the parent going to be a father or a mother?
	 * @param {number} minAgeDifference - integer or undefined
	 * @param {number} maxAgeDifference - integer or undefined
	 * @param {boolean} usePedoMode - caps parent age at 18, but only in cases where the existing slave is young enough to make this possible.
	 */
	function makeParent(slave, father, minAgeDifference=0, maxAgeDifference=999, usePedoMode) {
		slave.mother = SID.GENERIC;
		slave.father = SID.GENERIC;

		// select age
		const absoluteAgeRange = getParentAgeRange(slave, father);
		if (absoluteAgeRange.min > absoluteAgeRange.max) {
			throw Error("Cannot generate parent (slave too old).");
		}
		if (usePedoMode && absoluteAgeRange.min <= 18) {
			absoluteAgeRange.max = 18;
		}
		const minAge = Math.max(slave.actualAge + minAgeDifference, absoluteAgeRange.min);
		const maxAge = Math.min(slave.actualAge + maxAgeDifference, absoluteAgeRange.max);
		const targetAge = random(minAge, maxAge);
		fastForward(slave, targetAge - slave.actualAge);
		slave.birthWeek = random(0, 51);

		// parent always has less devotion/trust
		slave.devotion -= 10;
		slave.trust -= 10;

		// mother always has more boobs/butt
		if (!father) {
			slave.boobs += 100;
			slave.butt += 1;
		}

		fuzzPhysicalTraits(slave);

		// mother has had one child (at least)
		if (!father) {
			slave.vagina = Math.max(slave.vagina, 1);
			slave.counter.birthsTotal = 1;
		}

		randomizeFetishFlaws(slave);
	}

	/**
	 * Fuzz some physical traits so we don't start out identical
	 * @param {FC.SlaveState} slave
	 */
	function fuzzPhysicalTraits(slave) {
		// fuzz boobs/butt
		if (slave.boobs > 200) {
			const fuzz = either(-100, 0, 100);
			slave.boobs += fuzz;
			slave.natural.boobs += fuzz;
		}
		if (slave.butt > 1) {
			slave.butt += random(-1, 1);
		}
		// fuzz height
		const heightAdjust = random(-5, Math.min(maxHeight(slave) - slave.height, 5));
		slave.natural.height += heightAdjust;
		slave.height += heightAdjust;
		// reset art seed
		slave.natural.artSeed = jsRandom(0, 10 ** 14);
	}

	/**
	 * Randomize fetish and flaws
	 * @param {FC.SlaveState} slave
	 */
	function randomizeFetishFlaws(slave) {
		slave.fetishStrength = random(0, 90);
		slave.fetish = either("buttslut", "cumslut", "dom", "humiliation", "masochist", "boobs", "none", "none", "none", "none", "none", "none", "pregnancy", "sadist", "submissive");
		slave.behavioralFlaw = either("anorexic", "arrogant", "bitchy", "devout", "gluttonous", "hates men", "hates women", "liberated", "none", "none", "none", "odd");
		if (slave.behavioralFlaw === "devout") {
			slave.sexualFlaw = either("apathetic", "none", "repressed", "shamefast");
		} else {
			slave.sexualFlaw = either("apathetic", "crude", "hates anal", "hates oral", "hates penetration", "idealistic", "judgemental", "none", "none", "none", "none", "repressed", "shamefast");
		}
		randomizeAttraction(slave);
	}

	/**
	 * Fix age-related factors such as physical immaturity, height, etc
	 * Must be after age is recomputed, obviously; should not be needed for twins
	 * @param {FC.SlaveState} slave - the new relative
	 */
	function ageFixup(slave) {
		// adjust immature slaves
		if (slave.physicalAge <= 15) {
			ageAdjustYoungRelative(slave);
		}

		// reset height
		slave.height = Height.forAge(slave.natural.height, slave);

		// reset puberty status
		generatePuberty(slave);

		// reset teeth
		if (slave.physicalAge < 6) {
			slave.teeth = "baby";
		} else if (slave.physicalAge < 12) {
			slave.teeth = "mixed";
		} else if (slave.teeth === "baby" || slave.teeth === "mixed") {
			slave.teeth = either("crooked", "normal", "normal");
		}

		// reset pregAdaptation (copied from generateNewSlaveJS's generateXX/XYPregAdaptation functions)
		if (slave.genes === "XX") { // female
			if (slave.physicalAge <= 6) {
				slave.pregAdaptation = 5;
			} else if (slave.physicalAge <= 11) {
				slave.pregAdaptation = slave.physicalAge - 1;
			} else if (slave.physicalAge <= 14) {
				slave.pregAdaptation = 4 * (slave.physicalAge - 12) + 14;
			} else if (slave.physicalAge <= 15) {
				slave.pregAdaptation = 28;
			} else if (slave.physicalAge <= 16) {
				slave.pregAdaptation = 34;
			} else if (slave.physicalAge <= 17) {
				slave.pregAdaptation = 42;
			} else {
				slave.pregAdaptation = 50;
			}
		} else { // male
			if (slave.physicalAge <= 6) {
				slave.pregAdaptation = 5;
			} else if (slave.physicalAge <= 11) {
				slave.pregAdaptation = slave.physicalAge - 1;
			} else if (slave.physicalAge <= 15) {
				slave.pregAdaptation = 2 * (slave.physicalAge - 12) + 12;
			} else {
				slave.pregAdaptation = 20;
			}
		}
	}

	/**
	 * Reset pregnancy, health, and other "status" variables dependent on age or gender that exist on newly-generated
	 * slaves but which shouldn't be identical even on twins. Must be after age is recomputed.
	 * @param {FC.SlaveState} slave - the new relative
	 */
	function resetStatus(slave) {
		// reset pregnancy
		WombFlush(slave);
		if (V.arcologies[0].FSRepopulationFocusSMR === 1 && canGetPregnant(slave)) {
			slave.preg = random(1, 38);
			slave.pregWeek = slave.preg;
			slave.pregKnown = 1;
			slave.pregType = setPregType(slave);
			if (slave.vagina === 0) {
				slave.vagina = 1;
			}
		}
		SetBellySize(slave);

		// reset lactation
		slave.lactation = 0;
		slave.lactationDuration = 0;

		// reset health (but keep condition, which disproportionately influences slave value)
		setHealth(slave, slave.health.condition);

		// reset career
		slave.career = randomCareer(slave);
	}

	/**
	 * When generating a younger relative by cloning an older one (for example, for Household Liquidators),
	 * clamp certain physical parameters of the younger relative appropriately for their physical age.
	 * Generally these adjustments should match the age limiters found in generateNewSlave.js.
	 * @param {FC.SlaveState} slave - the slave to adjust
	 */
	function ageAdjustYoungRelative(slave) {
		/* breast size */
		const origBoobs = slave.boobs;
		if (slave.physicalAge <= 10) {
			slave.boobs = Math.clamp(slave.boobs, 0, 100);
		} else if (slave.physicalAge <= 12) {
			slave.boobs = Math.clamp(slave.boobs, 0, 300);
		} else if (slave.physicalAge <= 14) {
			slave.boobs = Math.clamp(slave.boobs, 0, 400);
		} else if (slave.physicalAge <= 15) {
			slave.boobs = Math.clamp(slave.boobs, 0, 450);
		}

		/* if we've reduced breast size because of age, reapply minimum weight modifiers */
		if (origBoobs > slave.boobs && V.weightAffectsAssets !== 0) {
			if (slave.weight > 190) {
				slave.boobs += 300;
			} else if (slave.weight > 160) {
				slave.boobs += 200;
			} else if (slave.weight > 30) {
				slave.boobs += 100;
			}
		}

		/* if we've managed to *increase* breast size, just put it back */
		if (origBoobs < slave.boobs) {
			slave.boobs = origBoobs;
		}

		/* breast shape - preserve if it would have been valid, otherwise reset to normal (don't reroll) */
		/* nipple size - checks for flat nipples and their validity, otherwise reroll */
		const AllowedBoobShapes = [];
		const AllowedNippleShapes = [];
		if (slave.boobs.isBetween(250, 800)) {
			AllowedBoobShapes.push("perky");
			AllowedBoobShapes.push("downward-facing");
		}
		if (slave.boobs.isBetween(400, 1200)) {
			AllowedBoobShapes.push("torpedo-shaped");
			AllowedBoobShapes.push("wide-set");
		}
		if (slave.boobsImplant / slave.boobs >= 0.90) {
			AllowedBoobShapes.push("spherical");
			AllowedNippleShapes.push("flat");
		}
		if (!AllowedBoobShapes.includes(slave.boobShape)) {
			slave.boobShape = "normal";
		}
		if (!AllowedNippleShapes.includes(slave.nipples)) {
			slave.nipples = either("cute", "puffy", "inverted");
		}

		/* voice */
		if (slave.physicalAge <= 16 && slave.voice <= 1) {
			slave.voice = 2;
		}

		/* XX genitals */
		if (slave.physicalAge < 20 && slave.vagina > 1) {
			slave.vagina = 1;
		}

		if (slave.physicalAge <= 13 && slave.clit > 1) {
			slave.clit = 1;
		}

		if (slave.physicalAge <= 13 && slave.labia > 1) {
			slave.labia = 1;
		} else if (slave.physicalAge <= 15 && slave.labia > 2) {
			slave.labia = 2;
		}

		/* XY genitals */
		if (slave.physicalAge <= 13) {
			if (slave.geneticQuirks.wellHung === 2 && slave.physicalAge >= 8 && slave.dick > 4) {
				slave.dick = 4;
			} else if (slave.dick > 3) {
				slave.dick = 3;
			}
			if (slave.balls > 3) {
				slave.balls = 3;
				slave.scrotum = slave.balls;
			}
		} else if (slave.physicalAge <= 15) {
			if (slave.geneticQuirks.wellHung === 2 && slave.dick > 5) {
				slave.dick = 5;
			} else if (slave.dick > 3) {
				slave.dick = 3;
			}
			if (slave.balls > 4) {
				slave.balls = 4;
				slave.scrotum = slave.balls;
			}
		}
	}

	function fastForward(slave, years) {
		for (let i = 0; i < years; ++i) {
			ageSlave(slave, true);
		}
	}

	/**
	 * Give a slave a realistic chance to activate a sex-linked genetic quirk which her opposite-sex relative was only a carrier for.
	 * @param {FC.SlaveState} slave - the slave to adjust
	 * @param {string} quirk - the sex-linked quirk to test
	 */
	function activateSexLinkedGeneticQuirk(slave, quirk) {
		if (slave.geneticQuirks[quirk] === 1) {
			if (random(1, 4) > 3) {
				slave.geneticQuirks[quirk] = 2;
			}
		}
	}

	/**
	 * Make a slave a carrier for a genetic sex-linked quirk which her opposite-sex relative had active.
	 * @param {FC.SlaveState} slave - the slave to adjust
	 * @param {string} quirk - the sex-linked quirk to test
	 */
	function deactivateSexLinkedGeneticQuirk(slave, quirk) {
		if (slave.geneticQuirks[quirk] === 2) {
			slave.geneticQuirks[quirk] = 1;
		}
	}

	/**
	 * Changes the new relative's sex from XY to XX.
	 * @param {FC.SlaveState} slave - the slave to adjust
	 */
	function changeSexToXX(slave) {
		slave.genes = "XX";
		slave.pronoun = App.Data.Pronouns.Kind.female;

		// activate/deactivate sex-linked genetic quirks
		["wellHung"].forEach((q) => deactivateSexLinkedGeneticQuirk(slave, q));
		["fertility", "hyperFertility", "macromastia", "gigantomastia"].forEach((q) => activateSexLinkedGeneticQuirk(slave, q));

		// alter body proportions
		slave.hips = Math.clamp(slave.hips + 1, -2, 2);
		slave.butt++;
		slave.waist = Math.clamp(slave.waist - random(15, 25), -100, 100);
		slave.face = Math.clamp(slave.face + random(15, 25), -100, 100);
		slave.boobs = either(200, 300, 400, 450, 500, 550, 600, 700);

		// apply male/female average height ratio of 1:1.07 - actual height will be adjusted in ageFixup()
		slave.natural.height = Math.round(slave.natural.height / 1.07);

		// alter genitals
		slave.dick = 0;
		slave.balls = 0;
		slave.scrotum = 0;
		slave.prostate = 0;
		slave.vagina = 1; // no virgins here?
		slave.clit = either(0, 0, 0, 0, 0, 0, 1, 1, 2);
		slave.labia = either(0, 0, 0, 1, 1, 1, 1, 2, 2, 3);
		slave.ovaries = 1;
		slave.preg = -1; // might get pregnant from repop check later on anyway

		// swap penetrative skill for vaginal
		slave.skill.vaginal = slave.skill.penetrative;
		slave.skill.penetrative = 0;

		// swap attraction
		[slave.attrXX, slave.attrXY] = [slave.attrXY, slave.attrXX];

		// consider face
		if (slave.faceShape === "masculine") {
			slave.faceShape = either("androgynous", "androgynous", "cute", "cute", "exotic", slave.faceShape, "normal", "normal", "sensual");
		}

		// adjust voice and hair
		slave.voice = Math.min(3, slave.voice + 1);
		slave.hLength = 60;

		// rotate hormone balance around a center of 10 (60 becomes -40 and vice versa)
		slave.hormoneBalance -= (slave.hormoneBalance - 10) * 2;

		// regenerate piercings (would be nice to just call generateXXMods here)
		slave.piercing.ear.weight = jsEither([0, 1]);
		slave.piercing.nose.weight = jsEither([0, 0, 0, 1]);
		slave.piercing.eyebrow.weight = jsEither([0, 0, 0, 0, 0, 1]);
		slave.piercing.genitals.weight = jsEither([0, 0, 0, 0, 0, 1]);
		slave.piercing.dick.weight = 0;
		slave.piercing.lips.weight = jsEither([0, 0, 0, 0, 0, 1]);
		slave.piercing.navel.weight = jsEither([0, 0, 0, 1]);
		slave.piercing.nipple.weight = jsEither([0, 0, 0, 0, 1]);
	}

	/**
	 * Changes the new relative's sex from XX to XY.
	 * @param {FC.SlaveState} slave - the slave to adjust
	 */
	function changeSexToXY(slave) {
		slave.genes = "XY";
		slave.pronoun = App.Data.Pronouns.Kind.male;

		// activate/deactivate sex-linked genetic quirks
		["wellHung"].forEach((q) => activateSexLinkedGeneticQuirk(slave, q));
		["fertility", "hyperFertility", "macromastia", "gigantomastia"].forEach((q) => deactivateSexLinkedGeneticQuirk(slave, q));

		// alter body proportions
		slave.hips = Math.clamp(slave.hips - 1, -2, 2);
		slave.butt = Math.max(0, slave.butt - 1);
		slave.waist = Math.clamp(slave.waist + random(15, 25), -100, 100);
		slave.face = Math.clamp(slave.face - random(15, 25), -100, 100);
		slave.boobs = either(100, 200);

		// apply male/female average height ratio of 1:1.07 - actual height will be adjusted in ageFixup()
		slave.natural.height = Math.round(slave.natural.height * 1.07);

		// alter genitals
		slave.vagina = -1;
		slave.clit = 0;
		slave.labia = 0;
		slave.ovaries = 0;
		slave.preg = 0;
		if (slave.geneticQuirks.wellHung === 2) {
			slave.dick = either(5, 5, 6);
		} else {
			slave.dick = either(1, 2, 2, 2, 3, 3, 3, 4, 4, 5);
		}
		slave.balls = either(1, 2, 2, 2, 3, 3, 3, 4, 4, 5);
		slave.scrotum = slave.balls;
		slave.prostate = 1;

		// swap vaginal skill for penetrative
		slave.skill.penetrative = slave.skill.vaginal;
		slave.skill.vaginal = 0;

		// swap attraction
		[slave.attrXX, slave.attrXY] = [slave.attrXY, slave.attrXX];

		// consider face
		if (slave.faceShape !== "masculine" && slave.faceShape !== "androgynous") {
			slave.faceShape = either("androgynous", "masculine", slave.faceShape, slave.faceShape);
		}

		// adjust voice and hair
		slave.voice = Math.max(1, slave.voice - 1);
		slave.hLength = 10;

		// rotate hormone balance around a center of 10 (60 becomes -40 and vice versa)
		slave.hormoneBalance -= (slave.hormoneBalance - 10) * 2;

		// regenerate piercings (would be nice to just call generateXYMods here)
		slave.piercing.ear.weight = jsEither([0, 0, 0, 1]);
		slave.piercing.nose.weight = jsEither([0, 0, 0, 0, 1]);
		slave.piercing.eyebrow.weight = jsEither([0, 0, 0, 0, 0, 1]);
		slave.piercing.genitals.weight = jsEither([0, 0, 0, 0, 0, 1]);
		slave.piercing.lips.weight = jsEither([0, 0, 0, 0, 0, 1]);
		slave.piercing.navel.weight = jsEither([0, 0, 0, 0, 1]);
		slave.piercing.nipple.weight = jsEither([0, 0, 0, 0, 1]);
	}

	return generateRelative;
})();

/** Return the valid age range for a parent for the given slave
 * NOTE: If minimum age is GREATER than maximum age, the parent cannot be generated. Check first!
 * @param {FC.HumanState} slave
 * @param {boolean} maleParent - true if the desired parent is a father
 * @returns {{min: number, max: number}}
 */
globalThis.getParentAgeRange = function(slave, maleParent) {
	const childAge = slave.actualAge;
	const min = childAge + (maleParent ? V.potencyAge : V.fertilityAge) + 1;
	const lastFertileYear = maleParent ? 75 : 45;
	const max = Math.min(V.retirementAge - 1, childAge + lastFertileYear);
	return {min, max};
};

repo_name: Nithhogg/fc

license: mit


code:
// cSpell:ignore Dextreme, Dincest, lolify, lolifying, lolification

/** creates an array from App.Data.HeroSlaves that will be similar to the old $heroSlaves.
 * @returns {FC.HeroSlaveTemplate[]}
 */
App.Utils.buildHeroArray = function() {
	let chunks = [];
	const IDs = [];
	for (const hero of [
		...App.Data.HeroSlaves.XX, ...App.Data.HeroSlaves.XXExtreme,
		...App.Data.HeroSlaves.XXFruitGirls, ...App.Data.HeroSlaves.XXFruitGirlsExtreme,
		...App.Data.HeroSlaves.XXHyperPreg, ...App.Data.HeroSlaves.XXIncest,
		...App.Data.HeroSlaves.XY, ...App.Data.HeroSlaves.XYExtreme,
		...App.Data.HeroSlaves.XYHyperPreg, ...App.Data.HeroSlaves.XYIncest,
		// ...App.Data.HeroSlaves.Custom,
	]) {
		if (hero.ID in IDs) {
			throw new Error(`ID '${hero.ID}' is in use by more than one hero slave`);
		}
		IDs.push(hero.ID);
	}
	if (V.seeDicks < 100) { // XX slaves
		chunks.push(App.Data.HeroSlaves.XX);
		chunks.push(App.Data.HeroSlaves.XXFruitGirls);
		if (V.seeExtreme === 1) {
			chunks.push(App.Data.HeroSlaves.XXExtreme);
			chunks.push(App.Data.HeroSlaves.XXFruitGirlsExtreme);
		}
		if (V.seeIncest === 1) {
			chunks.push(App.Data.HeroSlaves.XXIncest);
		}
		if (V.seeHyperPreg === 1) {
			chunks.push(App.Data.HeroSlaves.XXHyperPreg);
		}
	}
	if (V.seeDicks > 0) { // XY slaves
		chunks.push(App.Data.HeroSlaves.XY);
		if (V.seeExtreme === 1) {
			chunks.push(App.Data.HeroSlaves.XYExtreme);
		}
		if (V.seeIncest === 1) {
			chunks.push(App.Data.HeroSlaves.XYIncest);
		}
		if (V.seeHyperPreg === 1) {
			chunks.push(App.Data.HeroSlaves.XYHyperPreg);
		}
	}
	App.UI.DOM.renderPassage("custom Slaves Database"); // populate V.heroSlaves from user DB
	chunks.push(V.heroSlaves);
	let array = [].concat(...chunks);
	delete V.heroSlaves;

	/** @type {function(FC.SlaveState):boolean} */
	const disallowedPregnantSlave = (s) => (V.seePreg !== 1 && s.preg > 0);
	/** @type {function(FC.SlaveState):boolean} */
	const underAgedSlave = (s) => (s.actualAge < V.minimumSlaveAge);
	array.deleteWith((s) => V.heroSlavesPurchased.includes(s.ID) || disallowedPregnantSlave(s) || underAgedSlave(s));

	const collator = new Intl.Collator('en', {usage: "sort", ignorePunctuation: true});
	return array.sort((a, b) => collator.compare(a.slaveName, b.slaveName));
};

/**
 * Compile an object that pairs the database ID's of related hero slaves, for use in randomly selecting slaves during arcology
 * acquisition. To make sure both family members will be included in the output of "buildHeroArray", use the appropriate checks
 * (see below, and the examples in "buildHeroArray", above) to avoid referencing slaves who won't be found in the game.
 *
 * The slave whose ID serves as the value will appear first during acrology acquisition, with the key slave appearing
 * immediately afterward.
 *
 * @returns {object}
 */
App.Utils.getHeroFamilies = function() {
	let families = {};
	if (V.seeDicks < 100) {
		families = {...families, ...{906007: 906006}};
	}
	if (V.seeDicks > 0 && V.seeDicks < 100) {
		families = {...families, ...{800018: 900076}};
	}
	if (V.seeIncest === 1) {
		families.incest = {};
		if (V.seeDicks < 100) {
			const incestFamilies = {902002: 902001, 902003: 802001};
			families = {...families, ...incestFamilies};
			families.incest = {...families.incest, ...incestFamilies};
		}
	}
	return families;
};

/**
 * @param {FC.HeroSlaveTemplate} heroTemplate
 * @returns {FC.SlaveState}
 */
App.Utils.getHeroSlave = function(heroTemplate) {
	const template = clone(heroTemplate);
	let newAge = undefined;
	/**
	 * @param {FC.SlaveState & FC.HeroSlaveTemplate} slave
	 */
	function repairLimbs(slave) {
		if (slave.hasOwnProperty("_limbs")) {
			if (slave._limbs[0] >= 0) {
				configureLimbs(slave, "left arm", slave._limbs[0]);
			}
			if (slave._limbs[1] >= 0) {
				configureLimbs(slave, "right arm", slave._limbs[1]);
			}
			if (slave._limbs[2] >= 0) {
				configureLimbs(slave, "left leg", slave._limbs[2]);
			}
			if (slave._limbs[3] >= 0) {
				configureLimbs(slave, "right leg", slave._limbs[3]);
			}
			delete slave._limbs;
		}
	}

	// Nationalities, races, surnames random fill
	if (!template.nationality) {
		// Check for a pre-set race and if the nationality fits, else regenerate
		if (template.race && App.Data.misc.filterRaces.has(template.race)) {
			raceToNationality(/** @type {FC.HumanState} */ (template));
		} else {
			template.nationality = hashChoice(V.nationalities);
		}
	}
	if (!template.race || !App.Data.misc.filterRaces.has(template.race)) {
		nationalityToRace(/** @type {FC.HumanState} */ (template));
	}
	if (!template.birthSurname && template.birthSurname !== "") {
		template.birthSurname = (App.Data.misc.surnamePoolSelector[template.nationality + "." + template.race] ||
			App.Data.misc.surnamePoolSelector[template.nationality] ||
			App.Data.misc.whiteAmericanSlaveSurnames).random();
	}
	// Custom hero slaves are created using Twine rather than pure JavaScript, and apparently Twine sets missing
	// values to defaults, which is what "App.Utils.assignMissingDefaults" does for hero slaves from the normal
	// hero databases. This isn't a problem for "birthSurname", since its default of 0 evaluates as "False" and
	// therefore passes the first Boolean test in the block above.
	if ((!template.birthName && template.birthName !== "") || template.birthName === "blank") {
		template.birthName = (App.Data.misc.namePoolSelector[template.nationality + "." + template.race] ||
			App.Data.misc.namePoolSelector[template.nationality] ||
			App.Data.misc.whiteAmericanSlaveNames).random();
	}
	// Setting both "slaveSurname" and "birthSurname" to empty strings removes all mention of surnames from a
	// slave's description--but setting "slaveSurname" to an ampty string and "birthSurname" to anything else
	// produces a description that displays the empty string as if it were an actual name. We need to fix
	// this corner case by making sure "slaveSurname" gets reset to the default (no slave surname displayed).
	if (template.slaveSurname === "" && template.birthSurname !== "") {
		delete template.slaveSurname;
	}
	if (template.slaveName === -1) {
		template.slaveName = template.birthName;
	}
	if (template.slaveSurname === -1) {
		template.slaveSurname = template.birthSurname;
	}

	/** @type {FC.SlaveState} */
	let heroSlave = /** @type {FC.SlaveState} */ (template);
	// make the template a full slave object
	App.Utils.assignMissingDefaults(heroSlave, baseSlave(template.seed));

	generatePronouns(heroSlave);
	heroSlave.ID = generateSlaveID();
	repairLimbs(heroSlave);
	heroSlave.weekAcquired = V.week;
	if (heroSlave.overrideRace !== 1) {
		heroSlave.origRace = heroSlave.race;
	}
	if (heroSlave.overrideEyeColor !== 1) {
		resetEyeColor(heroSlave, "both");
	}
	if (heroSlave.overrideHColor !== 1) {
		heroSlave.hColor = getGeneticHairColor(heroSlave);
	}
	if (heroSlave.overrideArmHColor !== 1) {
		heroSlave.underArmHColor = getGeneticHairColor(heroSlave);
	}
	if (heroSlave.overridePubicHColor !== 1) {
		heroSlave.pubicHColor = getGeneticHairColor(heroSlave);
	}
	if (heroSlave.overrideBrowHColor !== 1) {
		heroSlave.eyebrowHColor = getGeneticHairColor(heroSlave);
	}
	if (heroSlave.overrideSkin !== 1) {
		heroSlave.skin = getGeneticSkinColor(heroSlave);
	}
	if (!template.natural?.height) {
		// assumes adult - child hero slaves MUST specify natural height separately!
		heroSlave.natural.height = heroSlave.height - heroSlave.heightImplant * 10;
	}
	if (!template.natural?.boobs) {
		// assumes a natural chest- child and male hero slaves, as well as those with unnatural busts, MUST specify natural size separately!
		heroSlave.natural.boobs = heroSlave.boobs;
	}
	if (!template.natural?.artSeed) {
		heroSlave.natural.artSeed = jsRandom(0, 10 ** 14);
	}

	/* If we're in pedo mode, later we're going	to "lolify" slaves older than 17, placing them in the pedo range. However, we want to avoid
		lolifying slaves with skills for leadership roles that benefit from their original age. We set the flag here in case there are
		siblings (see below) who need to be kept older. Currently, there are no such siblings in the database, but if there were, the
		custom siblings code for the family in question might have to set the flag to keep one sibling (with skills) from being lolified
		while the other (without skills) is. If the sibling without skills were acquired first, things would break unless that slave's
		database ID were saved before a slave ID is assigned, above, so that the database ID could be checked against "getHeroFamilies".
	*/
	const keepSlaveOld = App.Utils.oldIsGood(heroSlave);

	/* The following exceptions to keep siblings sensible in the Special Slave Market, mostly to make sure one acquired later ages the same
		amount as one acquired earlier, but also to restore incestuous relationships. During arcology acquisition, this code won't work,
		because family members are both generated before being added to the gene pool, and the relationship check functions use the gene
		pool. Thus, we do things differently there (and in any case we don't need to worry about age updates during arcology acquisition).

		For pedo mode, we use a smaller age gap, which should be the gap that results from lolifying both siblings at the same time (under
		typical pedo setting of minimum slave age at 12 or less), as happens if they're viewed in the Special Market before one of them is
		bought. This prevents minimizes the chance that a slave from changing age in the market display after her sibling is bought.
	*/
	let slave;
	if (heroSlave.mother === -9999 && heroSlave.father === -9998) { // The twins — Camille & Kennerly
		slave = getSlaves().find(s => areSisters(s, heroSlave) > 0)?.value;
		if (slave) {
			heroSlave.actualAge = slave.actualAge;
			heroSlave.physicalAge = heroSlave.actualAge;
			heroSlave.visualAge = heroSlave.actualAge;
			heroSlave.ovaryAge = heroSlave.actualAge;
			heroSlave.birthWeek = slave.birthWeek;
			heroSlave.natural.artSeed = slave.natural.artSeed;
			/* The twins will re-ignite their incestuous relationship unless the existing twin is in a relationship with another slave or married
				to the PC. The twincest *will* override "emotionally bound"; they love each other *that* much (and it gives a player who bought
				only one of them initially an easy way to keep her on ice until he can grab the other one). */
			if (slave.relationship >= -2 && slave.relationship <= 0) {
				slave.relationship = 3;
				slave.relationshipTarget = heroSlave.ID;
				heroSlave.relationship = 3;
				heroSlave.relationshipTarget = slave.ID;
			}
		}
	}
	if (heroSlave.mother === -9997 && heroSlave.father === -9996) { // The rebel siblings — Elisa & Martin
		slave = getSlaves().find(s => areSisters(s, heroSlave) > 0)?.value;
		if (slave) {
			const adjustedAge = adjustNewSiblingAge(heroSlave, slave, "Elisa", "Martin", 1, 1);
			newAge = adjustedAge;

			// In pedo mode, the lolify function uses "newAge" to set the age values, and we need to preserve the existing ones for now so it knows
			// what to work backware from.
			if (V.pedoMode === 0) {
				heroSlave.actualAge = newAge;
				heroSlave.physicalAge = heroSlave.actualAge;
				heroSlave.visualAge = heroSlave.actualAge;
				heroSlave.ovaryAge = heroSlave.actualAge;
			}
		}
	}
	if (heroSlave.mother === -9995 && heroSlave.father === -9994) { // The fruit siblings — Green & Purple Grape
		slave = getSlaves().find(s => areSisters(s, heroSlave) > 0)?.value;
		if (slave) {
			const adjustedAge = adjustNewSiblingAge(heroSlave, slave, "Green Grape", "Purple Grape", 5, 3);
			newAge = adjustedAge;

			// In pedo mode, the lolify function uses "newAge" to set the age values, and we need to preserve the existing ones for now so it knows what to work backware from.
			if (V.pedoMode === 0) {
				heroSlave.actualAge = newAge;
				heroSlave.physicalAge = heroSlave.actualAge;
				heroSlave.visualAge = heroSlave.actualAge;
				heroSlave.ovaryAge = heroSlave.actualAge;
			}
		}
	}
	if (heroSlave.mother === -9993 && heroSlave.father === -9992) { // The professional siblings — Bai & Qiang
		slave = getSlaves().find(s => areSisters(s, heroSlave) > 0)?.value;
		if (slave) {
			const adjustedAge = adjustNewSiblingAge(heroSlave, slave, "Qiang", "Bai", 2, 2);
			newAge = adjustedAge;

			// In pedo mode, the lolify function uses "newAge" to set the age values, and we need to preserve the existing ones for now so it knows what to work backware from.
			if (V.pedoMode === 0) {
				heroSlave.actualAge = newAge;
				heroSlave.physicalAge = heroSlave.actualAge;
				heroSlave.visualAge = heroSlave.actualAge;
				heroSlave.ovaryAge = heroSlave.actualAge;
			}
			if (slave.relationship >= -2 && slave.relationship <= 0) {
				slave.relationship = 3;
				slave.relationshipTarget = heroSlave.ID;
				heroSlave.relationship = 3;
				heroSlave.relationshipTarget = slave.ID;
			}
		}
	}

	/* In pedo mode, we're going to "lolify" slaves to fit them within the pedo age range (though not to younger than 12, since making them too
		young creates too much weirdness with origins, careers, and such). We do this close to the end of the function because we want to make
		sure that anything that needs to get adjusted for age (in particular, physical aseets) has already been set.

		There's no need to lolify slaves who are already under 18. 18 falls into the pedo mode range, but there are a LOT of 18-year-olds in the
		hero database, and they're meant to be close to the youngest heroes.

	*/
	if (newAge && newAge > 17 && heroSlave.actualAge !== newAge) {
		heroSlave.physicalAge += newAge - heroSlave.actualAge;
		heroSlave.visualAge += newAge - heroSlave.actualAge;
		heroSlave.ovaryAge += newAge - heroSlave.actualAge;
		heroSlave.actualAge = newAge;
	} else if (V.lolifier && heroSlave.physicalAge > 12) { // pregmodders variant, makes lolis based off age.
		if (heroSlave.physicalAge > 40) {
			heroSlave = lolifySlave(heroSlave, true, Math.max(12, V.minimumSlaveAge+4));
		}
		if (heroSlave.physicalAge > 30) {
			heroSlave = lolifySlave(heroSlave, true, Math.max(11, V.minimumSlaveAge+3));
		}
		if (heroSlave.physicalAge > 22) {
			heroSlave = lolifySlave(heroSlave, true, Math.max(10, V.minimumSlaveAge+2));
		}
		if (heroSlave.physicalAge > 16) {
			heroSlave = lolifySlave(heroSlave, true, Math.max(9, V.minimumSlaveAge+1));
		}
		if (heroSlave.physicalAge > 12) {
			heroSlave = lolifySlave(heroSlave, true, Math.max(8, V.minimumSlaveAge));
		}
		// In pedo mode with a high minimum age setting, it's possible two siblings with an age gap of 1 might have birthdays too close together.
		if (slave) {
			const lolifiedAgeGap = heroSlave.actualAge - slave.actualAge;
			if (lolifiedAgeGap === 1) {
				const minBirthWeek = slave.birthWeek - 10;
				if (heroSlave.birthWeek < minBirthWeek) {
					heroSlave.birthWeek = random(minBirthWeek, 51);
				}
			} else if (lolifiedAgeGap === 0) {
				heroSlave.birthWeek = slave.birthWeek; // Fraternal twins
			} else if (lolifiedAgeGap === -1) {
				const maxBirthWeek = slave.birthWeek + 10;
				if (heroSlave.birthWeek > maxBirthWeek) {
					heroSlave.birthWeek = random(0, maxBirthWeek);
				}
			}
		}
	} else if (V.pedoMode === 1 && heroSlave.physicalAge > 17 && !keepSlaveOld) {
		heroSlave = lolifySlave(heroSlave, newAge); // Except in the case of maintaining a sibling age gap, "newAge" is undefined.
		// In pedo mode with a high minimum age setting, it's possible two siblings with an age gap of 1 might have birthdays too close together.
		if (slave) {
			const lolifiedAgeGap = heroSlave.actualAge - slave.actualAge;
			if (lolifiedAgeGap === 1) {
				const minBirthWeek = slave.birthWeek - 10;
				if (heroSlave.birthWeek < minBirthWeek) {
					heroSlave.birthWeek = random(minBirthWeek, 51);
				}
			} else if (lolifiedAgeGap === 0) {
				heroSlave.birthWeek = slave.birthWeek; // Fraternal twins
			} else if (lolifiedAgeGap === -1) {
				const maxBirthWeek = slave.birthWeek + 10;
				if (heroSlave.birthWeek > maxBirthWeek) {
					heroSlave.birthWeek = random(0, maxBirthWeek);
				}
			}
		}
	}

	// The three functions called below are the only ones that depend on age, so we save them until after lolification.
	generatePuberty(heroSlave);
	setHealth(heroSlave, heroSlave.health.condition, 0, 0, 0, heroSlave.health.tired);
	SetBellySize(heroSlave);

	nationalityToAccent(heroSlave);
	return heroSlave;
};

/**
 * Returns true if an older age is better (career wise) for the given slave
 * @param {FC.SlaveState} slave
 * @returns {boolean}
 */
App.Utils.oldIsGood = function(slave) {
	const headGirlSkill = App.Data.Careers.Leader.HG.includes(slave.career) || slave.skill.headGirl >= Constant.MASTERED_XP;

	const madamSkill = App.Data.Careers.Leader.madam.includes(slave.career) || slave.skill.madam >= Constant.MASTERED_XP;
	const stewardessSkill = App.Data.Careers.Leader.stewardess.includes(slave.career) || slave.skill.stewardess >= Constant.MASTERED_XP;
	const actualAge36Skill = slave.actualAge >= 36 && (madamSkill || stewardessSkill);

	const attendantSkill = App.Data.Careers.Leader.attendant.includes(slave.career) || slave.skill.attendant >= Constant.MASTERED_XP;
	const schoolteacherSkill = App.Data.Careers.Leader.schoolteacher.includes(slave.career) || slave.skill.teacher >= Constant.MASTERED_XP;
	const visualAge36Skill = slave.physicalAge >= 36 && (attendantSkill || schoolteacherSkill);

	return headGirlSkill || actualAge36Skill || visualAge36Skill;
};

/**
 * Adjust sibling ages when acquiring them separately in the Special Market, making sure their final age gap is large enough to allow for the second
 * one to gestate, or turning them into fraternal twins.
 * @param {FC.SlaveState} newSibling
 * @param {FC.SlaveState} existingSibling
 * @param {string} youngerName
 * @param {string} olderName
 * @param {number} ageGap
 * @param {number} pedoAgeGap
 * @returns {number}
 */
const adjustNewSiblingAge = function(newSibling, existingSibling, youngerName, olderName, ageGap, pedoAgeGap) {
	let newAge;
	ageGap = (V.pedoMode === 0 ? ageGap : pedoAgeGap);
	if (newSibling.birthName === youngerName) {
		newAge = Math.max(existingSibling.actualAge - ageGap, V.minimumSlaveAge);
	} else if (newSibling.birthName === olderName) {
		newAge = Math.min(existingSibling.actualAge + ageGap, V.retirementAge - 1);
		if (V.pedoMode === 1) {
			newAge = Math.min(newAge, 18);
		}
	}
	return newAge;
};


code:
// cSpell:ignore unsalvageable

/**
 * @param {FC.SlaveState} slave
 * @param {FC.Zeroable<FC.SlaveMarketName | FC.SpecialMarketName>} [market=0]
 * @returns {string}
 */
App.Desc.lawCompliance = function(slave, market = 0) {
	let r = [];
	const fearList = [];
	const {
		he, him, his, himself, He, His, girl
	} = getPronouns(slave);

	if (V.arcologies[0].FSSlimnessEnthusiastSMR === 1) {
		r.push(FSSlimnessEnthusiastSMR());
	}
	if (V.arcologies[0].FSRepopulationFocusSMR === 1) {
		r.push(FSRepopulationFocusSMR());
	}
	if (V.arcologies[0].FSAssetExpansionistSMR === 1) {
		r.push(FSAssetExpansionistSMR());
	}
	if (V.arcologies[0].FSPhysicalIdealistSMR === 1) {
		r.push(FSPhysicalIdealistSMR());
	} else if (V.arcologies[0].FSHedonisticDecadenceSMR === 1) {
		r.push(FSHedonisticDecadenceSMR());
	}
	if (V.arcologies[0].FSGenderFundamentalistSMR === 1) {
		r.push(FSGenderFundamentalistSMR());
	} else if (V.arcologies[0].FSSupremacistSMR === 1) {
		r.push(FSSupremacistSMR());
	} else if (V.arcologies[0].FSSubjugationistSMR === 1) {
		r.push(FSSubjugationistSMR());
	}
	if (V.arcologies[0].FSRestartSMR === 1) {
		r.push(FSRestartSMR());
	}
	if (V.arcologies[0].FSPaternalistSMR === 1) {
		r.push(FSPaternalistSMR());
	}
	if (V.arcologies[0].FSDegradationistSMR === 1) {
		r.push(FSDegradationistSMR());
	}
	if (V.policies.gumjobFetishismSMR === 1) {
		r.push(gumjobFetishismSMR());
	}
	if (V.arcologies[0].FSPetiteAdmirationSMR === 1) {
		r.push(FSPetiteAdmirationSMR());
	} else if (V.arcologies[0].FSStatuesqueGlorificationSMR === 1) {
		r.push(FSStatuesqueGlorificationSMR());
	}
	if (V.arcologies[0].FSIntellectualDependencySMR === 1) {
		r.push(FSIntellectualDependencySMR());
	} else if (V.arcologies[0].FSSlaveProfessionalismSMR === 1) {
		r.push(FSSlaveProfessionalismSMR());
	}
	if (V.arcologies[0].FSBodyPuristSMR === 1) {
		r.push(FSBodyPuristSMR());
	}
	if (V.arcologies[0].FSTransformationFetishistSMR === 1) {
		r.push(FSTransformationFetishistSMR());
	}
	if (V.arcologies[0].FSYouthPreferentialistSMR === 1) {
		r.push(FSYouthPreferentialistSMR());
	}
	if (V.arcologies[0].FSMaturityPreferentialistSMR === 1) {
		r.push(FSMaturityPreferentialistSMR());
	}
	if (V.arcologies[0].FSPastoralistSMR === 1) {
		r.push(FSPastoralistSMR());
	}
	if (V.arcologies[0].FSChattelReligionistSMR === 1) {
		r.push(FSChattelReligionistSMR());
	}
	if (V.arcologies[0].FSRomanRevivalistSMR === 1) {
		r.push(FSRomanRevivalistSMR());
	}
	if (V.arcologies[0].FSNeoImperialistSMR === 1) {
		r.push(FSNeoImperialistSMR());
	}
	if (V.arcologies[0].FSAztecRevivalistSMR === 1) {
		r.push(FSAztecRevivalistSMR());
	}
	if (V.arcologies[0].FSEgyptianRevivalistSMR === 1) {
		r.push(FSEgyptianRevivalistSMR());
	}
	if (V.arcologies[0].FSEdoRevivalistSMR === 1) {
		r.push(FSEdoRevivalistSMR());
	}
	if (V.arcologies[0].FSArabianRevivalistSMR === 1) {
		r.push(FSArabianRevivalistSMR());
	}
	if (V.arcologies[0].FSChineseRevivalistSMR === 1) {
		r.push(FSChineseRevivalistSMR());
	}
	if (V.arcologies[0].FSAntebellumRevivalistSMR === 1) {
		r.push(FSAntebellumRevivalistSMR());
	}

	if (V.policies.SMR.basicSMR === 0) {
		r.push(basicSMR());
	}

	if (V.policies.SMR.healthInspectionSMR === 1) {
		r.push(healthInspectionSMR());
	}

	if (V.policies.SMR.educationSMR === 1) {
		r.push(educationSMR());
	}

	if (V.policies.SMR.frigiditySMR === 1) {
		r.push(frigiditySMR());
	}

	if (V.policies.SMR.beauty.basicSMR === 1) {
		fearList.push(beautyBasicSMR());
	}

	if (V.policies.SMR.beauty.qualitySMR === 1) {
		fearList.push(beautyQualitySMR());
	}

	if (V.policies.SMR.weightSMR === 1) {
		fearList.push(weightSMR());
	}

	if (V.policies.SMR.height.basicSMR === 1) {
		r.push(heightBasicSMRup());
	} else if (V.policies.SMR.height.basicSMR === -1) {
		r.push(heightBasicSMRdown());
	}

	if (V.policies.SMR.height.advancedSMR === 1) {
		r.push(heightAdvancedSMRup());
	} else if (V.policies.SMR.height.advancedSMR === -1) {
		r.push(heightAdvancedSMRdown());
	}

	if (V.policies.SMR.intelligence.basicSMR === 1) {
		fearList.push(intelligenceBasicSMR());
	}

	if (V.policies.SMR.intelligence.qualitySMR === 1) {
		fearList.push(intelligenceQualitySMR());
	}

	r.push(slaveFears(fearList));

	if (market !== "Elite Slave" && policies.countEugenicsSMRs() > 0) {
		r.push(eugenicsSMRsCount());
	}

	SlaveStatClamp(slave);

	return r.join(" ");

	function FSSlimnessEnthusiastSMR() {
		slave.weight = jsRandom(-80, 0);
		setHealth(slave, jsRandom(50, 90), 0, undefined, 0, 5);
		return `${He} has been kept on a rigorous diet for sale, has been required to exercise regularly, and has had excellent care taken of ${his} health.`;
	}

	function FSRepopulationFocusSMR() {
		if (slave.physicalAge < V.fertilityAge) {
			if (V.precociousPuberty === 0) {
				slave.physicalAge = V.fertilityAge;
				slave.visualAge = V.fertilityAge;
				slave.actualAge = V.fertilityAge;
				slave.ovaryAge = V.fertilityAge;
			}
		}
		slave.pubertyXX = 1;
		if (slave.ovaryAge >= 42) {
			// corrects menopausal mothers
			slave.ovaryAge = 40;
		}
		if (slave.preg < 1) {
			// "if" needed here to avoid conflicts with already pregnant slaves from repopulation FS arcologies markets
			slave.ovaries = 1;
			slave.vagina = either(0, 0, 1, 1, 1, 1, 1, 2, 2, 3);
			slave.pregType = either(1, 1, 1, 1, 2, 2, 2, 3, 3, 4, 5);
			slave.preg = jsRandom(5, 38);
			slave.pregWeek = slave.preg;
			slave.pregKnown = 1;
			SetBellySize(slave);
		}
		return `If ${he} was unable to become pregnant before, ${he} has been made to now. ${He} is fertilized surgically to ensure a healthy pregnancy.`;
	}

	function FSAssetExpansionistSMR() {
		slave.clit = either(0, 0, 0, 1, 1, 2);
		slave.labia = either(0, 1, 2, 3);
		slave.boobs += jsRandom(2, 5) * 100;
		slave.butt += jsRandom(1, 2);
		slave.lips = jsRandom(15, 55);
		if (slave.dick > 0) {
			slave.dick = jsRandom(4, 7);
			if (slave.foreskin > 0) {
				slave.foreskin = (slave.dick + either(-1, 0, 0));
			}
		}
		if (slave.balls > 0) {
			slave.balls = jsRandom(4, 7);
			if (slave.scrotum > 0) {
				slave.scrotum = (slave.balls + either(-1, 0, 0));
			}
		}
		return `${He} has been on powerful growth hormones for a long time, and has experienced growth in several areas as a result.`;
	}

	function FSPhysicalIdealistSMR() {
		const r = [];
		r.push(`Much of ${his} time before sale was spent`);
		if (V.arcologies[0].FSPhysicalIdealistLaw === 1) {
			slave.muscles = jsRandom(20, 50);
			r.push(`doing cardio,`);
		} else {
			slave.muscles = jsRandom(50, 100);
			r.push(`lifting weights,`);
		}
		r.push(`and ${his} health has been raised to an almost unnatural level.`);
		setHealth(slave, jsRandom(50, 90), 0, undefined, 0, 5);
		slave.weight = jsRandom(-20, 0);
		return r.join(" ");
	}

	function FSHedonisticDecadenceSMR() {
		const r = [];
		slave.weight = jsRandom(50, 200);
		if (V.arcologies[0].FSHedonisticDecadenceStrongFat === 1) {
			slave.muscles = jsRandom(10, 60);
			setHealth(slave, jsRandom(10, 40), 0, undefined, 0);
		} else {
			setHealth(slave, jsRandom(-30, 10), undefined, undefined, 0);
			slave.muscles = jsRandom(-80, 0);
		}
		r.push(`Much of ${his} time before sale was spent being fattened up and`);
		if (V.arcologies[0].FSHedonisticDecadenceStrongFat === 1) {
			r.push(`pumping iron.`);
		} else {
			r.push(`lying around.`);
		}
		if (slave.devotion <= 20) {
			r.push(`${He} had to be force-fed massive amounts of slave food while bound to meet requirements, filling ${him} with <span class="gold">fear</span> and <span class="mediumorchid">disgust.</span>`);
			slave.trust -= 5;
			slave.devotion -= 5;
		}
		return r.join(" ");
	}

	function FSGenderFundamentalistSMR() {
		if (slave.dick > 0) {
			slave.balls = 0;
			slave.scrotum = 0;
			slave.boobs += jsRandom(0, 2) * 100;
			slave.butt += jsRandom(0, 1);
			if (slave.face < 95) {
				slave.face = Math.clamp(slave.face + jsRandom(0, 20), -100, 100);
			}
			return `${His} balls were removed as soon as ${he} was enslaved, and ${he}'s been kept on powerful female hormones for long enough to have a general feminizing effect.`;
		}
	}

	function FSSupremacistSMR() {
		if (slave.dick > 0) {
			if (slave.race !== V.arcologies[0].FSSupremacistRace) {
				slave.balls = 0;
				slave.scrotum = 0;
				return `${His} balls were removed as soon as ${he} was enslaved to prevent ${him} from polluting fertile slaves with ${his} inferior semen.`;
			}
		}
	}

	function FSSubjugationistSMR() {
		if (slave.dick > 0) {
			if (slave.race === V.arcologies[0].FSSubjugationistRace) {
				slave.balls = 0;
				slave.scrotum = 0;
				return `${His} balls were removed as soon as ${he} was enslaved to prevent ${him} from polluting fertile slaves with ${his} subhuman semen.`;
			}
		}
	}

	function FSRestartSMR() {
		const r = [];
		if (slave.balls > 0 || slave.ovaries > 0) {
			TerminatePregnancy(slave);
			actX(slave, "abortions");
			slave.balls = 0;
			slave.ovaries = 0;
			r.push(`As a member of the lowest class,`);
			if (slave.preg > 0) {
				r.push(`${his} pregnancy is immediately terminated, and`);
			}
			r.push(`${he} is promptly sterilized to prevent ${him} from passing on ${his} useless genes.`);
		}
		return r.join(" ");
	}

	function FSPaternalistSMR() {
		slave.trust += 10;
		if (slave.devotion <= 20) {
			return `While ${he} came through the slave markets, ${he} was treated with decency. ${He} has begun to wonder whether ${he} can <span class="mediumaquamarine">get away with</span> being disobedient.`;
		} else {
			return `While ${he} came through the slave markets, ${he} was treated with decency. ${He} has begun to hope that ${he} can expect <span class="mediumaquamarine">basic respect</span> despite being a sex slave.`;
		}
	}

	function FSDegradationistSMR() {
		slave.trust -= 10;
		slave.health.tired = Math.clamp(slave.health.tired + 30, 0, 100);
		return `${His} current owners <span class="gold">brutalized ${him}</span> before putting ${him} on sale, using clever methods to produce agony without seriously damaging ${his} health.`;
	}

	function gumjobFetishismSMR() {
		slave.teeth = "removable";
		if (slave.devotion <= 50) {
			slave.trust -= 10;
		}
		healthDamage(slave, 10);
		return `In order to qualify for sale, ${he} has had his teeth removed and replaced with high-quality, removable dentures.`;
	}

	function FSPetiteAdmirationSMR() {
		if (!heightPass(slave)) {
			slave.natural.height = Height.randomAdult(slave, {skew: -1, limitMult: [-5, -2]});
			slave.height = Height.forAge(slave.natural.height, slave);
		}
		return `${His} height was meticulously taken before being allowed into the markets.`;
	}

	function FSStatuesqueGlorificationSMR() {
		if (!heightPass(slave)) {
			slave.natural.height = Height.randomAdult(slave, {skew: 1, limitMult: [2, 5]});
			slave.height = Height.forAge(slave.natural.height, slave);
		}
		return `${His} height, as well as ${his} potential for growth, were meticulously taken before being allowed into the markets.`;
	}

	function FSIntellectualDependencySMR() {
		slave.intelligence = Intelligence.random({limitIntelligence: [-100, -50]});
		slave.devotion += 5;
		return `${He} passed the intelligence exam needed to be qualify ${him} for sale. A truly <span class="hotpink">joyous achievement</span> for ${him}.`;
	}

	function FSSlaveProfessionalismSMR() {
		if (slave.vagina >= 0) {
			slave.skill.vaginal = Math.clamp(slave.skill.vaginal + 50, 50, 100);
		}
		if ((canAchieveErection(slave) || slave.clit >= 3) && penetrativeSocialUse(slave) >= 40) {
			slave.skill.penetrative = Math.clamp(slave.skill.penetrative + 50, 50, 100);
		}
		slave.skill.anal = Math.clamp(slave.skill.anal + 50, 50, 100);
		slave.skill.oral = Math.clamp(slave.skill.oral + 50, 50, 100);
		slave.skill.entertainment = Math.clamp(slave.skill.entertainment + 50, 50, 100);
		slave.skill.whoring = Math.clamp(slave.skill.whoring + 50, 50, 100);
		if (slave.accent > 1) {
			slave.accent = 1;
		}
		return `${He} underwent an intense lesson plan to hone ${his} skills to a level appropriate for sale.`;
	}

	function FSBodyPuristSMR() {
		slave.piercing.nipple.weight = 0;
		slave.piercing.areola.weight = 0;
		slave.piercing.lips.weight = 0;
		slave.piercing.vagina.weight = 0;
		slave.piercing.dick.weight = 0;
		slave.piercing.genitals.weight = 0;
		slave.piercing.genitals.smart = false;
		slave.piercing.anus.weight = 0;
		slave.piercing.corset.weight = 0;
		slave.piercing.ear.weight = 0;
		slave.piercing.eyebrow.weight = 0;
		slave.piercing.nose.weight = 0;
		slave.piercing.navel.weight = 0;
		slave.lipsTat = 0;
		slave.buttTat = 0;
		slave.anusTat = 0;
		slave.vaginaTat = 0;
		slave.dickTat = 0;
		slave.boobsTat = 0;
		slave.armsTat = 0;
		slave.legsTat = 0;
		slave.shouldersTat = 0;
		slave.backTat = 0;
		slave.stampTat = 0;
		if (slave.boobsImplant > 0) {
			slave.boobs -= slave.boobsImplant;
			slave.boobsImplant = 0;
			slave.boobsImplantType = "none";
			if (slave.boobShape === "spherical") {
				slave.boobShape = "normal";
			}
			if (slave.nipples === "flat") {
				slave.nipples = "cute";
			}
		}
		if (slave.buttImplant > 0) {
			slave.butt -= slave.buttImplant;
			slave.buttImplant = 0;
			slave.buttImplantType = "none";
		}
		if (slave.lipsImplant > 0) {
			slave.lips -= slave.lipsImplant;
			slave.lipsImplant = 0;
		}
		return `In order to qualify for sale, ${he} has been meticulously inspected for implants, tattoos, and piercings, and any present have been removed.`;
	}

	function FSTransformationFetishistSMR() {
		const r = [];
		r.push(`In order to qualify for sale, ${he} has been kitted out with a basic set of breast, buttock, and lip implants.`);
		if (slave.devotion <= 20) {
			r.push(`Being turned into an assembly line bimbo fills ${him} with <span class="gold">fear</span> and <span class="mediumorchid">disgust.</span>`);
			slave.trust -= 5;
			slave.devotion -= 5;
		}
		if (slave.boobsImplant === 0) {
			slave.boobsImplant = 400;
			slave.boobs += slave.boobsImplant;
			slave.boobsImplantType = "normal";
		}
		if (slave.buttImplant === 0) {
			slave.buttImplant = 1;
			slave.butt += slave.buttImplant;
			slave.buttImplantType = "normal";
		}
		if (slave.lipsImplant === 0) {
			slave.lipsImplant = 10;
			slave.lips += slave.lipsImplant;
		}
		return r.join(" ");
	}

	function FSYouthPreferentialistSMR() {
		if (slave.physicalAge > 35) {
			slave.trust -= 5;
			return `${He} was treated as a disposable old bitch in the slave pens, <span class="gold">worrying ${him}.</span>`;
		} else if (slave.physicalAge <= 25) {
			slave.trust += 5;
			return `${He} was treated reasonably well in the slave pens due to ${his} youth, <span class="mediumaquamarine">reassuring ${him}.</span>`;
		}
	}

	function FSMaturityPreferentialistSMR() {
		if (slave.physicalAge > 35) {
			slave.trust += 5;
			return `${He} was treated reasonably well in the slave pens due to ${his} maturity, <span class="mediumaquamarine">reassuring ${him}.</span>`;
		} else if (slave.physicalAge <= 25) {
			slave.trust -= 5;
			return `${He} was treated as a disposable little slut in the slave pens, <span class="gold">worrying ${him}.</span>`;
		}
	}

	function FSPastoralistSMR() {
		if (slave.lactation === 0) {
			slave.lactation = 2;
			slave.lactationDuration = 2;
			return `${He} was implanted with permanent lactation drugs in order to qualify for sale, and was kept in the slave pens until ${he} could be put up on the block with dripping nipples.`;
		}
	}

	function FSChattelReligionistSMR() {
		const r = [];
		r.push(`${He} has been consecrated for sale by being offered to the public for a full night, from sunset to sunrise.`);
		if (slave.devotion <= 20) {
			r.push(`Since ${he} was not willing to accept this treatment, ${his} consecration took the form of twelve hours of rape, terrifying ${him} and filling ${him} with hatred.`);
			slave.trust -= 15;
			slave.devotion -= 15;
		} else {
			r.push(`${He} accepted this treatment, <span class="hotpink">breaking ${him}</span> to sexual slavery.`);
			slave.devotion += 5;
		}

		seX(slave, "oral", "public", "penetrative", 10);
		if (slave.vagina > 0) {
			seX(slave, "vaginal", "public", "penetrative", 10);
			if (slave.vagina < 3) {
				slave.vagina += 1;
			}
		} else {
			seX(slave, "oral", "public", "penetrative", 10);
		}
		if (slave.anus > 0) {
			seX(slave, "anal", "public", "penetrative", 10);
			if (slave.anus < 3) {
				slave.anus += 1;
			}
		} else {
			seX(slave, "oral", "public", "penetrative", 10);
		}
		return r.join(" ");
	}

	function FSRomanRevivalistSMR() {
		slave.trust -= 10;
		return `As ${he} passed though the slave markets, ${he} saw many less valuable slaves selected to die in gladiatorial combats. ${He} is relieved ${he} wasn't one of them, but terrified that will be ${his} fate if ${he} makes mistakes.`;
	}

	function FSNeoImperialistSMR() {
		slave.trust -= 5;
		return `As ${he} passed though the slave markets, ${he} saw countless marvels of technological advancement, and the absolute obedience of tightly-disciplined slaves to their noble masters. The almost utter subservience of other slaves is deeply concerning to ${him}.`;
	}

	function FSAntebellumRevivalistSMR() {
		slave.trust -= 5;
		return `As ${he} passed though the slave markets, ${he} saw the dehumanizing horror of chattel slavery. The lack of sympathy from slave owners <span class="gold">scares</span> ${him}.`;
	}

	function FSAztecRevivalistSMR() {
		const r = [];
		r.push(`As ${he} passed though the golden city, ${he} saw a less valuable`);
		if (FutureSocieties.isActive('FSPaternalist')) {
			r.push(`slave's virginity get sacrificed`);
		} else {
			r.push(`slave get sacrificed`);
		}
		r.push(`on one of the many altars spread throughout the city. ${He} was`);
		if (FutureSocieties.isActive('FSPaternalist')) {
			r.push(`<span class="gold">startled</span>`);
		} else {
			r.push(`<span class="gold">mortified</span>`);
		}
		r.push(`to learn what befalls lesser slaves here.`);
		if (FutureSocieties.isActive('FSPaternalist')) {
			slave.trust -= 5;
		} else {
			slave.trust -= 20;
		}
		return r.join(" ");
	}

	function FSEgyptianRevivalistSMR() {
		const r = [];
		r.push(`While ${he} was in the slave pens, ${he} was subjected to Egyptian cultural indoctrination.`);
		if (slave.skill.entertainment > 10) {
			r.push(`${He} is now <span class="mediumaquamarine">confident</span> that ${his} entertainment skills will be valued here.`);
			slave.trust += 10;
		} else if (slave.intelligenceImplant >= 15) {
			r.push(`${He} now <span class="mediumaquamarine">hopes</span> that ${his} education will be valued here.`);
			slave.trust += 5;
		} else {
			r.push(`${He} is <span class="gold">concerned</span> that ${he} doesn't have what it takes to fit in.`);
			slave.trust -= 5;
		}
		return r.join(" ");
	}

	function FSEdoRevivalistSMR() {
		const r = [];
		r.push(`While ${he} was in the slave pens, ${he} was subjected to Edo Revivalist cultural indoctrination.`);
		if (slave.nationality === "Japanese") {
			r.push(`${He} is now <span class="mediumaquamarine">confident</span> that, as a proper Japanese ${girl}, ${he} will receive preferential treatment.`);
			slave.trust += 10;
		} else if (slave.skill.entertainment > 30) {
			r.push(`${He} now <span class="mediumaquamarine">hopes</span> that ${his} refined entertainment skills will make ${him} culturally as well as sexually valuable.`);
			slave.trust += 5;
		} else {
			r.push(`${He} is <span class="gold">concerned</span> that ${he} doesn't have what it takes to fit in.`);
			slave.trust -= 5;
		}
		return r.join(" ");
	}

	function FSArabianRevivalistSMR() {
		const r = [];
		r.push(`While ${he} was in the slave pens, ${he} was subjected to classical Arabian cultural indoctrination.`);
		if (slave.face > 40) {
			r.push(`${He} is now <span class="mediumaquamarine">confident</span> that ${his} great beauty will be valued here.`);
			slave.trust += 10;
		} else if (slave.accent <= 1) {
			r.push(`${He} now <span class="mediumaquamarine">hopes</span> that ${his} linguistic skill will be valued here.`);
			slave.trust += 5;
		} else {
			r.push(`${He} is <span class="gold">concerned</span> that ${he} doesn't have what it takes to fit in.`);
			slave.trust -= 5;
		}
		return r.join(" ");
	}

	function FSChineseRevivalistSMR() {
		const r = [];
		r.push(`While ${he} was in the slave pens, ${he} was subjected to Imperial Chinese cultural indoctrination.`);
		if (slave.intelligence > 50) {
			r.push(`${He} is now <span class="mediumaquamarine">confident</span> that ${his} brilliance will be valued here.`);
			slave.trust += 10;
		} else if (slave.intelligenceImplant >= 15) {
			r.push(`${He} now <span class="mediumaquamarine">hopes</span> that ${his} education will be valued here.`);
			slave.trust += 5;
		} else {
			r.push(`${He} is <span class="gold">concerned</span> that ${he} doesn't have what it takes to fit in.`);
			slave.trust -= 5;
		}
		return r.join(" ");
	}

	function basicSMR() {
		slave.trust -= 5;
		return `While ${he} came through the slave markets, ${he} was treated with inhuman callousness. It was very obvious and very terrifying to ${him} that ${he} is now considered a piece of meat.`;
	}

	function healthInspectionSMR() {
		if (slave.health.condition < -10) {
			improveCondition(slave, 10);
			slave.health.tired = Math.clamp(slave.health.tired - 30, 0, 100);
			return `${His} current owners held ${him} for basic medical care before putting ${him} on sale, resolving some of ${his} health issues.`;
		}
	}

	function educationSMR() {
		if (slave.intelligenceImplant === 0) {
			slave.intelligenceImplant = 15;
			return `${He} arrived at ${V.arcologies[0].name} in a pitiably undereducated state, and has been held back until ${he} completed a basic course of slave education.`;
		}
	}

	function frigiditySMR() {
		if (slave.energy > 20) {
			slave.energy = 20;
			return `Before reaching the market, ${he} was trained to disregard sex until ${he} showed no interest in sex at all.`;
		}
	}

	function beautyBasicSMR() {
		if (slave.face < 0) {
			slave.face = jsRandom(0, 60);
		}
		slave.trust -= 5;
		return `beauty`;
	}

	function beautyQualitySMR() {
		if (slave.face < 20) {
			slave.face = jsRandom(20, 100);
		}
		slave.trust -= 5;
		return `beauty`;
	}

	function weightSMR() {
		if (slave.weight > 95) {
			slave.weight = jsRandom(-50, 50);
		}
		slave.trust -= 5;
		return `weight`;
	}

	function heightBasicSMRup() {
		if (slave.height < 170) {
			if (slave.height >= 160) {
				slave.trust -= 10;
				slave.height += 10;
				slave.heightImplant = 1;
				healthDamage(slave, 40);
				return `Before ${he} was put up for sale, ${he} underwent height increasing surgery to fulfill your arcology's height SMR. ${He} was horrified by how drastically ${his} body was altered just for a chance at sale.`;
			} else {
				slave.devotion += 10;
				slave.trust -= 10;
				return `While ${he} was in the slave pens, ${he} was treated as nothing more than an unsalvageable slab of meat suitable only for the lowliest of jobs. ${He}'s desperate enough to follow any order in fear that should ${he} fail, ${he}'ll be discarded like so many other shorties.`;
			}
		} else {
			slave.devotion -= 10;
			return `While ${he} was in the slave pens, ${he} saw that short and even average sized slaves were commonly designated as menials and Fuckdolls. ${His} tall stature gives ${him} a promising future compared to ${his} shorter peers, leaving ${him} inappropriately proud of ${his} height.`;
		}
	}

	function heightBasicSMRdown() {
		if (slave.height >= 160) {
			if (slave.height <= 169) {
				slave.trust -= 10;
				slave.height -= 10;
				slave.heightImplant = -1;
				healthDamage(slave, 40);
				return `Before ${he} was put up for sale, ${he} underwent height reduction surgery to fulfill your arcology's height SMR. ${He} was horrified by how drastically ${his} body was altered just for a chance at sale.`;
			} else {
				slave.devotion += 10;
				slave.trust -= 10;
				return `While ${he} was in the slave pens, ${he} was treated as nothing more than an unsalvageable slab of meat suitable only for the lowliest of jobs. ${He}'s desperate enough to follow any order in fear that should ${he} fail, ${he}'ll be discarded like all the other tall ${girl}s.`;
			}
		} else {
			slave.devotion -= 10;
			return `While ${he} was in the slave pens, ${he} saw that tall and even average sized slaves were commonly designated as menials and Fuckdolls. ${His} short stature gives ${him} a promising future compared to ${his} towering peers, leaving ${him} inappropriately proud of ${his} height.`;
		}
	}

	function heightAdvancedSMRup() {
		slave.natural.height = Height.randomAdult(slave, {
			skew: V.policies.SMR.height.advancedSMR,
			limitMult: [0, 5 * V.policies.SMR.height.advancedSMR]
		});
		slave.height = Height.forAge(slave.natural.height, slave);
		let t = [`While ${he} was in the slave pens, ${he} saw that slaves on the shorter end of the height curve were immediately designated as menials and Fuckdolls.`];
		if (slave.physicalAge < 16) {
			t.push(`${He} is <span class="gold">terrified</span> that if ${he} doesn't keep growing, ${he}'ll be reassigned on the spot without a second thought.`);
			slave.trust -= 5;
		} else {
			t.push(`${His} above average stature gives ${him} a promising future compared to ${his} shorter peers, leaving ${him} <span class="mediumOrchid">inappropriately proud</span> of ${his} height.`);
			slave.devotion -= 10;
		}
		return t.join(" ");
	}

	function heightAdvancedSMRdown() {
		slave.natural.height = Height.randomAdult(slave, {
			skew: V.policies.SMR.height.advancedSMR,
			limitMult: [0, 5 * V.policies.SMR.height.advancedSMR]
		});
		slave.height = Height.forAge(slave.natural.height, slave);
		let t = [`While ${he} was in the slave pens, ${he} saw that slaves on the taller end of the height curve were immediately designated as menials and Fuckdolls.`];
		if (slave.physicalAge < 16) {
			t.push(`${He} is <span class="gold">terrified</span> that if ${he} goes through a growth spurt, ${he}'ll be reassigned on the spot without a second thought.`);
			slave.trust -= 5;
		} else {
			t.push(`${His} below average stature gives ${him} a promising future compared to ${his} taller peers, leaving ${him} <span class="mediumOrchid">inappropriately proud</span> of ${his} height.`);
			slave.devotion -= 10;
		}
		return t.join(" ");
	}

	function intelligenceBasicSMR() {
		if (slave.intelligence <= -15) {
			slave.intelligence = Intelligence.random({limitIntelligence: [0, 40]});
		}
		slave.trust -= 5;
		return `intelligence`;
	}

	function intelligenceQualitySMR() {
		if (slave.intelligence <= 15) {
			slave.intelligence = Intelligence.random({limitIntelligence: [16, 100]});
		}
		slave.trust -= 5;
		return `intelligence`;
	}

	function slaveFears(fearList) {
		const r = [];
		let objectFeared = "";
		let destination = "";
		let destinationList = {menials: 1, Fuckdolls: 0, cows: 0}; // Always menials as a fear
		let lossFeared = "";
		if (fearList.length === 0) {
			return;
		}
		for (let fear = 0; fear < fearList.length; fear++) {
			if (fearList[fear] === "intelligence") {
				objectFeared += "less intelligent";
				destinationList.Fuckdolls = 1;
				lossFeared += `if ${he} makes a mistake, `;
			} else if (fearList[fear] === "beauty") {
				objectFeared += "less attractive";
				destinationList.Fuckdolls = 1;
				lossFeared += `should ${he} lose ${his} beauty, `;
			} else if (fearList[fear] === "weight") {
				objectFeared += "obese";
				destinationList.cows = 1;
				lossFeared += `should ${he} get fat, `;
			} else {
				console.log(`error: ${fearList[fear]} not found.`);
			}
			if (fearList.length - fear > 2) {
				objectFeared += ", ";
			} else if (fearList.length - fear > 1) {
				objectFeared += " and ";
				lossFeared += "or ";
			}
		}

		if (destinationList.Fuckdolls === 1 && destinationList.cows === 1) {
			destination = `menials, Fuckdolls, and cows`;
		} else if (destinationList.Fuckdolls === 1) {
			destination = `menials and Fuckdolls`;
		} else if (destinationList.cows === 1) {
			destination = `menials and cows`;
		}

		r.push(`While ${he} was in the slave pens, ${he} saw that ${objectFeared} slaves were immediately designated ${destination} and ${he} is terrified that ${lossFeared} ${he}'ll be considered worthless and be reassigned on the spot.`);

		return r.join(" ");
	}

	function eugenicsSMRsCount() {
		const r = [];
		const sterilizationReasons = [];
		r.push(`As soon as ${he} arrived in the slave market, ${he} was subjected to a battery of testing:`);
		if (V.policies.SMR.eugenics.intelligenceSMR === 1) {
			r.push(`an intelligence test,`);
			if (slave.intelligence + slave.intelligenceImplant <= 50) {
				sterilizationReasons.push(`too stupid`);
			}
		}
		if (V.policies.SMR.eugenics.heightSMR === 1) {
			r.push(`rigorous height and bone measurements,`);
			if (slave.height < (Height.mean(slave) + 15)) {
				sterilizationReasons.push(`too short`);
			}
		}
		if (V.policies.SMR.eugenics.faceSMR === 1) {
			r.push(`strict judgments of ${his} facial attractiveness,`);
			if (slave.face < 40) {
				sterilizationReasons.push(`too ugly`);
			}
		}
		r.push(`a physical exam, and more.`);
		if (sterilizationReasons.length > 0) {
			r.push(`${He} failed, being found <span class="red">${toSentence(sterilizationReasons)},</span> and only then learned that by failing ${he} placed ${himself} under the merciless dictates of eugenic theory.`);
			if (slave.balls > 0 && (isFertile(slave, true) || slave.preg > 0)) {
				r.push(`${His} balls were promptly`);
				if (V.seeExtreme === 1) {
					r.push(`removed`);
					slave.balls = 0;
					slave.scrotum = 0;
				} else {
					r.push(`flooded with chemicals`);
					slave.ballType = "sterile";
				}
				r.push(`to prevent ${him} from passing on ${his} inferior genes, <span class="mediumorchid">infuriating</span> and <span class="gold">terrifying</span> ${him}.`);
				r.push(`Furthermore, ${his}`);
				if (slave.preg > 0) {
					r.push(`pregnancy was terminated and afterwards ${his}`);
					TerminatePregnancy(slave);
					actX(slave, "abortions");
				}
				r.push(`tubes were promptly tied to prevent ${him} from passing on ${his} inferior genes, <span class="mediumorchid">saddening</span> and <span class="gold">frightening</span> ${him}.`);
				slave.preg = -3;
				slave.devotion -= 30;
				slave.trust -= 30;
			} else if (slave.balls > 0) {
				r.push(`${His} balls were promptly`);
				if (V.seeExtreme === 1) {
					r.push(`removed`);
					slave.balls = 0;
					slave.scrotum = 0;
				} else {
					r.push(`flooded with chemicals`);
					slave.ballType = "sterile";
				}
				r.push(`to prevent ${him} from passing on ${his} inferior genes, <span class="mediumorchid">infuriating</span> and <span class="gold">terrifying</span> ${him}.`);
				slave.devotion -= 20;
				slave.trust -= 20;
			} else if (isFertile(slave, true) || slave.preg > 0) {
				r.push(`${His}`);
				if (slave.preg > 0) {
					r.push(`pregnancy was terminated and afterwards ${his}`);
					TerminatePregnancy(slave);
					actX(slave, "abortions");
				}
				r.push(`tubes were promptly tied to prevent ${him} from passing on ${his} inferior genes, <span class="mediumorchid">saddening</span> and <span class="gold">frightening</span> ${him}.`);
				slave.preg = -3;
				slave.devotion -= 10;
				slave.trust -= 10;
			} else {
				r.push(`Fortunately for ${him}, ${he} was already incapable of passing on ${his} inferior genes.`);
			}
		} else {
			r.push(`${He} passed, and only then learned that passing marked ${him} as fit to pass on ${his} genes, should ${his} owners decide to breed ${him}.`);
			if (slave.balls > 0 && (isFertile(slave, true) || slave.preg > 0)) {
				r.push(`${His} relief that ${he} won't have ${his} balls cut off and ${his} tubes tied is mixed with fear at being introduced to your society in this way.`);
			} else if ((isFertile(slave, true) || slave.preg > 0)) {
				r.push(`${His} relief that ${he} won't have ${his} tubes tied is mixed with fear at being introduced to your society in this way.`);
			} else {
				r.push(`This is merely confusing to ${him}, since ${he} isn't able to reproduce as it is. Of course, ${he} doesn't know that you're quite capable of changing that.`);
			}
		}
		return r.join(" ");
	}
};