code:
/* eslint-disable camelcase */
/* eslint-disable no-console */
let lastActiveSlave, lastSlaves, lastPC;

/*
	To use, add something like:

		<<link "Pull up the file on $his family tree.">>
		<<replace #familyTreeLink>>
			<<run renderFamilyTree($slaves, $activeSlave.ID)>>
		<</replace>>
	<</link>>

*/

globalThis.renderFamilyTree = function(slaves, filterID) {
	'use strict';

	let ftreeWidth, ftreeHeight;
	let chartWidth, chartHeight;
	let margin;
	d3.select('#ftree-canvas').remove();
	let svg = d3.select('#familyTree')
		.append('svg')
		.attr('id', 'ftree-canvas');
	let chartLayer = svg.append('g').classed('chartLayer', true);

	let data = buildFamilyTree(slaves, filterID);

	initFtreeSVG(data);
	runFtreeSim(data);

	function initFtreeSVG(data) {
		ftreeWidth = data.nodes.length * 45;
		ftreeHeight = data.nodes.length * 35;
		if (ftreeWidth < 600) {
			ftreeWidth = 600;
		} else if (ftreeWidth > 1920) {
			ftreeWidth = 1920;
		}
		if (ftreeHeight < 480) {
			ftreeHeight = 480;
		} else if (ftreeHeight > 1200) {
			ftreeHeight = 1200;
		}

		margin = {
			top: 0,
			left: 0,
			bottom: 0,
			right: 0
		};

		chartWidth = ftreeWidth - (margin.left + margin.right);
		chartHeight = ftreeHeight - (margin.top + margin.bottom);

		svg.attr('width', ftreeWidth).attr('height', ftreeHeight);

		svg.append('defs');

		svg.append('defs').append('marker')
			.attr('id', 'arrowhead')
			.attr('viewBox', '-0 -5 10 10')
			.attr('refX', 13)
			.attr('refY', 0)
			.attr('orient', 'auto')
			.attr('markerWidth', 13)
			.attr('markerHeight', 13)
			.attr('xoverflow', 'visible')
			.append('svg:path')
			.attr('d', 'M 0,-1 L 5,0 L 0,1')
			.attr('fill', '#a1a1a1')
			.style('stroke', 'none');

		chartLayer
			.attr('width', chartWidth)
			.attr('height', chartHeight)
			.attr('transform', `translate(${[margin.left, margin.top]})`);
	}

	function runFtreeSim(data) {
		let simulation = d3.forceSimulation()
			.force('link', d3.forceLink().id(function(d) {
				return d.index;
			}))
			// eslint-disable-next-line no-unused-vars
			.force('collide', d3.forceCollide(function(d) {
				return 60;
			}).iterations(4))
			.force('charge', d3.forceManyBody().strength(-200).distanceMin(100).distanceMax(1000))
			.force('center', d3.forceCenter(chartWidth / 2, chartHeight / 2))
			.force('y', d3.forceY(100))
			.force('x', d3.forceX(200));

		let link = svg.append('g')
			.attr('class', 'link')
			.selectAll('link')
			.data(data.links)
			.enter()
			.append('line')
			.attr('marker-end', 'url(#arrowhead)')
			.attr('stroke', function(d) {
				if (d.type === 'homologous') {
					return '#862d59';
				} else if (d.type === 'paternal') {
					return '#24478f';
				} else {
					return '#aa909b';
				}
			})
			.attr('stroke-width', 2)
			.attr('fill', 'none');

		let node = svg.selectAll('.node')
			.data(data.nodes)
			.enter().append('g')
			.attr('class', 'node')
			.call(d3.drag()
				.on('start', dragstarted)
				.on('drag', dragged)
				.on('end', dragended));

		node.append('circle')
			.attr('r', function(d) { return d.r; })
			.attr('stroke', function(d) {
				if (d.ID === filterID) {
					return '#ffff20';
				} else {
					return '#5a5a5a';
				}
			})
			.attr('class', 'node-circle')
			.attr('r', 20);

		node.append('text')
			.text(function(d) {
				let ssym;
				if (d.dick > 0 && d.vagina > -1) {
					ssym = '☿';
				} else if (d.dick > 0) {
					ssym = '♂';
				} else if (d.vagina > -1) {
					ssym = '♀';
				} else {
					ssym = '?';
				}
				return `${d.name}(${ssym})`;
			})
			.attr('dy', 4)
			.attr('dx', function(d) {
				return -(8 * d.name.length) / 2;
			})
			.attr('class', 'node-text')
			.style('fill', function(d) {
				if (d.is_mother && d.is_father) {
					return '#b84dff';
				} else if (d.is_father) {
					return '#00ffff';
				} else if (d.is_mother) {
					return '#ff3399';
				} else if (d.unborn) {
					return '#a3a3c2';
				} else {
					return '#66cc66';
				}
			});

		svg.selectAll('.node-circle');
		svg.selectAll('.node-text');

		let ticked = function() {
			link
				.attr('x1', function(d) { return d.source.x; })
				.attr('y1', function(d) { return d.source.y; })
				.attr('x2', function(d) { return d.target.x; })
				.attr('y2', function(d) { return d.target.y; });

			node
				.attr("transform", function(d) { return `translate(${d.x}, ${d.y})`; });
		};

		simulation.nodes(data.nodes)
			.on('tick', ticked);

		simulation.force('link')
			.links(data.links);

		function dragstarted(d) {
			if (!d3.event.active) { simulation.alphaTarget(0.3).restart(); }
			d.fx = d.x;
			d.fy = d.y;
		}

		function dragged(d) {
			d.fx = d3.event.x;
			d.fy = d3.event.y;
		}

		function dragended(d) {
			if (!d3.event.active) { simulation.alphaTarget(0); }
			d.fx = null;
			d.fy = null;
		}
	}
};

globalThis.buildFamilyTree = function(slaves, filterID) {
	let family_graph = {nodes: [], links: []};
	let node_lookup = {};
	let preset_lookup = {
		'-2': 'A citizen',
		'-3': 'Former Master',
		'-4': 'An arcology owner',
		'-5': 'Client',
		'-6': 'Social Elite',
		'-7': 'Gene lab'
	};
	let outdads = {};
	let outmoms = {};
	let kids = {};

	let fake_pc = {
		slaveName: `${V.PC.slaveName}(You)`,
		mother: V.PC.mother,
		father: V.PC.father,
		dick: V.PC.dick,
		vagina: V.PC.vagina,
		ID: V.PC.ID
	};
	let charList = [fake_pc];
	charList.push.apply(charList, slaves);
	charList.push.apply(charList, V.tanks);

	let unborn = {};
	for (let i = 0; i < V.tanks.length; i++) {
		unborn[V.tanks[i].ID] = true;
	}
	for (let i = 0; i < V.cribs.length; i++) {
		unborn[V.cribs[i].ID] = true;
	}

	for (let i = 0; i < charList.length; i++) {
		let mom = charList[i].mother;
		let dad = charList[i].father;

		if (mom) {
			if (!kids[mom]) {
				kids[mom] = {};
			}
			kids[mom].mother = true;
		}
		if (dad) {
			if (!kids[dad]) {
				kids[dad] = {};
			}
			kids[dad].father = true;
		}
	}

	for (let i = 0; i < charList.length; i++) {
		let character = charList[i];
		if (character.mother === 0 && character.father === 0 && !kids[character.ID]) {
			continue;
		}
		let mom = character.mother;
		if (mom < -6) {
			if (mom in V.missingTable && V.showMissingSlaves) {
				if (typeof node_lookup[mom] === 'undefined') {
					node_lookup[mom] = family_graph.nodes.length;
					let missing = V.missingTable[mom];
					charList.push({
						ID: mom,
						mother: 0,
						father: 0,
						is_mother: true,
						dick: missing.dick,
						vagina: missing.vagina,
						slaveName: missing.slaveName
					});
				}
			} else {
				if (typeof outmoms[mom] === 'undefined') {
					outmoms[mom] = [];
				}
				outmoms[mom].push(character.slaveName);
			}
		} else if (mom < 0 && typeof node_lookup[mom] === 'undefined' && typeof preset_lookup[mom] !== 'undefined') {
			node_lookup[mom] = family_graph.nodes.length;
			charList.push({
				ID: mom,
				mother: 0,
				father: 0,
				is_father: true,
				dick: 0,
				vagina: 1,
				slaveName: preset_lookup[mom]
			});
		}

		let dad = character.father;
		if (dad < -6) {
			if (dad in V.missingTable && V.showMissingSlaves) {
				if (typeof node_lookup[dad] === 'undefined') {
					node_lookup[dad] = family_graph.nodes.length;
					let missing = V.missingTable[dad];
					charList.push({
						ID: dad,
						mother: 0,
						father: 0,
						is_father: true,
						dick: missing.dick,
						vagina: missing.vagina,
						slaveName: missing.slaveName
					});
				}
			} else {
				if (typeof outdads[dad] === 'undefined') {
					outdads[dad] = [];
				}
				outdads[dad].push(character.slaveName);
			}
		} else if (dad < 0 && typeof node_lookup[dad] === 'undefined' && typeof preset_lookup[dad] !== 'undefined') {
			node_lookup[dad] = family_graph.nodes.length;
			charList.push({
				ID: dad,
				mother: 0,
				father: 0,
				is_father: true,
				dick: 1,
				vagina: -1,
				slaveName: preset_lookup[dad]
			});
		}
	}
	let mkeys = Object.keys(outmoms);
	for (let i = 0; i < mkeys.length; i++) {
		let name;
		let key = mkeys[i];
		let names = outmoms[key];
		if (names.length === 1) {
			name = names[0];
		} else if (names.length === 2) {
			name = names.join(' and ');
		} else {
			names[-1] = `and ${names[-1]}`;
			name = names.join(', ');
		}
		node_lookup[key] = family_graph.nodes.length;
		// Outside extant slaves set
		charList.push({
			ID: key,
			mother: 0,
			father: 0,
			is_mother: true,
			dick: 0,
			vagina: 1,
			slaveName: `${name}'s mother`
		});
	}

	let dkeys = Object.keys(outdads);
	for (let i = 0; i < dkeys.length; i++) {
		let name;
		let key = dkeys[i];
		let names = outdads[key];
		if (names.length === 1) {
			name = names[0];
		} else if (names.length === 2) {
			name = names.join(' and ');
		} else {
			names[-1] = `and ${names[-1]}`;
			name = names.join(', ');
		}
		node_lookup[key] = family_graph.nodes.length;
		// Outside extant slaves set
		charList.push({
			ID: key,
			mother: 0,
			father: 0,
			is_father: true,
			dick: 1,
			vagina: -1,
			slaveName: `${name}'s father`
		});
	}

	let charHash = {};
	for (let i = 0; i < charList.length; i++) {
		charHash[charList[i].ID] = charList[i];
	}

	let related = {};
	let seen = {};
	let saveTree = {};

	function relatedTo(character, targetID, relIDs = {tree: {}, related: false}) {
		relIDs.tree[character.ID] = true;
		if (related[character.ID]) {
			relIDs.related = true;
			return relIDs;
		}
		if (character.ID === targetID) {
			relIDs.related = true;
		}
		if (seen[character.ID]) {
			return relIDs;
		}
		seen[character.ID] = true;
		if (character.mother !== 0) {
			if (charHash[character.mother]) {
				relatedTo(charHash[character.mother], targetID, relIDs);
			}
		}
		if (character.father !== 0) {
			if (charHash[character.father]) {
				relatedTo(charHash[character.father], targetID, relIDs);
			}
		}
		return relIDs;
	}
	if (filterID) {
		if (charHash[filterID]) {
			let relIDs = relatedTo(charHash[filterID], filterID);
			for (let k in relIDs.tree) {
				related[k] = true;
			}
			for (let i = 0; i < charList.length; i++) {
				if (charHash[charList[i].ID]) {
					let pRelIDs = relatedTo(charHash[charList[i].ID], filterID);
					if (pRelIDs.related) {
						for (let k in pRelIDs.tree) {
							related[k] = true;
							if (saveTree[k]) {
								for (let k2 in saveTree[k].tree) {
									related[k2] = true;
								}
							}
						}
					}
					saveTree[charList[i].ID] = pRelIDs;
				}
			}
		}
	}

	for (let i = 0; i < charList.length; i++) {
		let character = charList[i];
		let char_id = character.ID;
		if (char_id !== filterID) {
			if (character.mother === 0 && character.father === 0 && !kids[char_id]) {
				continue;
			}
			if (filterID && !related[char_id]) {
				continue;
			}
		}
		node_lookup[char_id] = family_graph.nodes.length;
		let char_obj = {
			ID: char_id,
			name: character.slaveName,
			dick: character.dick,
			unborn: !!unborn[char_id],
			vagina: character.vagina
		};
		if (kids[char_id]) {
			char_obj.is_mother = !!kids[char_id].mother;
			char_obj.is_father = !!kids[char_id].father;
		} else {
			char_obj.is_mother = false;
			char_obj.is_father = false;
		}
		family_graph.nodes.push(char_obj);
	}

	for (let i = 0; i < charList.length; i++) {
		let character = charList[i];
		let char_id = character.ID;
		if (character.mother === 0 && character.father === 0 && !kids[char_id]) {
			continue;
		}
		if (filterID && !related[char_id]) {
			if (related[character.mother]) {
				console.log('wtf, mom');
			}
			if (related[character.father]) {
				console.log('wtf, dad');
			}
			continue;
		}
		if (typeof node_lookup[character.mother] !== 'undefined') {
			let ltype;
			if (character.mother === character.father) {
				ltype = 'homologous';
			} else {
				ltype = 'maternal';
			}
			family_graph.links.push({
				type: ltype,
				target: node_lookup[char_id] * 1,
				source: node_lookup[character.mother] * 1
			});
		}
		if (character.mother === character.father) {
			continue;
		}
		if (typeof node_lookup[character.father] !== 'undefined') {
			family_graph.links.push({type: 'paternal', target: node_lookup[char_id] * 1, source: node_lookup[character.father] * 1});
		}
	}
	return family_graph;
};

/* Old version. To use, do something like:

<<run updateFamilyTree($activeSlave, $slaves, $PC)>>
html_script_tag updateFamilyTree() end_html_script_tag
// tweego freaks out if the actual end of script tag is used even inside a comment
If you want to update the tree, just re-call the run line.

If there's no active slave, you can do:

<<run updateFamilyTree(null, $slaves, $PC)>>
*/

globalThis.updateFamilyTree = function(activeSlave = lastActiveSlave, slaves = lastSlaves, PC = lastPC) {
	lastActiveSlave = activeSlave;
	lastSlaves = slaves;
	lastPC = PC;
	let treeDepth = 0;
	let numTreeNodes = 0;

	let graphElement = document.getElementById("graph");
	if (!graphElement) {
		return;
	}
	graphElement.innerHTML = "";

	/* The way this code works is that we start with the activeSlave then we call
		slaveInfo() recursively to work our way up the tree finding their parents.

	*/

	function getSlave(id, expectedGenes) {
		if (id === -1) {
			return {
				"slaveName": "YOU",
				"ID": id,
				"physicalAge": PC.physicalAge,
				"genes": PC.genes,
				"father": PC.father,
				"mother": PC.mother
			};
		}
		if (id === 0) {
			return {"slaveName": "-", "ID": id, "genes": expectedGenes};
		}
		if (id === activeSlave.ID) {
			return activeSlave;
		}
		for (let i = 0; i < slaves.length; ++i) {
			if (slaves[i].ID === id) {
				return slaves[i];
			}
		}
		return {
			"slaveName": "-",
			"ID": id,
			"genes": expectedGenes
		};
	}

	function slaveInfo(slave, activeSlaveId, recursionProtectSlaveId = {}) {
		numTreeNodes = 0;
		treeDepth = 0;
		if (recursionProtectSlaveId[slave.ID]) {
			console.log("Recursion protection");
			return slaveInfo_(slave, activeSlaveId);
		}
		recursionProtectSlaveId[slave.ID] = true;

		if (typeof slave.father === "undefined" || typeof slave.mother === "undefined") {
			return slaveInfo_(slave, activeSlaveId);
		}

		if (slave.father === -1 || slave.mother === -1) {
			return slaveInfo(getSlave(-1), activeSlaveId, recursionProtectSlaveId);
		}
		if (slave.father !== 0) {
			return slaveInfo(getSlave(slave.father, "unknownXY"), activeSlaveId, recursionProtectSlaveId);
		}

		if (slave.mother !== 0) {
			return slaveInfo(getSlave(slave.mother, "unknownXX"), activeSlaveId, recursionProtectSlaveId);
		}
		return slaveInfo_(slave, activeSlaveId);
	}

	function slaveInfo_(slave, activeSlaveId, slavesAdded = {}, depth = 1) {
		numTreeNodes += 1;
		treeDepth = Math.max(treeDepth, depth);
		let shouldAddChildren = false;
		if (!slavesAdded[slave.ID]) {
			shouldAddChildren = true;
			slavesAdded[slave.ID] = true;
		}
		let data = {
			"name": slave.slaveName + (slave.physicalAge ? (` (${slave.physicalAge})`) : ""),
			"class": slave.genes,
			"textClass": (activeSlaveId === slave.ID) ? "emphasis" : "",
			"marriages": [],
		};

		let spouseToChild = {};

		function maybeAddSpouseToChild(child) {
			if (child.ID === slave.ID) {
				return;
			}
			if (child.father === slave.ID) {
				if (!spouseToChild[child.mother]) {
					spouseToChild[child.mother] = [];
				}
				spouseToChild[child.mother].push(child);
			} else if (child.mother === slave.ID) {
				if (!spouseToChild[child.father]) {
					spouseToChild[child.father] = [];
				}
				spouseToChild[child.father].push(child);
			}
		}

		if (activeSlave.ID !== PC.ID) {
			maybeAddSpouseToChild(activeSlave);
		}
		maybeAddSpouseToChild(getSlave(-1));

		for (let i = 0; i < slaves.length; ++i) {
			let child = slaves[i];
			if (child.ID !== activeSlave.ID) {
				maybeAddSpouseToChild(child);
			}
		}

		for (let key in spouseToChild) {
			if (spouseToChild.hasOwnProperty(key)) {
				let children = shouldAddChildren ? spouseToChild[key] : [];
				let spouse = getSlave(key, (slaves.genes === "XX") ? "unknownXY" : (slaves.genes === "XY") ? "unknownXX" : "unknown");
				let spouseName;
				if (spouse.ID !== slave.ID) {
					spouseName = spouse.slaveName + (spouse.physicalAge ? (` (${spouse.physicalAge})`) : "");
				} else {
					spouseName = (spouse.ID === -1) ? "(yourself)" : "(themselves)";
				}
				let marriage = {
					"spouse": {"name": spouseName, "class": spouse.genes},
					"children": children.map(function(x) { return slaveInfo_(x, activeSlaveId, slavesAdded, depth + 1); })
				};
				data.marriages.push(marriage);
			}
		}
		return data;
	}

	if (activeSlave === PC || activeSlave === null) {
		activeSlave = getSlave(-1);
	}
	const treeData = [slaveInfo(activeSlave, activeSlave.ID)];
	console.log("Family tree is", treeData, 'and has:', numTreeNodes);

	let parentWidth = document.getElementById('editFamily').offsetWidth;

	console.log(parentWidth, document.getElementById('passages').offsetWidth);
	if (!parentWidth) {
		parentWidth = document.body.offsetWidth - 483;
	}

	console.log(parentWidth, Math.min(200 + 40 * numTreeNodes, parentWidth - 200) + 200);

	dTree.init(treeData, {
		target: "#graph",
		debug: true,
		height: 50 + 50 * treeDepth,
		/* very rough heuristics */
		width: Math.min(200 + 40 * numTreeNodes,
			parentWidth - 200) + 200,
		callbacks: {
			nodeClick: function( /* name, extra*/ ) {}
		}
	});
};


code:
App.FindSlave = {};

/**
 * Fragment searching: See if every needle can found somewhere in the field of haystacks
 * @param {string[]} haystacks
 * @param {RegExp[]} needles
 * @returns {boolean}
 */
App.FindSlave._fragmentSearch = function(haystacks, needles) {
	const hs = haystacks.join(" ");
	return needles.every((needle) => { return needle.test(hs); });
};

/**
 * Get slave ids which match a predicate
 * @param {function(App.Entity.SlaveState): boolean} predicate
 * @returns {number[]}
 */
App.FindSlave._slaveIDs = function(predicate) {
	return V.slaves.reduce((acc, slave) => {
		if (predicate(createReadonlyProxy(slave))) {
			acc.push(slave.ID);
		}
		return acc;
	}, []);
};

/**
 * Generate a slave list as the result of fragment searching all the name-type fields
 * @param {string} query
 * @returns {DocumentFragment}
 */
App.FindSlave.searchByName = function(query) {
	const needles = query.split(" ").map((needle) => { return new RegExp(needle, "i"); });
	const ids = this._slaveIDs((slave) => { return this._fragmentSearch([slave.slaveName, slave.slaveSurname, slave.birthName, slave.birthSurname], needles); });
	return App.UI.SlaveList.render.listDOM(ids, [], App.UI.SlaveList.SlaveInteract.stdInteract);
};

/**
 * Generate a slave list as the result of fragment searching profession and origin
 * @param {string} query
 * @returns {DocumentFragment}
 */
App.FindSlave.searchByBackground = function(query) {
	const needles = query.split(" ").map((needle) => { return new RegExp(needle, "i"); });
	const ids = this._slaveIDs((slave) => { return this._fragmentSearch([slave.career, slave.origin], needles); });
	return App.UI.SlaveList.render.listDOM(ids, [], App.UI.SlaveList.SlaveInteract.stdInteract);
};

/**
 * Generate a slave list as the result of evaluating an expression
 * @param {string} query
 * @returns {DocumentFragment}
 */
App.FindSlave.searchByExpression = function(query) {
	const pred = new Function("slave", "return (" + query + ");");
	const ids = runWithReadonlyProxy(() => { return this._slaveIDs(pred); });
	return App.UI.SlaveList.render.listDOM(ids, [], App.UI.SlaveList.SlaveInteract.stdInteract);
};

repo_name: KnightBoulegard/fc

license: mit


code:
/**
 * @param {App.Entity.SlaveState} slave
 * @returns {number}
 */
App.Facilities.Farmyard.foodAmount = function(slave) {
	if (!slave) {
		return `Current slave is not valid. Please report this`;
	}
	let food = 150;													// kg of food produced per week by slave, before upgrades
	if (V.farmyardUpgrade.pump === 1) {
		food += 15;													// pump upgrade for farmyard
	}
	if (V.farmyardUpgrade.fertilizer === 1) {
		food += 35;													// fertilizer upgrade for farmyard
	}
	if (V.farmyardUpgrade.seeds === 1) {
		food += 65;													// seeds upgrade for farmyard
	}
	if (V.farmyardUpgrade.machinery === 1) {
		food += 65;													// machinery upgrade for farmyard
	}
	if (V.Farmer !== 0) {										// if a farmer is assigned
		food *= 1.1;											// TODO: expand this to account for farmer XP and skill
		if (V.Farmer.skill.farmer >= V.masteredXP) {			// if farmer is master
			food *= 1.1;
		}
	}
	if (slave.devotion > 50) {
		food *= 1.1;
	} else if (slave.devotion < -50) {
		food *= 0.8;
	}
	if (slave.muscles > 30) {									// slave is muscular or more
		food *= 1.1;
	} else if (slave.muscles <= -6) {							// slave is weak or less
		food *= 0.8;
	}
	if (slave.weight > 95) {									// slave is overweight or more
		food *= 0.9;
	} else if (slave.weight > 130) {							// slave is fat or more
		food *= 0.8;
	} else if (slave.weight > 160) {							// slave is obese or more
		food *= 0.7;
	} else if (slave.weight > 190) {							// slave is very obese or more
		food *= 0.6;
	}
	if (!canSee(slave)) {										// slave is blind
		food *= 0.6;
	} else if (!canSeePerfectly(slave)) {						// slave is nearsighted
		food *= 0.8;
	}
	if (slave.hears === -1) {									// slave is hard of hearing
		food *= 0.8;
	} else if (slave.hears < -1) {								// slave is deaf
		food *= 0.6;
	}
	food *= restEffects(slave, 20);
	food = Math.trunc(food);
	food = Math.clamp(food, 1, 1000000000000000000);
	return food;
};

/**
 * @param {App.Entity.SlaveState} slave
 * @returns {number}
 */
App.Facilities.Farmyard.farmShowsIncome = function(slave) {
	// TODO: incorporate farmyardRestraints
	let arcology = V.arcologies[0];
	let cash = 100;													// TODO: this is just a placeholder
	if (!slave) {
		return null;
	} else {
		if (V.Farmer !== 0) {										// farmer is assigned
			cash *= 1.1;
			if (V.Farmer.skill.farmer >= V.masteredXP) {			// farmer is master
				cash *= 1.1;
			}
		}
		/* OPEN FS EFFECTS */
		if (arcology.FSSupremacist !== "unset") {
			if (slave.race === arcology.FSSupremacistRace) {		// slave is of supreme race
				cash *= 0.9;										// TODO: should supreme race slaves receive a penalty?
				repX(-10, "food");									// TODO: should this cause a rep loss
			}
		}
		if (arcology.FSSubjugationist !== "unset") {
			if (slave.race === arcology.FSSubjugationistRace) { 	// slave is of subjugated race
				cash *= 1.1;
				repX(10, "food");									// TODO: should this cause a rep gain?
			} else {
				cash *= 0.9;										// TODO: should unsubjugated race slaves receive a penalty?
			}
		}
		if (arcology.FSRepopulationFocus !== "unset") {
			if (slave.pregWeek > 16) {								// slave is visibly pregnant
				if (slave.eggType !== "human") {					// with a non-human
					cash *= 1.1;
				}
				cash *= 1.2;
			} else {												// slave is not visibly pregnant
				cash *= 0.8;										// TODO: should non-pregnant slaves receive a penalty?
			}
		}
		if (arcology.FSRestart !== "unset") {
			if (slave.pregWeek > 16) {								// slave is visibly pregnant
				if (slave.eggType !== "human") {					// with a non-human
					cash *= 0.9;
					repX(-15, "food");
				}
				cash *= 0.6;
				repX(-10, "food");
			}
		}
		if (arcology.FSGenderRadicalist !== "unset") {
			if (slave.dick > 0) {									// TODO: does this make sense?
				cash *= 1.1;
			} else {
				cash *= 0.8;
			}
		}
		if (arcology.FSGenderFundamentalist !== "unset") {
			if (slave.dick > 0) {
				cash *= 1.1;										// TODO: does this make sense?
			} else {
				cash *= 0.8;
			}
		}
		if (arcology.FSPaternalist !== "unset") {
			if (V.seeBestiality) {
				if (V.farmyardBreeding === 1) {						// slaves are being bred
					cash *= 0.8;
				} else {											// slaves are putting on shows
					cash *= 0.9;
				}
			}
		}
		if (arcology.FSDegradationist !== "unset") {
			if (V.seeBestiality) {
				if (V.farmyardBreeding === 1) {						// slaves are being bred
					cash *= 1.2;
				} else {											// slaves are putting on shows
					cash *= 1.1;
				}
			}
		}
		if (arcology.FSBodyPurist !== "unset") {
			if (slave.boobsImplant > 0) {							// slave has breast implants
				cash *= 0.9;
			}
			if (slave.buttImplant > 0) {							// slave has butt implants
				cash *= 0.9;
			}
			if (slave.faceImplant > 0) {							// slave has had facial surgery
				cash *= 0.9;
			}
			if (slave.lipsImplant > 0) {							// slave has lip implants
				cash *= 0.9;
			}
		}
		if (arcology.FSTransformationFetishist !== "unset") {
			if (slave.boobsImplant > 0) {							// slave has breast implants
				cash *= 1.1;
			}
			if (slave.buttImplant > 0) {							// slave has butt implants
				cash *= 1.1;
			}
			if (slave.faceImplant > 0) {							// slave has had facial surgery
				cash *= 1.1;
			}
			if (slave.lipsImplant > 0) {							// slave has lip implants
				cash *= 1.1;
			}
		}
		if (arcology.FSYouthPreferentialist !== "unset") {
			if (slave.visualAge > 25) {								// slave looks older than 25
				cash *= 0.8;										// TODO: not sure what age this should be
			} else {
				cash *= 1.2;
			}
		}
		if (arcology.FSMaturityPreferentialist !== "unset") {
			if (slave.visualAge > 25) {								// slave looks older than 25
				cash *= 1.2;										// TODO: not sure what age this should be
			} else {
				cash *= 0.8;
			}
		}
		if (arcology.FSSlimnessEnthusiast !== "unset") {
			if (slave.weight > 10) {								// slave is curvy or more
				cash *= 0.9;										// TODO: not sure what weight this should be
			}														// TODO: should implants affect income?
			if (slave.boobs > 799) {								// slave has larger than a D cup
				cash *= 0.9;										// TODO: should this be smaller?
			}
			if (slave.butt > 3) {									// slave has bigger than a bubble butt
				cash *= 0.9;
			}
		}
		if (arcology.FSAssetExpansionist !== "unset") {
			if (slave.weight > 10) {								// slave is curvy or more
				cash *= 1.1;										// TODO: not sure what weight this should be
			}														// TODO: should implants affect income?
			if (slave.boobs > 799) {								// slave has larger than a D cup
				cash *= 1.1;										// TODO: should this be smaller?
			}
			if (slave.butt > 3) {									// slave has bigger than a bubble butt
				cash *= 1.1;
			}
		}
		if (arcology.FSPastoralist !== "unset") {
			if (slave.boobs > 799) {								// slave has larger than a D cup
				cash *= 1.2;
			}
			if (slave.lactation > 0) {								// slave is lactating
				cash *= 1.1;
			}
		}
		if (arcology.FSPhysicalIdealist !== "unset") {
			if (slave.height > 169) {								// slave is tall or taller
				cash *= 1.1;
			} else if (slave.height < 160) {						// slave is short or shorter
				cash *= 0.9;
			}
		}
		if (arcology.FSHedonisticDecadence !== "unset") {
			if (slave.weight > 10) { 								// slave is curvy or fatter
				cash *= 1.1;
			} else if (slave.weight < -10) { 						// slave is thin or thinner
				cash *= 0.9;
			}
		}
		if (arcology.FSChattelReligionist !== "unset") { 			// TODO: I don't know what to put for this one
			if (slave.devotion < 21) { 								// if slave is careful or less
				cash *= 0.9;
			}
		}
		// TODO: should I add the ancient cultures FS?
		/* CLOSE FS EFFECTS */

		if (setup.entertainmentCareers.includes(slave.career)) {
			cash *= 1.1;
		} else if (setup.farmerCareers.includes(slave.career)) {
			cash *= 1.1;
		}
		if (slave.prestige === 1) { 								// slave is prestigious
			cash *= 1.1;
		} else if (slave.prestige === 2) { 							// slave is very prestigious
			cash *= 1.2;
		} else if (slave.prestige === 3) { 							// slave is extremely prestigious
			cash *= 1.3;
		}
		if (slave.porn.prestige === 1) { 							// slave is prestigious from porn
			cash *= 1.1;
		} else if (slave.porn.prestige === 2) { 						// slave is very prestigious from porn
			cash *= 1.2;
		} else if (slave.porn.prestige === 3) { 						// slave is extremely prestigious from porn
			cash *= 1.3;
		} 															// TODO: add relationship checks
		if (slave.face > 40) {										// slave is beautiful or more
			cash *= 1.3;
		} else if (slave.face > 10) {								// slave is very pretty or more
			cash *= 1.1;
		} else if (slave.face < -10) {								// slave is less than unattractive
			cash *= 0.9;
		} else if (slave.face < -40) {								// slave is less than ugly
			cash *= 0.7;
		}
		if (slave.devotion > 50) { 									// slave is devoted or more
			cash *= 1.1;
		} else if (slave.devotion < -50) { 							// slave is reluctant or less
			cash *= 0.8;
		}
		if (slave.trust > 50) { 									// slave is trusting or more
			cash *= 1.1;
		} else if (slave.trust < -50) { 							// slave is less than frightened
			cash *= 0.8;											// TODO: should trust be a factor?
		}
		if (slave.weight > 30) { 									// slave is curvy or more
			cash *= 0.8;											// TODO: tie in Hedonistic FS
		} else if (slave.weight < -30) { 							// slave is very thin or less
			cash *= 0.8;											// TODO: put this on a scale
		}
		if (slave.muscles > 30) { 									// slave is muscular or more
			cash *= 0.9;											// TODO: tie in height eugenics policy
		} else if (slave.muscles < -30) {							// slave is very weak or less
			cash *= 0.9;											// TODO: should this be on a scale?
		}
		if (!canSeePerfectly(slave)) {								// slaves eyesight is nearsighted or worse
			cash *= 0.9;											// TODO: should nearsighted and blind slaves receive a penalty?
		}
		if (slave.hears < 0) {										// slave is hard of hearing or less
			cash *= 0.9;											// TODO: should hard of hearing slaves receive a penalty?
		}
		if (slave.boobs > 800) {									// slave has a DD cup or bigger
			cash *= 0.9;											// TODO: would this make sense?
		}
		if (slave.butt > 4) {										// slave has an enormous butt or bigger
			cash *= 0.9;											// TODO: would this make sense?
		}
		if (slave.preg > 10) {										// slave is pregnant and showing
			cash *= 0.8;											// TODO: not sure how to incorporate pregnancy
		}															// TODO: incorporate skills
		cash *= healthPenalty(slave);
		if (slave.intelligence > 50) {								// slave is very smart or better
			cash *= 1.1;
		} else if (slave.intelligence < -50) {						// slave is very slow or less
			cash *= 0.8;
		}
		if (slave.energy <= 20) {									// slave has no sex drive
			cash *= 0.7;
		} else if (slave.energy <= 40) {							// slave has poor sex drive
			cash *= 0.8;
		} else if (slave.energy <= 60) {							// slave has average sex drive
			cash *= 0.9;
		} else if (slave.energy <= 80) {							// slave has good sex drive
			cash *= 1.1;
		} else if (slave.energy <= 95) {							// slave has powerful sex drive
			cash *= 1.2;
		} else {													// slave is a nymphomaniac
			cash *= 1.3;
		}
		if (slave.fetish === "submissive") {
			cash *= 1.1;
		} else if (slave.fetish === "humiliation") {
			cash *= 1.1;
		} else if (slave.fetish === "masochist") {
			cash *= 1.1;
		}															// TODO: tie in quirks and flaws
		return cash;
	}
};


code:
globalThis.FutureSocieties = (function() {
	"use strict";
	const FSString2Property = { // blame Hedonism and Eugenics for this - TODO: probably can be cleaned up, maybe eliminated
		Supremacist: "FSSupremacist",
		Subjugationist: "FSSubjugationist",
		GenderRadicalist: "FSGenderRadicalist",
		GenderFundamentalist: "FSGenderFundamentalist",
		Degradationist: "FSDegradationist",
		Paternalist: "FSPaternalist",
		BodyPurist: "FSBodyPurist",
		TransformationFetishist: "FSTransformationFetishist",
		YouthPreferentialist: "FSYouthPreferentialist",
		MaturityPreferentialist: "FSMaturityPreferentialist",
		SlimnessEnthusiast: "FSSlimnessEnthusiast",
		AssetExpansionist: "FSAssetExpansionist",
		Pastoralist: "FSPastoralist",
		PhysicalIdealist: "FSPhysicalIdealist",
		Hedonistic: "FSHedonisticDecadence",
		Hedonism: "FSHedonisticDecadence",
		ChattelReligionist: "FSChattelReligionist",
		Multiculturalist: "FSNull",
		RomanRevivalist: "FSRomanRevivalist",
		EgyptianRevivalist: "FSEgyptianRevivalist",
		EdoRevivalist: "FSEdoRevivalist",
		ArabianRevivalist: "FSArabianRevivalist",
		ChineseRevivalist: "FSChineseRevivalist",
		AztecRevivalist: "FSAztecRevivalist",
		RepopulationFocus: "FSRepopulationFocus",
		Repopulationist: "FSRepopulationFocus",
		Eugenics: "FSRestart",
		IntellectualDependency: "FSIntellectualDependency",
		SlaveProfessionalism: "FSSlaveProfessionalism",
		PetiteAdmiration: "FSPetiteAdmiration",
		StatuesqueGlorification: "FSStatuesqueGlorification"
	};
	const SocietyList = [...new Set(Object.keys(FSString2Property).map(key => FSString2Property[key]))]; // This returns an array containing the unique values of FSString2Property. E.g. "FSSupremacist" and "FSSubjugationist"
	const NPCSocietyList = [ "FSCummunism", "FSIncestFetishist" ]; // NPC arcologies may use these FSes, but the PC can't

	const FSMutexGroups = [
		[ "FSSupremacist" ],
		[ "FSSubjugationist" ],
		[ "FSGenderRadicalist", "FSGenderFundamentalist" ],
		[ "FSDegradationist", "FSPaternalist" ],
		[ "FSBodyPurist", "FSTransformationFetishist" ],
		[ "FSYouthPreferentialist", "FSMaturityPreferentialist" ],
		[ "FSSlimnessEnthusiast", "FSAssetExpansionist" ],
		[ "FSPastoralist", "FSCummunism" ],
		[ "FSPhysicalIdealist", "FSHedonisticDecadence" ],
		[ "FSChattelReligionist", "FSNull" ],
		[ "FSIncestFetishist" ],
		[ "FSRomanRevivalist", "FSEgyptianRevivalist", "FSEdoRevivalist", "FSArabianRevivalist", "FSChineseRevivalist", "FSAztecRevivalist" ],
		[ "FSRepopulationFocus", "FSRestart" ],
		[ "FSIntellectualDependency", "FSSlaveProfessionalism" ],
		[ "FSPetiteAdmiration", "FSStatuesqueGlorification"]
	];

	const DisplayName = {
		FSSupremacist: {noun: "Racial Supremacy", adj: "Supremacist"},
		FSSubjugationist: {noun: "Racial Subjugationism", adj: "Subjugationist"},
		FSGenderRadicalist: {noun: "Gender Radicalism", adj: "Gender Radicalist"},
		FSGenderFundamentalist: {noun: "Gender Fundamentalism", adj: "Gender Fundamentalist"},
		FSDegradationist: {noun: "Degradationism", adj: "Degradationist"},
		FSPaternalist: {noun: "Paternalism", adj: "Paternalist"},
		FSBodyPurist: {noun: "Body Purism", adj: "Body Purist"},
		FSTransformationFetishist: {noun: "Transformation Fetishism", adj: "Transformation Fetishist"},
		FSYouthPreferentialist: {noun: "Youth Preferentialism", adj: "Youth Preferentialist"},
		FSMaturityPreferentialist: {noun: "Maturity Preferentialism", adj: "Maturity Preferentialist"},
		FSSlimnessEnthusiast: {noun: "Slimness Enthusiasm", adj: "Slimness Enthusiast"},
		FSAssetExpansionist: {noun: "Asset Expansionism", adj: "Asset Expansionist"},
		FSPastoralist: {noun: "Pastoralism", adj: "Pastoralist"},
		FSCummunism: {noun: "Cummunism", adj: "Cummunist"},
		FSPhysicalIdealist: {noun: "Physical Idealism", adj: "Physical Idealist"},
		FSHedonisticDecadence: {noun: "Decadent Hedonism", adj: "Decadent Hedonist"},
		FSChattelReligionist: {noun: "Chattel Religionism", adj: "Chattel Religionist"},
		FSNull: {noun: "Multiculturalism", adj: "Multiculturalist"},
		FSIncestFetishist: {noun: "Incest Fetishism", adj: "Incest Fetishist"},
		FSRomanRevivalist: {noun: "Roman Revivalism", adj: "Roman Revivalist"},
		FSEgyptianRevivalist: {noun: "Egyptian Revivalism", adj: "Egyptian Revivalist"},
		FSEdoRevivalist: {noun: "Edo Revivalism", adj: "Edo Revivalist"},
		FSArabianRevivalist: {noun: "Arabian Revivalism", adj: "Arabian Revivalist"},
		FSChineseRevivalist: {noun: "Chinese Revivalism", adj: "Chinese Revivalist"},
		FSAztecRevivalist: {noun: "Aztec Revivalism", adj: "Aztec Revivalist"},
		FSRepopulationFocus: {noun: "Repopulation Focus", adj: "Repopulationist"},
		FSRestart: {noun: "Eugenics", adj: "Eugenics"},
		FSIntellectualDependency: {noun: "Intellectual Dependency", adj: "Intellectual Dependency"},
		FSSlaveProfessionalism: {noun: "Slave Professionalism", adj: "Slave Professional"},
		FSPetiteAdmiration: {noun: "Petite Admiration", adj: "Petite Admiration"},
		FSStatuesqueGlorification: {noun: "Statuesque Glorification", adj: "Statuesque Glorification"}
	};

	return {
		activeFSes: activeFSes,
		activeCount: activeCount,
		applyBroadProgress: applyBroadProgress,
		influenceSources: influenceSources,
		diplomaticFSes: diplomaticFSes,
		displayName: displayName,
		displayAdj: displayAdj,
		decay: decayFSes,
		overflowToInfluence: overflowToInfluence,
		remove: removeFS,
		DecorationCleanup: DecorationCleanup,
		Change: FSChange,
		ChangePorn: FSChangePorn,
		HighestDecoration: FSHighestDecoration
	};

	/** get the list of FSes active for a particular arcology
	 * helper function, not callable externally
	 * @param {number} arcologyID
	 * @returns {Array<string>}
	 */
	function activeFSes(arcologyID) {
		let isSet = (fs) => V.arcologies[arcologyID][fs] !== "unset";
		const npcFSes = arcologyID !== 0 ? NPCSocietyList.filter(isSet) : [];
		return SocietyList.filter(isSet).concat(npcFSes);
	}

	/** call as FutureSocieties.activeCount(arcologyID)
	 * @param {number} arcologyID
	 * @returns {number}
	 */
	function activeCount(arcologyID) {
		return activeFSes(arcologyID).length;
	}

	/** call as FutureSocieties.applyBroadProgress(arcologyID, progress)
	 * @param {number} arcologyID
	 * @param {number} progress
	 */
	function applyBroadProgress(arcologyID, progress) {
		for (const fs of activeFSes(arcologyID)) {
			if (fs !== "FSNull") { // does not progress this way
				V.arcologies[arcologyID][fs] += progress;
			}
		}
	}

	/** converts excess progress into influence bonus
	 * call as FutureSocieties.overflowToInfluence(arcologyID)
	 * @param {number} arcologyID
	 */
	function overflowToInfluence(arcologyID) {
		const arcology = V.arcologies[arcologyID];
		for (const fs of activeFSes(arcologyID)) {
			if (fs !== "FSNull") { // no conventional progress
				if (arcology[fs] > V.FSLockinLevel) {
					arcology.influenceBonus += arcology[fs] - V.FSLockinLevel;
					arcology[fs] = V.FSLockinLevel;
				}
			}
		}
	}

	/** returns an array of FSes which the arcology has developed enough to influence others
	 * call as FutureSocieties.influenceSources(arcologyID)
	 * @param {number} arcologyID
	 */
	function influenceSources(arcologyID) {
		let fses = [];
		const arcology = V.arcologies[arcologyID];
		for (const fs of activeFSes(arcologyID)) {
			if (fs !== "FSNull") { // no influence from Multiculturalism?
				if (arcology[fs] > 60) {
					fses.push(fs);
				}
			}
		}
		return fses;
	}

	/** determines whether two named FSes are naturally conflicting or not
	 * @param {string} left FS
	 * @param {string} right FS
	 * @returns {boolean}
	 */
	function conflictingFSes(left, right) {
		if (left !== right) { // identical FSes are not opposed
			for (const group of FSMutexGroups) {
				if (group.includesAll(left, right)) {
					return true; // but any other FS in the mutex group is
				}
			}
		}
		return false;
	}

	/** returns the set of shared FSes between two arcologies, and the set of conflicts between pairs of FSes between the arcologies
	 * relatively expensive, try not to call frequently
	 * call as FutureSocieties.diplomaticFSes(arc1ID, arc2ID)
	 * @param {number} arc1ID
	 * @param {number} arc2ID
	 * @returns {{shared: string[], conflicting: string[][]}}
	 */
	function diplomaticFSes(arc1ID, arc2ID) {
		let shared = [], conflicting = [];
		const arc1FSes = activeFSes(arc1ID);
		const arc2FSes = activeFSes(arc2ID);
		// find ordinary shared and conflicting FSes
		for (const fs1 of arc1FSes) {
			for (const fs2 of arc2FSes) {
				if (fs1 === fs2) {
					shared.push(fs1);
				} else if (conflictingFSes(fs1, fs2)) {
					conflicting.push([fs1, fs2]);
				}
			}
		}
		// special cases: racial FSes might be conflicting even when shared
		const arc1 = V.arcologies[arc1ID];
		const arc2 = V.arcologies[arc2ID];
		if (shared.contains("FSSupremacist")) {
			// a different race is supreme
			if (arc1.FSSupremacistRace !== arc2.FSSupremacistRace) {
				shared.delete("FSSupremacist");
				conflicting.push(["FSSupremacist", "FSSupremacist"]);
			}
			// subjugating the supreme race
			if (arc2FSes.contains("FSSubjugationist") && arc2.FSSubjugationistRace === arc1.FSSupremacistRace) {
				shared.delete("FSSupremacist");
				conflicting.push(["FSSupremacist", "FSSubjugationist"]);
			}
		}
		if (shared.contains("FSSubjugationist")) {
			// subjugating a different race
			if (arc1.FSSubjugationistRace !== arc2.FSSubjugationistRace) {
				shared.delete("FSSubjugationist");
				conflicting.push(["FSSubjugationist", "FSSubjugationist"]);
			}
			// believe the subjugated race is supreme
			if (arc2FSes.contains("FSSupremacist") && arc2.FSSupremacistRace === arc1.FSSubjugationistRace) {
				shared.delete("FSSubjugationist");
				conflicting.push(["FSSubjugationist", "FSSupremacist"]);
			}
		}
		return {shared, conflicting};
	}

	/** returns the future society display name (typically an "ism") for the given property
	 * @param {string} FSProp
	 * @returns {string}
	 */
	function displayName(FSProp) {
		return DisplayName[FSProp].noun;
	}

	/** returns the future society adjective (typically an "ist") for the given property
	 * @param {string} FSProp
	 * @returns {string}
	 */
	function displayAdj(FSProp) {
		return DisplayName[FSProp].adj;
	}

	/** decays all the FSes adopted by a particular arcology (for example, because of government instability)
	 * call as FutureSocieties.decay(arcologyID)
	 * @param {number} arcologyID
	 * @returns {Array<string>} FSes which purged completely
	 */
	function decayFSes(arcologyID) {
		const fses = activeFSes(arcologyID);
		const arc = V.arcologies[arcologyID];
		let purged = [];
		for (const fs of fses) {
			if (fs !== "FSNull") { // exempt for some reason?
				if (arc[fs] < jsRandom(10, 150)) {
					purged.push(fs);
				} else {
					arc[fs] -= 10;
				}
			}
		}
		return purged;
	}

	/** Remove an FS and all associated attributes from an arcology
	 * call as FutureSocieties.remove(FS)
	 * @param {string} FS (e.g. "FSPaternalist" or "FSDegradationist")
	 */
	function removeFS(FS) {
		const arcology = V.arcologies[0];
		const FSDecoration = `${FS}Decoration`;
		const FSSMR = `${FS}SMR`;
		let FSLaw = `${FS}Law`;
		if (arcology[FS] === undefined) {
			// eslint-disable-next-line no-console
			console.log(`ERROR: bad FS reference, $arcologies[0].${FS} not defined`);
			return;
		}

		if (FS === "FSSupremacist" || FS === "FSSubjugationist") { FSLaw += "ME"; }
		if (FS !== "FSNull") { arcology[FSDecoration] = 20; }
		arcology[FS] = "unset";
		switch (FS) {
			case "FSPaternalist":
				arcology[FSLaw] = 0;
				arcology[FSSMR] = 0;
				V.slaveWatch = 0;
				break;
			case "FSDegradationist":
				arcology[FSLaw] = 0;
				arcology[FSSMR] = 0;
				V.liveTargets = 0;
				break;
			case "FSGenderRadicalist":
				arcology.FSGenderRadicalistLawBeauty = 0;
				arcology.FSGenderRadicalistLawFuta = 0;
				break;
			case "FSGenderFundamentalist":
				arcology.FSGenderFundamentalistLawBeauty = 0;
				arcology.FSGenderFundamentalistLawBimbo = 0;
				arcology.FSGenderFundamentalistSMR = 0;
				break;
			case "FSSupremacist":
				arcology[FSLaw] = 0;
				arcology[FSSMR] = 0;
				V.noSubhumansInArmy = 0;
				break;
			case "FSTransformationFetishist":
			case "FSAssetExpansionist":
				arcology[FSSMR] = 0;
				break;
			case "FSPhysicalIdealist":
				arcology.FSPhysicalIdealistLaw = 0;
				arcology.FSPhysicalIdealistSMR = 0;
				arcology.FSPhysicalIdealistStrongFat = 0;
				V.martialSchool = 0;
				break;
			case "FSHedonisticDecadence":
				arcology.FSHedonisticDecadenceLaw = 0;
				arcology.FSHedonisticDecadenceLaw2 = 0;
				arcology.FSHedonisticDecadenceSMR = 0;
				arcology.FSHedonisticDecadenceStrongFat = 0;
				break;
			case "FSChattelReligionist":
				arcology.FSChattelReligionistLaw = 0;
				arcology.FSChattelReligionistSMR = 0;
				arcology.FSChattelReligionistCreed = 0;
				V.subsidyChurch = 0;
				break;
			case "FSRepopulationFocus":
				arcology[FSLaw] = 0;
				arcology[FSSMR] = 0;
				V.universalRulesChildrenBecomeBreeders = 0;
				V.pregExemption = 0;
				break;
			case "FSRestart":
				arcology[FSLaw] = 0;
				arcology[FSSMR] = 0;
				V.eliteOfficers = 0;
				V.propOutcome = 0;
				V.failedElite = 0;
				break;
			case "FSIntellectualDependency":
				arcology.FSIntellectualDependencyLaw = 0;
				arcology.FSIntellectualDependencyLawBeauty = 0;
				arcology.FSIntellectualDependencySMR = 0;
				break;
			case "FSPetiteAdmiration":
				arcology.FSPetiteAdmirationLaw = 0;
				arcology.FSPetiteAdmirationLaw2 = 0;
				arcology.FSPetiteAdmirationSMR = 0;
				break;
			case "FSStatuesqueGlorification":
				arcology.FSStatuesqueGlorificationLaw = 0;
				arcology.FSStatuesqueGlorificationLaw2 = 0;
				arcology.FSStatuesqueGlorificationSMR = 0;
				break;
			case "FSNull":
				break;
			default: // all others have one law and one SMR
				arcology[FSLaw] = 0;
				arcology[FSSMR] = 0;
				break;
		}

		DecorationCleanup();
		resetFSCredits();
	}

	/* helper function, not callable */
	function resetFSCredits() {
		const arcology = V.arcologies[0];
		let activeFS = 0;
		for (let i = 0; i < SocietyList.length; i++) {
			if (arcology[SocietyList[i]] > 0) {
				activeFS++;
			}
		}
		if (arcology.FSNull > 0) { // multiculturalism is accounted for separately
			if (V.FSCreditCount === 4) {
				activeFS += arcology.FSNull / 25;
			} else if (V.FSCreditCount === 6) {
				activeFS += arcology.FSNull / 17;
			} else if (V.FSCreditCount === 7) {
				activeFS += arcology.FSNull / 15;
			} else {
				activeFS += arcology.FSNull / 20;
			}
		}
		V.FSCredits = Math.max(Math.trunc(V.FSGotRepCredits - activeFS), 0);
	}

	/* call as FutureSocieties.DecorationCleanup() */
	function DecorationCleanup() {
		ValidateFacilityDecoration("brothelDecoration");
		ValidateFacilityDecoration("dairyDecoration");
		ValidateFacilityDecoration("clubDecoration");
		ValidateFacilityDecoration("servantsQuartersDecoration");
		ValidateFacilityDecoration("schoolroomDecoration");
		ValidateFacilityDecoration("spaDecoration");
		ValidateFacilityDecoration("clinicDecoration");
		ValidateFacilityDecoration("arcadeDecoration");
		ValidateFacilityDecoration("cellblockDecoration");
		ValidateFacilityDecoration("masterSuiteDecoration");
		ValidateFacilityDecoration("nurseryDecoration");
		ValidateFacilityDecoration("farmyardDecoration");
	}

	/* helper function, not callable */
	/* decoration should be passed as "facilityDecoration" in quotes. For example, ValidateFacilityDecoration("brothelDecoration"). The quotes are important, do not pass it as a story variable. */
	function ValidateFacilityDecoration(decoration) {
		const FSString = V[decoration].replace(/ /g, ''); // removes spaces
		const activeFS = FSString2Property[FSString]; // gets the property name

		if (FSString === "standard") {
			// nothing to do
		} else if (activeFS === undefined) {
			// eslint-disable-next-line no-console
			console.log(`Error: $${decoration} is ${V[decoration]}`);
			V[decoration] = "standard";
		} else if (!Number.isFinite(V.arcologies[0][activeFS])) {
			if (V.arcologies[0][activeFS] !== "unset") {
				// eslint-disable-next-line no-console
				console.log(`Error: $arcologies[0].${activeFS} is ${V.arcologies[0][activeFS]}`);
			}
			V[decoration] = "standard";
		}
	}

	/* call as FutureSocieties.Change() */
	/* FSString should be in the FSString2Property object above */
	function FSChange(FSString, magnitude, bonusMultiplier = 1) {
		const arcology = V.arcologies[0];
		const activeFS = FSString2Property[FSString];

		if (activeFS === undefined) {
			return "ERROR: bad FS reference";
		} else if (Number.isFinite(arcology[activeFS])) {
			if (magnitude < 0) {
				repX(magnitude * V.FSSingleSlaveRep * (arcology[activeFS] / V.FSLockinLevel) / 3, 'futureSocieties'); // Reducing the reputation impact of slaves that are not adhering to societal ideals properly
			} else {
				repX(magnitude * V.FSSingleSlaveRep * (arcology[activeFS] / V.FSLockinLevel), 'futureSocieties');
			}
			arcology[activeFS] += 0.05 * magnitude * V.FSSingleSlaveRep * bonusMultiplier;
		}
	}

	/* call as FutureSocieties.ChangePorn() */
	/* FSString should be in the FSString2Property object above */
	function FSChangePorn(FSString, magnitude) {
		return FSChange(FSString, magnitude, V.pornFameBonus);
	}

	/**
	 * Returns the highest decoration level of active future societies, call as FutureSocieties.HighestDecoration()
	 * @returns {number}
	 */
	function FSHighestDecoration() {
		const arcology = V.arcologies[0];
		const decorationList = SocietyList.map(FS => `${FS}Decoration`);
		let level = 20; // All decorations start at 20

		for (let i = 0; i < decorationList.length; i++) {
			if (arcology[decorationList[i]] > level) {
				level = arcology[decorationList[i]];
			}
		}
		return level;
	}
})();


code:
// Generates a child's genetics based off mother and father and returns it as an object to be attached to an ovum

globalThis.generateGenetics = (function() {
	"use strict";
	let genes;
	let mother;
	let activeMother;
	let father;
	let activeFather;

	// intelligence and face parameters are the same so we can use the same distribution for both values
	// clamping makes edge values (-100, 100) more likely; this is expected behavior
	// please see https://gitgud.io/pregmodfan/fc-pregmod/issues/852
	const fuzzy = (a, b) => Math.clamp(normalRandInt((a + b) / 2, 20), -100, 100);

	/**
	 * @param {App.Entity.SlaveState} actor1
	 * @param {number} actor2 Slave ID of actor 2
	 * @param {number} x
	 * @returns
	 */
	function generateGenetics(actor1, actor2, x) {
		genes = {
			gender: "XX",
			name: "blank",
			surname: 0,
			mother: 0,
			motherName: "none",
			father: 0,
			fatherName: "none",
			nationality: "Stateless",
			race: "white",
			intelligence: 0,
			face: 0,
			faceShape: "cute",
			eyeColor: "brown",
			hColor: "black",
			skin: "light",
			markings: "none",
			behavioralFlaw: "none",
			sexualFlaw: "none",
			pubicHStyle: "bushy",
			underArmHStyle: "bushy",
			clone: 0,
			cloneID: 0,
			geneticQuirks: {},
			fetish: "none"
		};
		if (actor1.ID > 0) {
			mother = V.genePool.find(s => s.ID === actor1.ID);
			if (mother === undefined) {
				mother = actor1;
			}
			activeMother = V.slaves[V.slaveIndices[actor1]];
			if (activeMother === undefined) {
				activeMother = actor1;
			}
		} else {
			activeMother = V.PC;
			mother = V.PC;
		}
		if (actor2 > 0) {
			father = V.genePool.find(s => s.ID === actor2);
			activeFather = V.slaves[V.slaveIndices[actor2]];
			if (father === undefined) {
				father = V.slaves[V.slaveIndices[actor2]];
				activeFather = V.slaves[V.slaveIndices[actor2]];
			}
			if (father === undefined) {
				if (V.incubator > 0) {
					father = V.tanks.find(s => s.ID === actor2);
					activeFather = 0; // activeFather = father?
				}
			}
			if (father === undefined) {
				if (V.nursery > 0) {
					father = V.cribs.find(s => s.ID === actor2);
					activeFather = 0; // activeFather = father?
				}
			}
			if (father === undefined) {
				father = 0;
				activeFather = 0;
			}
		} else if (actor2 === -1) {
			father = V.PC;
			activeFather = V.PC;
		} else {
			father = 0;
			activeFather = 0;
		}

		genes.gender = setGender(father, mother);
		genes.name = setName(x);
		genes.mother = setMotherID(actor1.ID);
		genes.motherName = setMotherName(activeMother);
		genes.father = setFatherID(actor2);
		genes.fatherName = setFatherName(father, activeFather, actor2);
		genes.nationality = setNationality(father, mother);
		genes.geneticQuirks = setGeneticQuirks(activeFather, activeMother, genes.gender);
		genes.skin = setSkin(father, mother, actor2);
		genes.race = setRace(father, mother, actor2);
		genes.intelligence = setIntelligence(father, mother, activeMother, actor2);
		genes.face = setFace(father, mother, activeMother, actor2, genes.geneticQuirks);
		genes.faceShape = setFaceShape(father, mother, genes.geneticQuirks);
		genes.eyeColor = setEyeColor(father, mother, actor2);
		if (genes.geneticQuirks.heterochromia === 2) {
			genes.geneticQuirks.heterochromia = setHeterochromaticEyeColor(father, mother, actor2);
		}
		genes.hColor = setHColor(father, mother, actor2);
		genes.underArmHStyle = setUnderArmHStyle(father, mother);
		genes.pubicHStyle = setPubicHStyle(father, mother);
		genes.markings = setMarkings(father, mother);
		genes.sexualFlaw = setSexualFlaw(father, mother);
		genes.behavioralFlaw = setBehavioralFlaw(father, mother);
		genes.fetish = setFetish(father, mother);

		return genes;
	}

	// gender
	function setGender(father, mother) {
		let gender;
		if (mother.geneticQuirks.girlsOnly === 2) {
			gender = "XX";
		} else if (V.seeDicksAffectsPregnancy === 1) {
			gender = jsRandom(0, 99) < V.seeDicks ? "XY" : "XX";
		} else if (V.adamPrinciple === 1) {
			if (father !== 0) {
				if (father.genes === "XX" && mother.genes === "XX") {
					gender = "XX";
				} else if (father.genes !== mother.genes) {
					gender = jsEither(["XX", "XY"]);
				} else {
					gender = jsEither(["XX", "XY", "XY", "YY"]);
				}
			} else {
				gender = jsEither(["XX", "XY"]);
			}
		} else {
			gender = jsEither(["XX", "XY"]);
		}
		return gender;
	}

	// name
	function setName(x) {
		return `ovum${x}`;
	}

	// motherID
	function setMotherID(actor1ID) {
		return actor1ID;
	}

	// motherName
	function setMotherName(activeMother) {
		let motherName;
		motherName = activeMother.slaveName;
		if (activeMother.slaveSurname !== 0 && activeMother.slaveSurname !== "") {
			motherName += ` ${activeMother.slaveSurname}`;
		}
		return motherName;
	}

	// fatherID
	function setFatherID(actor2) {
		return actor2;
	}

	// fatherName
	function setFatherName(father, activeFather, actor2) {
		let fatherName;
		if (father !== 0) {
			fatherName = activeFather.slaveName;
			if (activeFather.slaveSurname !== 0 && activeFather.slaveSurname !== "") {
				fatherName += ` ${activeFather.slaveSurname}`;
			}
		} else {
			switch (actor2) {
				case -2:
				case -5:
					fatherName = "Citizen";
					break;
				case -3:
					fatherName = "Your Master";
					break;
				case -4:
					fatherName = "Another arcology owner";
					break;
				case -6:
					fatherName = "The Societal Elite";
					break;
				case -7:
					fatherName = "Lab designed";
					break;
				case -9:
					fatherName = "A Futanari Sister";
					break;
				default:
					fatherName = "Unknown";
			}
		}
		return fatherName;
	}

	// nationality
	function setNationality(father, mother) {
		if (father === 0 || father.nationality !== mother.nationality) {
			return "Stateless";
		} else {
			return mother.nationality;
		}
	}

	// race
	function setRace(father, mother, actor2) {
		let race;
		let fatherRace = 0;
		if (father !== 0) {
			if (mother.origRace === father.origRace) {
				race = mother.origRace;
			} else if (jsRandom(1, 4) === 4) {
				race = jsEither([father.origRace, mother.origRace]);
			} else {
				race = "mixed race";
			}
		} else {
			if (((actor2 === -2 || actor2 === -5) && V.arcologies[0].FSSupremacistLawME === 1) || (actor2 === -6 && V.arcologies[0].FSSupremacist !== "unset")) {
				fatherRace = V.arcologies[0].FSSupremacistRace;
			} else if (((actor2 === -2 || actor2 === -5) && V.arcologies[0].FSSubjugationistLawME === 1) || (actor2 === -6 && V.arcologies[0].FSSubjugationist !== "unset")) {
				let racesList = setup.filterRacesLowercase.filter(race => race !== V.arcologies[0].FSSubjugationistRace);
				fatherRace = racesList.random();
			}
			if (fatherRace !== 0) {
				if (mother.origRace === fatherRace) {
					race = mother.origRace;
				} else if (jsRandom(1, 4) === 4) {
					race = jsEither([fatherRace, mother.origRace]);
				} else {
					race = "mixed race";
				}
			} else {
				race = mother.origRace;
			}
		}
		return race;
	}

	// skin
	function setSkin(father, mother, actor2) {
		let fatherSkin = 0;
		let dadSkinIndex;
		const skinToMelanin = {
			"pure black": 25,
			"ebony": 24,
			"black": 23,
			"dark brown": 22,
			"brown": 21,
			"light brown": 20,
			"dark beige": 19,
			"beige": 18,
			"light beige": 17,
			"dark": 16,
			"dark olive": 15,
			"bronze": 14,
			"olive": 13,
			"tan": 12,
			"light olive": 11,
			"light": 10,
			"fair": 9,
			"very fair": 8,
			"extremely fair": 7,
			"pale": 6,
			"very pale": 5,
			"extremely pale": 4,
			"white": 3,
			"ivory": 2,
			"pure white": 1
		};
		const momSkinIndex = mother ? (skinToMelanin[mother.origSkin] || 13) : 8;
		if (((actor2 === -2 || actor2 === -5) && V.arcologies[0].FSSupremacistLawME === 1) || (actor2 === -6 && V.arcologies[0].FSSupremacist !== "unset")) {
			fatherSkin = randomRaceSkin(V.arcologies[0].FSSupremacistRace);
		} else if (((actor2 === -2 || actor2 === -5) && V.arcologies[0].FSSubjugationistLawME === 1) || (actor2 === -6 && V.arcologies[0].FSSubjugationist !== "unset")) {
			let racesList = setup.filterRacesLowercase.filter(race => race !== V.arcologies[0].FSSubjugationistRace);
			fatherSkin = randomRaceSkin(racesList.random());
		}
		if (fatherSkin !== 0) {
			dadSkinIndex = father !== 0 ? (skinToMelanin[fatherSkin] || 13) : 8;
		} else {
			dadSkinIndex = father !== 0 ? (skinToMelanin[father.origSkin] || 13) : 8;
		}
		const skinIndex = Math.round(Math.random() * (dadSkinIndex - momSkinIndex) + momSkinIndex);

		let prop = "";
		for (prop in skinToMelanin) {
			if (!skinToMelanin.hasOwnProperty(prop)) { continue; }
			if (skinIndex >= skinToMelanin[prop]) { return prop; }
		}
		return prop; // skinIndex can be zero - now false?
	}

	// eyeColor
	function setEyeColor(father, mother, actor2) {
		let eyeColor;
		let fatherEye = 0;
		if (father !== 0) {
			if (mother.eye.origColor === father.eye.origColor) {
				eyeColor = mother.eye.origColor;
			} else if (["light red", "milky white", "pale gray", "pale red", "red"].contains(mother.eye.origColor)) {
				eyeColor = father.eye.origColor;
			} else if (["light red", "milky white", "pale gray", "pale red", "red"].contains(father.eye.origColor)) {
				eyeColor = mother.eye.origColor;
			} else if (["blue", "dark blue", "deep blue", "light blue", "light grey"].contains(mother.eye.origColor)) {
				if (jsRandom(1, 4) === 2) {
					eyeColor = mother.eye.origColor;
				} else {
					eyeColor = father.eye.origColor;
				}
			} else if (["blue", "dark blue", "deep blue", "light blue", "light grey"].contains(father.eye.origColor)) {
				if (jsRandom(1, 4) === 2) {
					eyeColor = father.eye.origColor;
				} else {
					eyeColor = mother.eye.origColor;
				}
			} else {
				eyeColor = jsEither([father.eye.origColor, mother.eye.origColor]);
			}
		} else {
			if (((actor2 === -2 || actor2 === -5) && V.arcologies[0].FSSupremacistLawME === 1) || (actor2 === -6 && V.arcologies[0].FSSupremacist !== "unset")) {
				fatherEye = randomRaceEye(V.arcologies[0].FSSupremacistRace);
			} else if (((actor2 === -2 || actor2 === -5) && V.arcologies[0].FSSubjugationistLawME === 1) || (actor2 === -6 && V.arcologies[0].FSSubjugationist !== "unset")) {
				let racesList = setup.filterRacesLowercase.filter(race => race !== V.arcologies[0].FSSubjugationistRace);
				fatherEye = randomRaceEye(racesList.random());
			}
			if (fatherEye !== 0) {
				if (mother.eye.origColor === fatherEye) {
					eyeColor = mother.eye.origColor;
				} else if (["light red", "milky white", "pale gray", "pale red", "red"].contains(mother.eye.origColor)) {
					eyeColor = fatherEye;
				} else if (["blue", "dark blue", "deep blue", "light blue", "light grey"].contains(mother.eye.origColor)) {
					if (jsRandom(1, 4) === 2) {
						eyeColor = mother.eye.origColor;
					} else {
						eyeColor = fatherEye;
					}
				} else if (["blue", "dark blue", "deep blue", "light blue", "light grey"].contains(fatherEye)) {
					if (jsRandom(1, 4) === 2) {
						eyeColor = fatherEye;
					} else {
						eyeColor = mother.eye.origColor;
					}
				} else {
					eyeColor = jsEither([fatherEye, mother.eye.origColor]);
				}
			} else {
				eyeColor = mother.eye.origColor;
			}
		}
		// just in case something wrong gets through
		switch (eyeColor) {
			case "blind blue":
				eyeColor = "deep blue";
				break;
			case "milky white":
			case "implant":
				eyeColor = jsEither(["blue", "brown", "dark blue", "dark green", "green", "hazel", "light blue", "light green"]);
				break;
		}
		return eyeColor;
	}

	function setHeterochromaticEyeColor(father, mother, actor2) {
		let hEyeColor;
		let fatherHEye = 0;
		let eyeColorArray = [];
		if (father !== 0) {
			eyeColorArray.push(mother.eye.origColor);
			eyeColorArray.push(father.eye.origColor);
			if (father.geneticQuirks.heterochromia !== 0 && father.geneticQuirks.heterochromia !== 1) {
				eyeColorArray.push(father.geneticQuirks.heterochromia);
			}
		} else {
			if (((actor2 === -2 || actor2 === -5) && V.arcologies[0].FSSupremacistLawME === 1) || (actor2 === -6 && V.arcologies[0].FSSupremacist !== "unset")) {
				fatherHEye = randomRaceEye(V.arcologies[0].FSSupremacistRace);
			} else if (((actor2 === -2 || actor2 === -5) && V.arcologies[0].FSSubjugationistLawME === 1) || (actor2 === -6 && V.arcologies[0].FSSubjugationist !== "unset")) {
				let racesList = setup.filterRacesLowercase.filter(race => race !== V.arcologies[0].FSSubjugationistRace);
				fatherHEye = randomRaceEye(racesList.random());
			}
			if (fatherHEye !== 0) {
				eyeColorArray.push(fatherHEye);
				eyeColorArray.push(mother.eye.origColor);
			} else {
				eyeColorArray.push(mother.eye.origColor);
			}
		}
		if (mother.geneticQuirks.heterochromia !== 0 && mother.geneticQuirks.heterochromia !== 1) {
			eyeColorArray.push(mother.geneticQuirks.heterochromia);
		}
		// just in case something wrong gets through
		switch (hEyeColor) {
			case "blind blue":
				hEyeColor = ["deep blue"];
				break;
			case "milky white":
			case "implant":
				hEyeColor = jsEither(["blue", "brown", "dark blue", "dark green", "green", "hazel", "light blue", "light green"]);
				break;
		}
		return jsEither(eyeColorArray);
	}

	// hColor
	function setHColor(father, mother, actor2) {
		let hairColor;
		let fatherHair = 0;
		if (father !== 0) {
			if (mother.origHColor === father.origHColor) {
				hairColor = mother.origHColor;
			} else if (mother.origHColor === "white") {
				hairColor = jsRandom(1, 100) === 69 ? mother.origHColor : father.origHColor;
			} else if (father.origHColor === "white") {
				hairColor = jsRandom(1, 100) === 69 ? father.origHColor : mother.origHColor;
			} else if (["black", "jet black"].contains(mother.origHColor)) {
				hairColor = jsEither([father.origHColor, mother.origHColor, mother.origHColor, mother.origHColor, mother.origHColor, mother.origHColor, mother.origHColor, mother.origHColor]);
			} else if (["black", "jet black"].contains(father.origHColor)) {
				hairColor = jsEither([father.origHColor, father.origHColor, father.origHColor, father.origHColor, father.origHColor, father.origHColor, father.origHColor, mother.origHColor]);
			} else if (["brown", "chestnut", "chocolate", "dark brown"].contains(mother.origHColor)) {
				hairColor = jsEither([father.origHColor, mother.origHColor, mother.origHColor, mother.origHColor]);
			} else if (["brown", "chestnut", "chocolate", "dark brown"].contains(father.origHColor)) {
				hairColor = jsEither([father.origHColor, father.origHColor, father.origHColor, mother.origHColor]);
			} else {
				hairColor = jsEither([father.origHColor, mother.origHColor]);
			}
		} else {
			if (((actor2 === -2 || actor2 === -5) && V.arcologies[0].FSSupremacistLawME === 1) || (actor2 === -6 && V.arcologies[0].FSSupremacist !== "unset")) {
				fatherHair = randomRaceHair(V.arcologies[0].FSSupremacistRace);
			} else if (((actor2 === -2 || actor2 === -5) && V.arcologies[0].FSSubjugationistLawME === 1) || (actor2 === -6 && V.arcologies[0].FSSubjugationist !== "unset")) {
				let racesList = setup.filterRacesLowercase.filter(race => race !== V.arcologies[0].FSSubjugationistRace);
				fatherHair = randomRaceHair(racesList.random());
			}
			if (fatherHair !== 0) {
				if (mother.origHColor === fatherHair) {
					hairColor = mother.origHColor;
				} else if (mother.origHColor === "white") {
					hairColor = jsRandom(1, 100) === 69 ? mother.origHColor : fatherHair;
				} else if (["black", "jet black"].contains(mother.origHColor)) {
					hairColor = jsEither([fatherHair, mother.origHColor, mother.origHColor, mother.origHColor, mother.origHColor, mother.origHColor, mother.origHColor, mother.origHColor]);
				} else if (["black", "jet black"].contains(father.origHColor)) {
					hairColor = jsEither([fatherHair, fatherHair, fatherHair, fatherHair, fatherHair, fatherHair, fatherHair, mother.origHColor]);
				} else if (["brown", "chestnut", "chocolate", "dark brown"].contains(mother.origHColor)) {
					hairColor = jsEither([fatherHair, mother.origHColor, mother.origHColor, mother.origHColor]);
				} else if (["brown", "chestnut", "chocolate", "dark brown"].contains(father.origHColor)) {
					hairColor = jsEither([fatherHair, fatherHair, fatherHair, mother.origHColor]);
				} else {
					hairColor = jsEither([fatherHair, mother.origHColor]);
				}
			} else {
				hairColor = mother.origHColor;
			}
		}
		return hairColor;
	}

	// underArmHairStyle
	function setUnderArmHStyle(father, mother) {
		let hair;
		if (father !== 0) {
			if (mother.underArmHStyle === "hairless" && father.underArmHStyle === "hairless") {
				hair = "hairless";
			} else if (mother.underArmHStyle === "hairless" || father.underArmHStyle === "hairless") {
				hair = (jsRandom(1, 5) === 3) ? "hairless" : jsEither(["bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "hairless"]);
			} else {
				hair = jsEither(["bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "hairless"]);
			}
		} else if (mother.underArmHStyle === "hairless") {
			hair = (jsRandom(1, 5) === 3) ? "hairless" : jsEither(["bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "hairless"]);
		} else {
			hair = jsEither(["bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "hairless"]);
		}
		return hair;
	}

	// pubicHairStyle
	function setPubicHStyle(father, mother) {
		let hair;
		if (father !== 0) {
			if (mother.pubicHStyle === "hairless" && father.pubicHStyle === "hairless") {
				hair = "hairless";
			} else if (mother.pubicHStyle === "hairless" || father.pubicHStyle === "hairless") {
				hair = (jsRandom(1, 5) === 3) ? "hairless" : jsEither(["bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "hairless"]);
			} else {
				hair = jsEither(["bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "hairless"]);
			}
		} else if (mother.pubicHStyle === "hairless") {
			hair = (jsRandom(1, 5) === 3) ? "hairless" : jsEither(["bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "hairless"]);
		} else {
			hair = jsEither(["bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "hairless"]);
		}
		return hair;
	}

	// markings
	function setMarkings(father, mother) {
		let markings;
		if (jsRandom(1, 8) === 1) {
			markings = jsEither(["beauty mark", "birthmark"]);
		} else {
			markings = "none";
		}
		if (markings === "none") {
			if (father !== 0) {
				markings = jsEither(["none", "none", father.markings, mother.markings]);
			} else {
				markings = jsEither(["none", "none", mother.markings, mother.markings]);
			}
		}
		return markings;
	}

	// sexualFlaw
	function setSexualFlaw(father, mother) {
		let flaw;
		if (father !== 0) {
			flaw = jsEither(["none", "none", father.sexualFlaw, mother.sexualFlaw]);
		} else {
			flaw = jsEither(["none", "none", mother.sexualFlaw, mother.sexualFlaw]);
		}
		return flaw;
	}

	// behavioralFlaw
	function setBehavioralFlaw(father, mother) {
		let flaw;
		if (father !== 0) {
			flaw = jsEither(["none", "none", father.behavioralFlaw, mother.behavioralFlaw]);
		} else {
			flaw = jsEither(["none", "none", mother.behavioralFlaw, mother.behavioralFlaw]);
		}
		return flaw;
	}

	// fetish
	function setFetish(father, mother) {
		let fetish;
		if (father !== 0) {
			fetish = jsEither(["none", "none", "none", "none", "none", father.fetish, mother.fetish]);
		} else {
			fetish = jsEither(["none", "none", "none", "none", "none", mother.fetish, mother.fetish]);
		}
		if (fetish === "mindbroken") { fetish = "none"; }
		return fetish;
	}

	// intelligence
	function setIntelligence(father, mother, activeMother, actor2) {
		let smarts;
		if (mother.ID === -1) {
			if (actor2 === -6) {
				smarts = Math.clamp(normalRandInt(95, 2.5), -100, 100);
			} else if (father !== 0) {
				smarts = fuzzy(father.intelligence, mother.intelligence);
				// player is considered "good stock"
				while (smarts < 50) {
					smarts = fuzzy(father.intelligence, mother.intelligence);
				}
			} else {
				smarts = Math.clamp(normalRandInt(75, 12.5), -100, 100);
			}
		} else if (father !== 0) {
			smarts = fuzzy(father.intelligence, mother.intelligence);
			// elite slaves are also considered "good stock"
			while (activeMother.breedingMark && smarts < 50) {
				smarts = fuzzy(father.intelligence, mother.intelligence);
			}
		} else {
			smarts = mother.intelligence;
		}
		if (V.inbreeding === 1) {
			if (mother.ID !== -1) {
				if (father !== 0 && father.ID === -1 && activeMother.breedingMark !== 1) {
					if (smarts >= -95 && jsRandom(1, 100) < 40) {
						smarts -= jsRandom(1, 10);
						if (smarts >= -95 && jsRandom(1, 100) < 20) {
							smarts -= jsRandom(1, 5);
						}
					}
				} else {
					if (smarts >= -95 && jsRandom(1, 100) < 50) {
						smarts -= jsRandom(1, 15);
						if (smarts >= -95 && jsRandom(1, 100) < 30) {
							smarts -= jsRandom(1, 15);
						}
					}
				}
			}
		}
		return Math.clamp(smarts, -100, 100);
	}

	// face
	function setFace(father, mother, activeMother, actor2, genes) {
		let face;
		if (genes.pFace > 0 && genes.uFace > 0) {
			face = 0;
		} else if (genes.pFace > 0) {
			face = 100;
		} else if (genes.uFace > 0) {
			face = -100;
		} else if (mother.ID === -1) {
			if (actor2 === -6) {
				face = Math.clamp(normalRandInt(95, 2.5), -100, 100);
			} else if (father !== 0) {
				face = fuzzy(father.face, mother.face);
				// the player is considered "good stock"
				while (face < 50) {
					face = fuzzy(father.face, mother.face);
				}
			} else {
				face = Math.clamp(normalRandInt(60, 20), -100, 100);
			}
		} else if (father !== 0) {
			face = fuzzy(father.face, mother.face);
			// elite slaves are also considered "good stock"
			while (activeMother.breedingMark && face < 50) {
				face = fuzzy(father.face, mother.face);
			}
		} else {
			face = mother.face;
		}
		if (V.inbreeding === 1 && genes.pFace === 0 && genes.uFace === 0) {
			if (mother.ID !== -1) {
				if (father !== 0 && father.ID === -1 && activeMother.breedingMark !== 1) {
					if (face > -100 && jsRandom(1, 100) > 60) {
						face -= jsRandom(2, 20);
					}
				} else {
					if (face > -100 && jsRandom(1, 100) < 50) {
						face -= jsRandom(1, 15);
						if (face >= -95 && jsRandom(1, 100) < 30) {
							face -= jsRandom(5, 20);
						}
					}
				}
			}
		}
		return Math.clamp(face, -100, 100);
	}

	// face shape
	function setFaceShape(father, mother, genes) {
		let shape;
		if (genes.androgyny === 2) {
			shape = "androgynous";
		} else if (father !== 0) {
			if (mother.faceShape === father.faceShape) {
				shape = mother.faceShape;
			} else {
				shape = jsEither(["androgynous", "androgynous", "cute", "cute", "exotic", "normal", "normal", "sensual", mother.faceShape, mother.faceShape]);
			}
		} else {
			shape = jsEither(["androgynous", "androgynous", "cute", "cute", "exotic", "normal", "normal", "sensual", mother.faceShape, mother.faceShape]);
		}
		return shape;
	}

	/**
	 * Genetic quirks
	 * @param {App.Entity.SlaveState|number} father
	 * @param {App.Entity.SlaveState} mother
	 * @param {string} sex
	 * @returns {Object}
	 */
	function setGeneticQuirks(father, mother, sex) {
		let quirks = {
			macromastia: 0,
			gigantomastia: 0,
			fertility: 0,
			hyperFertility: 0,
			superfetation: 0,
			gigantism: 0,
			dwarfism: 0,
			pFace: 0,
			uFace: 0,
			albinism: 0,
			heterochromia: 0,
			rearLipedema: 0,
			wellHung: 0,
			wGain: 0,
			wLoss: 0,
			mGain: 0,
			mLoss: 0,
			androgyny: 0
		};
		let chance = 0;
		let fathergenes = 0;
		let genetarget = 0;

		// Genetics implementation
		// Autosomal recessive: For each gene, add up gene level (0, 1, or 2) for both parents; if total level 1 or above, then roll a d16.
		// If result is less than 2^(total gene level), child manifests the quirk.
		// Otherwise, if result is less than 3*2^(total gene level), child is carrier of the quirk.
		// This precisely duplicates autosomal recessive behavior for cases where both parents manifest, one manifests and one is carrier, or where both are carriers.
		// If one manifests and one normal, it behaves the same as both carriers instead of having 100% carrier children; result is more interesting this way.
		// If one carrier and one normal, it gives 1/8th manifesting, 1/4th carrier; small overall chance, more interesting than the realistic 50% carrier.

		// Sex-linked recessive: ???
		// realism would be, looking at hypothetical x-carried gene that by its nature can only affect women
		// Male carrier, female with condition:		100% of daughters have condition, 100% of sons carriers					genetotal 3
		// Normal male, female with condition:		100% of daughters are carriers, 100% of sons are carriers				genetotal 2
		// Carrier male, carrier female:			50% of daughters have condition, 50% carriers; 50% of sons carriers.	genetotal 2
		// Normal male, carrier female:				50% of daughters carriers,	50% of sons carriers						genetotal 1
		// Carrier male, normal female:				100% of daughters carriers, sons normal									genetotal 1

		// Sex-linked traits (fertility-affecting, well-hung) left handled by the old method; latter made mirror image to former.

		// fertility
		if (mother.geneticQuirks.fertility === 2) {
			if (sex === "XX") {
				quirks.fertility = 2;
			} else {
				quirks.fertility = 1;
			}
		} else if (mother.geneticQuirks.fertility === 1) {
			chance = jsRandom(0, 1000);
			if (father !== 0) {
				if (father.geneticQuirks.fertility >= 1) {
					if (sex === "XX") {
						if (chance > 500) {
							quirks.fertility = 2;
						} else if (chance > 50) {
							quirks.fertility = 1;
						}
					} else {
						if (chance > 500) {
							quirks.fertility = 1;
						}
					}
				}
			} else {
				if (sex === "XX") {
					if (chance > 950) {
						quirks.fertility = 2;
					} else if (chance > 200) {
						quirks.fertility = 1;
					}
				} else {
					if (chance > 500) {
						quirks.fertility = 1;
					}
				}
			}
		}

		// hyper fertility
		if (mother.geneticQuirks.hyperFertility === 2) {
			if (sex === "XX") {
				quirks.hyperFertility = 2;
			} else {
				quirks.hyperFertility = 1;
			}
		} else if (mother.geneticQuirks.hyperFertility === 1) {
			chance = jsRandom(0, 1000);
			if (father !== 0) {
				if (father.geneticQuirks.hyperFertility >= 1) {
					if (sex === "XX") {
						if (chance > 750) {
							quirks.hyperFertility = 2;
						} else if (chance > 500) {
							quirks.hyperFertility = 1;
						}
					} else {
						if (chance > 500) {
							quirks.hyperFertility = 1;
						}
					}
				}
			} else {
				if (sex === "XX") {
					if (chance > 950) {
						quirks.hyperFertility = 2;
					} else if (chance > 700) {
						quirks.hyperFertility = 1;
					}
				} else {
					if (chance > 700) {
						quirks.hyperFertility = 1;
					}
				}
			}
		}

		// superfetation
		if (mother.geneticQuirks.superfetation === 2) {
			if (sex === "XX") {
				quirks.superfetation = 2;
			} else {
				quirks.superfetation = 1;
			}
		} else if (mother.geneticQuirks.superfetation === 1) {
			chance = jsRandom(0, 1000);
			if (father !== 0) {
				if (father.geneticQuirks.superfetation >= 1) {
					if (sex === "XX") {
						if (chance > 750) {
							quirks.superfetation = 2;
						} else if (chance > 700) {
							quirks.superfetation = 1;
						}
					} else {
						if (chance > 700) {
							quirks.superfetation = 1;
						}
					}
				}
			} else {
				if (sex === "XX") {
					if (chance > 950) {
						quirks.superfetation = 2;
					} else if (chance > 900) {
						quirks.superfetation = 1;
					}
				} else {
					if (chance > 900) {
						quirks.superfetation = 1;
					}
				}
			}
		}

		// well hung
		if (father !== 0) {
			if (father.geneticQuirks.wellHung === 2) {
				if (sex === "XY") {
					quirks.wellHung = 2;
				} else {
					quirks.wellHung = 1;
				}
			} else if (father.geneticQuirks.wellHung === 1) {
				chance = jsRandom(0, 1000);
				if (mother.geneticQuirks.wellHung >= 1) {
					if (sex === "XY") {
						if (chance > 750) {
							quirks.wellHung = 2;
						} else if (chance > 700) {
							quirks.wellHung = 1;
						}
					} else {
						if (chance > 700) {
							quirks.wellHung = 1;
						}
					}
				}
			}
		}

		// perfect face
		if (father !== 0) {
			fathergenes = father.geneticQuirks.pFace;
		}
		genetarget = Math.pow(2, mother.geneticQuirks.pFace + fathergenes);
		if (genetarget >= 2) {
			chance = jsRandom(1, 16);
			if (chance <= genetarget) {
				quirks.pFace = 2;
			} else if (chance <= 3 * genetarget) {
				quirks.pFace = 1;
			}
		}

		// ugly face
		if (father !== 0) {
			fathergenes = father.geneticQuirks.uFace;
		}
		genetarget = Math.pow(2, mother.geneticQuirks.uFace + fathergenes);
		if (genetarget >= 2) {
			chance = jsRandom(1, 16);
			if (chance <= genetarget) {
				quirks.uFace = 2;
			} else if (chance <= 3 * genetarget) {
				quirks.uFace = 1;
			}
		}

		// gigantism
		if (father !== 0) {
			fathergenes = father.geneticQuirks.gigantism;
		}
		genetarget = Math.pow(2, mother.geneticQuirks.gigantism + fathergenes);
		if (genetarget >= 2) {
			chance = jsRandom(1, 16);
			if (chance <= genetarget) {
				quirks.gigantism = 2;
			} else if (chance <= 3 * genetarget) {
				quirks.gigantism = 1;
			}
		}

		// dwarfism
		if (father !== 0) {
			fathergenes = father.geneticQuirks.dwarfism;
		}
		genetarget = Math.pow(2, mother.geneticQuirks.dwarfism + fathergenes);
		if (genetarget >= 2) {
			chance = jsRandom(1, 16);
			if (chance <= genetarget) {
				quirks.dwarfism = 2;
			} else if (chance <= 3 * genetarget) {
				quirks.dwarfism = 1;
			}
		}

		// albinism
		if (father !== 0) {
			fathergenes = father.geneticQuirks.albinism;
		}
		genetarget = Math.pow(2, mother.geneticQuirks.albinism + fathergenes);
		if (genetarget >= 2) {
			chance = jsRandom(1, 16);
			if (chance <= genetarget) {
				quirks.albinism = 2;
			} else if (chance <= 3 * genetarget) {
				quirks.albinism = 1;
			}
		}

		// heterochromia
		if (father !== 0) {
			fathergenes = father.geneticQuirks.heterochromia;
		}
		genetarget = Math.pow(2, mother.geneticQuirks.heterochromia + fathergenes);
		if (genetarget >= 2) {
			chance = jsRandom(1, 16);
			if (chance <= genetarget) {
				quirks.heterochromia = 2;
			} else if (chance <= 3 * genetarget) {
				quirks.heterochromia = 1;
			}
		}

		// rear lipedema
		if (father !== 0) {
			fathergenes = father.geneticQuirks.rearLipedema;
		}
		genetarget = Math.pow(2, mother.geneticQuirks.rearLipedema + fathergenes);
		if (genetarget >= 2) {
			chance = jsRandom(1, 16);
			if (chance <= genetarget) {
				quirks.rearLipedema = 2;
			} else if (chance <= 3 * genetarget) {
				quirks.rearLipedema = 1;
			}
		}

		// Gigantomastia
		if (father !== 0) {
			fathergenes = father.geneticQuirks.gigantomastia;
		}
		genetarget = Math.pow(2, mother.geneticQuirks.gigantomastia + fathergenes);
		if (genetarget >= 2) {
			chance = jsRandom(1, 16);
			if (chance <= genetarget) {
				quirks.gigantomastia = 2;
			} else if (chance <= 3 * genetarget) {
				quirks.gigantomastia = 1;
			}
		}

		// Macromastia
		if (father !== 0) {
			fathergenes = father.geneticQuirks.macromastia;
		}
		genetarget = Math.pow(2, mother.geneticQuirks.macromastia + fathergenes);
		if (genetarget >= 2) {
			chance = jsRandom(1, 16);
			if (chance <= genetarget) {
				quirks.macromastia = 2;
			} else if (chance <= 3 * genetarget) {
				quirks.macromastia = 1;
			}
		}

		// myotonic hypertrophy
		if (father !== 0) {
			fathergenes = father.geneticQuirks.mGain;
		}
		genetarget = Math.pow(2, mother.geneticQuirks.mGain + fathergenes);
		if (genetarget >= 2) {
			chance = jsRandom(1, 16);
			if (chance <= genetarget) {
				quirks.mGain = 2;
			} else if (chance <= 3 * genetarget) {
				quirks.mGain = 1;
			}
		}

		// myotonic dystrophy
		if (father !== 0) {
			fathergenes = father.geneticQuirks.mLoss;
		}
		genetarget = Math.pow(2, mother.geneticQuirks.mLoss + fathergenes);
		if (genetarget >= 2) {
			chance = jsRandom(1, 16);
			if (chance <= genetarget) {
				quirks.mLoss = 2;
			} else if (chance <= 3 * genetarget) {
				quirks.mLoss = 1;
			}
		}

		// hyperleptinemia
		if (father !== 0) {
			fathergenes = father.geneticQuirks.wGain;
		}
		genetarget = Math.pow(2, mother.geneticQuirks.wGain + fathergenes);
		if (genetarget >= 2) {
			chance = jsRandom(1, 16);
			if (chance <= genetarget) {
				quirks.wGain = 2;
			} else if (chance <= 3 * genetarget) {
				quirks.wGain = 1;
			}
		}

		// hypoleptinemia
		if (father !== 0) {
			fathergenes = father.geneticQuirks.wLoss;
		}
		genetarget = Math.pow(2, mother.geneticQuirks.wLoss + fathergenes);
		if (genetarget >= 2) {
			chance = jsRandom(1, 16);
			if (chance <= genetarget) {
				quirks.wLoss = 2;
			} else if (chance <= 3 * genetarget) {
				quirks.wLoss = 1;
			}
		}

		// androgyny
		if (father !== 0) {
			fathergenes = father.geneticQuirks.androgyny;
		}
		genetarget = Math.pow(2, mother.geneticQuirks.androgyny + fathergenes);
		if (genetarget >= 2) {
			chance = jsRandom(1, 16);
			if (chance <= genetarget) {
				quirks.androgyny = 2;
			} else if (chance <= 3 * genetarget) {
				quirks.androgyny = 1;
			}
		}

		return clone(quirks);
	}

	return generateGenetics;
})();

/**
 * @param {App.Entity.SlaveState} mother
 * @returns {App.Entity.SlaveState|App.Entity.InfantState}
 */
globalThis.generateChild = function(mother, ova, destination) {
	let genes = ova.genetics; // maybe just argument this? We'll see.
	let child = {};

	function setSurname() {
		child.slaveSurname = genes.surname;
		if (genes.clone) {
			if (genes.cloneID === -1) {
				child.slaveSurname = V.PC.slaveSurname;
			} else {
				let cloneSeed = getSlave(genes.cloneID);
				if (cloneSeed !== undefined) {
					if (cloneSeed.slaveSurname !== 0 && cloneSeed.slaveSurname !== "") {
						child.slaveSurname = cloneSeed.slaveSurname;
					}
				}
			}
		} else if (genes.mother === -1 || genes.father === -1) {
			child.slaveSurname = V.PC.slaveSurname;
		} else if (genes.father > 0) {
			let currentMother = getSlave(genes.mother);
			if (currentMother !== undefined) {
				if (currentMother.slaveSurname !== 0 && currentMother.slaveSurname !== "") {
					child.slaveSurname = currentMother.slaveSurname;
				}
			} else {
				let currentFather = getSlave(genes.father);
				if (currentFather !== undefined) {
					if (currentFather.slaveSurname !== 0 && currentFather.slaveSurname !== "") {
						child.slaveSurname = currentFather.slaveSurname;
					}
				}
			}
		} else {
			let currentMother = getSlave(genes.mother);
			if (currentMother !== undefined) {
				if (currentMother.slaveSurname !== 0 && currentMother.slaveSurname !== "") {
					child.slaveSurname = currentMother.slaveSurname;
				}
			}
		}
	}

	function setSlaveName() {
		const relString = child.genes === "XX" ? "daughter" : "son";
		if (!(V.pregnancyMonitoringUpgrade) || genes.name.indexOf("ovum") === 0) {
			if (genes.clone) {
				if (genes.cloneID === -1) {
					child.slaveName = `Your clone`;
				} else {
					child.slaveName = `${genes.clone}'s clone`;
				}
			} else if (genes.mother === -1) {
				if (genes.father <= 0) {
					child.slaveName = `Your ${relString}`;
				} else {
					child.slaveName = `Your and ${genes.fatherName}'s ${relString}`;
				}
			} else if (genes.father === -1) {
				child.slaveName = `${genes.motherName}'s and your ${relString}`;
			} else if (genes.father > 0) {
				child.slaveName = `${genes.motherName} and ${genes.fatherName}'s ${relString}`;
			} else {
				child.slaveName = `${genes.motherName}'s bastard ${relString}`;
			}
		} else {
			child.slaveName = genes.name;
		}
	}

	function makeAlbinismOverride(race) {
		let albinismOverride = {
			skin: 0,
			eyeColor: "red",
			hColor: "white"
		};
		switch (race) {
			case "black":
			case "indo-aryan":
			case "malay":
			case "pacific islander":
			case "amerindian":
				albinismOverride.skin = "very fair";
				break;
			case "latina":
				albinismOverride.skin = "extremely fair";
				break;
			case "asian":
			case "middle eastern":
			case "semitic":
			case "southern european":
			case "white":
				albinismOverride.skin = "pure white";
				break;
			default:
				albinismOverride.skin = "pure white";
		}
		return albinismOverride;
	}

	if (!destination) { // does extra work for the incubator if defined, otherwise builds a simple object
		child.genes = genes.gender;
		setSlaveName();
		setSurname();

		child.mother = genes.mother;
		child.father = genes.father;
		child.nationality = genes.nationality;
		child.race = genes.race;
		child.intelligence = genes.intelligence;
		if (mother.prematureBirth > 0) {
			if (child.intelligence >= -90) {
				child.intelligence -= jsRandom(0, 10);
			}
			child.premature = 1;
		}
		child.face = genes.face;
		child.underArmHStyle = genes.underArmHStyle;
		child.pubicHStyle = genes.pubicHStyle;
		child.markings = genes.markings;
		child.sexualFlaw = genes.sexualFlaw;
		child.behavioralFlaw = genes.behavioralFlaw;
		child.fetish = genes.fetish;
		child.geneticQuirks = clone(genes.geneticQuirks);
		if (child.geneticQuirks.albinism === 2) {
			child.albinismOverride = makeAlbinismOverride(child.race);
		}
		child.origSkin = genes.skin;
		child.eyeColor = genes.eyeColor;
		child.origHColor = genes.hColor;
		child.skin = getGeneticSkinColor(child);
		child.hColor = getGeneticHairColor(child);
		child.pubicHColor = child.hColor;
		child.underArmHColor = child.hColor;
		child.eyebrowHColor = child.hColor;
		child.birthWeek = 0;
		child.actualAge = 0;
		if (genes.faceShape !== undefined) {
			child.faceShape = genes.faceShape;
		}
		if (mother.addict > 0) {
			child.addict = Math.trunc(mother.addict / 2);
		}
		child.weekAcquired = V.week;
		if (child.nationality === "Stateless") {
			const revivalistNationality = getRevivalistNationality();
			if (typeof revivalistNationality === 'string') {
				child.nationality = revivalistNationality;
			}
		}
	} else {
		const fixedAge = {
			minAge: V.targetAge,
			maxAge: V.targetAge,
			ageOverridesPedoMode: 1,
			mature: 0
		};

		child = GenerateNewSlave(genes.gender, fixedAge);
		setSlaveName();
		setSurname();

		child.actualAge = 0;
		if (genes.clone !== undefined) {
			child.clone = genes.clone;
			child.cloneID = genes.cloneID;
		}
		child.mother = genes.mother;
		child.father = genes.father;
		child.nationality = genes.nationality;
		child.race = genes.race;
		child.origRace = child.race;
		child.geneticQuirks = clone(genes.geneticQuirks);
		if (child.geneticQuirks.albinism === 2) {
			child.albinismOverride = makeAlbinismOverride(child.race);
		}
		child.origSkin = genes.skin;
		child.eye.origColor = genes.eyeColor;
		child.origHColor = genes.hColor;
		child.skin = getGeneticSkinColor(child);
		child.hColor = getGeneticHairColor(child);
		resetEyeColor(child, "both");
		child.pubicHColor = child.hColor;
		child.underArmHColor = child.hColor;
		child.eyebrowHColor = child.hColor;
		child.intelligence = genes.intelligence;
		if (mother.prematureBirth > 0) {
			if (child.intelligence >= -90) {
				child.intelligence -= jsRandom(0, 10);
			}
			child.premature = 1;
		}
		if (child.geneticQuirks.dwarfism === 2 && child.geneticQuirks.gigantism !== 2) {
			child.height = Height.random(child, {limitMult: [-4, -1], spread: 0.15});
		} else if (child.geneticQuirks.gigantism === 2 && child.geneticQuirks.dwarfism !== 2) {
			child.height = Height.random(child, {limitMult: [3, 10], spread: 0.15});
		}
		child.face = genes.face;
		child.underArmHStyle = genes.underArmHStyle;
		child.pubicHStyle = genes.pubicHStyle;
		child.markings = genes.markings;
		child.sexualFlaw = genes.sexualFlaw;
		child.behavioralFlaw = genes.behavioralFlaw;
		child.fetish = genes.fetish;
		child.birthWeek = 0;
		child.energy = 0;
		child.anus = 0;
		if (child.vagina > 0) { child.vagina = 0; }
		if (child.fetish !== "none") { child.fetishStrength = 20; }
		if (child.dick > 0) { child.foreskin = 1; child.balls = 1; child.scrotum = 1; }
		if (genes.faceShape !== undefined) { child.faceShape = genes.faceShape; }
		if (mother.addict > 0) { child.addict = Math.trunc(mother.addict / 2); }
		child.career = "a slave since birth";
		child.birthName = child.slaveName;
		child.birthSurname = child.slaveSurname;
		child.devotion = 0;
		child.trust = 0;
		child.weekAcquired = V.week;
		if (child.nationality === "Stateless") {
			const revivalistNationality = getRevivalistNationality();
			if (typeof revivalistNationality === 'string') {
				child.nationality = revivalistNationality;
			}
		}

		child.weight = -100;
		child.muscles = -100;
		child.boobs = 0;
		child.butt = 0;
		child.chem = 990;
		child.areolaePiercing = 0;
		child.corsetPiercing = 0;
		child.boobsImplant = 0;
		child.boobsImplantType = "none";
		child.nipplesPiercing = 0;
		child.areolaePiercing = 0;
		child.lactation = 0;
		child.hipsImplant = 0;
		child.buttImplant = 0;
		child.buttImplantType = "none";
		child.lipsImplant = 0;
		child.lipsPiercing = 0;
		child.tonguePiercing = 0;
		child.vaginaPiercing = 0;
		child.preg = 0;
		child.pregType = 0;
		child.pregKnown = 0;
		child.belly = 0;
		child.bellyPreg = 0;
		child.bellyFluid = 0;
		child.bellyImplant = -1;
		child.cervixImplant = 0;
		child.clitPiercing = 0;
		child.dickPiercing = 0;
		child.makeup = 0;
		child.nails = 0;
		child.earPiercing = 0;
		child.nosePiercing = 0;
		child.eyebrowPiercing = 0;
		child.stampTat = 0;
		child.bellyTat = 0;
		child.anusPiercing = 0;
		child.anusTat = 0;
		child.shouldersTat = 0;
		child.armsTat = 0;
		child.legsTat = 0;
		child.backTat = 0;
		child.skill.combat = 0;
		child.skill.whoring = 0;
		child.skill.entertainment = 0;
		child.skill.oral = 0;
		child.skill.anal = 0;
		child.skill.vaginal = 0;
		child.accent = 4;
		child.canRecruit = 0;
		child.hStyle = "long";
		child.hLength = 300;
		if (V.incubatorImprintSetting === "terror") {
			child.origin = "$He was conditioned from birth into mindless terror in an aging tank.";
			child.tankBaby = 2;
		} else if (V.incubatorImprintSetting === "trust") {
			child.origin = "$He was conditioned from birth into trusting obedience in an aging tank.";
			child.tankBaby = 1;
		} else {
			child.origin = "$His brain is blank outside of the most basic of functions.";
			child.fetish = "mindbroken";
			child.tankBaby = 3;
		}
		child.intelligenceImplant = 0;
		child.navelPiercing = 0;
	}

	generatePronouns(child);

	return child;
};