code:
/*
This is a womb processor/simulator script. It takes care of calculation of belly sizes based on individual fetus sizes, with full support of broodmothers implant random turning on and off possibility. Also this can be expanded to store more parents data in each individual fetus in future.
Design limitations:
- Mother can't gestate children with different speeds at same time. All speed changes apply to all fetuses.
- Sizes of individual fetuses updated only on call of WombGetVolume - not every time as called WombProgress. This is for better overall code speed.
- For broodmothers we need actual "new ova release" code now. But it's possible to control how many children will be added each time, and so - how much children is ready to birth each time.

Usage from SugarCube code (samples):

WombInit(V.slave) - before first pregnancy, at slave creation, of as backward compatibility update.

WombImpregnate(V.slave, V.fetus_count, V.fatherID, V.initial_age) - should be added after normal impregnation code, with already calculated fetus count. ID of father - can be used in future for processing children from different fathers in one pregnancy. Initial age normally 1 (as .preg normally set to 1), but can be raised if needed. Also should be called at time as broodmother implant add another fetus(es), or if new fetuses added from other sources in future (transplanting maybe?)

WombProgress(V.slave, V.time_to_add_to_fetuses, V.real_time_to_add_to_fetuses) - after code that update V.slave.preg, time to add should be the same.

V.isReady = WombBirthReady(V.slave, V.birth_ready_age) - how many children ready to be birthed if their time to be ready is V.birth_ready_age (40 is for normal length pregnancy). Return int - count of ready to birth children, or 0 if no ready exists.

WombFlush(V.slave) - clean womb (array). Can be used at broodmother birthstorm or abortion situations in game. But birthstorm logically should use WombBirth(V.slave, 35) or so before - some children in this event is live capable, others is not.

V.slave.bellyPreg = WombGetVolume(V.slave) - return double, with current womb volume in CC - for updating V.slave.bellyPreg, or if need to update individual fetuses sizes.

*/

/**
 * Init womb system.
 * @param {FC.HumanState} actor
 */
globalThis.WombInit = function(actor) {
	let i;

	if (!Array.isArray(actor.womb)) {
		// alert("creating new womb"); // debugging
		actor.womb = [];
	}

	// console.log("broodmother:" + typeof actor.broodmother);

	if (typeof actor.broodmother !== "number") {
		actor.broodmother = 0;
		actor.broodmotherFetuses = 0;
	}

	if (typeof actor.readyOva !== "number") {
		actor.readyOva = 0;
	}

	if (actor.pregData === undefined) {
		actor.pregData = clone(App.Data.misc.pregData.human);
		// Setup should be through deep copy, so in future, if we like, these values can be changed individually. Gameplay expansion possibilities. But for dev time to simplify debugging:
		// actor.pregData = App.Data.misc.pregData.human; // any changes in App.Data.misc pregData template will be applied immediately to all. But can't be made separate changes.
	}

	if (typeof actor.eggType !== 'string') {
		actor.eggType = "human";
	}

	// backward compatibility setup. Fully accurate for normal pregnancy only.
	if (actor.womb.length > 0 && actor.womb[0].genetics === undefined) {
		i = 0;
		actor.womb.forEach(function(ft) {
			if (typeof ft.reserve !== 'string') {
				ft.reserve = "";
			}
			if (typeof ft.motherID !== 'number') { // setting missing biological mother ID for fetus.
				ft.motherID = actor.ID;
			}
			if (ft.ID === undefined) {
				ft.ID = generateNewID();
			}
			if (typeof ft.realAge !== 'number') { // setting missing chronological age
				ft.realAge = ft.age;
			}

			ft.genetics = generateGenetics(actor, actor.pregSource, i);
			i++;
		});
	} else if (actor.womb.length === 0 && actor.pregType > 0 && actor.broodmother === 0) {
		WombImpregnate(actor, actor.pregType, actor.pregSource, actor.preg);
	} else if (actor.womb.length === 0 && actor.pregType > 0 && actor.broodmother > 0 && actor.broodmotherOnHold < 1) {
		// sorry but for already present broodmothers it's impossible to calculate fully, approximation used.
		let pw = actor.preg;
		if (pw > actor.pregData.normalBirth) { pw = actor.pregData.normalBirth; } // to avoid disaster.
		const bCount = Math.floor(actor.pregType / pw);
		const bLeft = actor.pregType - (bCount * pw);
		if (pw > actor.pregType) {
			pw = actor.pregType; // low children count broodmothers not supported here. It's emergency/backward compatibility code, and they not in game anyway. So minimum is 1 fetus in week.
			actor.preg = pw; // fixing initial pregnancy week.
		}
		for (i = 0; i < pw; i++) {
			WombImpregnate(actor, bCount, actor.pregSource, i); // setting fetuses for every week, up to 40 week at max.
		}

		if (bLeft > 0) {
			WombImpregnate(actor, bLeft, actor.pregSource, i + 1); // setting up leftover of fetuses.
		}
	}

	actor.womb.forEach(f => {
		if (!jsDef(f.genetics.inbreedingCoeff)) {
			f.genetics.inbreedingCoeff = ibc.coeff(
				{ID: null, mother: f.genetics.mother, father: f.genetics.father}
			);
		}
		if (!jsDef(f.genetics.artSeed)) {
			// probably could detect and fix clones/twins here too, but I'm not bothering
			f.genetics.artSeed = jsRandom(0, 10 ** 14);
		}
		if (!jsDef(f.genetics.adultHeight)) {
			f.genetics.adultHeight = Height.randomAdult({
				nationality: f.genetics.nationality, race: f.genetics.race, genes: f.genetics.gender, physicalAge: 20, birthWeek: 0
			});
		}
		if (!jsDef(f.genetics.boobPotential)) {
			f.genetics.boobPotential = adjustBreastSize(f.genetics);
		}
	});
};

App.Entity.Fetus = class {
	/** Construct a new fetus
	 * @param {number} age - initial age, after conception, in weeks
	 * @param {number} fatherID
	 * @param {FC.HumanState} mother
	 * @param {string} name - name of ovum (generally ovumNN where NN is the number in the batch)
	 */
	constructor(age, fatherID, mother, name) {
		/** Unique identifier for this fetus */
		this.ID = generateNewID();
		/** Week since conception */
		this.age = age;
		/** Week in mother (age since implantation) */
		this.realAge = 1;
		this.fatherID = fatherID;
		this.volume = 1;
		this.reserve = "";
		/** All identical multiples share the same twinID */
		this.twinID = "";
		this.motherID = mother.ID;
		this.genetics = generateGenetics(mother, fatherID, name);
	}
};

/**
 * @param {FC.HumanState} actor
 * @param {number} fCount
 * @param {number} fatherID
 * @param {number} age
 * @param {FC.HumanState} [surrogate] genetic mother
 */
globalThis.WombImpregnate = function(actor, fCount, fatherID, age, surrogate) {
	for (let i = 0; i < fCount; i++) {
		const tf = new App.Entity.Fetus(age, fatherID, surrogate || actor, `ovum${i}`);
		try {
			if (actor.womb.length === 0) {
				actor.pregWeek = age;
				actor.preg = age;
			}
			actor.womb.push(tf);
		} catch (err) {
			WombInit(actor);
			actor.womb.push(tf);
			alert("WombImpregnate warning - " + actor.slaveName + " " + err);
		}
	}
	MissingParentIDCorrection(actor);
	WombUpdatePregVars(actor);
};

/**
 * @param {FC.HumanState} actor (surrogate mother)
 * @param {number} fCount
 * @param {FC.HumanState} mother (genetic mother)
 * @param {number} fatherID
 * @param {number} age
 */
globalThis.WombSurrogate = function(actor, fCount, mother, fatherID, age) {
	WombImpregnate(actor, fCount, fatherID, age, mother);
};

/**
 * @param {FC.HumanState} actor (surrogate mother)
 * @param {number} fCount
 * @param {FC.HumanState} mother (genetic parent being cloned)
 * @param {number} age
 */
globalThis.WombImpregnateClone = function(actor, fCount, mother, age) {
	setMissingParents(mother);
	const motherOriginal = V.genePool.find(s => s.ID === mother.ID) || mother;

	for (let i = 0; i < fCount; i++) {
		const tf = new App.Entity.Fetus(age, mother.ID, mother, `ovum${i}`);

		// gene corrections
		tf.fatherID = -7;
		tf.genetics.gender = mother.genes;
		tf.genetics.mother = mother.mother;
		tf.genetics.father = mother.father;
		tf.genetics.motherName = mother.slaveName;
		tf.genetics.fatherName = mother.slaveName;
		tf.genetics.cloneID = mother.ID;
		if (mother.ID === -1) {
			tf.genetics.clone = PlayerName();
		} else {
			tf.genetics.clone = SlaveFullName(asSlave(mother));
		}
		tf.genetics.inbreedingCoeff = mother.inbreedingCoeff;
		tf.genetics.intelligence = motherOriginal.intelligence;
		tf.genetics.face = motherOriginal.face;
		tf.genetics.faceShape = motherOriginal.faceShape;
		tf.genetics.geneticQuirks = clone(mother.geneticQuirks);
		tf.genetics.skin = motherOriginal.skin;
		tf.genetics.artSeed = mother.natural.artSeed;

		try {
			if (actor.womb.length === 0) {
				actor.pregWeek = age;
				actor.preg = age;
				actor.pregSource = -7;
			}
			actor.womb.push(tf);
		} catch (err) {
			WombInit(actor);
			actor.womb.push(tf);
			alert("WombImpregnate warning - " + actor.slaveName + " " + err);
		}
	}
	WombUpdatePregVars(actor);
};

// Should be used to set biological age for fetus (ageToAdd), AND chronological (realAgeToAdd). Speed up or slow down gestation drugs should affect ONLY biological.
globalThis.WombProgress = function(actor, ageToAdd, realAgeToAdd = ageToAdd) {
	ageToAdd = Math.ceil(ageToAdd * 10) / 10;
	realAgeToAdd = Math.ceil(realAgeToAdd * 10) / 10;
	try {
		actor.womb.forEach(ft => {
			ft.age += ageToAdd;
			ft.realAge += realAgeToAdd;
		});
	} catch (err) {
		WombInit(actor);
		alert("WombProgress warning - " + actor.slaveName + " " + err);
	}
};

globalThis.WombBirth = function(actor, readyAge) {
	try {
		WombSort(actor); // For normal processing fetuses that more old should be first. Now - they are.
	} catch (err) {
		WombInit(actor);
		alert("WombBirth warning - " + actor.slaveName + " " + err);
	}

	let birthed = [];
	let ready = WombBirthReady(actor, readyAge);
	let i;

	for (i = 0; i < ready; i++) { // here can't be used "for .. in .." syntax.
		birthed.push(actor.womb.shift());
	}

	return birthed;
};

globalThis.WombFlush = function(actor) {
	actor.womb = [];
	WombUpdatePregVars(actor);
};

globalThis.WombBirthReady = function(actor, readyAge) {
	let readyCnt = 0;
	try {
		readyCnt += actor.womb.filter(ft => ft.age >= readyAge).length;
	} catch (err) {
		WombInit(actor);
		alert("WombBirthReady warning - " + actor.slaveName + " " + err);
		return 0;
	}

	return readyCnt;
};

/**
 * @param {FC.PregnancyData} pregData
 * @param {number} age
 */
globalThis.PregDataAtAge = function(pregData, age) {
	let i = 0;
	while (pregData.fetusWeek[i + 1] < age && i < pregData.fetusWeek.length - 1) {
		i++;
	}

	const min = pregData.fetusSize[i];
	const max = pregData.fetusSize[i + 1];
	const ageMin = pregData.fetusWeek[i];
	const ageMax = pregData.fetusWeek[i + 1];
	const rateMin = pregData.fetusRate[i];
	const rateMax = pregData.fetusRate[i + 1];

	const cAge = age - ageMin;

	const one = (max - min) / (ageMax - ageMin);
	const rateOne = (rateMax - rateMin) / (ageMax - ageMin);

	const rate = rateMin + (rateOne * cAge);
	const size = min + (one * cAge);
	// console.log("min:"+min+" max:"+max+" ageMin:"+ageMin+" ageMax:"+ageMax+" one:"+one+" rateOne:"+rateOne+" cAge:"+cAge+" rate:"+rate+" cSize:"+cSize+" final size:"+cSize*rate);

	return {rate, size};
};

/** @param {FC.HumanState} actor */
globalThis.WombGetVolume = function(actor) {
	let cache = {age: -1, size: -1, rate: -1};
	let vol = 0;

	if (actor.pregData.sizeType === 0) {
		vol = getVolByLen();
	} else if (actor.pregData.sizeType === 1) {
		vol = getVolByWeight();
	} else if (actor.pregData.sizeType === 2) {
		vol = getVolByRaw();
	}

	// must be positive
	return vol >= 0 ? vol : 0;

	/** @param {number} age */
	function getCurData(age) {
		if (age !== cache.age) {
			const {size, rate} = PregDataAtAge(actor.pregData, age);
			// the next baby in this litter will probably want exactly the same data, don't calculate it again
			cache = {age, size, rate};
		}

		return cache;
	}

	function getVolByLen() {
		const phi = 1.618;
		let wombSize = 0;
		actor.womb.forEach(ft => {
			/* legacy block for debug only
			let gestationWeek = ft.age;
			let oldLen;
			let oldVol;
			if (gestationWeek <= 32) {
				oldLen = (0.00006396 * Math.pow(gestationWeek, 4)) -
					(0.005501 * Math.pow(gestationWeek, 3)) +
					(0.161 * Math.pow(gestationWeek, 2)) -
					(0.76 * gestationWeek) +
					0.208;
			} else if (gestationWeek <= 106) {
				oldLen = (-0.0000004675 * Math.pow(gestationWeek, 4)) +
					(0.0001905 * Math.pow(gestationWeek, 3)) -
					(0.029 * Math.pow(gestationWeek, 2)) +
					(2.132 * gestationWeek) -
					16.575;
			} else {
				oldLen = (-0.00003266 * Math.pow(gestationWeek,2)) +
					(0.076 * gestationWeek) +
					43.843;
			}
			*/

			const targetData = getCurData(ft.age);
			const len = targetData.size * targetData.rate;

			ft.volume = ((4 / 3) * (Math.PI) * (phi / 2) * (Math.pow((len / 2), 3)));
			wombSize += ft.genetics.geneticQuirks.polyhydramnios === 2 ? ft.volume * 2 : ft.volume;

			// oldVol = ((4 / 3) * (Math.PI) * (phi / 2) * (Math.pow((oldLen / 2), 3))); // for debug

			// console.log("fetus.age:" + ft.age + " oldLen:"+oldLen+" len:"+len+" ft.volume:"+ft.volume+ " old volume:"+oldVol );
			/*
				I found, that previous targetLen calculation not exactly accurate if compared to the actual medical data chart for fetal length. It's been rough approximation based only on pregnancy week (giving smaller fetus size then it should in most cases). So I need all this debug code to compare data and verify calculations. After final tweaking I will remove or comment out legacy code. Please not touch this before it.
				Pregmodfan.
			*/
		});
		return wombSize;
	}

	function getVolByWeight() {
		let wombSize = 0;

		actor.womb.forEach(ft => {
			const targetData = getCurData(ft.age);
			ft.volume = targetData.size * targetData.rate;
			wombSize += ft.genetics.geneticQuirks.polyhydramnios === 2 ? ft.volume * 2 : ft.volume;
		});
		return wombSize;
	}

	function getVolByRaw() {
		let wombSize = 0;

		actor.womb.forEach(ft => {
			const targetData = getCurData(ft.age);
			ft.volume = targetData.size;
			wombSize += ft.genetics.geneticQuirks.polyhydramnios === 2 ? ft.volume * 2 : ft.volume;
		});
		return wombSize;
	}
};

/**
 * @param {FC.HumanState} actor
 * @param {number} age
 */
globalThis.FetusGetPrediction = function(actor, age) {
	const targetData = PregDataAtAge(actor.pregData, age);
	let vol = 0.1;

	if (actor.pregData.sizeType === 0) {
		vol = getVolByLen();
	} else if (actor.pregData.sizeType === 1) {
		vol = getVolByWeight();
	} else if (actor.pregData.sizeType === 2) {
		vol = getVolByRaw();
	}

	return vol >= 0.1 ? vol : 0.1;

	function getVolByLen() {
		const phi = 1.618;
		const targetLen = targetData.size * targetData.rate;
		const volume = ((4 / 3) * (Math.PI) * (phi / 2) * (Math.pow((targetLen / 2), 3)));
		/*
		if (targetData.genetics.geneticQuirks.polyhydramnios === 2) {
			volume *= 2;
		}
		*/

		return volume;
	}

	function getVolByWeight() {
		const volume = targetData.size * targetData.rate;
		/*
		if (targetData.genetics.geneticQuirks.polyhydramnios === 2) {
			volume *= 2;
		}
		*/

		return volume;
	}

	function getVolByRaw() {
		const volume = targetData.size;
		/*
		if (targetData.genetics.geneticQuirks.polyhydramnios === 2) {
			volume *= 2;
		}
		*/

		return volume;
	}
};

globalThis.WombUpdatePregVars = function(actor) {
	WombSort(actor);
	if (actor.womb.length > 0) {
		if (actor.preg > 0 && actor.womb[0].age > 0) {
			actor.preg = actor.womb[0].age;
		}
		actor.pregType = actor.womb.length;
		actor.bellyPreg = WombGetVolume(actor);

		if (actor.womb[0].age >= 10 && actor.pregKnown === 0) {
			actor.pregKnown = 1;
		}
	} else {
		actor.pregType = 0;
		WombNormalizePreg(actor);
	}
};

globalThis.WombMinPreg = function(actor) {
	WombSort(actor);
	if (actor.womb.length > 0) {
		return actor.womb[actor.womb.length - 1].age;
	} else {
		return 0;
	}
};

globalThis.WombMaxPreg = function(actor) {
	WombSort(actor);
	if (actor.womb.length > 0) {
		return actor.womb[0].age;
	} else {
		return 0;
	}
};

globalThis.WombNormalizePreg = function(actor) {
	WombInit(actor);

	// this is broodmother on hold.
	if (actor.womb.length === 0 && actor.broodmother >= 1) {
		actor.pregType = 0;
		actor.pregKnown = 0;

		// to avoid legacy code conflicts - broodmother on hold
		// can't be impregnated, but she is not on normal contraceptives.
		// So we set this for special case.
		if (actor.preg >= 0) {
			actor.preg = 0.1;
		}

		if (actor.pregSource !== 0) {
			actor.pregSource = 0;
		}

		if (actor.pregWeek > 0) {
			actor.pregWeek = 0;
		}

		actor.broodmotherCountDown = 0;
	}

	if (actor.womb.length > 0) {
		let max = WombMaxPreg(actor);
		// console.log("max: " + max);
		// console.log(".preg: "+ actor.preg);
		if (actor.pregWeek < 1) {
			actor.pregWeek = 1;
		}

		if (max < actor.preg) {
			WombProgress(actor, actor.preg - max, actor.preg - max);
			// console.log("progress in womb");
		} else if (max > actor.preg) {
			actor.preg = max;
			// console.log("advancing .preg");
		}

		if (actor.womb[0].age >= 10 && actor.pregKnown === 0) {
			actor.pregKnown = 1;
		}

		actor.pregType = actor.womb.length;
		actor.pregSource = actor.womb[0].fatherID;
	} else if (actor.womb.length === 0 && actor.broodmother < 1) {
		// not broodmother
		// console.log("preg fixing");
		actor.pregType = 0;
		actor.pregKnown = 0;

		if (actor.preg > 0) {
			actor.preg = 0;
		}

		if (actor.pregSource !== 0) {
			actor.pregSource = 0;
		}

		// We can't properly set postpartum here,
		// but can normalize obvious error with forgotten property.
		if (actor.pregWeek > 0) {
			actor.pregWeek = 0;
		}
	}
	actor.bellyPreg = WombGetVolume(actor);
};

globalThis.WombChangeID = function(actor, fromID, toID) {
	WombInit(actor);
	for (const ft of actor.womb) {
		if (ft.fatherID === fromID) {
			ft.fatherID = toID;
		}
		if (ft.genetics.father === fromID) {
			ft.genetics.father = toID;
		}
		if (ft.genetics.mother === fromID) {
			ft.genetics.mother = toID;
		}
	}
	WombNormalizePreg(actor);
};

globalThis.WombForceFatherID = function(actor, ID) {
	WombInit(actor);
	for (const ft of actor.womb) {
		ft.fatherID = ID;
		ft.genetics.father = ID;
	}
	WombNormalizePreg(actor);
};

/* Sorts the womb object by age with oldest and thus soonest to be born, first. This will be needed in the future once individual fertilization is a possibility.*/
globalThis.WombSort = function(actor) {
	actor.womb.sort((a, b) => {
		return b.age - a.age;
	});
};

/** Split fetuses into identical twins based on chance
 * @param {FC.HumanState} actor
 * @param {number} chance
 */
globalThis.fetalSplit = function(actor, chance) {
	for (const s of actor.womb) {
		if (jsRandom(1, chance) >= chance || (actor.geneticQuirks.twinning === 2 && (actor.womb.length < Math.floor(actor.pregAdaptation / 32) || actor.womb.length === 1) && (s.twinID === "" || s.twinID === undefined))) {
			// if this fetus is not already an identical, generate a new twin ID before cloning it
			if (s.twinID === "" || s.twinID === undefined) {
				s.twinID = generateNewID();
			}

			// clone the fetus with a new fetus ID
			const nft = clone(s);
			nft.ID = generateNewID();
			nft.reserve = ""; // new fetus does not inherit reserve status

			actor.womb.push(nft);
		}
	}
	WombNormalizePreg(actor);
};

// safe alternative to .womb.length.
globalThis.WombFetusCount = function(actor) {
	WombInit(actor);
	return actor.womb.length;
};

// give reference to fetus object, but not remove fetus, use for manipulation in the womb.
globalThis.WombGetFetus = function(actor, fetusNum) {
	WombInit(actor);
	if (actor.womb.length >= fetusNum) {
		return actor.womb[fetusNum];
	} else {
		return null;
	}
};

// give reference to fetus object, and remove it form the womb.
globalThis.WombRemoveFetus = function(actor, fetusNum) {
	WombInit(actor);
	if (actor.womb.length >= fetusNum) {
		let ft = actor.womb[fetusNum];
		actor.womb.splice(fetusNum, 1);
		WombUpdatePregVars(actor);
		return ft;
	} else {
		return null;
	}
};

/* to add fetus object in the womb. Be warned - you can add one single fetus to many wombs, or even add it many times to one womb. It will not show error, but behavior becomes strange, as fetus object will be the same - it's reference, not full copies. If this is not desired - use clone() on fetus before adding.*/
globalThis.WombAddFetus = function(actor, fetus) {
	WombInit(actor);
	actor.womb.push(fetus);
	WombSort(actor);
};

// change property for all fetuses. Like fetus.age = X.
globalThis.WombChangeFetus = function(actor, propName, newValue) {
	WombInit(actor);
	actor.womb.forEach(ft => ft[propName] = newValue);
};

// change genetic property of all fetuses. Like fetus.genetic.intelligence = X
globalThis.WombChangeGene = function(actor, geneName, newValue) {
	WombInit(actor);
	actor.womb.forEach(ft => ft.genetics[geneName] = newValue);
};

// change genetic property of all fetuses based on race
globalThis.WombFatherRace = function(actor, raceName) {
	let skinColor = randomRaceSkin(raceName);
	let eyeColor = randomRaceEye(raceName);
	let hairColor = randomRaceHair(raceName);
	WombChangeGene(actor, "race", raceName);
	WombChangeGene(actor, "skin", skinColor);
	WombChangeGene(actor, "eyeColor", eyeColor);
	WombChangeGene(actor, "hColor", hairColor);
};

// replaces untraceable fatherIDs with missingParentID. Required for concurrent pregnancy to differentiate between siblings.
globalThis.MissingParentIDCorrection = function(actor) {
	WombInit(actor);
	actor.womb
		.filter(ft => !specificCharacterID(ft.genetics.father))
		.forEach(ft => ft.genetics.father = V.missingParentID);
	V.missingParentID--;
};

globalThis.WombCleanYYFetuses = function(actor) {
	let reserved = [];

	let i = actor.womb.length - 1;
	let ft;

	while (i >= 0) {
		ft = actor.womb[i];

		if (ft.genetics.gender === "YY") {
			reserved.push(ft);
			actor.womb.splice(i, 1);
		}

		i--;
	}
	WombUpdatePregVars(actor);

	return reserved;
};

globalThis.FetusGlobalReserveCount = function(reserveType) {
	let cnt = 0;

	if (typeof reserveType !== 'string') {
		return 0;
	}

	V.slaves.forEach(function(slave) {
		slave.womb.forEach(function(ft) {
			if (ft.reserve === reserveType) {
				cnt++;
			}
		});
	});

	V.PC.womb.forEach(function(ft) {
		if (ft.reserve === reserveType) {
			cnt++;
		}
	});

	return cnt;
};

globalThis.WombSetGenericReserve = function(actor, type, count) {
	// console.log ("actor: " + actor + " type: " + type + " typeof: " + typeof type + " count: " + count);
	actor.womb.forEach(function(ft) {
		// console.log (" type: " + ft.reserve + " typeof: " + typeof ft.reserve);
		if ((ft.reserve === "" || ft.reserve === type) && count > 0) {
			// console.log ("!trigger");
			ft.reserve = type;
			if (type !== "incubator") {
				delete ft.tankSetting;
			}
			count--;
		}
	});
};

globalThis.WombAddToGenericReserve = function(actor, type, count) {
	WombSetGenericReserve(actor, type, (WombReserveCount(actor, type) + count));
};

globalThis.WombChangeReserveType = function(actor, oldType, newType) {
	let count = 0;

	actor.womb.forEach(function(ft) {
		if (ft.reserve === oldType) {
			ft.reserve = newType;
			if (newType !== "incubator") {
				delete ft.tankSetting;
			}
			count++;
		}
	});

	return count;
};

globalThis.WombCleanGenericReserve = function(actor, type, count) {
	actor.womb.forEach(function(ft) {
		if (ft.reserve === type && count > 0) {
			ft.reserve = "";
			delete ft.tankSetting;
			count--;
		}
	});
};

globalThis.WombReserveCount = function(actor, type) {
	let cnt = 0;

	actor.womb.forEach(function(ft) {
		if (ft.reserve === type) /* the lazy equality will catch "" case */ {
			cnt++;
		}
	});

	return cnt;
};

globalThis.WombGetReservedFetuses = function(actor, type) {
	let reserved = [];

	actor.womb.forEach(function(ft) {
		if (ft.reserve === type) {
			reserved.push(ft);
		}
	});

	return reserved;
};

globalThis.WombRemoveReservedFetuses = function(actor, type) {
	let reserved = [];

	let i = actor.womb.length - 1;
	let ft;

	while (i >= 0) {
		ft = actor.womb[i];

		if (ft.reserve === type) {
			reserved.push(ft);
			actor.womb.splice(i, 1);
		}

		i--;
	}

	return reserved;
};

globalThis.WombCleanAllReserve = function(actor) {
	actor.womb.forEach(function(ft) {
		ft.reserve = "";
		delete ft.tankSetting;
	});
};

/** Return object with data about litters in actor womb. This data can be used for descriptions of pregnancy with complicated structure.
 * @param {FC.HumanState} actor
 * @returns {{litters: Array<number>, litterData: Array<Array<App.Entity.Fetus>>}}
 *
 * Return contents:
 * - data.litters.length = summary count of separate litters in the womb.
 * - data.litters[x] = age (.realAge) of litter "x".
 * - data.litterData[x].length = count of fetuses in "x" litter.
 * - data.litterData[x] = array with actual fetuses that belong to a litter "x". Can be used to check anything related to fetus. (This is not a copy, but a reference to actual fetuses, so be careful with changes).
 */
globalThis.WombGetLittersData = function(actor) {
	let unicLiters = []; // array with realAges of separate litters.
	let litterData = [];

	// we need to know how many litters here (Assuming that unique litters have similar .realAge). Also we will know their ages.
	for (const ft of actor.womb) {
		const age = Math.ceil(ft.realAge);
		if (!unicLiters.includes(age)) {
			unicLiters.push(age);
			litterData.push(actor.womb.filter(f2 => Math.ceil(f2.realAge) === age));
		}
	}

	return {litters: unicLiters, litterData: litterData};
};

/** simple function used for splitting actual size from total size due to polyhydramnios.
 * @param {FC.HumanState} actor
 * @returns {number}
 */
globalThis.WombGetFetalSizeSum = function(actor) {
	return actor.womb.reduce((acc, cur) => acc + cur.volume, 0);
};

repo_name: Arkerthan/fc

license: mit


code:
App.Markets.bulkSlaveIntro = function() {
	const el = new DocumentFragment();
	const r = [];
	const discount = getDiscount();
	let seed;
	let p;

	if (V.market.newSlaves.length === 0) {
		bulkSlaveGenerate();
	}

	if (!V.market.introType || V.market.newSlaves.length === 0) {
		V.market.introType = "";
	}
	if (isNaN(V.market.newSlaveIndex)) {
		V.market.newSlaveIndex = 0;
	}

	switch (V.market.introType) {
		case "":
			/* No message to give */
			break;
		case "multi":
			if (V.market.newSlaves.length > 1) {
				r.push(`Your selection of ${V.market.newSlaves.length} slaves arrives from ${App.Markets.marketName(V.market.slaveMarket, V.market.numArcology)}.`);
			} else {
				r.push(`Your new slave from ${App.Markets.marketName(V.market.slaveMarket, V.market.numArcology)} has arrived.`);
			}
			break;
		case "bulk":
			r.push(`Your delivery of ${V.market.newSlaves.length} slaves arrives from ${App.Markets.marketName(V.market.slaveMarket, V.market.numArcology)}.`);
			if (V.market.newSlaves.length !== V.market.numSlaves) {
				r.push(`You were going to order ${V.market.numSlaves}, but ${V.assistant.name} lowered it on financial grounds.`);
			}
			seed = Math.ceil(V.slavesSeen - V.slaveMarketLimit);
			if (seed > V.market.newSlaves.length) {
				r.push(`You have cast such a wide net for slaves this week that all are more expensive than normal.`);
			} else if (seed > 0) {
				r.push(`You have cast such a wide net for slaves this week that some (${seed}) are more expensive than normal.`);
			}
			if (V.market.slaveMarket === "TFS") {
				/* Put line about The Futanari Sisters discount & pricing */
			} else if (discount === 475) {
				r.push(`Your bulk delivery came with a <span class="yellowgreen">5%</span> discount.`);
			} else {
				r.push(`With all your discounts factored in you got a <span class="yellowgreen">${(500 - discount) / 5}%</span> discount;`);
			}
			r.push(`You spent`, App.UI.DOM.cashFormat(V.market.totalCost), `on your new slaves.`);
			break;
		case "inStock":
			r.push(`You clear out ${App.Markets.marketName(V.market.slaveMarket, V.market.numArcology)} of its stock of ${V.market.newSlaves.length} slaves.`);
			if (V.market.slaveMarket === "TFS") {
				/* Put line about The Futanari Sisters discount & pricing */
			} else if (discount === 475) {
				r.push(`Your bulk delivery came with a <span class="yellowgreen">5%</span> discount.`);
			} else {
				r.push(`With all your discounts factored in you got a <span class="yellowgreen">${(500 - discount) / 5}%</span> discount`);
			}
			r.push(`You spent`, App.UI.DOM.cashFormat(V.market.totalCost), `on your new slaves.`);
			break;
		case "liquidator":
			r.push(`Your new pair of slaves look frightened and uncertain, but seem encouraged by each other's presence.`);
			break;
		case "egyptian":
			r.push(`They arrive hand-in-hand and don't let go of each other until the end of the enslavement process, and even after they break their grip and undress at your instruction, their eyes never stray far from each other. With a closer look at them, their blood relation is as obvious as their infatuation with one another. No wonder they couldn't keep it a secret.`);
			break;
		case "event":
			r.push(`This is placeholder event info.`);
			break;
	}
	App.Events.addParagraph(el, r);
	/* remove the below line to make the intro blurb show up for every slave, not just the first */
	V.market.introType = "";

	if ((V.market.newSlaveIndex >= V.market.newSlaves.length) || (V.market.newSlavesDone === 1)) {
		/* Variable Clean-up */
		V.market.newSlaves = [];
		V.market.newSlavesDone = 0;
		V.market.introType = "";
	} else {
		if (V.market.newSlaves.length > 1) {
			App.UI.DOM.appendNewElement("div", el, `Showing new slave ${V.market.newSlaveIndex + 1} of ${V.market.newSlaves.length}:`);
		}

		/* Set slave to the desired newSlave so that existing code can be used */
		const slave = V.market.newSlaves[V.market.newSlaveIndex];

		el.append(App.Desc.longSlave(slave, {market: V.market.slaveMarket}));

		/* Use existing New Slave Intro */
		el.append(App.UI.newSlaveIntro(slave));

		/* Override nextButton setting from New Slave Intro */
		V.nextButton = "Continue";
		V.nextLink = "Bulk Slave Intro";
		App.Utils.updateUserButton();

		/* Add an option = goto the next slave below the New Slave Intro section */

		p = document.createElement("p");
		if (V.market.newSlaveIndex < (V.market.newSlaves.length - 1)) {
			p.append(
				App.UI.DOM.passageLink(
					`Next Slave`,
					`Bulk Slave Intro`
				)
			);
		} else {
			p.append(
				App.UI.DOM.passageLink(
					`Finish introducing slaves`,
					V.returnTo
				)
			);
			V.nextButton = "Back";
			V.nextLink = V.returnTo;
			App.Utils.updateUserButton();
			V.market.newSlavesDone = 1;
		}
		el.append(p);
	} /* Closes no new Slaves check */

	V.market.newSlaveIndex++;
	return el;

	function bulkSlaveGenerate() {
		V.market.newSlaves = [];
		V.market.newSlavesDone = 0;
		V.market.newSlaveIndex = 0;
		V.market.introType = "bulk";
		let cost;
		if (!V.market.numSlaves) {
			V.market.numSlaves = 5;
		}

		for (let i = 0; i < V.market.numSlaves; i++) {
			let slave = (generateMarketSlave(V.market.slaveMarket, V.market.numArcology)).slave;
			V.slavesSeen++;
			if (!App.Data.misc.lawlessMarkets.includes(V.market.slaveMarket)) {
				App.Desc.lawCompliance(slave, V.market.slaveMarket); /* slave stats may change, affecting price */
			}
			cost = slaveCost(slave);

			/* Adjust cost according to V.slavesSeen */
			if (V.slavesSeen > V.slaveMarketLimit) {
				cost += cost * ((V.slavesSeen - V.slaveMarketLimit) * 0.1);
				if (V.market.introType === "inStock") {
					break;
				}
			}

			/* Apply discount modifier */
			cost = discount * Math.trunc(cost / 500);

			/* Charge the Player for the slave, or break out if cannot afford */
			if (V.cash < cost) {
				i = V.market.numSlaves;
				break;
			} else {
				cashX(forceNeg(cost), "slaveTransfer", slave);
				V.market.newSlaves.push(slave);
				V.market.totalCost += cost;
			}
		}

		/* Max Buy clean-up */
		if (V.market.numSlaves === 9999) {
			V.market.numSlaves = V.market.newSlaves.length;
		}

		/* increment Slave school purchase counts if needed */
		if (App.Data.misc.schools.has(V.market.slaveMarket)) {
			V[V.market.slaveMarket].studentsBought += V.market.newSlaves.length;
		}
	}

	function getDiscount() {
		/* Discount calculation. Gives 5% on top of slave school discount */
		let discount = 475;
		let opinion;

		if (V.market.slaveMarket === "TFS") {
			if (V.TFS.schoolUpgrade !== 0) {
				discount = 300;
			} else {
				discount = 380;
			}
		} else if (V.market.slaveMarket === "corporate") {
			if (V.corp.Market === 1) {
				discount = 350;
			}
		} else if (V.market.slaveMarket === "neighbor") {
			if (V.market.numArcology >= V.arcologies.length) {
				V.market.numArcology = 1;
			}
			opinion = App.Neighbor.opinion(V.arcologies[0], V.arcologies[V.market.numArcology]);
			opinion = Math.clamp(Math.trunc(opinion / 20), -10, 10);
			discount -= (opinion * 25);
		} else if (App.Data.misc.schools.has(V.market.slaveMarket)) {
			if (V[V.market.slaveMarket].schoolUpgrade !== 0) {
				discount = 375;
			}
		}
		return discount;
	}
};


code:
App.Entity.GingeringParameters = class {
	/** Get gingering parameters for a particular slave and market.
	 * @param {App.Entity.SlaveState} slave
	 * @param {FC.Zeroable<FC.SlaveMarketName | FC.SpecialMarketName>} market
	 * @param {number} [arcIndex] - arcology index if market is "neighbor"
	 */
	constructor(slave, market, arcIndex) {
		/** @type {FC.Gingering} */
		this.type = 0;
		/** @type {FC.GingeringDetection} */
		this.detection = 0;
		/** @type {boolean} */
		this.detected = false;

		// figure out what type of gingering applies, if any
		if (applyLawCheck(market) && V.policies.SMR.honestySMR === 1) {
			/* SMR prohibits gingering and is enforced for this seller - do nothing */
		} else if (App.Data.misc.schools.has(market)) {
			/* slave schools have a reputation to maintain, and will never ginger their slaves */
		} else if (["wetware", "heap", "gangs and smugglers", "low tier criminals", "military prison", "juvenile detention", "white collar", "corporate"].includes(market)) {
			/* these sellers see no reason to ginger their slaves */
		} else if (market === "neighbor" && App.Neighbor.opinion(V.arcologies[0], V.arcologies[arcIndex]) >= 50) {
			/* socially-aligned neighbors will not try to cheat you */
		} else if (slave.indenture > 0) {
			/* indentured servants cannot be gingered */
		} else if (isShelterSlave(slave)) {
			/* is a Shelter slave - do nothing */
		} else {
			if (slave.trust < -20 && jsRandom(1, 3) === 1) {
				this.type = "antidepressant";
			} else if (slave.devotion < -20 && jsRandom(1, 3) === 1) {
				this.type = "depressant";
			} else if (slave.health.condition < 60 && jsRandom(1, 3) === 1) {
				this.type = "stimulant";
			} else if (slave.balls > 0 && slave.dick > 2 && jsRandom(1, 3) === 1) {
				this.type = "vasoconstrictor";
			} else if (slave.balls > 0 && slave.dick.isBetween(0, 5) && jsRandom(1, 3) === 1) {
				this.type = "vasodilator";
			} else if (slave.attrKnown === 0 && jsRandom(1, 3) === 1) {
				this.type = "aphrodisiac";
			} else if (slave.anus > 0 && slave.fetishKnown === 0 && jsRandom(1, 3) === 1) {
				this.type = "ginger";
			}
		}

		// figure out if the PC has detected gingering
		if (this.type !== 0) {
			if (V.PC.skill.slaving >= 100) {
				this.detected = true;
				this.detection = "slaver";
			} else if (V.PC.skill.warfare >= 100 && jsRandom(1, 2) === 1) {
				this.detected = true;
				this.detection = "mercenary";
			} else if (V.PC.rumor === "force" && jsRandom(1, 2) === 1) {
				this.detected = true;
				this.detection = "force";
			} else if (jsRandom(1, 3) === 1) {
				this.detected = true;
			}
		}
		console.log(`Gingering: ${this.type}; detected: ${this.detected} by ${this.detection}.`);
	}
};

/** Make the proxy handler for the gingered slave.
 * Do not call directly (use getGingeredSlave instead); must be global for serialization.
 * @param {App.Entity.GingeringParameters} gParams
 * @param {Map<string, string|number>} gKeys
 * @returns {ProxyHandler<App.Entity.SlaveState>}
 */
globalThis._makeGingeredSlaveHandler = function(gParams, gKeys) {
	return {
		get(target, key) {
			switch (key) {
				/* extra slave properties from proxy */
				case "gingering":
					return gParams;
				case "beforeGingering":
					return target;
				/* serialization methods for Sugarcube */
				case "clone": // WARNING: masks slave.clone.
					return () => new Proxy(clone(target), _makeGingeredSlaveHandler(clone(gParams), clone(gKeys)));
				case "toJSON":
					return () => JSON.reviveWrapper('new Proxy($ReviveData$.slave, _makeGingeredSlaveHandler($ReviveData$.params, $ReviveData$.keys))', {slave: target, params: gParams, keys: gKeys});
			}
			/* specific overrides for gingered properties */
			if (typeof key === "string" && gKeys.has(key)) {
				return gKeys.get(key);
			}
			return target[key];
		}
	};
};

/** Get a gingered proxy for a slave.
 * @param {App.Entity.SlaveState} slave
 * @param {FC.Zeroable<FC.SlaveMarketName | FC.SpecialMarketName>} market
 * @param {number} arcIndex - arcology number if market is "neighbor"
 * @returns {FC.GingeredSlave}
 * */
globalThis.getGingeredSlave = function(slave, market, arcIndex) {
	const gingering = new App.Entity.GingeringParameters(slave, market, arcIndex);

	// if the slave didn't actually get gingered, just give the original back
	if (gingering.type === 0) {
		return slave;
	}

	/** @type {Map<string, string|number>} */
	const gingeredKeys = new Map();
	/** Add a key to override on the gingered slave object
	 * @param {string} key the key to ginger
	 * @param {any} val the initial value to use
	 * @param {number} [min] minimum value to add to initial
	 * @param {number} [max] maximum value to add to initial
	 */
	const addGingerKey = (key, val, min, max) => {
		if ((typeof val === "number") && !_.isNil(min) && !_.isNil(max)) {
			val += jsRandom(min, max);
		}
		gingeredKeys.set(key, val);
	};
	switch (gingering.type) {
		case "antidepressant":
			addGingerKey("trust", slave.trust, 10, 40);
			break;
		case "depressant":
			addGingerKey("devotion", slave.devotion, 10, 40);
			break;
		case "stimulant": {
			const health = slave.health;
			health.condition += jsRandom(20, 40);
			health.health = computeAggregateHealth(slave, health.condition);
			addGingerKey("health", health);
			break;
		}
		case "vasoconstrictor":
			addGingerKey("dick", slave.dick, -2, -1);
			break;
		case "vasodilator":
			addGingerKey("dick", slave.dick, 1, 2);
			break;
		case "aphrodisiac":
			addGingerKey("aphrodisiacs", 2);
			addGingerKey("attrKnown", 1);
			addGingerKey("attrXX", 0, 60, 90);
			addGingerKey("attrXY", 0, 60, 90);
			addGingerKey("energy", 0, 50, 90);
			break;
		case "ginger":
			addGingerKey("fetish", "buttslut");
			addGingerKey("fetishKnown", 1);
			addGingerKey("fetishStrength", 65);
			break;
	}

	return new Proxy(slave, _makeGingeredSlaveHandler(gingering, gingeredKeys));
};


code:
/**
 *
 * @param {FC.SlaveMarketName} slaveMarket
 * @param {object} [param1]
 * @param {string} [param1.sTitleSingular]
 * @param {string} [param1.sTitlePlural]
 * @param {Array<{factor: number, reason:string}>} [param1.modifiers]
 */
App.Markets.purchaseFramework = function(slaveMarket, {sTitleSingular = "slave", sTitlePlural = "slaves", modifiers = []} = {}) {
	const el = new DocumentFragment();
	const {slave, text} = generateMarketSlave(slaveMarket, (V.market.numArcology || 1));
	const {He, him, his} = getPronouns(slave);
	let prisonCrime = "";
	if (slaveMarket === V.prisonCircuit[V.prisonCircuitIndex]) {
		prisonCrime = `${He} ${pronounsForSlaveProp(slave, text)}`;
	} else {
		$(el).append(` ${text}`);
	}

	const applyLaw = applyLawCheck(slaveMarket);
	const complianceResult = applyLaw ? App.Desc.lawCompliance(slave, slaveMarket) : ``;
	const limitReached = V.slavesSeen > V.slaveMarketLimit;
	if (limitReached) {
		modifiers.push({factor: (V.slavesSeen - V.slaveMarketLimit) * 0.1, reason: "market reach exceeded"});
	}
	const {cost, report} = slaveCost(slave, false, applyLaw, false, true, modifiers);

	App.Events.addParagraph(el, [
		`The offered price is`, App.UI.DOM.combineNodes(report, "."),
		limitReached ? `You have cast such a wide net for slaves this week that it is becoming more expensive to find more for sale. Your reputation helps determine your reach within the slave market.` : ``
	]);

	el.append(choices());
	return el;

	function interruptImageGeneration() {
		if (V.seeImages === 1 && !V.seeCustomImagesOnly && V.imageChoice === 6) {
			App.Art.GenAI.sdQueue.interrupt();
		}
	}

	function choices() {
		const el = document.createElement("p");
		let title = {};
		V.slavesSeen++;
		if (sTitleSingular === "prisoner") {
			title = {
				decline: `Inspect a different prisoner`,
				buyAndKeepShopping: `Buy ${him} and check out other available ${sTitlePlural}`,
				buyJustHer: `Enslave ${him}`,
				buyHerAndFinish: `Enslave ${him} and finish your inspection`,
				finish: `Finish your enslavement of prisoners`
			};
		} else {
			title = {
				decline: `Decline to purchase ${him} and check out another ${sTitleSingular}`,
				buyAndKeepShopping: `Buy ${him} and check out other ${sTitlePlural} to order`,
				buyJustHer: `Buy ${his} slave contract`,
				buyHerAndFinish: `Buy ${him} and finish your order of slaves`,
				finish: `Finish your order of slaves`
			};
		}

		App.UI.DOM.appendNewElement(
			"div",
			el,
			App.UI.DOM.link(
				title.decline,
				() => {
					interruptImageGeneration();
					jQuery("#slave-markets").empty().append(App.Markets[slaveMarket]);
				},
			)
		);
		if (V.cash >= cost) {
			App.UI.DOM.appendNewElement(
				"div",
				el,
				App.UI.DOM.link(
					title.buyAndKeepShopping,
					() => {
						interruptImageGeneration();
						cashX(forceNeg(cost), "slaveTransfer", slave);
						V.market.totalCost += cost;
						V.market.newSlaves.push(slave);
						V.market.introType = "multi";
						student();
						jQuery("#slave-markets").empty().append(App.Markets[slaveMarket]);
						V.nextLink = "Bulk Slave Intro";
						V.nextButton = "Continue";
						V.returnTo = "Main";
						App.Utils.updateUserButton();
					},
				)
			);
			if (V.market.newSlaves.length === 0) {
				App.UI.DOM.appendNewElement(
					"div",
					el,
					App.UI.DOM.link(
						title.buyJustHer,
						() => {
							interruptImageGeneration();
							cashX(forceNeg(cost), "slaveTransfer", slave);
							V.market.totalCost += cost;
							V.market.newSlaves.push(slave);
							V.nextButton = "Continue";
							V.returnTo = "Main";
							student();
							jQuery("#slave-markets").empty().append(
								App.Desc.longSlave(slave, {market: slaveMarket}),
								App.UI.newSlaveIntro(slave)
							);
						},
					)
				);
			} else {
				App.UI.DOM.appendNewElement(
					"div",
					el,
					App.UI.DOM.link(
						title.buyHerAndFinish,
						() => {
							student();
							cashX(forceNeg(cost), "slaveTransfer", slave);
							V.market.totalCost += cost;
							V.market.newSlaves.push(slave);
							V.returnTo = "Main";
						},
						[],
						"Bulk Slave Intro"
					)
				);
			}
		} else {
			App.UI.DOM.appendNewElement("span", el, `You lack the necessary funds to buy this slave.`, "note");
		}
		if (V.market.newSlaves.length > 0) {
			App.UI.DOM.appendNewElement(
				"div",
				el,
				App.UI.DOM.passageLink(
					title.finish,
					"Bulk Slave Intro",
				)
			);
		}

		el.append(App.Desc.longSlave(slave, {market: slaveMarket, marketText: prisonCrime || complianceResult}));
		return el;

		function student() {
			if (App.Data.misc.schools.has(slaveMarket)) {
				V[slaveMarket].schoolSale = 0;
				V[slaveMarket].studentsBought++;
			}
		}
	}
};

/** Construct the market global
 * @param {FC.SlaveMarketName | FC.SpecialMarketName} market
 */
App.Markets.Global = function(market) {
	/** @type {FC.SlaveMarketName | FC.SpecialMarketName} */
	this.slaveMarket = market;
	this.introType = "";
	/** @type {Array<FC.GingeredSlave>} */
	this.newSlaves = [];
	this.newSlaveIndex = 0;
	this.newSlavesDone = 0;
	this.numSlaves = 0;
	this.numArcology = 0;
	this.totalCost = 0;
	return this;
};

/**
 * User facing names for the markets
 * @param {FC.SlaveMarketName} market
 * @param {number} arcIndex
 */
App.Markets.marketName = function(market, arcIndex = 1) {
	if (App.Data.misc.schools.has(market)) {
		return App.Data.misc.schools.get(market).title;
	} else {
		switch (market) {
			case "corporate":
				return `your corporation`;
			case "neighbor":
				return `${V.arcologies[arcIndex].name}`;
			case "kidnappers":
				return `the Kidnappers' Market`;
			case "indentures":
				return `the Indentures Market`;
			case "hunters":
				return `the Runaway Hunters' Market`;
			case "raiders":
				return `the Raiders' Market`;
			case "underage raiders":
				return `the Raiders' Black Market`;
			case "heap":
				return `the Flesh Heap as alive as when you purchased them.`;
			case "wetware":
				return `the Wetware CPU market`;
			case "trainers":
				return `the Trainers' Market`;
			case "low tier criminals":
			case "gangs and smugglers":
			case "white collar":
			case "military prison":
			case "juvenile detention":
				return `the prisoner sale`;
			default:
				return `Someone messed up. ${market} is not known.`;
		}
	}
};


code:
App.Markets.JobFulfillmentCenterOrder = function() {
	const el = document.createElement("p");
	el.id = "job-fulfillment-center-order";
	el.append(JobFulfillmentCenterOrder());
	return el;

	function JobFulfillmentCenterOrder() {
		const el = new DocumentFragment();
		const r = [];
		const linkArray = [];
		let span = document.createElement("span");
		const {heA} = getPronouns(assistant.pronouns().main).appendSuffix('A');

		if (V.JFC.order === 0) {
			r.push(`You work up a new slave order for posting where slave merchants can work to fulfill it.`);
		} else {
			r.push(`You review your posted slave order for a <span class="bold">${V.JFC.role}.</span>`);
		}
		if (V.assistant.personality === 1) {
			r.push(`As you work, ${V.assistant.name} makes lewd comments about what ${heA} looks forward to doing to this new slave.`);
		}
		$(el).append(r.join(" "));

		if (V.JFC.order === 0) {
			const security = [
				{title: "Bodyguard"},
				{title: "Wardeness"},
			];
			const management = [
				{title: "Headgirl"},
				{title: "Teacher"},
				{title: "Nurse"},
				{title: "Attendant (normal)", value: "Attendant"},
				{title: "Attendant (motherly)", value: "Motherly Attendant"},
				{title: "Matron"},
				{title: "Stewardess"},
				{title: "Milkmaid"},
				{title: "Farmer"},
			];
			const entertainment = [
				{title: "DJ"},
				{title: "Madam"},
				{title: "Concubine"},

			];
			linkArray.push(
				App.UI.DOM.link(
					"Security",
					() => {
						jobSelection(security);
					}
				)
			);
			linkArray.push(
				App.UI.DOM.link(
					"Management",
					() => {
						jobSelection(management);
					}
				)
			);
			linkArray.push(
				App.UI.DOM.link(
					"Entertainment",
					() => {
						jobSelection(entertainment);
					}
				)
			);
			App.UI.DOM.appendNewElement("div", el, App.UI.DOM.generateLinksStrip(linkArray));
			span.id = "job-type";
			el.append(span);
		} else {
			App.UI.DOM.appendNewElement("div", el,
				App.UI.DOM.link(
					"Withdraw slave order",
					() => {
						V.JFC = {order: 0, reorder: 0};
						refresh();
					}
				)
			);
		}
		return el;

		function jobSelection(choices) {
			const el = new DocumentFragment();
			for (const choice of choices) {
				App.UI.DOM.appendNewElement("div", el,
					App.UI.DOM.link(
						choice.title,
						() => {
							V.JFC.order = 1;
							V.JFC.role = choice.value || choice.title;
							refresh();
						}
					)
				);
			}
			return jQuery("#job-type").empty().append(el);
		}
	}
	function refresh() {
		jQuery("#job-fulfillment-center-order").empty().append(JobFulfillmentCenterOrder());
	}
};


code:
App.Markets.corporate = function() {
	const el = new DocumentFragment();
	const r = new SpacedTextAccumulator(el);
	let dickVision;
	if (V.corp.SpecGender === 2) {
		dickVision = 100;
	} else if (V.corp.SpecGender === 1) {
		dickVision = 0;
	} else {
		dickVision = V.seeDicks;
	}
	const {
		HeU, heU, HisU, hisU, himU, himselfU, womanU, girlU, womenU
	} = getNonlocalPronouns(dickVision).appendSuffix('U');

	r.push(`You're in your corporation's flagship slave market.`);

	r.push(`The corporation pays you only nominal rent for this space, but will offer you a discount on slaves here based on your shares in it. Additionally, having the corporation's premiere products sold here in ${V.arcologies[0].name} will associate them with you, allowing your reputation to improve the corporate brand. A pretty`);
	if (V.corp.SpecDick === 1 && V.corp.SpecPussy === 1) {
		r.push(`futanari`);
	} else if (V.corp.SpecGender === 2) {
		if (V.corp.SpecBalls === -1) {
			r.push(`clipped`);
		} else {
			r.push(`shemale`);
		}
	}
	r.push(`sales${V.corp.SpecAge === 3 ? womanU : girlU}`);
	r.push(`in a slutty suit greets you properly. ${HeU}'s a corporate slave chosen for loyalty and intelligence, and to physically represent the brand.`);
	if (V.corp.SpecHeight === 5) {
		r.push(`Even before ${heU} engaged with you ${hisU} towering height made ${himU} stand out and caught your eye; from up close ${heU}'s even more impressive.`);
	} else if (V.corp.SpecHeight === 1) {
		r.push(`${HeU}'s so short you have to physically look down on ${himU}.`);
	}
	if (V.corp.SpecInjection === 4) {
		r.push(`${HeU} has awe-inspiring breasts which ${hisU} suit jacket helps support; even so, their bottoms reach past ${hisU} navel.`);
	} else if (V.corp.SpecInjection === 5) {
		r.push(`${HeU} has gigantic, milk-swollen breasts almost spilling out of ${hisU} suit.`);
	} else if (V.corp.SpecImplants === 2) {
		r.push(`${HeU} has enormous fake tits that ride inhumanly high on ${hisU} chest.`);
	} else if (V.corp.SpecInjection === 3) {
		r.push(`${HeU} has huge tits and a nice broad butt which ${hisU} tight skirt hugs closely.`);
	} else if (V.corp.SpecImplants === 1) {
		r.push(`${HeU} has nice fake bimbo tits and a silicone bubble butt.`);
	} else if (V.corp.SpecInjection === 2) {
		r.push(`${HeU}'s nice and curvy, with nice breasts and a lovely ass.`);
	} else if (V.corp.SpecInjection === 1) {
		r.push(`${HeU} has cute little breasts and a petite rump.`);
	}
	if (V.corp.SpecCosmetics === 1) {
		r.push(`${HisU} face is flawless.`);
	}
	if (V.corp.SpecRaces.length === 1 || V.corp.SpecNationality) {
		let corpSpecRace;
		if (V.corp.SpecNationality) {
			corpSpecRace = V.corp.SpecNationality;
		} else if (V.corp.SpecRaces.length > 0) {
			corpSpecRace = jsEither(V.corp.SpecRaces);
		}
		r.push(`${HeU}'s ${corpSpecRace}, of course${(V.corp.SpecAccent === 1) ? `, and has a cute accent` : ``}.`);
	}
	if (V.corp.SpecHormones === 1) {
		r.push(`${HisU} voice is soft and feminine, and ${heU} bats ${hisU} eyes at you.`);
	} else if (V.corp.SpecHormones === 2) {
		r.push(`${HeU} has a deep voice, and speaks assertively.`);
	}
	if (V.corp.SpecMuscle === 4) {
		r.push(`${HisU} hot body is perfectly toned under ${hisU} suit.`);
	} else if (V.corp.SpecMuscle === 5) {
		r.push(`${HisU} suit is specially tailored to flatter ${hisU} visible musculature.`);
	}
	r.push(`Of course, you know all about the corporation, but sales${V.corp.SpecAge === 3 ? womenU : `${girlU}s`} here have standing orders to treat you like any other customer, so you can review the market properly. The slave turns to`);
	if (V.corp.SpecAmputee !== 1) {
		r.push(`a multimedia presentation on`);
	} else {
		r.push(`a rack of`);
	}
	r.push(`your corporation's slaves.`);
	r.toParagraph();

	r.push(`${HeU} explains that the corporation captures many people, so it only retains and trains those that fit its product lines.`);
	const modifiers = [];
	if (V.corp.Market === 1) {
		r.push(`Your own local franchise of your corporation allows you to enjoy a discount.`);
		modifiers.push({factor: -0.1, reason: "local corporate franchise"});
		r.push(App.UI.DOM.link(
			"End corporate slave sales here and return this sector to standard markets.",
			() => {
				V.corp.Market = 0;
				App.Arcology.cellUpgrade(V.building, App.Arcology.Cell.Market, "Corporate Market", "Markets");
			},
			[],
			"Main"
		));
	}
	r.toParagraph();

	el.append(App.Markets.purchaseFramework("corporate", {modifiers}));

	r.push(`${HisU} presentation done, the`);
	if (V.corp.SpecDick === 1 && V.corp.SpecPussy === 1) {
		r.push(`futanari`);
	} else if (V.corp.SpecGender === 2) {
		if (V.corp.SpecBalls === -1) {
			r.push(`clipped`);
		} else {
			r.push(`shemale`);
		}
	}
	r.push(`sales${V.corp.SpecAge === 3 ? womanU : girlU} directs you to the inventory interface that will allow you to peruse the corporation's slaves, and moves over to stand next to it, ready to answer any questions.`);
	if (V.PC.dick !== 0) {
		r.push(`${HeU} positions ${himselfU} facing away from you and hikes up ${hisU} skirt to bare ${hisU}`);
		if (V.corp.SpecInjection > 4) {
			r.push(`gigantic ass`);
		} else if (V.corp.SpecImplants > 0) {
			r.push(`fake ass`);
		} else if (V.corp.SpecInjection === 3) {
			r.push(`big butt`);
		} else if (V.corp.SpecInjection === 2) {
			r.push(`cute butt`);
		} else {
			r.push(`little behind`);
		}
		r.push(`and cocks ${hisU} hips${(V.corp.SpecBalls === -1 && (V.arcologies[0].FSGenderFundamentalist === 0 || V.seeDicks > 0)) ? `, keeping ${hisU} soft cock demurely out of sight` : ``}, if you feel like`);
		if (V.corp.SpecGender === 2 && V.corp.SpecPussy !== 1) {
			r.push(`sodomizing`);
		} else {
			r.push(`fucking`);
		}
		r.push(himU);
	} else {
		r.push(`${HeU} gets down on ${hisU} knees right there, making ${hisU} mouth available if you feel like riding ${hisU} face`);
	}
	r.push(`while you browse.`);
	r.toParagraph();

	const result = App.UI.DOM.appendNewElement("span", el);
	result.append(
		App.UI.DOM.link(
			`Use ${himU}`,
			() => {
				const r = [];
				if (V.PC.dick !== 0) {
					r.push(HisU);
					if (V.corp.SpecGender === 2 && V.corp.SpecPussy !== 1) {
						r.push(`asshole is thoughtfully pre-lubed, and your cock slides easily up it`);
					} else {
						r.push(`pussy is nice and wet, and your cock slides inside ${himU} easily`);
					}
					r.push(`as you consider the slaves on offer. ${HeU}'s well trained, and uses ${hisU}`);
					if (V.corp.SpecGender === 2 && V.corp.SpecPussy !== 1) {
						r.push(`sphincter`);
					} else {
						r.push(`womanly muscles`);
					}
					r.push(`to please your member expertly as ${heU} ruts ${himselfU} gently back against you, doing almost all the work. When you orgasm, ${heU} retains the load carefully, keeping your cum inside ${himU} and away from ${hisU} nice clothes.`);
				} else {
					r.push(`${HeU}'s very well trained, and does all the work. Once you move in and straddle ${himU}, ${heU} gets right down to it, or in this case, up to it. ${HisU} clever tongue runs gently along your labia to build anticipation before starting a delightful dance around and then atop your clitoris. You orgasm in no time at all, a climax which ${heU} carefully prolongs by kissing you full on the pussy in just the right way, only breaking the oral embrace when you've ridden it as far as you'll go.`);
				}
				jQuery(result).empty().append(r.join(" "));
			}
		)
	);

	return el;
};


code:
App.Markets["low tier criminals"] = function() {
	const el = new DocumentFragment();
	let r = [];
	// mixed prisoners
	r.push(`You board the transport to a small prison on the edge of the city spanning from the foot of your arcology with the appearance of an old world police station. You arrive inside and are met by a cute receptionist, asking why one such as yourself would visit such a place. With formalities out of the way, the head of the station is called and arrives shortly. "Well now, didn't expect to see you here. Came to pick up a criminal and give them a chance at not being a menial?" You are then led further into the station and into the prisoners' quarters; most of them appear downcast and dismal, but some look up to see what the commotion is about. "You! Come here! This fine`);
	r.push(`${V.PC.title === 1 ? 'gentleman' : 'lady'}`);
	r.push(`wants to take a look, so be on your best behavior!" the station's head calls out, bringing one of the criminals close enough to be inspected.`);
	el.append(r.join(" "));
	el.append(App.Markets.purchaseFramework("low tier criminals", {sTitleSingular: "prisoner", sTitlePlural: "prisoners"}));
	return el;
};

App.Markets["gangs and smugglers"] = function() {
	const el = new DocumentFragment();
	let r = [];
	/* males only, or females only if ${V.seeDicks} === 0 */
	App.UI.DOM.appendNewElement("p", el, `You board the transport to a large gray prison at the edge of the Free City as word of your approach is sent ahead. The visage of the prison itself is a grim one, enclosed by a large gray concrete fence with electrified barbed wire coiled at the top. As the vehicle rolls to a stop before a large gate, the guard in control of the gate operates a board of buttons and levers, promptly opening it so it may continue. "Stay on the road," he respectfully advises, a final remark before letting your driver carry on. Beyond the fence is an array of guard towers manned with drones for 24/7 surveillance, giving you a clear idea of what was to happen if you were not to heed the guard's words and decided to wander off from the group.`);
	App.UI.DOM.appendNewElement("p", el, `Your destination at the end of the road is a garage, where upon your disembarking, no less than 4 heavily armored guards with electric batons at their sides come to greet you. You could almost mistake them for unmanned drones had they not started speaking. "We've been expecting you; please follow us. We house some dangerous individuals in our establishment and we need to keep you safe." You accept their advice and enter the premises.`);

	r.push(`You are met with a dimly lit concrete hallway as you enter, only for it to open up to a multi-floor prison that had placed its prisoners underground for the most`);
	if ((V.PC.title === 0 || V.PC.boobs >= 1000 || V.PC.butt >= 3) && V.seeDicks > 0) {
		r.push(`part, where you are met with a hailstorm of whistles, cheers and other catcalls.`);
	} else {
		r.push(`part.`);
	}
	r.push(`You come to a bridge and look down to see its inhabitants, hard criminals that are coming together for a meal. At one point, you watch a fight break out; an armored guard quickly steps in and applies his baton to the offenders, coupled with a loud zap that silences the entire area. Around your level, you see the better stock: the attractive prisoners one would show to an interested customer.`);
	App.UI.DOM.appendNewElement("p", el, r.join(" "));
	r = [];
	r.push(`You finally reach the warden's office. "Welcome,`);
	r.push(`${V.PC.title === 1 ? 'Mister' : 'Miss'}`);
	r.push(`${V.PC.slaveSurname}. I never thought my prison could have turned to a trade such as this, but times are hard." He beckons you to the seat opposite him. "But I'm not one to complain when opportunities come my way." He motions to his guards to bring in a select stock for your perusal. "We house some tough nuts in this establishment, but if you believe you can capitalize on these criminals, we'll be happy to have you take them off our hands." The guard returns with a convict ready to be sold.`);

	el.append(r.join(" "));
	el.append(App.Markets.purchaseFramework("gangs and smugglers", {sTitleSingular: "prisoner", sTitlePlural: "prisoners"}));
	return el;
};

App.Markets["white collar"] = function() {
	const el = new DocumentFragment();
	// mixed prisoners
	App.UI.DOM.appendNewElement("p", el, `You board the transport to what looks more like an oversized vacation home rather than something one goes to for committing crimes. You enter the premises into a small chamber with a booth to the side and are immediately met with a cheerful greeting. "Ah! We've been waiting for you. Please go in. We'll call someone to guide you immediately."`);
	App.UI.DOM.appendNewElement("p", el, `You nod at the prompt and enter further into the luxurious building to find a formally dressed guide and what appears to be the holding area of the prison. The cells, if you can call them that, are large and cozy, each with their own computer and television. The air is also conditioned for their comfort and old world classical music is playing to provide further ambiance. Walking through, you see the prisoners themselves, lazing about in their beds, sitting at their computers, or just talking to each other. If there was ever a prison you'd rather end up in, this would be it.`);
	App.UI.DOM.appendNewElement("p", el, `You enter the warden's office and are greeted with all due formalities. "Glad to see you at our establishment. When I heard that someone as renowned as yourself was coming over, I made the appropriate preparations." You are served a cup of tea as she brings in the chosen stock for you to observe at your leisure.`);

	el.append(App.Markets.purchaseFramework("white collar", {sTitleSingular: "prisoner", sTitlePlural: "prisoners"}));
	return el;
};

App.Markets["military prison"] = function() {
	const el = new DocumentFragment();
	let r = [];
	r.push(`You board the transport to a holding facility for prisoners of war located on the outskirts of the Free City, which is reasonable due to the dangers inherent to the criminals imprisoned in such an establishment. The driver radios ahead that you and several other interested parties will be arriving shortly. Once you disembark at the edge of the grounds, you are recognized by the stationed guard and ushered into a heavily armored vehicle.`);
	if (V.PC.career === "mercenary" || V.PC.career === "recruit") {
		r.push(`The interior brings back memories and you completely`);
	} else {
		r.push(`Although the interior makes you feel like a prisoner, you`);
	}
	r.push(`understand the reasoning behind such a design choice.`);
	App.UI.DOM.appendNewElement("p", el, r.join(" "));
	App.UI.DOM.appendNewElement("p", el, `Once you arrive at the prison, the door opens to a garage and another door, brightly lit to welcome you to the premises. Through the door, you find the prisoner's quarters, each of them enclosed by electrified fences to ensure your safety as you see the stock. A guard enters, asking you to follow him as he leads you across the chamber to meet with the warden.`);
	App.UI.DOM.appendNewElement("p", el, `Entering the warden's quarters, you take a seat across from him as he takes the initiative. "Ah, fancy meeting one such as yourself here. Though we house some of the hardiest criminals you can find, I'm sure someone of your capabilities could find great use for them." He smirks. "... or perhaps great entertainment through the pits," he says as he beckons to his staff as they bring in the selected stock.`);

	el.append(App.Markets.purchaseFramework("military prison", {sTitleSingular: "prisoner", sTitlePlural: "prisoners"}));
	return el;
};

App.Markets["juvenile detention"] = function() {
	const r = new SpacedTextAccumulator();
	// mixed prisoners
	r.push(`You board the transport to a small brick-clad juvenile detention facility on the outskirts of the Free City. You might have confused the building for a high school, if not for the double fence topped with razor wire. Although the owners of this facility once viewed it as a rehabilitation center, a lack of funding and serious overcrowding have made them open to seeing slavery as a path to that goal.`);
	if (V.PC.career === "hoodlum" || V.PC.career === "street urchin" || V.PC.career === "child prostitute") {
		r.push(`You brace yourself inwardly; some of your friends from the streets ended up in places like this, and you easily could have too, if your fortunes hadn't turned.`);
	}
	r.toParagraph();
	r.push(`You walk into the lobby of the facility and are greeted by a stern-looking heavyset woman in a warden's uniform. "Always happy to have you visit, `);
	r.push(`${V.PC.title === 1 ? 'Mister' : 'Miss'}`);
	r.push(`${V.PC.slaveSurname}. Your patronage is appreciated. I've arranged for a few of our detainees that need some extra discipline to be present in the courtyard, right this way."`);
	r.toParagraph();
	r.push(`Entering the courtyard, you see lines painted on the asphalt for athletic activity, faded through time and wear. A pair of tired basketball hoops and patched soccer goals frame the scene, while a cluster of teens${V.minimumSlaveAge < 13 ? " and preteens" : ""} mills around in one corner. A short blast from a guard's whistle brings them to attention and the warden begins calling them forward, one at a time, and ordering them to strip for inspection.`);
	r.toParagraph();

	const el = r.container();
	el.append(App.Markets.purchaseFramework("juvenile detention", {sTitleSingular: "detainee", sTitlePlural: "detainees"}));
	return el;
};


code:
App.Markets["Custom Slave"] = function() {
	const el = document.createElement("p");
	const slave = V.customSlave;
	el.append(intro());
	el.append(age());
	el.append(health());
	el.append(muscles());
	el.append(lips());
	el.append(voice());
	el.append(height());
	el.append(weight());
	el.append(face());
	el.append(race());
	el.append(skin());
	el.append(boobs());
	el.append(butt());
	el.append(sex());
	el.append(virgin());
	el.append(dick());
	el.append(balls());
	el.append(clit());
	el.append(labia());
	el.append(vaginaLube());
	el.append(analVirgin());
	el.append(skills());
	el.append(skillWhore());
	el.append(skillCombat());
	el.append(intelligence());
	el.append(intelligenceImplant());
	el.append(eyes());
	el.append(hears());
	el.append(smells());
	el.append(tastes());
	el.append(limbs());
	el.append(nationality());

	// Links to reset things
	el.append(reset());
	el.append(orderControls());

	return el;

	function intro() {
		const {heA} = getPronouns(assistant.pronouns().main).appendSuffix('A');
		let r = [];
		if (V.customSlaveOrdered === 0) {
			r.push(`You work up a new slave order for posting where slave merchants can work to fulfill it.`);
		} else {
			r.push(`You review your posted slave order.`);
		}
		if (V.assistant.personality === 1) {
			r.push(`As you work, ${V.assistant.name} makes lewd comments about what ${heA} looks forward to doing to this new slave.`);
		}
		r.push(`Your order requests a slave with the following characteristics:`);
		return App.UI.DOM.makeElement("p", r.join(" "));
	}

	function age() {
		const el = document.createElement("div");
		const ages = [2, 4, 6, 9, 12, 14, 17, 19, 24, 29, 39, 49, 59, 69, 79, 89, 99, 109, 119, 129, 130];

		createDescription(el, description, "age");

		// Choices
		const options = [];
		for (let i = 0; i < ages.length; i++) {
			const high = ages[i];
			const low = (ages[i - 1] + 1) || (ages[i] - 1); // First element of array has nothing before it, obviously, so display low as one less than high.
			if (low < V.minimumSlaveAge) {
				continue;
			} else if (high > V.retirementAge) {
				options.push({key: low.toString(), name: `${low}+`});
				break;
			}

			options.push({key: high.toString(), name: `${low}-${high}`});
		}
		el.append(App.UI.DOM.makeSelect(options, slave.age.toString(), a => {
			slave.age = Number(a);
			jQuery("#age-text").empty().append(description());
		}));
		return el;

		function description() {
			// Age is displayed as a range, but recorded as the higher of the two numbers in the range
			const highIndex = ages.indexOf(slave.age);

			if (highIndex === 0) {
				return `${(ages[highIndex] - 1)}-${ages[highIndex]} years old. `;
			} else if (highIndex === ages.length) {
				// Highest possible number
				return `${ages[highIndex]}+ years old. `;
			} else {
				// Lower age should be the previous number in the array, +1
				return `${(ages[highIndex - 1]) + 1}-${ages[highIndex]} years old. `;
			}
		}
	}

	function health() {
		const el = document.createElement("div");
		const slaveProperty = "health";
		const choices = new Map([
			[1, "Extremely healthy"],
			[0, "Healthy"],
		]);
		createDescription(el, description, slaveProperty);
		el.append(choicesMaker(slaveProperty, choices, description));

		function description() {
			for (const [value, text] of choices) {
				if (slave.health === value) {
					return `${text}. `;
				}
			}
		}

		return el;
	}

	function muscles() {
		const el = document.createElement("div");
		const slaveProperty = "muscles";
		const choices = new Map([
			[96, "Ripped"],
			[65, "Muscular"],
			[45, "Well built"],
			[20, "Toned"],
			[0, "Normal"],
			[-21, "Weak"],
			[-51, "Very weak"],
			[-97, "Frail"],
		]);

		createDescription(el, description, slaveProperty);
		el.append(choicesMaker(slaveProperty, choices, description));

		function description() {
			for (const [value, text] of choices) {
				if (slave.muscles >= value) {
					return `${text}. `;
				}
			}
		}

		return el;
	}

	function lips() {
		const el = document.createElement("div");
		const slaveProperty = "lips";
		const choices = new Map([
			[100, "Facepussy"],
			[85, "Enormous"],
			[65, "Big"],
			[35, "Plush"],
			[15, "Normal"],
			[5, "Thin"],
		]);

		createDescription(el, description, slaveProperty);
		el.append(choicesMaker(slaveProperty, choices, description));

		function description() {
			for (const [value, text] of choices) {
				if (slave.lips >= value) {
					if (value < 100) {
						return `Lips are ${text.toLowerCase()}. `;
					} else {
						return `Lips are a ${text.toLowerCase()}. `;
					}
				}
			}
		}

		return el;
	}

	function voice() {
		const el = document.createElement("div");
		const slaveProperty = "voice";
		const choices = new Map([
			[3, "High, girly"],
			[2, "Feminine"],
			[1, "Deep"],
			[0, "Mute"],
			[-1, "Voice is unimportant"],
		]);

		createDescription(el, description, slaveProperty);
		el.append(choicesMaker(slaveProperty, choices, description));

		function description() {
			for (const [value, text] of choices) {
				if (slave.voice === value) {
					if (slave.voice === -1) {
						return `${text}. `;
					} else {
						return `${text} voice. `;
					}
				}
			}
			return `Voice is unimportant. `;
		}

		return el;
	}

	function height() {
		const el = document.createElement("div");
		const slaveProperty = "heightMod";
		const choices = new Map([
			["greatly below average", "Petite"],
			["below average", "Short"],
			["normal", "Average"],
			["above average", "Tall"],
			["greatly above average", "Very tall"],
		]);

		createDescription(el, description, slaveProperty);
		el.append(choicesMaker(slaveProperty, choices, description));

		function description() {
			for (const [value, text] of choices) {
				if (slave.heightMod === value) {
					return `${text} height. `;
				}
			}
		}

		return el;
	}

	function weight() {
		const el = document.createElement("div");
		const slaveProperty = "weight";
		const choices = new Map([
			[200, "Immobile"],
			[150, "Very Fat"],
			[100, "Fat"],
			[50, "Chubby"],
			[15, "Plush"],
			[0, "Average"],
			[-15, "Thin"],
			[-50, "Very thin"],
		]);

		createDescription(el, description, slaveProperty);
		el.append(choicesMaker(slaveProperty, choices, description));

		function description() {
			for (const [value, text] of choices) {
				if (slave.weight >= value) {
					return `${text} weight. `;
				}
			}
		}

		return el;
	}

	function face() {
		const el = document.createElement("div");
		const slaveProperty = "face";
		const choices = new Map([
			[55, "Very attractive"],
			[15, "Attractive"],
			[0, "Average"],
			[-15, "Unattractive"],
			[-55, "Very unattractive"],
		]);

		createDescription(el, description, slaveProperty);
		el.append(choicesMaker(slaveProperty, choices, description));

		function description() {
			for (const [value, text] of choices) {
				if (slave.face >= value) {
					return `${text} face. `;
				}
			}
		}

		return el;
	}

	function race() {
		const el = document.createElement("div");
		const slaveProperty = "race";
		const choices = [{key: "ethnicity is unimportant", name: "Ethnicity is unimportant"}];
		for (const [race, capRace] of App.Data.misc.filterRacesPublic) {
			choices.push({key: race, name: capRace});
		}

		createDescription(el, description, slaveProperty);

		// Choices
		el.append(App.UI.DOM.makeSelect(choices, slave.race, r => {
			slave.race = r;
			jQuery("#race-text").empty().append(description());
		}));

		function description() {
			const el = new DocumentFragment();
			el.append("Ethnicity: ");
			el.append(
				App.UI.DOM.makeTextBox(
					slave.race,
					(v) => {
						slave.race = v;
						jQuery("#race-text").empty().append(description());
					}
				)
			);
			return el;
		}

		return el;
	}

	function skin() {
		const el = document.createElement("div");
		const slaveProperty = "skin";
		const choices = [{key: "left natural", name: "Left natural"}];
		for (const skin of App.Medicine.Modification.naturalSkins) {
			choices.push({key: skin, name: capFirstChar(skin)});
		}

		createDescription(el, description, slaveProperty);

		// Choices
		el.append(App.UI.DOM.makeSelect(choices, slave.skin, s => {
			slave.skin = s;
			jQuery("#skin-text").empty().append(description());
		}));

		function description() {
			const el = new DocumentFragment();
			el.append("Skin tone: ");
			el.append(
				App.UI.DOM.makeTextBox(
					slave.skin,
					(v) => {
						slave.skin = v;
						jQuery("#skin-text").empty().append(description());
					}
				)
			);
			return el;
		}

		return el;
	}

	function boobs() {
		const el = document.createElement("div");
		const slaveProperty = "boobs";
		const choices = new Map([
			[6000, "Massive"],
			[2100, "Giant"],
			[1400, "Huge"],
			[800, "Big"],
			[500, "Healthy"],
			[200, "Flat"],
		]);

		createDescription(el, description, slaveProperty);
		el.append(choicesMaker(slaveProperty, choices, description));

		function description() {
			for (const [value, text] of choices) {
				if (slave.boobs >= value) {
					if (slave.boobs <= 200) {
						return `${text} chest. `;
					} else {
						return `${text} breasts. `;
					}
				}
			}
		}

		return el;
	}

	function butt() {
		const el = document.createElement("div");
		const slaveProperty = "butt";
		const choices = new Map([
			[8, "Massive"],
			[5, "Huge"],
			[3, "Healthy"],
			[1, "Flat"],
		]);

		createDescription(el, description, slaveProperty);
		el.append(choicesMaker(slaveProperty, choices, description));

		function description() {
			for (const [value, text] of choices) {
				if (slave.butt >= value) {
					return `${text} buttocks. `;
				}
			}
		}

		return el;
	}

	function sex() {
		const el = document.createElement("div");
		const slaveProperty = "sex";
		const choices = new Map([
			[3, "Both"],
			[2, "Male"],
			[1, "Female"],
		]);

		createDescription(el, description, slaveProperty);
		el.append(choicesMaker(slaveProperty, choices, description));

		function description() {
			switch (slave.sex) {
				case 3:
					return `Futanari (clit options not applied). `;
				case 2:
					return `Male (pussy options not applied). `;
				case 1:
					return `Female (cock & balls options not applied). `;
			}
		}

		return el;
	}

	function virgin() {
		const el = document.createElement("div");
		const slaveProperty = "virgin";
		const choices = new Map([
			[1, "Not Important"],
			[0, "Vaginal virgin"],
		]);

		createDescription(el, description, slaveProperty);
		el.append(choicesMaker(slaveProperty, choices, description));

		function description() {
			for (const [value, text] of choices) {
				if (slave.virgin === value) {
					return `${text}. `;
				}
			}
			return `Virginity not important. `;
		}

		return el;
	}

	function dick() {
		const el = document.createElement("div");
		const slaveProperty = "dick";
		const choices = new Map([
			[4, "Large penis"],
			[2, "Small penis"],
			[0, "No penis"],
		]);

		createDescription(el, description, slaveProperty);
		el.append(choicesMaker(slaveProperty, choices, description));

		function description() {
			for (const [value, text] of choices) {
				if (slave.dick >= value) {
					return `${text}. `;
				}
			}
		}

		return el;
	}

	function balls() {
		const el = document.createElement("div");
		const slaveProperty = "balls";
		const choices = new Map([
			[6, "Huge testicles"],
			[4, "Large testicles"],
			[2, "Small testicles"],
			[0, "No testicles"],
		]);

		createDescription(el, description, slaveProperty);
		el.append(choicesMaker(slaveProperty, choices, description));

		function description() {
			for (const [value, text] of choices) {
				if (slave.balls >= value) {
					return `${text}. `;
				}
			}
		}

		return el;
	}

	function clit() {
		const el = document.createElement("div");
		const slaveProperty = "clit";
		const choices = new Map([
			[5, "Clit dick"],
			[3, "Enormous clitoris"],
			[1, "Big clitoris"],
			[0, "Normal clitoris"],
		]);

		createDescription(el, description, slaveProperty);
		el.append(choicesMaker(slaveProperty, choices, description));

		function description() {
			for (const [value, text] of choices) {
				if (slave.clit === 5) {
					return `Pseudophallus. `;
				} else {
					if (slave.clit >= value) {
						return `${text}. `;
					}
				}
			}
		}

		return el;
	}

	function labia() {
		const el = document.createElement("div");
		const slaveProperty = "labia";
		const choices = new Map([
			[3, "Enormous labia"],
			[2, "Huge labia"],
			[1, "Big labia"],
			[0, "Normal labia"],
		]);

		createDescription(el, description, slaveProperty);
		el.append(choicesMaker(slaveProperty, choices, description));

		function description() {
			for (const [value, text] of choices) {
				if (slave.labia === value) {
					return `${text}. `;
				}
			}
		}

		return el;
	}

	function vaginaLube() {
		const el = document.createElement("div");
		const slaveProperty = "vaginaLube";
		const choices = new Map([
			[2, "Sopping wet vagina"],
			[1, "Wet vagina"],
			[0, "Dry vagina"],
		]);

		createDescription(el, description, slaveProperty);
		el.append(choicesMaker(slaveProperty, choices, description));

		function description() {
			for (const [value, text] of choices) {
				if (slave.vaginaLube === value) {
					return `${text}. `;
				}
			}
		}

		return el;
	}

	function analVirgin() {
		const el = document.createElement("div");
		const slaveProperty = "analVirgin";
		const choices = new Map([
			[1, "Anal virginity is not important"],
			[0, "Anal virgin"],
		]);

		createDescription(el, description, slaveProperty);
		el.append(choicesMaker(slaveProperty, choices, description));

		function description() {
			for (const [value, text] of choices) {
				if (slave.analVirgin === value) {
					return `${text}. `;
				}
			}
		}

		return el;
	}

	function skills() {
		const el = document.createElement("div");
		const slaveProperty = "skills";
		const choices = new Map([
			[65, "Expert"],
			[35, "Skilled"],
			[0, "Unskilled"],
		]);

		createDescription(el, description, slaveProperty);
		el.append(choicesMaker(slaveProperty, choices, description));

		function description() {
			if (slave.skills < 35) {
				return `Sexually unskilled. `;
			} else if (slave.skills < 65) {
				return `Sexually skilled. `;
			} else {
				return `Sexually expert. `;
			}
		}

		return el;
	}

	function skillWhore() {
		const el = document.createElement("div");
		const slaveProperty = "skill.whore";
		const choices = new Map([
			[35, "Expert"],
			[15, "Skilled"],
			[0, "Unskilled"],
		]);

		createDescription(el, description, slaveProperty);
		el.append(choicesMaker(slaveProperty, choices, description));

		function description() {
			if (slave.skill.whore <= 10) {
				return `Unskilled at prostitution and entertainment. `;
			} else if (slave.skill.whore <= 15) {
				return `Basic skills at prostitution and entertainment. `;
			} else {
				return `Skilled at prostitution and entertainment. `;
			}
		}

		return el;
	}

	function skillCombat() {
		const el = document.createElement("div");
		const slaveProperty = "skill.combat";
		const choices = new Map([
			[35, "Expert"],
			[15, "Skilled"],
			[0, "Unskilled"],
		]);

		createDescription(el, description, slaveProperty);
		el.append(choicesMaker(slaveProperty, choices, description));

		function description() {
			if (slave.skill.combat <= 10) {
				return `Unskilled at fighting. `;
			} else if (slave.skill.combat <= 30) {
				return `Basic skills at fighting. `;
			} else {
				return `Skilled at fighting. `;
			}
		}

		return el;
	}

	function intelligence() {
		const el = document.createElement("div");
		const slaveProperty = "intelligence";
		const choices = new Map([
			[-3, "Moronic"],
			[-2, "Very stupid"],
			[-1, "Stupid"],
			[0, "Average intelligence"],
			[1, "Smart"],
			[2, "Very smart"],
			[3, "Brilliant"],
		]);

		createDescription(el, description, slaveProperty);
		el.append(choicesMaker(slaveProperty, choices, description));

		function description() {
			for (const [value, text] of choices) {
				if (slave.intelligence === value) {
					return `${text}. `;
				}
			}
		}

		return el;
	}

	function intelligenceImplant() {
		const el = document.createElement("div");
		const slaveProperty = "intelligenceImplant";
		const choices = new Map([
			[30, "Well educated"],
			[15, "Educated"],
			[0, "Uneducated"],
		]);

		createDescription(el, description, slaveProperty);
		el.append(choicesMaker(slaveProperty, choices, description));

		function description() {
			for (const [value, text] of choices) {
				if (slave.intelligenceImplant >= value) {
					return `${text}. `;
				}
			}
		}

		return el;
	}

	function eyes() {
		const el = document.createElement("div");

		// Desc setup
		const descText = document.createElement("span");
		descText.id = (`eye-text`);
		descText.append(description());
		el.append(descText);

		const linkArray = [];
		if (V.seeExtreme) {
			linkArray.push(
				App.UI.DOM.link(
					"Blind",
					() => {
						eyeSurgery(slave, "both", "blind");
						jQuery(`#${descText.id}`).empty().append(description());
					}
				)
			);
		}
		linkArray.push(
			App.UI.DOM.link(
				"Nearsighted",
				() => {
					eyeSurgery(slave, "both", "blur");
					jQuery(`#${descText.id}`).empty().append(description());
				}
			)
		);
		linkArray.push(
			App.UI.DOM.link(
				"Normal Vision",
				() => {
					eyeSurgery(slave, "both", "fix");
					jQuery(`#${descText.id}`).empty().append(description());
				}
			)
		);
		el.append(App.UI.DOM.generateLinksStrip(linkArray));

		function description() {
			const vision = getBestVision(slave);
			switch (vision) {
				case 2:
					return "Normal Vision. ";
				case 1:
					return "Nearsighted. ";
				case 0:
					return "Blind. ";
				default:
					return `Error: ${vision}. `;
			}
		}

		return el;
	}

	function hears() {
		const el = document.createElement("div");
		const slaveProperty = "hears";
		const choices = new Map([
			[0, "Normal Hearing"],
			[-1, "Hard of Hearing"],
		]);
		if (V.seeExtreme) {
			choices.set(-2, "Deaf");
		}

		createDescription(el, description, slaveProperty);
		el.append(choicesMaker(slaveProperty, choices, description));

		function description() {
			for (const [value, text] of choices) {
				if (slave.hears === value) {
					return `${text}. `;
				}
			}
			return `Hard of hearing. `;
		}

		return el;
	}

	function smells() {
		const el = document.createElement("div");
		const slaveProperty = "smells";
		const choices = new Map([
			[0, "Normal Sense of smell"],
			[-1, "No Sense of smell"],
		]);

		createDescription(el, description, slaveProperty);
		el.append(choicesMaker(slaveProperty, choices, description));

		function description() {
			for (const [value, text] of choices) {
				if (slave.smells === value) {
					return `${text}.`;
				}
			}
			return `Normal Sense of smell. `;
		}

		return el;
	}

	function tastes() {
		const el = document.createElement("div");
		const slaveProperty = "tastes";
		const choices = new Map([
			[0, "Normal Sense of taste"],
			[-1, "No Sense of taste"],
		]);

		createDescription(el, description, slaveProperty);
		el.append(choicesMaker(slaveProperty, choices, description));

		function description() {
			for (const [value, text] of choices) {
				if (slave.tastes === value) {
					return `${text}.`;
				}
			}
			return `Normal Sense of taste. `;
		}

		return el;
	}

	function limbs() {
		const el = new DocumentFragment();
		const limbs = new Map([
			["arm.left", "Left arm"],
			["arm.right", "Right arm"],
			["leg.left", "Left leg"],
			["leg.right", "Right leg"],
		]);

		for (const [value, text] of limbs) {
			const div = document.createElement("div");

			// Desc setup
			const descText = document.createElement("span");
			descText.id = (`${value}-text`).replace(/\./g, "-");
			const hasLimb = (_.get(slave, value) !== null);
			descText.append(description(hasLimb, text));
			div.append(descText);

			const linkArray = [];
			linkArray.push(
				App.UI.DOM.link(
					"Add",
					() => {
						const limb = value.startsWith("leg.") ? new App.Entity.LegState() : new App.Entity.ArmState();
						_.set(slave, value, limb);
						jQuery(`#${descText.id}`).empty().append(description(true, text));
					}
				)
			);
			linkArray.push(
				App.UI.DOM.link(
					"Remove",
					() => {
						_.set(slave, value, null);
						jQuery(`#${descText.id}`).empty().append(description(false, text));
					}
				)
			);
			div.append(App.UI.DOM.generateLinksStrip(linkArray));

			el.append(div);
		}

		function description(hasLimb, text) {
			return `${text}: ${(hasLimb) ? `Yes` : `No`}. `;
		}

		return el;
	}

	function nationality() {
		const el = document.createElement("div");
		const slaveProperty = "nationality";
		const choices = [{key: "slave", name: "Slave"},
			{key: "Stateless", name: "Stateless"},
			{key: "Nationality is unimportant", name: "Nationality is unimportant"},
		];
		for (const nationality of App.Data.misc.baseNationalities) {
			choices.push({key: nationality, name: nationality});
		}

		createDescription(el, description, slaveProperty);

		// Choices
		el.append(App.UI.DOM.makeSelect(choices, slave.nationality, nat => {
			slave.nationality = nat;
			jQuery("#nationality-text").empty().append(description());
		}));

		function description() {
			for (const choice of choices) {
				if (slave.nationality === choice.key) {
					return `${choice.name}. `;
				}
			}
		}

		return el;
	}

	function reset() {
		return App.UI.DOM.makeElement(
			"p",
			App.UI.DOM.link(
				"Reset custom order form",
				() => {
					V.customSlave = new App.Entity.CustomSlaveOrder();
					V.market = new App.Markets.Global("Custom Slave");
				},
				[],
				"Market"
			)
		);
	}

	function orderControls() {
		const linkArray = [];
		if (V.customSlaveOrdered === 1) {
			linkArray.push(
				App.UI.DOM.passageLink(
					"Update custom slave order",
					"Buy Slaves"
				)
			);
			linkArray.push(
				App.UI.DOM.link(
					"Withdraw custom slave order",
					() => { V.customSlaveOrdered = 0; },
					[],
					"Buy Slaves"
				)
			);
		} else {
			linkArray.push(
				App.UI.DOM.link(
					"Post custom slave order",
					() => { V.customSlaveOrdered = 1; },
					[],
					"Buy Slaves"
				)
			);
		}

		return App.UI.DOM.makeElement("p", App.UI.DOM.generateLinksStrip(linkArray));
	}

	/**
	 *
	 * @param {HTMLElement} element
	 * @param {Function} text
	 * @param {string} id
	 */
	function createDescription(element, text, id) {
		const span = document.createElement('span');
		span.id = (`${id}-text`).replace(/\./g, "-");
		span.append(text());
		element.append(span);
	}

	/**
	 *
	 * @param {string} slaveParam
	 * @param {Map} choices
	 * @param {Function} description
	 */
	function choicesMaker(slaveParam, choices, description) {
		const linkArray = [];
		for (const [value, text] of choices) {
			linkArray.push(
				App.UI.DOM.link(
					text,
					() => {
						_.set(slave, slaveParam, value);
						jQuery(`#${(slaveParam).replace(/\./g, "-")}-text`).empty().append(description());
					}
				)
			);
		}
		return App.UI.DOM.generateLinksStrip(linkArray);
	}
};


code:
App.Markets["Elite Slave"] = function() {
	const el = new DocumentFragment();

	el.append(`You check to see if any potential breeding slaves are on auction. `);
	if (V.eliteAuctioned === 1) {
		el.append(`There are none available right now, but there probably will be next week. `);
	} else {
		V.eliteAuctioned = 1;

		let minAge;
		let maxAge;
		if (V.arcologies[0].FSMaturityPreferentialist > 20) {
			minAge = 25;
		} else {
			minAge = V.fertilityAge;
		}
		if (V.arcologies[0].FSYouthPreferentialist > 20) {
			maxAge = 23;
		} else {
			maxAge = 40;
		}
		let race;
		if (FutureSocieties.isActive('FSSupremacist')) {
			race = V.arcologies[0].FSSupremacistRace;
		} else if (FutureSocieties.isActive('FSSubjugationist')) {
			let races = ["amerindian", "asian", "asian", "asian", "asian", "asian", "asian", "black", "black", "indo-aryan", "indo-aryan", "latina", "latina", "latina", "malay", "malay", "middle eastern", "middle eastern", "mixed race", "pacific islander", "semitic", "semitic", "southern european", "southern european", "white", "white", "white", "white", "white", "white", "white", "white", "white"];
			races = races.delete(V.arcologies[0].FSSubjugationistRace);
			race = races.random();
		}
		let slave = GenerateNewSlave("XX", {
			minAge: minAge, maxAge: maxAge, ageOverridesPedoMode: 1, race: race, disableDisability: 1
		});
		slave.origin = "$He was purchased from a member of the Elite.";
		slave.career = "a slave";
		slave.prestige = 1;
		slave.trust = random(60, 100);
		slave.devotion = random(60, 100);
		if (!heightPass(slave)) {
			if (V.arcologies[0].FSPetiteAdmiration > 20) {
				slave.natural.height = Height.randomAdult(slave, {limitMult: [-4, -2]});
			} else if (V.arcologies[0].FSStatuesqueGlorification > 20) {
				slave.natural.height = Height.randomAdult(slave, {limitMult: [3, 5]});
			}
			slave.height = Height.forAge(slave.natural.height, slave);
		}
		if (V.arcologies[0].FSPaternalist > 20) {
			setHealth(slave, 100, 0, 0, 0, 0);
			slave.intelligenceImplant = 30;
		} else {
			setHealth(slave, jsRandom(10, 60), undefined, undefined, 0);
			slave.intelligenceImplant = either(0, 15, 30);
		}
		let weightMin;
		let weightMax;
		if (V.arcologies[0].FSSlimnessEnthusiast > 20) {
			if (V.arcologies[0].FSHedonisticDecadence > 20 || V.arcologies[0].FSPhysicalIdealistLaw === 1) {
				weightMax = 30;
			} else {
				weightMax = 0;
			}
		} else if (V.arcologies[0].FSPhysicalIdealistLaw === 1) {
			weightMax = 30;
		} else {
			weightMax = 100;
		}
		if (V.arcologies[0].FSHedonisticDecadence > 20) {
			if (V.arcologies[0].FSSlimnessEnthusiast > 20) {
				weightMin = 10;
			} else {
				weightMin = 95;
			}
		} else {
			weightMin = -70;
		}
		slave.weight = random(weightMin, weightMax);

		let musclesMin;
		let musclesMax;
		if (V.arcologies[0].FSPhysicalIdealist > 20) {
			if (V.arcologies[0].FSPhysicalIdealistLaw === 1) {
				musclesMin = 20;
				musclesMax = 50;
			} else {
				musclesMin = 95;
				musclesMax = 100;
			}
		} else if (V.arcologies[0].FSHedonisticDecadence > 20) {
			if (V.arcologies[0].FSHedonisticDecadenceStrongFat === 1) {
				musclesMin = 50;
				musclesMax = 100;
			} else {
				musclesMin = -100;
				musclesMax = 50;
			}
		} else {
			musclesMin = -50;
			musclesMax = 50;
		}
		slave.muscles = random(musclesMin, musclesMax);
		if (V.arcologies[0].FSPhysicalIdealist > 20) {
			slave.height += 5;
		}
		eyeSurgery(slave, "both", "normal");
		slave.hears = 0;
		if (slave.weight < -30) {
			slave.waist = random(-55, 0);
		} else if (slave.physicalAge < 13) {
			slave.waist = random(-25, 25);
		} else if (slave.weight <= 30) {
			slave.waist = random(-45, 5);
		} else if (slave.weight <= 160) {
			slave.waist = random(-20, 10);
		}
		if (slave.voice < 2) {
			slave.voice++;
		}
		if (slave.accent > 1) {
			slave.accent = 1;
		}
		if (slave.shoulders > 0) {
			slave.shoulders -= 2;
		}
		if (V.arcologies[0].FSAssetExpansionist > 20) {
			slave.boobs = (random(50, 500) * 100);
			slave.butt = random(6, 20);
			if (V.arcologies[0].FSTransformationFetishist > 20) {
				slave.boobsImplant = slave.boobs - 200;
				if (slave.boobsImplant > 10000) {
					slave.boobsImplantType = "hyper fillable";
				} else {
					slave.boobsImplantType = "advanced fillable";
				}
				slave.buttImplant = slave.butt - 1;
				if (slave.buttImplant > 9) {
					slave.buttImplantType = "hyper fillable";
				} else {
					slave.buttImplantType = "advanced fillable";
				}
			}
		} else if (V.arcologies[0].FSSlimnessEnthusiast > 20) {
			slave.boobs = (random(3, 5) * 100);
			slave.butt = random(0, 2);
			if (V.arcologies[0].FSTransformationFetishist > 20) {
				slave.boobsImplant = slave.boobs - 50;
				slave.boobsImplantType = "normal";
				if (slave.butt > 1) {
					slave.buttImplant = 1;
					slave.buttImplantType = "normal";
				}
			}
		} else {
			slave.boobs = (random(5, 10) * 100);
			slave.butt = random(3, 7);
			if (V.arcologies[0].FSTransformationFetishist > 20) {
				slave.boobsImplant = slave.boobs - 200;
				slave.boobsImplantType = "normal";
				slave.buttImplant = slave.butt - 1;
				slave.buttImplantType = "normal";
			}
		}
		if (slave.nipples === "tiny") {
			slave.nipples = "puffy";
		}
		if (V.arcologies[0].FSTransformationFetishist > 20) {
			if (slave.boobsImplant / slave.boobs >= 0.90) {
				slave.boobShape = "spherical";
				slave.nipples = "flat";
			} else {
				slave.boobShape = "normal";
			}
		} else {
			slave.boobShape = either("perky", "perky", "torpedo-shaped", "wide-set");
		}
		slave.face = 100;
		slave.faceShape = either("cute", "cute", "exotic", "exotic", "normal", "normal", "sensual", "sensual", "sensual");
		slave.lips = random(45, 75);
		if (V.arcologies[0].FSTransformationFetishist > 20) {
			slave.lips += 25;
			slave.lipsImplant = 25;
		}
		if (slave.teeth !== "normal" && slave.physicalAge >= 12) {
			slave.teeth = "normal";
		}
		if (slave.vaginaLube === 0) {
			slave.vaginaLube++;
		}
		slave.preg = 0;
		slave.counter.birthsTotal = either(0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 3, 4);
		if (V.arcologies[0].FSPastoralist > 20 || slave.counter.birthsTotal > 0) {
			slave.lactation = 1;
			if (V.arcologies[0].FSPastoralist > 20) {
				slave.lactationAdaptation = V.arcologies[0].FSPastoralist;
			} else {
				slave.lactationAdaptation = Math.min(slave.counter.birthsTotal * 10, 100);
			}
		} else {
			slave.lactation = either(0, 0, 0, 0, 1);
		}
		if (slave.lactation > 0) {
			slave.lactationDuration = 2;
		}
		if (slave.counter.birthsTotal > 0) {
			slave.vagina = random(2, 3);
			slave.pregWeek = either(-4, -3, -2, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
		} else {
			slave.vagina = random(0, 3);
			slave.pregWeek = 0;
		}
		if (slave.hips <= 0) {
			slave.hips = 1;
		}
		if (V.arcologies[0].FSGenderRadicalist > 20) {
			slave.dick = random(2, 5);
			slave.balls = random(2, 10);
			slave.scrotum = slave.balls;
			slave.prostate = either(1, 2, 3);
			slave.vasectomy = 1;
		}
		slave.makeup = 2;
		slave.nails = 1;
		if (slave.vagina === 0) {
			slave.skill.vaginal = 30;
		} else {
			slave.skill.vaginal = 60;
		}
		if (slave.anus === 0) {
			slave.skill.anal = 10;
		} else {
			slave.skill.anal = 60;
		}
		slave.skill.oral = 60;
		slave.chastityVagina = 1;
		slave.intelligence = random(51, 100);
		if (V.arcologies[0].FSIntellectualDependency > 20) {
			slave.energy = 100;
			slave.attrXY = 100;
		} else if (V.arcologies[0].FSSlaveProfessionalism > 20) {
			slave.intelligence = Intelligence.random({limitIntelligence: [80, 100]});
			if (slave.vagina >= 0) {
				slave.skill.vaginal = 100;
			}
			if (penetrativeSocialUse(slave) >= 40) {
				slave.skill.penetrative = 100;
			}
			slave.skill.anal = 100;
			slave.skill.oral = 100;
			slave.skill.entertainment = 100;
			slave.skill.whoring = 100;
			slave.intelligenceImplant = 30;
		}
		slave.attrKnown = 1;
		slave.fetishKnown = 1;
		slave.behavioralQuirk = "confident";
		slave.sexualQuirk = either("caring", "caring", "gagfuck queen", "perverted", "romantic", "romantic", "strugglefuck queen", "tease", "unflinching");
		slave.pubertyXX = 1;
		slave.breedingMark = 1;

		const complianceText = App.Desc.lawCompliance(slave, "Elite Slave");
		const cost = slaveCost(slave, false, true);

		App.UI.DOM.appendNewElement("p", el, `It will take ${cashFormat(cost)} to win the auction.`);

		if (V.cash >= cost) {
			App.UI.DOM.appendNewElement(
				"p",
				el,
				App.UI.DOM.link(
					`Place that bid`,
					() => {
						cashX(forceNeg(cost), "slaveTransfer", slave);
						jQuery("#slave-markets").empty().append(App.UI.newSlaveIntro(slave));
					}
				)
			);
		} else {
			App.UI.DOM.appendNewElement("p", el, `You lack the necessary funds to place a winning bid.`, "note");
		}

		el.append(App.Desc.longSlave(slave, {market: "Elite Slave", marketText: complianceText}));
	}
	return el;
};