code:
/*
receiver - What is the state of the network box.
	-1: PC has not been given a welcome packet. (default)
	0: PC has been given a welcome packet but has not installed it.
	1+: box is installed.
channel[numberAsString] - how many times it has been viewed.
channel.last - program viewed last week.
pcViewership.count - How many weeks since the PC last watched FCTV.
pcViewership.frequency - How often should the PC watch FCTV.
	-1: Never.
	1: at least once a week.
	2: least every two weeks.
	4: at least once a month. (default)
remote - Does the PC have a FCTV branded remote (also used to trigger a slave acquisition event).
weekEnabled - The week FCTV was installed.
*/
globalThis.FCTV = (function() {
	return {
		channels: channels,
		initChannels: initChannels,
		channelCount: channelCount,
		FinalTouches: FinalTouches,
	};

	/** Produce an array of numbers representing all available channels in App.Data.FCTV.channels
	 * @returns {number[]}
	 */
	function channels() {
		return Object.keys(App.Data.FCTV.channels).map(chStr => parseInt(chStr, 10));
	}

	function initChannels() {
		const channelList = FCTV.channels();
		for (let i = 0; i < channelList.length; i++) {
			V.FCTV.channel[num(channelList[i], true)] = 0;
		}
	}

	/**
	 * checks value of selected channel in relation to an arbitrary number. Have we watched it more/less/same than i.
	 * @param {number} selectedChannel
	 * @param {number} i
	 * @param {string} operation eq/gt/lt: equals/greater than/less than.
	 * @returns {boolean}
	 */
	function channelCount(selectedChannel, i, operation = 'eq') {
		if (operation === 'eq') {
			if (V.FCTV.channel[num(selectedChannel, true)] === i) {
				return true;
			}
		} else if (operation === 'gt') {
			if (V.FCTV.channel[num(selectedChannel, true)] > i) {
				return true;
			}
		} else if (operation === 'lt') {
			if (V.FCTV.channel[num(selectedChannel, true)] < i) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Applies some universal changes to FCTV slaves
	 * @param {App.Entity.SlaveState} slave
	 * @returns {App.Entity.SlaveState}
	 */
	function FinalTouches(slave) {
		slave.pubertyXX = 1;
		slave.career = "a slave";
		slave.origin = "You purchased $him from FCTV's Home Slave Shopping stream channel.";
		setHealth(slave, 80, 0, 0, 0, 0);
		return slave;
	}
})();


code:
// @ts-nocheck
App.Update.FCTV = function() {
	function convert(a, b) {
		return V.FCTVreceiver ? a : b;
	}

	function initChannels() {
		const channelList = FCTV.channels();
		for (let i = 0; i < channelList.length; i++) {
			const channel = num(channelList[i], true);
			const currentChannel = 'show' + capFirstChar(channel);
			V.FCTV.channel[channel] = convert(V[currentChannel], V.FCTV.channel[channel]) || 0;
		}
	}

	V.FCTV = V.FCTV || {};
	V.FCTV.receiver = V.FCTV.receiver > -1 ? V.FCTV.receiver : -1;
	if (convert && V.receiverAvailable) {
		V.FCTV.receiver = V.FCTVreceiver > 0 ? V.FCTVreceiver : 0;
	}
	V.FCTV.pcViewership = V.FCTV.pcViewership || {};
	V.FCTV.channel = V.FCTV.channel || {};
	initChannels();
	V.FCTV.channel.last = convert(V.lastShow, V.FCTV.channel.last) || -1;

	V.FCTV.pcViewership.count = convert(V.FCTVcount, V.FCTV.pcViewership.count) || 0;
	V.FCTV.pcViewership.frequency = convert(V.FCTVrate, V.FCTV.pcViewership.frequency) || 4;
	V.FCTV.remote = convert(V.FCTVremote, V.FCTV.remote) || 0;

	if (V.FCTVreceiver && !V.FCTV.weekEnabled) {
		V.FCTV.weekEnabled = V.receiverAvailable > 1 ? V.receiverAvailable : 0;
	}

	if (V.FCTV.channel.hasOwnProperty("selected")) {
		delete V.FCTV.channel.selected;
	}
};

license: mit


code:
button.selectedslave { background-color: #b84; }
table.slave-genetic-details { width: 100%; margin: 0 10px; border: 1px solid white; table-layout: fixed; font-size: 85%; border-collapse: separate; padding: 2px; }
table.slave-genetic-details tr { padding: 0 3px; }
table.slave-genetic-details td { vertical-align: top; border: 1px solid black; padding: 0 3px; }
table.slave-genetic-details th { vertical-align: top; background-color: rgba(127, 127, 127, 0.2); padding: 0 3px; }
table.slave-genetic-details td.editor { border: 1px solid #777; }
table.slave-genetic-details td.editor:hover { border: 1px solid #8cf; transition-duration: 0.3s; }
table.slave-genetic-details td.editor input { width: calc(100% + 8px); height: 100%; min-width: 0; margin: 0 -4px; padding: 4px 5px; box-sizing: border-box; }
table.slave-genetic-details td.editor select { width: calc(100% + 8px); height: 100%; min-width: 0; margin: 0 -4px; padding: 3px 5px; box-sizing: border-box; border: 1px solid #777; }
table.slave-genetic-details td.editor select:hover { border: 1px solid #8cf; transition-duration: 0.3s; }


code:
App.UI.editGenetics = function() {
	const node = new DocumentFragment();
	const cupCat = new Categorizer(
		[0, 'flat'],
		[300, 'A-cup'],
		[400, 'B-cup'],
		[500, 'C-cup'],
		[650, 'D-cup'],
		[800, 'DD-cup'],
		[1000, 'F-cup'],
		[1200, 'G-cup'],
		[1400, 'H-cup'],
		[1600, 'I-cup'],
		[1800, 'J-cup'],
		[2050, 'K-cup'],
		[2300, 'L-cup'],
		[2600, 'M-cup'],
		[2900, 'N-cup'],
		[3250, 'O-cup'],
		[3600, 'P-cup'],
		[3950, 'Q-cup'],
		[4300, 'R-cup'],
		[4700, 'S-cup'],
		[5100, 'massive']
	);
	const faceCat = new Categorizer(
		[-Infinity, 'very ugly'],
		[-95, 'ugly'],
		[-40, 'unattractive'],
		[-10, 'attractive'],
		[11, 'very pretty'],
		[41, 'gorgeous'],
		[96, 'mind blowing']
	);
	const lipsCat = new Categorizer(
		[0, 'thin'],
		[11, 'normal'],
		[21, 'pretty'],
		[41, 'plush'],
		[71, 'huge'],
		[96, 'facepussy']
	);
	const hormonalCat = new Categorizer(
		[-Infinity, 'heavy male'],
		[-49, 'male'],
		[-24, 'natural'],
		[25, 'female'],
		[50, 'heavy female']
	);
	const intelligenceCat = new Categorizer(
		[-Infinity, 'borderline retarded'],
		[-95, 'very slow'],
		[-50, 'slow'],
		[-15, 'average'],
		[16, 'smart'],
		[51, 'very smart'],
		[96, 'brilliant']
	);
	function birthFullName(s) {
		let r = [];
		if (V.surnameOrder !== 1) {
			if (["Cambodian", "Chinese", "Ancient Chinese Revivalist", "Hungarian", "Japanese", "Edo Revivalist", "Korean", "Mongolian", "Taiwanese", "Vietnamese"].includes(s.nationality)) {
				if (s.birthSurname || s.surname) {
					r.push(s.birthSurname || s.surname);
				}
				r.push(App.UI.DOM.makeElement("span", (s.birthName || s.name), ["bold", "underline"]));
			} else {
				r.push(App.UI.DOM.makeElement("span", (s.birthName || s.name), ["bold", "underline"]));
				if (s.birthSurname || s.surname) {
					r.push(s.birthSurname || s.surname);
				}
			}
		} else {
			r.push(App.UI.DOM.makeElement("span", (s.birthName || s.name), ["bold", "underline"]));
			if (s.birthSurname || s.surname) {
				r.push(s.birthSurname || s.surname);
			}
		}
		return App.Events.makeNode(r);
	}
	function currentFullName(s) {
		let r = [];
		if (V.surnameOrder !== 1) {
			if (["Cambodian", "Chinese", "Ancient Chinese Revivalist", "Hungarian", "Japanese", "Edo Revivalist", "Korean", "Mongolian", "Taiwanese", "Vietnamese"].includes(s.nationality)) {
				if (s.slaveSurname) {
					r.push(s.slaveSurname);
				}
				r.push(App.UI.DOM.makeElement("span", s.slaveName, ["bold", "underline"]));
			} else {
				r.push(App.UI.DOM.makeElement("span", s.slaveName, ["bold", "underline"]));
				if (s.slaveSurname) {
					r.push(s.slaveSurname);
				}
			}
		} else {
			r.push(App.UI.DOM.makeElement("span", s.slaveName, ["bold", "underline"]));
			if (s.slaveSurname) {
				r.push(s.slaveSurname);
			}
		}
		return App.Events.makeNode(r);
	}
	function nameButton(s) {
		const button = App.UI.DOM.makeElement("button", birthFullName(s), "slavepicker");
		button.setAttribute("data-slave", s.ID);
		button.style.width = "100%";
		return button;
	}
	function geneDetailsFunction(s) {
		function makeRow() {
			row = App.UI.DOM.appendNewElement("tr", tbody);
		}
		function makeCell(text, colSpan) {
			let cell = App.UI.DOM.appendNewElement("td", row, text);
			if (colSpan) {
				cell.colSpan = colSpan;
			}
		}
		function makeHeader(text) {
			App.UI.DOM.appendNewElement("th", row, text);
		}

		function makeHr() {
			makeRow();
			row.append(document.createElement("td"));
			cell = App.UI.DOM.appendNewElement("td", row, App.UI.DOM.makeElement("hr"));
			cell.colSpan = 6;
			row.append(document.createElement("td"));
		}

		const table = document.createElement("table");
		table.classList.add("slave-genetic-details");
		const tbody = App.UI.DOM.appendNewElement("tbody", table);

		let row = document.createElement("tr");
		let cell;
		makeRow();

		makeHeader(`Given name(s)`);
		makeCell(s.birthName, 2);

		makeHeader(`Family name`);
		makeCell(s.birthSurname || '', 2);

		makeHeader(`Karyotype`);
		cell = App.UI.DOM.appendNewElement("td", row, `${s.genes} (${toSex(s.genes)})`, ["editor", "choice-editor"]);
		cell.setAttribute("data-param", "genes");
		cell.setAttribute("data-choices", "XX, XY");

		makeRow();

		makeHeader(`Nationality`);
		cell = App.UI.DOM.appendNewElement("td", row, s.nationality, ["editor", "string-editor"]);
		cell.colSpan = 2;
		cell.setAttribute("data-param", "nationality");

		makeHeader(`Ethnicity`);
		cell = App.UI.DOM.appendNewElement("td", row, s.origRace, ["editor", "choice-editor"]);
		cell.colSpan = 2;
		cell.setAttribute("data-param", "origRace");
		cell.setAttribute("data-choices", "amerindian, asian, black, indo-aryan, latina, malay, middle eastern, mixed race, pacific islander, semitic, southern european, catgirl, white");

		makeHeader(`Career`);
		cell = App.UI.DOM.appendNewElement("td", row, s.career ? s.career : '', ["note"]);

		makeRow();

		makeHeader(`Father`);
		makeCell(parentFullName(s.father), 2);

		makeHeader(`Mother`);
		makeCell(parentFullName(s.mother), 2);

		makeHeader(`Age`);
		makeCell(ageDesc(s));

		if (s.birthName !== s.slaveName || s.birthSurname !== s.slaveSurname) {
			makeRow();

			cell = App.UI.DOM.appendNewElement("td", row, App.Events.makeNode([`Currently called`, currentFullName(s)]), ["note"]);
			cell.colSpan = 8;
		}

		makeHr();

		makeRow();

		makeHeader(`Skin`);
		cell = App.UI.DOM.appendNewElement("td", row, s.origSkin, ["editor", "string-editor"]);
		cell.setAttribute("data-param", "origSkin");

		makeHeader(`Skin markings`);
		cell = App.UI.DOM.appendNewElement("td", row, s.origSkin, ["editor", "choice-editor"]);
		cell.setAttribute("data-param", "markings");
		cell.setAttribute("data-choices", "none, beauty mark, birthmark, freckles, heavily freckled");

		makeHeader(`Eyes`);
		cell = App.UI.DOM.appendNewElement("td", row, s.eye.origColor, ["editor", "string-editor"]);
		cell.setAttribute("data-param", "eye.origColor");

		makeHeader(`Hair`);
		cell = App.UI.DOM.appendNewElement("td", row, s.origHColor, ["editor", "string-editor"]);
		cell.setAttribute("data-param", "origHColor");

		makeRow();

		makeHeader(`Height`);
		cell = App.UI.DOM.appendNewElement("td", row, `${s.height} cm (${toFeet(s.height)})`, ["editor", "number-editor"]);
		cell.setAttribute("data-param", "height");
		cell.setAttribute("data-min", "50");
		cell.setAttribute("data-max", "250");

		makeHeader(`Weight`);
		cell = App.UI.DOM.appendNewElement("td", row, percent(s.weight), ["editor", "number-editor"]);
		cell.setAttribute("data-param", "weight");

		makeHeader(`Muscles`);
		cell = App.UI.DOM.appendNewElement("td", row, percent(s.muscles), ["editor", "number-editor"]);
		cell.setAttribute("data-param", "muscles");

		cell = App.UI.DOM.appendNewElement("td", row);
		cell.colSpan = 2;

		makeRow();

		makeHeader(`Shoulders`);
		cell = App.UI.DOM.appendNewElement("td", row, shouldersDesc(s.shoulders), ["editor", "number-editor"]);
		cell.setAttribute("data-param", "shoulders");
		cell.setAttribute("data-min", "-2");
		cell.setAttribute("data-max", "2");

		makeHeader(`Waist`);
		cell = App.UI.DOM.appendNewElement("td", row, percent(s.waist), ["editor", "number-editor"]);
		cell.setAttribute("data-param", "waist");

		makeHeader(`Hips`);
		cell = App.UI.DOM.appendNewElement("td", row, hipsDesc(s.hips), ["editor", "number-editor"]);
		cell.setAttribute("data-param", "hips");
		cell.setAttribute("data-min", "-2");
		cell.setAttribute("data-max", "3");

		makeHeader(`Rear`);
		cell = App.UI.DOM.appendNewElement("td", row, buttDesc(s.butt), ["editor", "number-editor"]);
		cell.setAttribute("data-param", "butt");
		cell.setAttribute("data-min", "0");
		cell.setAttribute("data-max", "20");

		makeRow();

		makeHeader(`Breasts`);
		cell = App.UI.DOM.appendNewElement("td", row, `${s.boobs} cc (${cupCat.cat(s.boobs)})`, ["editor", "number-editor"]);
		cell.setAttribute("data-param", "boobs");
		cell.setAttribute("data-min", "0");
		cell.setAttribute("data-max", "100000");

		makeHeader(`Breast shape`);
		cell = App.UI.DOM.appendNewElement("td", row, s.boobShape, ["editor", "choice-editor"]);
		cell.setAttribute("data-param", "boobShape");
		cell.setAttribute("data-choices", "normal, perky, saggy, torpedo-shaped, downward-facing, wide-set");

		makeHeader(`Nipples`);
		cell = App.UI.DOM.appendNewElement("td", row, s.nipples, ["editor", "choice-editor"]);
		cell.setAttribute("data-param", "nipples");
		cell.setAttribute("data-choices", "huge, puffy, inverted, tiny, cute, partially inverted");

		makeHeader(`Areolae`);
		cell = App.UI.DOM.appendNewElement("td", row, areolaeDesc(s.areolae), ["editor", "number-editor"]);
		cell.setAttribute("data-param", "areolae");
		cell.setAttribute("data-min", "0");
		cell.setAttribute("data-max", "5");

		makeRow();

		makeHeader(`Face`);
		cell = App.UI.DOM.appendNewElement("td", row, `${faceCat.cat(s.face)} (${percent(s.face)})`, ["editor", "number-editor"]);
		cell.setAttribute("data-param", "face");

		makeHeader(`Lips`);
		cell = App.UI.DOM.appendNewElement("td", row, `${lipsCat.cat(s.lips)} (${s.lips})`, ["editor", "number-editor"]);
		cell.setAttribute("data-param", "lips");
		cell.setAttribute("data-min", "0");
		cell.setAttribute("data-max", "100");

		makeHeader(`Teeth`);
		cell = App.UI.DOM.appendNewElement("td", row, s.teeth, ["editor", "choice-editor"]);
		cell.setAttribute("data-param", "teeth");
		cell.setAttribute("data-choices", "normal, crooked, straightening braces, cosmetic braces, removable, pointy, baby, mixed");

		makeHeader(`Voice`);
		cell = App.UI.DOM.appendNewElement("td", row, voiceDesc(s.voice), ["editor", "number-editor"]);
		cell.setAttribute("data-param", "voice");
		cell.setAttribute("data-min", "0");
		cell.setAttribute("data-max", "3");

		makeHr();

		if (s.vagina >= 0) {
			makeRow();

			makeHeader(`Labia`);
			cell = App.UI.DOM.appendNewElement("td", row, labiaDesc(s.labia), ["editor", "number-editor"]);
			cell.setAttribute("data-param", "labia");
			cell.setAttribute("data-min", "0");
			cell.setAttribute("data-max", "3");

			makeHeader(`Clitoris`);
			cell = App.UI.DOM.appendNewElement("td", row, clitDesc(s.clit), ["editor", "number-editor"]);
			cell.setAttribute("data-param", "clit");
			cell.setAttribute("data-min", "0");
			cell.setAttribute("data-max", "5");

			makeHeader(`Ovaries`);
			cell = App.UI.DOM.appendNewElement("td", row,
				s.ovaries
					? App.Events.makeNode(['working', App.UI.DOM.makeElement("span", "\u2714", "green")])
					: App.Events.makeNode(['broken', App.UI.DOM.makeElement("span", "\u2718", "red")])
			);
			cell.setAttribute("data-param", "ovaries");
			cell.setAttribute("data-min", "0");
			cell.setAttribute("data-max", "5");

			makeHeader(`Ovum type`);
			makeCell(s.eggType);
		}
		if (s.dick > 0 || s.balls > 0) {
			makeRow();

			makeHeader(`Penis`);
			cell = App.UI.DOM.appendNewElement("td", row, penisDesc(s.dick), ["editor", "number-editor"]);
			cell.setAttribute("data-param", "dick");
			cell.setAttribute("data-min", "0");
			cell.setAttribute("data-max", "11");

			makeHeader(`Testes`);
			cell = App.UI.DOM.appendNewElement("td", row, testesDesc(s.balls), ["editor", "number-editor"]);
			cell.setAttribute("data-param", "balls");
			cell.setAttribute("data-min", "0");
			cell.setAttribute("data-max", "10");

			makeHeader(`Prostate`);
			cell = App.UI.DOM.appendNewElement("td", row,
				s.prostate
					? App.Events.makeNode(['working', App.UI.DOM.makeElement("span", "\u2714", "green")])
					: App.Events.makeNode(['broken', App.UI.DOM.makeElement("span", "\u2718", "red")]),
			);
			cell.setAttribute("data-param", "prostate");

			makeHeader(`Sperm type`);
			makeCell(s.ballType);
		}

		makeRow();

		makeHeader(`Hormonal`);
		cell = App.UI.DOM.appendNewElement("td", row, `${hormonalCat.cat(s.hormoneBalance)} (${s.hormoneBalance})`, ["editor", "number-editor"]);
		cell.setAttribute("data-param", "hormoneBalance");

		makeHeader(`Anal area`);
		cell = App.UI.DOM.appendNewElement("td", row, s.analArea, ["editor", "number-editor"]);
		cell.setAttribute("data-param", "analArea");
		cell.setAttribute("data-min", "0");
		cell.setAttribute("data-max", "3");

		cell = App.UI.DOM.appendNewElement("td", row);
		cell.colSpan = 4;

		makeHr();

		makeRow();

		makeHeader(`Intelligence`);
		cell = App.UI.DOM.appendNewElement("td", row, intelligenceCat.cat(s.intelligence), ["editor", "number-editor"]);
		cell.setAttribute("data-param", "intelligence");
		cell.setAttribute("data-min", "-100");
		cell.setAttribute("data-max", "100");

		makeHeader(`Behavioral`);
		cell = App.UI.DOM.appendNewElement("td", row, s.behavioralFlaw !== 'none' ? s.behavioralFlaw : s.behavioralQuirk);

		makeHeader(`Sexual`);
		cell = App.UI.DOM.appendNewElement("td", row, s.sexualFlaw !== 'none' ? s.sexualFlaw : s.sexualQuirk);

		cell = App.UI.DOM.appendNewElement("td", row);
		cell.colSpan = 2;

		if (s.chem > 0 || s.addict > 0) {
			makeRow();
			makeCell(chemicalsDesc(s), 8);
		}
		return table;
	}
	function percent(n) {
		if (n > 0) { return '+' + Number(n).toFixed(0) + '%'; }
		if (n === 0) { return '\u00B10%'; }
		return Number(n).toFixed(0) + '%';
	}
	function toFeet(n) {
		let realFeet = Number(n) / 30.48;
		let feet = Math.floor(realFeet);
		let inches = Math.round((realFeet - feet) * 12);
		return feet + "\u2032" + inches + '\u2033';
	}
	function toSex(karyotype) {
		return {
			XX: 'female',
			XY: 'male',
			X: 'Turner syndrome female',
			X0: 'Turner syndrome female',
			XYY: 'XYY syndrome male',
			XXY: 'Klinefelter syndrome male',
			XXX: 'triple X syndrome female'
		}[String(karyotype).toUpperCase()] || 'unknown/not viable';
	}
	function ageDesc(slave) {
		let age = slave.actualAge + (slave.birthWeek + V.week - slave.weekAcquired) / 52.0;
		let years = Math.floor(age);
		let months = Math.floor((age - years) * 12);
		return (years > 0 ? years + 'y ' + months + 'm' : months + 'm');
	}
	function shouldersDesc(s) {
		return ({
			'-2': 'very narrow',
			'-1': 'narrow',
			'0': 'feminine',
			'1': 'broad',
			'2': 'very broad'
		}[s] || 'unknown') + ' (' + Number(s) + ')';
	}
	function hipsDesc(s) {
		return ({
			'-2': 'very narrow',
			'-1': 'narrow',
			'0': 'normal',
			'1': 'wide',
			'2': 'very wide',
			'3': 'inhumanly wide'
		}[s] || 'unknown') + ' (' + Number(s) + ')';
	}
	function buttDesc(s) {
		return ({
			'0': 'flat',
			'1': 'small',
			'2': 'plump',
			'3': 'big',
			'4': 'huge',
			'5': 'enormous',
			'6': 'gigantic',
			'7': 'ridiculous',
			'8': 'immense',
			'9': 'immense',
			'10': 'immense',
			'11': 'inhuman',
			'12': 'inhuman',
			'13': 'inhuman',
			'14': 'inhuman',
			'15': 'inhuman',
			'16': 'inhuman',
			'17': 'inhuman',
			'18': 'inhuman',
			'19': 'inhuman',
			'20': 'inhuman'
		}[s] || 'unknown') + ' (' + Number(s) + ')';
	}
	function areolaeDesc(s) {
		return ({
			'0': 'normal',
			'1': 'large',
			'2': 'unusually wide',
			'3': 'huge',
			'4': 'heart shaped',
			'5': 'star shaped'
		}[s] || 'unknown') + ' (' + Number(s) + ')';
	}
	function voiceDesc(s) {
		return ({
			'0': 'mute',
			'1': 'deep',
			'2': 'feminine',
			'3': 'high'
		}[s] || 'unknown') + ' (' + Number(s) + ')';
	}
	function labiaDesc(s) {
		return ({
			'0': 'minimal',
			'1': 'big',
			'2': 'huge',
			'3': 'huge dangling'
		}[s] || 'unknown') + ' (' + Number(s) + ')';
	}
	function clitDesc(s) {
		return ({
			'0': 'normal',
			'1': 'large',
			'2': 'huge',
			'3': 'enormous',
			'4': 'penis-like',
			'5': 'like a massive penis'
		}[s] || 'unknown') + ' (' + Number(s) + ')';
	}
	function penisDesc(s) {
		return ({
			'0': 'none',
			'1': 'tiny',
			'2': 'little',
			'3': 'normal',
			'4': 'big',
			'5': 'huge',
			'6': 'gigantic',
			'7': 'gigantic',
			'8': 'titanic',
			'9': 'absurd',
			'10': 'inhuman',
			'11': 'hypertrophied'
		}[s] || 'unknown') + ' (' + Number(s) + ')';
	}
	function testesDesc(s) {
		return ({
			'0': 'none',
			'1': 'vestigial',
			'2': 'small',
			'3': 'average',
			'4': 'big',
			'5': 'huge',
			'6': 'enormous',
			'7': 'hypertrophied', '8': 'hypertrophied', '9': 'hypertrophied', '10': 'hypertrophied'
		}[s] || 'unknown') + ' (' + Number(s) + ')';
	}
	function chemicalsDesc(slave) {
		let res = [];
		if (slave.chem > 0) {
			res.push('Immune system damaged (' + slave.chem.toFixed(0) + ').');
		}
		if (slave.addict > 0) {
			res.push('Addicted to aphrodisiacs (' + slave.addict.toFixed(0) + ').');
		}
		return res.join(' ');
	}
	function parentFullName(id) {
		if (id === 0) {
			return App.UI.DOM.makeElement("span", "unknown", "note");
		}
		if (id === -1) {
			/* The PC */
			return birthFullName(V.PC) + ' (PC)';
		} else {
			let parent = V.genePool.find(function(s) {
				return s.ID === id;
			});
			return parent
				? birthFullName(parent)
				: App.Events.makeNode([
					App.UI.DOM.makeElement("span", "missing", "note"),
					`(${id})`
				]);
		}
	}

	let outerDiv = App.UI.DOM.appendNewElement("div", node);
	outerDiv.style.position = "relative";
	outerDiv.style.width = "100%";
	outerDiv.style.height = "100%";

	let innerDiv = App.UI.DOM.appendNewElement("div", outerDiv);
	innerDiv.id = "slaveList";
	innerDiv.style.width = "20em";
	innerDiv.style.float = "left";

	innerDiv = App.UI.DOM.appendNewElement("div", outerDiv);
	innerDiv.id = "geneDetails";
	innerDiv.style.position = "fixed";
	innerDiv.style.left = "0";
	innerDiv.style.right = "0";
	innerDiv.style.top = "0";
	innerDiv.style.bottom = "0";
	innerDiv.style.marginLeft = "40em";
	innerDiv.style.marginRight = "2.5em";
	innerDiv.style.marginTop = "2.5em";

	jQuery(function() {
		let slaveList = jQuery('#slaveList');
		_(V.genePool).sortBy(['birthName', 'birthSurname']).forEach(function(s) {
			slaveList.append(nameButton(s));
		});
		jQuery(document).off('.edit-genetics');
		jQuery(document).on('keyup.edit-genetics', function(e) {
			if (slaveList.is(':visible')) {
				let currentSelection = jQuery('button.selectedslave');
				switch (e.keyCode) {
					case 38: /* ArrowUp */
						if (currentSelection.length === 0) {
							slaveList.find('button').last().trigger('click');
						} else {
							currentSelection.prev().trigger('click');
						}
						break;
					case 40: /* ArrowDown */
						if (currentSelection.length === 0) {
							slaveList.find('button').first().trigger('click');
						} else {
							currentSelection.next().trigger('click');
						}
						break;
					default:
						/* do nothing */
						break;
				}
			}
		});
		let geneDetails = jQuery('#geneDetails');
		jQuery('.slavepicker').each(function() {
			let el = jQuery(this);
			let id = Number(el.attr('data-slave'));
			el.on('click', function() {
				jQuery('button.selectedslave').removeClass('selectedslave');
				el.addClass('selectedslave');
				let slave = V.genePool.find(function(s) { return s.ID === id; });
				geneDetails.html(geneDetailsFunction(slave));

				let numberEditorOpen = function() {
					let td = jQuery(this);
					td.off('click');
					let attr = String(td.attr('data-param'));
					let min = Number(td.attr('data-min')) || -100;
					let max = Number(td.attr('data-max')) || 100;
					let editableText = jQuery('<input type="number" min="' + min + '" max="' + max + '" />');
					editableText.val(Number(slave[attr]));
					editableText.keyup(function(e) {
						if (e.keyCode === 13) {
							slave[attr] = Number(jQuery(this).val());
							el.trigger('click');
						}
					});
					editableText.blur(function() {
						slave[attr] = Number(jQuery(this).val());
						el.trigger('click');
					});
					td.empty().append(editableText);
					editableText.focus();
				};
				geneDetails.find('td.number-editor').click(numberEditorOpen);
				let stringEditorOpen = function() {
					let td = jQuery(this);
					td.off('click');
					let attr = String(td.attr('data-param'));
					let editableText = jQuery('<input type="text" />');
					editableText.val(String(slave[attr]));
					editableText.keyup(function(e) {
						if (e.keyCode === 13) {
							slave[attr] = String(jQuery(this).val());
							el.trigger('click');
						}
					});
					editableText.blur(function() {
						slave[attr] = String(jQuery(this).val());
						el.trigger('click');
					});
					td.empty().append(editableText);
					editableText.focus();
				};
				geneDetails.find('td.string-editor').click(stringEditorOpen);
				let choiceEditorOpen = function() {
					let td = jQuery(this);
					td.off('click');
					let attr = String(td.attr('data-param'));
					let choices = String(td.attr('data-choices')).split(/,\s*/);
					let editableText = jQuery('<select>' + choices.map(ch => '<option>' + ch + '</option>').join('') + '</select>');
					editableText.val(String(slave[attr]));
					editableText.keyup(function(e) {
						if (e.keyCode === 13) {
							slave[attr] = String(jQuery(this).val());
						}
					});
					editableText.blur(function() {
						slave[attr] = String(jQuery(this).val());
					});
					td.empty().append(editableText);
					editableText.focus();
				};
				geneDetails.find('td.choice-editor').each(choiceEditorOpen);
			});
		});
	});

	return node;
};


code:
App.Interact.eliteBreedingExam = function(slave = null) {
	const frag = new DocumentFragment();
	const eliteAngered = V.failedElite > 100;
	const SlaveProfessionalismEstablished = V.arcologies[0].FSSlaveProfessionalism > 20;
	const cost = 5000;

	let r = [];

	if (!slave) {
		r.push(`A fee of <span class='yellowgreen'>${cashFormat(cost)}</span> will be deducted from your account for each slave be to examined.`);
	}
	r.push(
		`Updates will be posted periodically. It is your responsibility to keep up to date.`,
		`Failures will be sterilized. Please make sure your slave fits the following criteria before submitting them for testing. We shall not be held responsible for time wasted.`,
	);
	if (!slave) {
		r.push(`The slave must not be inside a fuckdoll suit, infertile or already pregnant.`);
	} else {
		r.push(`${slave.slaveName} is up for review:`);
	}
	App.Events.addNode(frag, r, "div");
	r = [];

	const table = App.UI.DOM.appendNewElement("table", frag, null, ["padded-columns"]);
	/** @type {[(slave: FC.SlaveState) => boolean, string][]} */
	const checks = [];

	checks.push(
		[
			slave => slave.intelligence + slave.intelligenceImplant >= (eliteAngered ? 95 : 50),
			`Must be ${eliteAngered ? 'highly intelligent' : 'very smart'}`
		],
		[
			slave => Beauty(slave) >= (eliteAngered ? 120 : 100),
			`Must be ${eliteAngered ? 'unbelievably' : ''} beautiful`
		],
		[
			slave => slave.face >= (eliteAngered ? 95 : 40),
			`Must have a ${eliteAngered ? 'breathtaking' : 'gorgeous'} face`
		],
		[
			slave => slave.chem <= 20,
			`Must not be overly impacted by drug use`
		],
		[
			slave => slave.addict === 0,
			`Must not be addicted to drugs`
		],
	);

	if (V.arcologies[0].FSMaturityPreferentialist > 20 || V.arcologies[0].FSYouthPreferentialist > 20) {
		const targetAge = 24;

		if (V.arcologies[0].FSMaturityPreferentialist > 20) {
			checks.push([
				slave => slave.physicalAge > targetAge,
				`Must be older than ${targetAge}`
			]);
		} else {
			checks.push([
				slave => slave.physicalAge <= targetAge,
				`Must be ${targetAge} or younger`
			]);
		}
	}

	if (V.arcologies[0].FSIntellectualDependency > 20) {
		checks.push([
			slave => slave.energy >= 96,
			`Must be sexually receptive at all times`
		]);
	} else if (V.arcologies[0].FSSlaveProfessionalism > 20) {
		checks.push(
			[
				slave => slave.skill.vaginal + slave.skill.anal + slave.skill.oral + slave.skill.entertainment + slave.skill.whoring >= (eliteAngered ? 500 : 400),
				`Must be capable of mastering skills`
			],
			[
				slave => slave.accent <= 1,
				`Must be capable of eloquent and accurate speech`
			],
		);
	}

	if (V.arcologies[0].FSPetiteAdmiration > 20 || V.arcologies[0].FSStatuesqueGlorification > 20) {
		const targetHeight = V.arcologies[0].FSPetiteAdmiration > 20 ? 150 : 186;
		if (V.arcologies[0].FSPetiteAdmiration > 20) {
			checks.push(
				[
					slave => (slave.height - (slave.heightImplant * 10)) < targetHeight,
					`Must naturally be very short (${targetHeight}cm)`
				],
				[
					slave => (slave.height - (slave.heightImplant * 10)) < Height.mean(slave) - 15,
					`Must naturally be very short for their age`
				],
			);
		} else if (V.arcologies[0].FSStatuesqueGlorification > 20) {
			checks.push([
				slave => (slave.height - (slave.heightImplant * 10)) > targetHeight,
				`Must naturally be very tall (${targetHeight}cm)`
			]);
		}
	}

	if (V.arcologies[0].FSSupremacist > 20 || V.arcologies[0].FSSubjugationist > 20) {
		const targetRace = V.arcologies[0].FSSupremacist > 20 ? V.arcologies[0].FSSupremacistRace : V.arcologies[0].FSSubjugationistRace;
		if (V.arcologies[0].FSSupremacist > 20) {
			checks.push([
				slave => slave.race === targetRace,
				`Must be ${targetRace}`
			]);
		} else {
			checks.push([
				slave => slave.race !== targetRace,
				`Must not be ${targetRace}`
			]);
		}
	}

	if (V.arcologies[0].FSGenderRadicalist > 20) {
		checks.push([
			slave => slave.dick >= 1 && slave.balls >= 1,
			`Must have a functional penis`
		]);
	} else if (V.arcologies[0].FSGenderFundamentalist > 20) {
		checks.push([
			slave => slave.dick === 0 && slave.balls === 0,
			`Must be physically female`
		]);
	}

	if (V.arcologies[0].FSPaternalist > 20) {
		checks.push([
			slave => slave.intelligenceImplant >= (SlaveProfessionalismEstablished ? 30 : 15),
			`Must be ${SlaveProfessionalismEstablished ? 'well' : ''} educated`
		]);
		checks.push([
			slave => slave.health.condition >= 60,
			`Must be in good health`
		]);
	}

	if (SlaveProfessionalismEstablished) {
		checks.push([
			slave => slave.intelligenceImplant >= 15,
			`Must be well educated`
		]);
	}

	if (V.arcologies[0].FSBodyPurist > 20) {
		checks.push([
			slave => slave.chem <= (eliteAngered ? 0 : 15),
			`Must have low carcinogen levels`
		]);
		checks.push([
			slave => slave.boobsImplant + slave.buttImplant + slave.lipsImplant + slave.hipsImplant + slave.shouldersImplant === 0 && slave.faceImplant <= 5 && slave.bellyImplant === -1,
			`Must be implant free`
		]);
	}

	if (V.arcologies[0].FSSlimnessEnthusiast > 20) {
		if (V.arcologies[0].FSHedonisticDecadence > 20) {
			checks.push([
				slave => slave.weight <= 30,
				`May be no larger than "plush"`
			]);
		} else {
			checks.push([
				slave => slave.weight <= 0,
				`Must be thin`
			]);
		}
		checks.push([
			slave => slave.butt <= 2,
			`Must have a trim rear`
		]);
		checks.push([
			slave => slave.boobs <= 400,
			`Must have a sleek chest`
		]);
	} else if (V.arcologies[0].FSAssetExpansionist > 20) {
		if (V.arcologies[0].FSTransformationFetishist > 20) {
			checks.push(
				[
					slave => slave.butt >= 6,
					`Must have a gigantic rear`
				],
				[
					slave => slave.boobs >= 1000,
					`Must be busty`
				],
			);
		} else {
			checks.push(
				[
					slave => slave.butt - slave.buttImplant >= 6,
					`Must have a naturally gigantic rear`
				],
				[
					slave => slave.boobs - slave.boobsImplant >= 1000,
					`Must be naturally busty`
				],
			);
		}
	}

	if (V.arcologies[0].FSPastoralist > 20) {
		checks.push([
			slave => slave.lactation === 1,
			`Must be lactating naturally`
		]);
	}
	if (V.arcologies[0].FSPhysicalIdealist > 20) {
		if (V.arcologies[0].FSPhysicalIdealistLaw === 1) {
			const musclesMin = 20;
			const musclesMax = 50;

			checks.push(
				[
					slave => slave.weight <= 30,
					`Must not be overweight`
				],
				[
					slave => slave.muscles.isBetween(musclesMin, musclesMax),
					`Must be fit, but not too muscular`
				],
			);
		} else {
			checks.push([
				slave => slave.muscles > 95,
				`Must be extremely muscular`
			]);
		}
	} else if (V.arcologies[0].FSHedonisticDecadence > 20) {
		const targetWeight = V.arcologies[0].FSSlimnessEnthusiast > 20 ? 10 : 95;

		if (V.arcologies[0].FSSlimnessEnthusiast > 20) {
			checks.push([
				slave => slave.weight > targetWeight,
				`Must be more than "trim"`
			]);
		} else {
			checks.push([
				slave => slave.weight > targetWeight,
				`Must be big, soft and fat`
			]);
		}
	}

	if (slave) {
		checks.forEach(check => {
			const tr = App.UI.DOM.appendNewElement("tr", table);

			App.UI.DOM.appendNewElement("td", tr, `• ${check[1]}`);

			if (!check[0](slave)) {
				App.UI.DOM.appendNewElement("td", tr, `FAILED`, ["red"]);
			} else {
				App.UI.DOM.appendNewElement("td", tr, `PASSED`, ["lime"]);
			}
		});

		if (checks.some(check => !check[0](slave))) {
			r.push(`The aforementioned slave has been deemed <span class="red">unsuitable</span> for breeding.`);

			slave.preg = -3;
		} else {
			r.push(`The aforementioned slave has been deemed <span class="green">worthy</span> of being used for breeding and has been marked as such. Please note the increased restrictions on breeding slaves.`);
			r.push(`They are not permitted for public use or anything that may harm their growing child. The child within them is considered a member of the Elite class and as such, any harm that comes to them will result in severe penalties to the breeder's owner. Development of the child will be closely monitored; should the fetus be identified as not of the owner's blood (or any other member of the Elite class), said owner shall face severe fines.`);

			slave.breedingMark = 1;
			slave.pregControl = "none";

			const job = App.Utils.jobForAssignment(slave.assignment);
			const consequences = [];
			if (slave.assignment === Job.BODYGUARD || (job && job.desc.publicSexUse)) {
				removeJob(slave, slave.assignment);
				consequences.push(`reassigned to <span class="green">rest</span>`);
			}
			if (V.pit && V.pit.fighterIDs.includes(slave.ID)) {
				removeJob(slave, Job.PIT);
				consequences.push(`<span class="yellow">removed</span> from ${V.pit.name}'s fighting roster`);
			}
			if (consequences.length > 0) {
				App.Events.addNode(frag, r, "div");
				r = [];
				const {He} = getPronouns(slave);
				r.push(`${He} has been automatically ${toSentence(consequences)}.`);
			}
		}

		cashX(-cost, "capEx");
	} else {
		checks.forEach(check => {
			const tr = App.UI.DOM.appendNewElement("tr", table);

			App.UI.DOM.appendNewElement("td", tr, check[1]);
		});
	}

	// hack to circumvent default SugarCube styling
	table.style.marginLeft = "1em";

	App.Events.addNode(frag, r, "div");

	return frag;
};


code:
App.UI.surrogacy = function() {
	const node = new DocumentFragment();
	const receptrix = getReceptrix();
	const donatrix = getDonatrix();
	const impregnatrix = getImpregnatrix();
	const wombIndex = V.wombIndex;
	const slave = getSlave(V.AS);
	let r = [];

	function getReceptrix() {
		if (V.receptrix && V.receptrix.ID) {
			return V.receptrix.ID === -1 ? V.PC : getSlave(V.receptrix.ID);
		}
	}

	function getDonatrix() {
		if (V.donatrix && V.donatrix.ID) {
			return V.donatrix.ID === -1 ? V.PC : getSlave(V.donatrix.ID);
		}
	}

	function getImpregnatrix() {
		if (V.impregnatrix && V.impregnatrix.ID) {
			if (V.impregnatrix.ID === -1) {
				return V.PC;
			} else {
				return getSlave(V.impregnatrix.ID) || V.incubator.tanks.find(t => t.ID === V.impregnatrix.ID);
			}
		}
	}

	switch (V.surgeryType) {
		case "surrogacy":
			if (receptrix.ID === -1) {
				r.push(`Since the surgery required only a local anesthetic, you remain fully aware of the procedure as the autosurgery carries it out. You slowly rise to your feet, a hand to your lower belly, appreciating the new life growing within you.`);
				V.PC.pregKnown = 1;
				WombSurrogate(V.PC, 1, donatrix, impregnatrix.ID, 1);
				WombNormalizePreg(V.PC);
			} else {
				receptrix.pregKnown = 1;
				WombSurrogate(receptrix, 1, donatrix, impregnatrix.ID, 1);
				WombNormalizePreg(receptrix);
				const {
					He,
					he, his, him
				} = getPronouns(receptrix);
				if (receptrix.fetish === "mindbroken") {
					r.push(`${receptrix.slaveName} leaves the surgery with a certain warmth in ${his} lower abdomen, ${he} knows that ${he} has been impregnated.`);
				} else if (receptrix.fetish === "pregnancy" && receptrix.fetishStrength > 60 && receptrix.fetishKnown === 1) {
					if (canSee(receptrix)) {
						r.push(`Since the surgery required only a local anesthetic, ${receptrix.slaveName} remained fully aware throughout the procedure. ${He} was overjoyed at the sight of the syringe containing ${his} future child emptying into ${his} womb.`);
					} else {
						r.push(`${receptrix.slaveName} leaves the surgery with a certain warmth in ${his} lower abdomen, ${he} knows that ${he} has been impregnated.`);
					}
					r.push(`${He} is  filled with joy over the life settling into ${his} womb and can't wait to see the result. ${He}'s so pleased that ${he} now trusts your plans for ${his} body.`);
					receptrix.trust += 4;
					receptrix.devotion += 10;
				} else if (receptrix.devotion > 50) {
					if (canSee(receptrix)) {
						r.push(`Since the surgery required only a local anesthetic, ${receptrix.slaveName} remained fully aware throughout the procedure. ${He} watched the syringe containing ${his} new pregnancy empty into ${his} womb with rapt attention.`);
					} else {
						r.push(`${receptrix.slaveName} leaves the surgery with a certain warmth in ${his} lower abdomen, ${he} knows that ${he} has been impregnated.`);
					}
					r.push(`${He}'s <span class="devotion inc">grateful</span> that you think ${him} worthy of carrying this child, and a little nervous about how ${he}'ll perform as a surrogate.`);
					receptrix.devotion += 4;
				} else if (receptrix.devotion >= -20) {
					if (canSee(receptrix)) {
						r.push(`Since the surgery required only a local anesthetic, ${receptrix.slaveName} remained fully aware throughout the procedure. From the syringe making contact with ${his} skin, to the egg's delivery into ${his} womb and ${his} subsequent impregnation, ${he} couldn't look away.`);
					} else {
						r.push(`${receptrix.slaveName} leaves the surgery with a certain warmth in ${his} lower abdomen, ${he} knows that ${he} has been impregnated.`);
					}
					r.push(`${He} understands the realities of ${his} life as a slave, so it isn't much of a shock. ${He} is <span class="trust dec">sensibly fearful</span> of your total power over ${his} body.`);
					receptrix.trust -= 10;
				} else {
					if (canSee(receptrix)) {
						r.push(`Since the surgery required only a local anesthetic, ${receptrix.slaveName} remained fully aware throughout the procedure. The moment ${he} realized what was happening, ${he} shut ${his} eyes tight, only opening them again as ${he} feels the slight tingle of the injector exiting ${his} lower abdomen.`);
					} else {
						r.push(`${receptrix.slaveName} leaves the surgery with a certain warmth in ${his} lower abdomen, ${he} knows that ${he} has been impregnated.`);
					}
					r.push(`${He} does not understand the realities of ${his} life as a slave at a core level, so ${he}'s terrified and angry that you have forced ${him} to bear this child, even more so as ${he} realizes ${he} doesn't know who the father is. ${He} is sensibly fearful of your total power over ${his} body and the future of the life ${he} now harbors within ${him}.`);
					receptrix.trust -= 15;
					receptrix.devotion -= 15;
				}
			}
			V.receptrix = 0;
			V.impregnatrix = 0;
			V.donatrix = 0;
			break;
		case "transplant":
			if (receptrix.ID === -1) {
				r.push(`Since the surgery required only a local anesthetic, you are very aware that you are now carrying ${slave.slaveName}'s child. You slowly rise to your feet, a hand to your lower belly, appreciating the new life growing within you.`);
				const fetus = WombRemoveFetus(slave, wombIndex);
				WombAddFetus(V.PC, fetus);
				V.PC.pregKnown = 1;
				V.PC.preg = WombMaxPreg(V.PC);
				slave.preg = WombMaxPreg(slave);
				WombNormalizePreg(V.PC);
				WombNormalizePreg(slave);
			} else {
				const fetus = WombRemoveFetus(donatrix, wombIndex);
				WombAddFetus(receptrix, fetus);
				receptrix.pregKnown = 1;
				receptrix.preg = WombMaxPreg(receptrix);
				donatrix.preg = WombMaxPreg(donatrix);
				WombNormalizePreg(receptrix);
				WombNormalizePreg(donatrix);
				if (donatrix.ID === -1) {
					V.PC = donatrix;
				}
				const {
					He,
					he, his, him
				} = getPronouns(receptrix);
				if (receptrix.fetish === "mindbroken") {
					r.push(`${receptrix.slaveName} leaves the surgery with a certain warmth in ${his} lower abdomen, ${he} knows that ${he} has been impregnated.`);
				} else if ((receptrix.fetish === "pregnancy") && (receptrix.fetishStrength > 60) && (receptrix.fetishKnown === 1)) {
					if (canSee(receptrix)) {
						r.push(`Since the surgery required only a local anesthetic, ${receptrix.slaveName} remained fully aware throughout the procedure. ${He} was overjoyed at the sight of the syringe containing ${his} future child emptying into ${his} womb.`);
					} else {
						r.push(`${receptrix.slaveName} leaves the surgery with a certain warmth in ${his} lower abdomen, ${he} knows that ${he} has been impregnated.`);
					}
					r.push(`${He} is  filled with joy over the life resettling into ${his} womb and can't wait to see the result. ${He}'s so pleased that ${he} now trusts your plans for ${his} body.`);
					receptrix.trust += 4;
					receptrix.devotion += 10;
				} else if (receptrix.devotion > 50) {
					if (canSee(receptrix)) {
						r.push(`Since the surgery required only a local anesthetic, ${receptrix.slaveName} remained fully aware throughout the procedure. ${He} watched the syringe containing ${his} new pregnancy empty into ${his} womb with rapt attention.`);
					} else {
						r.push(`${receptrix.slaveName} leaves the surgery with a certain warmth in ${his} lower abdomen, ${he} knows that ${he} has been impregnated.`);
					}
					r.push(`${He}'s <span class="devotion inc">grateful</span> that you think ${him} worthy of carrying this child in`);
					if (donatrix.ID === -1) {
						r.push(`your stead,`);
					} else {
						r.push(`lieu of ${donatrix.slaveName},`);
					}
					r.push(`and a little nervous about how ${he}'ll perform as a surrogate.`);
					receptrix.devotion += 4;
				} else if (receptrix.devotion >= -20) {
					if (canSee(receptrix)) {
						r.push(`Since the surgery required only a local anesthetic, ${receptrix.slaveName} remained fully aware throughout the procedure. From the syringe making contact with ${his} skin, to the egg's delivery into ${his} womb and ${his} subsequent impregnation, ${he} couldn't look away.`);
					} else {
						r.push(`${receptrix.slaveName} leaves the surgery with a certain warmth in ${his} lower abdomen, ${he} knows that ${he} has been impregnated.`);
					}
					r.push(`${He} understands the realities of ${his} life as a slave, so it isn't much of a shock. ${He} is sensibly fearful of your total power over ${his} body and how you are forcing`);
					if (donatrix.ID === -1) {
						r.push(`your pregnancy upon ${him}.`);
					} else {
						r.push(`${him} to carry what should have been ${donatrix.slaveName}'s burden.`);
					}
					receptrix.trust -= 10;
				} else {
					if (canSee(receptrix)) {
						r.push(`Since the surgery required only a local anesthetic, ${receptrix.slaveName} remained fully aware throughout the procedure. The moment ${he} realized what was happening, ${he} shut ${his} eyes tight, only opening them again as ${he} feels the slight tingle of the injector exiting ${his} lower abdomen.`);
					} else {
						r.push(`${receptrix.slaveName} leaves the surgery with a certain warmth in ${his} lower abdomen, ${he} knows that ${he} has been impregnated.`);
					}
					r.push(`${He} does not understand the realities of ${his} life as a slave at a core level, so ${he}'s <span class="devotion dec">terrified and angry</span> that you have forced ${him} to`);
					if (donatrix.ID === -1) {
						r.push(`carry your child instead of doing so yourself.`);
					} else {
						r.push(`bear this child in lieu of ${donatrix.slaveName}.`);
					}
					r.push(`${He} is <span class="trust dec">sensibly fearful</span> of your total power over ${his} body and who else's troubles may be levied upon ${him}.`);
					receptrix.trust -= 15;
					receptrix.devotion -= 15;
				}
			}
			V.receptrix = 0;
			V.donatrix = 0;
			V.wombIndex = 0;
			break;
		case "clone":
			if (receptrix.ID === -1) {
				r.push(`Since the surgery required only a local anesthetic, you remain fully aware of the procedure as the autosurgery carries it out. You slowly rise to your feet, a hand to your lower belly, appreciating the clone growing within you.`);
				V.PC.pregKnown = 1;
				WombImpregnateClone(V.PC, 1, donatrix, 1);
				WombNormalizePreg(V.PC);
			} else {
				receptrix.pregKnown = 1;
				WombImpregnateClone(receptrix, 1, donatrix, 1);
				WombNormalizePreg(receptrix);
				const {
					He,
					he, his, him
				} = getPronouns(receptrix);
				if (receptrix.fetish === "mindbroken") {
					r.push(`${receptrix.slaveName} leaves the surgery with a certain warmth in ${his} lower abdomen,`);
					if (receptrix.ID === donatrix.ID) {
						r.push(`but has no idea ${he} carries ${his} own clone.`);
					} else {
						r.push(`${he} knows that ${he} has been impregnated.`);
					}
				} else if (receptrix.fetish === "pregnancy" && receptrix.fetishStrength > 60 && receptrix.fetishKnown === 1) {
					if (canSee(receptrix)) {
						r.push(`Since the surgery required only a local anesthetic, ${receptrix.slaveName} remained fully aware throughout the procedure. ${He} was overjoyed at the sight of the syringe containing ${his}`);
						if (receptrix.ID === donatrix.ID) {
							r.push(`clone`);
						} else {
							r.push(`future child`);
						}
						r.push(`emptying into ${his} womb.`);
					} else {
						r.push(`${receptrix.slaveName} leaves the surgery with a certain warmth in ${his} lower abdomen, ${he} knows that ${he} has been impregnated${(receptrix.ID === donatrix.ID) ? `, but not that ${he} now bears ${his} own clone` : ''}.`);
					}
					r.push(`${He} is  filled with joy over the life settling into ${his} womb and can't wait to see the result. ${He}'s so pleased that ${he} now trusts your plans for ${his} body.`);
					receptrix.trust += 4;
					receptrix.devotion += 10;
				} else if (receptrix.devotion > 50) {
					if (canSee(receptrix)) {
						r.push(`Since the surgery required only a local anesthetic, ${receptrix.slaveName} remained fully aware throughout the procedure. ${He} watched the syringe containing ${his} new pregnancy empty into ${his} womb with rapt attention.`);
					} else {
						r.push(`${receptrix.slaveName} leaves the surgery with a certain warmth in ${his} lower abdomen, ${he} knows that ${he} has been impregnated${(receptrix.ID === donatrix.ID) ? `, but not that ${he} now bears ${his} own clone` : ''}.`);
					}
					r.push(`${He}'s <span class="devotion inc">grateful</span> that you think ${him} worthy of carrying this child, and a little nervous about how ${he}'ll perform as a surrogate.`);
					receptrix.devotion += 4;
				} else if (receptrix.devotion >= -20) {
					if (canSee(receptrix)) {
						r.push(`Since the surgery required only a local anesthetic, ${receptrix.slaveName} remained fully aware throughout the procedure. From the syringe making contact with ${his} skin, to the egg's delivery into ${his} womb and ${his} subsequent impregnation, ${he} couldn't look away.`);
					} else {
						r.push(`${receptrix.slaveName} leaves the surgery with a certain warmth in ${his} lower abdomen, ${he} knows that ${he} has been impregnated${(receptrix.ID === donatrix.ID) ? `, but not that ${he} now bears ${his} own clone` : ''}.`);
					}
					r.push(`${He} understands the realities of ${his} life as a slave, so it isn't much of a shock. ${He} is <span class="trust dec">sensibly fearful</span> of your total power over ${his} body.`);
					receptrix.trust -= 10;
				} else {
					if (canSee(receptrix)) {
						r.push(`Since the surgery required only a local anesthetic, ${receptrix.slaveName} remained fully aware throughout the procedure. The moment ${he} realized what was happening, ${he} shut ${his} eyes tight, only opening them again as ${he} feels the slight tingle of the injector exiting ${his} lower abdomen.`);
					} else {
						r.push(`${receptrix.slaveName} leaves the surgery with a certain warmth in ${his} lower abdomen, ${he} knows that ${he} has been impregnated.`);
					}
					r.push(`${He} does not understand the realities of ${his} life as a slave at a core level, so ${he}'s <span class="devotion dec">terrified and angry</span> that you have forced ${him} to bear`);
					if (receptrix.ID === donatrix.ID) {
						r.push(`${his} own clone and potential replacement.`);
					} else {
						r.push(`this child, even more so as ${he} realizes ${he} doesn't know who the father is.`);
					}
					r.push(`${He} is <span class="trust dec">sensibly fearful</span> of your total power over ${his} body and the future of the life ${he} now harbors within ${him}.`);
					receptrix.trust -= 15;
					receptrix.devotion -= 15;
				}
			}
			V.receptrix = 0;
			V.donatrix = 0;
	}
	App.Events.addParagraph(node, r);
	return node;
};


code:
App.Art.cacheArtData();
App.Corporate.Init();
App.RA.Activation.populateGetters();

// TODO: remove once setup object is no longer required.
for (let key in App.Data.misc) {
	setup[key] = App.Data.misc[key];
}


code:
/* subscribe to game save/load events */
Save.onLoad.add(App.EventHandlers.onLoad);
Save.onSave.add(App.EventHandlers.onSave);

/* ### Every-time listeners #### */

$(document).on(":storyready", () => {
	App.EventHandlers.storyReady();
});

$(document).on(":passageinit", () => {
	if (V.passageSwitchHandler) {
		V.passageSwitchHandler();
		delete V.passageSwitchHandler;
	}
	App.UI.Tabs.clear();
	profileEvents.passageinit();
});

$(document).on(":passagestart", event => {
	App.Debug.slavesConsistency(event);
	Object.defineProperty(State.temporary, "S", {
		get: () => S,
		enumerable: true
	});
	profileEvents.passagestart();
});

$(document).on(":passagerender", () => {
	profileEvents.passagerender();
});

$(document).on(":passagedisplay", () => {
	profileEvents.passagedisplay();
});

$(document).on(":passageend", ev => {
	profileEvents.passageend(ev.content);
});

/* ### One-time listeners ### */

$(document).one(":passagestart", () => {
	App.EventHandlers.optionsChanged();
});


code:
[submodule "submodules/sugarcube-2"]
	branch = fc


code:
{
	const getProxy = App.Utils.Diff.getProxy;

	App.Testing.executeTest("Overwrite first level", () => {
	}, () => {
		let proxy = getProxy({a: 1});

		proxy.a = 2;

		const original = proxy.diffOriginal;

		App.Testing.equals(original, {a: 1});
		App.Testing.equals(proxy.a, 2);

		App.Utils.Diff.applyDiff(original, proxy.diffChange);
		App.Testing.equals(original, {a: 2});
	}, () => {
	});

	App.Testing.executeTest("Overwrite second level", () => {
	}, () => {
		let proxy = getProxy({c: {a: 1}});

		proxy.c.a = 2;

		const original = proxy.diffOriginal;

		App.Testing.equals(original, {c: {a: 1}});
		App.Testing.equals(proxy.c.a, 2);

		App.Utils.Diff.applyDiff(original, proxy.diffChange);
		App.Testing.equals(original, {c: {a: 2}});
	}, () => {
	});

	App.Testing.executeTest("Delete first level", () => {
	}, () => {
		let proxy = getProxy({a: 1});

		delete proxy.a;

		const original = proxy.diffOriginal;

		App.Testing.equals(original, {a: 1});
		App.Testing.hasNoProperty(proxy, "a");

		App.Utils.Diff.applyDiff(original, proxy.diffChange);
		App.Testing.equals(original, {});
	}, () => {
	});

	App.Testing.executeTest("Delete in second level", () => {
	}, () => {
		let proxy = getProxy({c: {a: 1}});

		delete proxy.c.a;

		const original = proxy.diffOriginal;

		App.Testing.equals(original, {c: {a: 1}});
		App.Testing.hasProperty(proxy, "c");
		App.Testing.hasNoProperty(proxy.c, "a");

		App.Utils.Diff.applyDiff(original, proxy.diffChange);
		App.Testing.equals(original, {c: {}});
	}, () => {
	});

	App.Testing.executeTest("Delete with second level", () => {
	}, () => {
		let proxy = getProxy({c: {a: 1}});

		delete proxy.c;

		const original = proxy.diffOriginal;

		App.Testing.equals(original, {c: {a: 1}});
		App.Testing.hasNoProperty(proxy, "c");

		App.Utils.Diff.applyDiff(original, proxy.diffChange);
		App.Testing.equals(original, {});
	}, () => {
	});

	App.Testing.executeTest("add value", () => {
	}, () => {
		let proxy = getProxy({});

		proxy.d = 7;

		const original = proxy.diffOriginal;

		App.Testing.equals(original, {});
		App.Testing.hasProperty(proxy, "d");
		App.Testing.equals(proxy.d, 7);

		App.Utils.Diff.applyDiff(original, proxy.diffChange);
		App.Testing.equals(original, {d: 7});
	}, () => {
	});

	App.Testing.executeTest("add object", () => {
	}, () => {
		let proxy = getProxy({});

		proxy.d = {a: 5};

		const original = proxy.diffOriginal;

		App.Testing.equals(original, {});
		App.Testing.hasProperty(proxy, "d");
		App.Testing.hasProperty(proxy.d, "a");
		App.Testing.equals(proxy.d.a, 5);

		App.Utils.Diff.applyDiff(original, proxy.diffChange);
		App.Testing.equals(original, {d: {a: 5}});
	}, () => {
	});

	App.Testing.executeTest("Overwrite array entry", () => {
	}, () => {
		let proxy = getProxy({b: [1, 2]});

		proxy.b[1] = 5;

		const original = proxy.diffOriginal;

		App.Testing.equals(original, {b: [1, 2]});
		App.Testing.equals(proxy.b, [1, 5]);

		App.Utils.Diff.applyDiff(original, proxy.diffChange);
		App.Testing.equals(original, {b: [1, 5]});
	}, () => {
	});

	App.Testing.executeTest("array push", () => {
	}, () => {
		let proxy = getProxy({b: [1, 2]});

		proxy.b.push(5);

		const original = proxy.diffOriginal;

		App.Testing.equals(original, {b: [1, 2]});
		App.Testing.equals(proxy.b, [1, 2, 5]);

		App.Utils.Diff.applyDiff(original, proxy.diffChange);
		App.Testing.equals(original, {b: [1, 2, 5]});
	}, () => {
	});

	App.Testing.executeTest("array pop", () => {
	}, () => {
		let proxy = getProxy({b: [1, 2]});

		proxy.b.pop();

		const original = proxy.diffOriginal;

		App.Testing.equals(original, {b: [1, 2]});
		App.Testing.equals(proxy.b, [1]);

		App.Utils.Diff.applyDiff(original, proxy.diffChange);
		App.Testing.equals(original, {b: [1]});
	}, () => {
	});

	App.Testing.executeTest("new array unshift", () => {
	}, () => {
		let proxy = getProxy({});

		proxy.d = {a: [5]};
		proxy.d.a.unshift(9);

		const original = proxy.diffOriginal;

		App.Testing.equals(original, {});
		App.Testing.hasProperty(proxy.d, "a");
		App.Testing.equals(proxy.d.a, [9, 5]);

		App.Utils.Diff.applyDiff(original, proxy.diffChange);
		App.Testing.equals(original, {d: {a: [9, 5]}});
	}, () => {
	});

	App.Testing.executeTest("remove one eye", () => {
	}, () => {
		let proxy = getProxy({eye: new App.Entity.EyeState()});

		eyeSurgery(proxy, "left", "remove");

		const original = proxy.diffOriginal;

		App.Testing.equals(original, {eye: new App.Entity.EyeState()});
		App.Testing.equals(proxy.eye.left, null);

		App.Utils.Diff.applyDiff(original, proxy.diffChange);
		App.Testing.equals(original.eye.left, null);
	}, () => {
	});

	App.Testing.executeTest("remove both eyes", () => {
	}, () => {
		let proxy = getProxy({eye: new App.Entity.EyeState()});

		eyeSurgery(proxy, "both", "remove");

		const original = proxy.diffOriginal;

		App.Testing.equals(original, {eye: new App.Entity.EyeState()});
		App.Testing.equals(proxy.eye.left, null);
		App.Testing.equals(proxy.eye.right, null);

		App.Utils.Diff.applyDiff(original, proxy.diffChange);
		App.Testing.equals(original.eye.left, null);
		App.Testing.equals(original.eye.right, null);
	}, () => {
	});

	// Do this last
	App.Testing.unitDone();
}


code:
{
	class MockSlave {
		ID;
		mother;
		father;

		constructor(id) {
			this.ID = id;
		}
	}

	class MockMating {
		constructor(fatherId, motherId, ...childrenIds) {
			for (let id of childrenIds) {
				if (id === fatherId || id === motherId) {
					throw new Error("cannot give birth to self");
				}
			}

			this.fatherId = fatherId;
			this.motherId = motherId;
			this.childrenIds = childrenIds;
		}
	}

	class MockWorld {
		slavesArray;

		constructor(matings) {
			this.slavesArray = [];

			let slavesById = {};
			let meetSlave = (id) => {
				if (!slavesById[id]) {
					let slave = new MockSlave(id);
					this.slavesArray.push(slave);
					slavesById[id] = slave;
				}

				return slavesById[id];
			};

			for (let mating of matings) {
				let father = meetSlave(mating.fatherId);
				let mother = meetSlave(mating.motherId);
				for (let id of mating.childrenIds) {
					let child = meetSlave(id);
					child.father = father.ID;
					child.mother = mother.ID;
				}
			}
		}

		findSlaveState(id) {
			return this.slavesArray.find(slave => slave.ID === id) || null;
		}
	}

	let testCoefficientForSlave = function(name, mockMatings, slaveId, expectedCoefficientOfInbreeding) {
		App.Testing.executeTest(name, () => {}, () => {
			let tolerance = .00000000001;

			let world = new MockWorld(mockMatings);
			let c = ibc._test.coeff_slave(world, world.findSlaveState(slaveId));
			App.Testing.isType(c, "number");
			App.Testing.notNaN(c);
			App.Testing.inRange(c, expectedCoefficientOfInbreeding, tolerance);
		}, () => {});
	};

	// references:
	// Introduction to Quantitative Genetics - Doulas S. Falconer, 1989
	// Genetic and Quantitative Aspects of Genealogy - F.M. Lancaster, 2015
	testCoefficientForSlave("basic outbred mating, 1",[new MockMating(1, 2, 3, 4)], 1, 0);
	testCoefficientForSlave("basic outbred mating, 2",[new MockMating(1, 2, 3, 4)], 4, 0);
	testCoefficientForSlave("basic self-mating, 1", [new MockMating(1, 1, 3, 4)], 4, 1 / 2);
	testCoefficientForSlave("basic self-mating, 2", [new MockMating(1, 1, 3, 4)], 1, 0);
	testCoefficientForSlave("basic child-parent mating",[new MockMating(1, 2, 3), new MockMating(2, 3, 4)], 4, 1 / 4);
	testCoefficientForSlave("basic sibling mating",[new MockMating(1, 2, 3, 4), new MockMating(3, 4, 5)], 5, 1 / 4);
	testCoefficientForSlave("basic half-sibling mating",[new MockMating(1, 2, 3), new MockMating(2, 4, 5), new MockMating(3, 5, 6)], 6, 1 / 8);
	testCoefficientForSlave("basic first cousin mating",[new MockMating(1, 2, 3, 4), new MockMating(5, 3, 6), new MockMating(4, 7, 8), new MockMating(6, 8, 9)], 9, 1 / 16);
	testCoefficientForSlave("double first cousin mating",[new MockMating(1, 2, 3, 4), new MockMating(5, 6, 7, 8),
		new MockMating(3, 7, 9), new MockMating(4, 8, 10), new MockMating(9, 10, 11)], 11, 1 / 8);
	testCoefficientForSlave("aunt-niece mating",[new MockMating(1, 2, 3, 4), new MockMating(4, 5, 6), new MockMating(3, 6, 7)], 7, 1 / 8);
	let scenario53 = [new MockMating(1, 2, 3, 4), new MockMating(5, 6, 7, 8), new MockMating(3, 7, 9, 10), new MockMating(4, 8, 11),
		new MockMating(12, 9, 13), new MockMating(10, 11, 14), new MockMating(13, 14, 15)];
	testCoefficientForSlave("problem 5.3 from Falconer, 1",scenario53, 14, 1 / 8);  //
	testCoefficientForSlave("problem 5.3 from Falconer, 2",scenario53, 15, 3 / 32);
	testCoefficientForSlave("example in figure 64 from Lancaster",[new MockMating(1, 2, 4, 5), new MockMating(3, 4, 7), new MockMating(5, 6, 8), new MockMating(7, 8, 9), new MockMating(9, 10, 12, 13),
		new MockMating(11, 12, 15), new MockMating(13, 14, 16), new MockMating(15, 16, 17, 18)], 17, 33 / 512);
	testCoefficientForSlave("example in figure 65 from Lancaster",[new MockMating(1, 2, 4, 5), new MockMating(3, 4, 7), new MockMating(5, 6, 8), new MockMating(7, 8, 10, 11),
		new MockMating(9, 10, 13), new MockMating(11, 12, 14), new MockMating(13, 14, 15)], 15, 9 / 128);
	testCoefficientForSlave("example in figure 66 from Lancaster",[new MockMating(1, 2, 3, 4), new MockMating(3, 4, 5, 6), new MockMating(5, 6, 7, 8), new MockMating(7, 8, 9)], 9, 1 / 2);
	testCoefficientForSlave("example in figure 66 from Lancaster, extended by one more generation's worth of mating between siblings",[new MockMating(1, 2, 3, 4), new MockMating(3, 4, 5, 6), new MockMating(5, 6, 7, 8), new MockMating(7, 8, 9, 10),
		new MockMating(9, 10, 11)], 11, 19 / 32);
	// from here the preceding scenario of regular sibling-mating is extended by several more steps; by rules given in Falconer, the coefficient ck for step k, where step k follows
	// step j which follows step i, should be ck = 1/4 + cj/2 + ci/4, and so that is how the expected values used in the next few tests were calculated
	testCoefficientForSlave("extended, 1: 43/64 = 1/4 + (19/32) / 2 + (1/2) / 4",[new MockMating(1, 2, 3, 4), new MockMating(3, 4, 5, 6), new MockMating(5, 6, 7, 8), new MockMating(7, 8, 9, 10),
		new MockMating(9, 10, 11, 12), new MockMating(11, 12, 13)], 13, 43 / 64);
	testCoefficientForSlave("extended, 2",[new MockMating(1, 2, 3, 4), new MockMating(3, 4, 5, 6), new MockMating(5, 6, 7, 8), new MockMating(7, 8, 9, 10),
		new MockMating(9, 10, 11, 12), new MockMating(11, 12, 13, 14), new MockMating(13, 14, 15)], 15, 94 / 128);
	testCoefficientForSlave("extended, 3", [new MockMating(1, 2, 3, 4), new MockMating(3, 4, 5, 6), new MockMating(5, 6, 7, 8), new MockMating(7, 8, 9, 10),
		new MockMating(9, 10, 11, 12), new MockMating(11, 12, 13, 14), new MockMating(13, 14, 15, 16), new MockMating(15, 16, 17)], 17, 201 / 256);
	testCoefficientForSlave("extended, 4", [new MockMating(1, 2, 3, 4), new MockMating(3, 4, 5, 6), new MockMating(5, 6, 7, 8), new MockMating(7, 8, 9, 10),
		new MockMating(9, 10, 11, 12), new MockMating(11, 12, 13, 14), new MockMating(13, 14, 15, 16), new MockMating(15, 16, 17, 18), new MockMating(17, 18, 19)], 19, 423 / 512);
	testCoefficientForSlave("extended, 5", [new MockMating(1, 2, 3, 4), new MockMating(3, 4, 5, 6), new MockMating(5, 6, 7, 8), new MockMating(7, 8, 9, 10),
		new MockMating(9, 10, 11, 12), new MockMating(11, 12, 13, 14), new MockMating(13, 14, 15, 16), new MockMating(15, 16, 17, 18), new MockMating(17, 18, 19, 20),
		new MockMating(19, 20, 21)], 21, 880 / 1024);
	// Does not apply to current algorithm: as of this writing, the ten-generations-of-sisterfucking test immediately above is enough to occupy the game's coefficient-of-inbreeding calculation algorithm for a few minutes
	// on a ~4Ghz CPU.  this is very silly.  when you read this, that algorithm should have been replaced by a much faster one, allowing the above test to be comfortably run again.
	let scenario70 = [new MockMating(1, 2, 6), new MockMating(2, 3, 7), new MockMating(3, 4, 8), new MockMating(5, 6, 9), new MockMating(7, 8, 10),
		new MockMating(9, 10, 12), new MockMating(10, 11, 13), new MockMating(12, 13, 14), new MockMating(12, 14, 15)];  // example in figure 70 from Lancaster
	testCoefficientForSlave("example in figure 70 from Lancaster, 1", scenario70, 2, 0);
	testCoefficientForSlave("example in figure 70 from Lancaster, 2", scenario70, 10, 1 / 8);
	testCoefficientForSlave("example in figure 70 from Lancaster, 3", scenario70, 12, 1 / 32);
	testCoefficientForSlave("example in figure 70 from Lancaster, 4", scenario70, 15, 85 / 256);
	let scenarioXXX = [new MockMating(1, 2, 5, 6), new MockMating(3, 4, 7, 8), new MockMating(5, 6, 9, 10), new MockMating(5, 7, 11), new MockMating(5, 8, 12),
		new MockMating(5, 9, 13), new MockMating(9, 10, 14), new MockMating(5, 11, 15), new MockMating(10, 12, 16), new MockMating(13, 16, 17),
		new MockMating(4, 16, 18), new MockMating(14, 15, 19), new MockMating(6, 17, 20), new MockMating(6, 18, 21), new MockMating(6, 19, 22),
		new MockMating(20, 21, 23), new MockMating(22, 23, 24)];  // an invented example meant to represent a typical situation in a respectable arcology
	testCoefficientForSlave("change, 1",scenarioXXX, 24, 633 / 2048);  // here the expected coefficient was retrieved from the algorithm being tested and was not separately verified by hand,
	// so this test could conceivably be wrong, but at least it can catch changes...
	testCoefficientForSlave("change, 2",[new MockMating(1, 2, 3, 4), new MockMating(3, 4, 5, 6), new MockMating(5, 6, 7, 8), new MockMating(7, 8, 9, 10), new MockMating(9, 10, 11, 12),
		new MockMating(11, 12, 13), new MockMating(13, 14, 15),
		new MockMating(5, 5, 16), new MockMating(15, 16, 17)], 17, 1 / 4);  // an example in which the old and new algorithms actually gave different results - the first one such to have been observed! It seems that the old algorithm was wrong
	testCoefficientForSlave("negative IDs, 1",[new MockMating(-120, -120, 87)], 87, 1 / 2);  // negative IDs need to be accepted (not bothering to test any weird special IDs like -1 here though)
	testCoefficientForSlave("negative IDs, 2",[new MockMating(-999, -998, -300), new MockMating(-300, -998, 300)], 300, 1 / 4);

	// Do this last
	App.Testing.unitDone();
}


code:
// Define Test framework and register all tests

// To run all tests, type 'App.Loader.executeTests()' in the browser console. Make sure the HTML file has not been
// moved from the 'bin/' directory

// First, create the test framework
App.Testing = (function() {
	// First, abstract away anything test unrelated
	const group = App.Loader.getGroup("../tests");

	function addTestUnit(path) {
		group.queueSubscript(path);
	}

	let unitTotal = 0;
	let unitSuccesses = 0;

	function start() {
		App.Loader.nextScript();
	}

	function unitDone() {
		console.log("Group '" + App.Loader.lastScript + "' done. Successful tests:", unitSuccesses, "of", unitTotal);
		unitTotal = 0;
		unitSuccesses = 0;
		App.Loader.nextScript();
	}

	// Actual Testing functionality
	class TestError extends Error {
	}

	/**
	 * @param {string} name Unique test name
	 * @param {null|(()=>void)} prepare Prepare the global state
	 * @param {!(()=>void)} test Do the actual testing
	 * @param {null|(()=>void)} cleanup Clean the global state up
	 */
	function executeTest(name, prepare, test, cleanup) {
		unitTotal++;
		try {
			if (prepare != null) {
				prepare();
			}
		} catch (e) {
			console.log("PREPARE_FAILED", name, e);
			tryCleanup(name, cleanup);
			return;
		}
		try {
			test();
		} catch (e) {
			console.log("TEST_FAILED", name, e);
			tryCleanup(name, cleanup);
			return;
		}
		if (tryCleanup(name, cleanup)) {
			unitSuccesses++;
		}
	}

	/**
	 * @param {string} name Unique test name
	 * @param {null|(()=>void)} cleanup Clean the global state up
	 */
	function tryCleanup(name, cleanup) {
		try {
			if (cleanup != null) {
				cleanup();
			}
		} catch (e) {
			console.log("CLEANUP_FAILED", name, e);
			return false;
		}
		return true;
	}

	function equals(actual, expected) {
		if (!_.isEqual(actual, expected)) {
			throw new TestError("Actual value does not match expected value");
		}
	}

	/**
	 * @param {Object} obj
	 * @param {string} property
	 */
	function hasProperty(obj, property) {
		if (obj[property] === undefined) {
			throw new TestError("Expected property does not exist");
		}
	}

	/**
	 * @param {Object} obj
	 * @param {string} property
	 */
	function hasNoProperty(obj, property) {
		if (obj[property] !== undefined) {
			throw new TestError("Unexpected property exists");
		}
	}

	function isType(value, expected) {
		if (!(typeof value === expected)) {
			throw new TestError("Actual type does not match expected type");
		}
	}

	function notNaN(value) {
		if (Number.isNaN(value)) {
			throw new TestError("Expected valid number, got NaN.");
		}
	}

	function inRange(value, target, tolerance) {
		if (Math.abs(value - target) > tolerance) {
			throw new TestError(`Value ${value} outside range. Expected ${target} with tolerance ${tolerance}.`);
		}
	}

	return {
		addTestUnit, start, unitDone, executeTest,
		equals, hasProperty, hasNoProperty: hasNoProperty, isType, notNaN, inRange,
	};
})();

// Now load all tests
App.Testing.addTestUnit("diffProxy");
App.Testing.addTestUnit("ibc");

// Finally, execute the tests
App.Testing.start();


code:
:root {
    /* base */
    --background-color: #1c1d24;
    --sidebar-color: #212534;/*#2b2f42;*/
    --sidebar-hover-color: #40486e;
    --text-color: #e1e1e1;
    --divider-color: #888;

    /* button */
    --button-color: #2b2640;
    --button-border-color: #2b2640;
    --button-selected-color: #524064;
    --button-hover-color: #524064;
    --button-disabled-color: #22162e;

    --link-color: #6574cd;
    --link-hover-color: #6589e7;
}

.aquamarine, .aquamarine a, .skill, .skill a { color: #43e9e9 }
.coral, .coral a, .fetish.loss, .fetish.loss a { color: #ff8b39 }
.cyan, .cyan a, .hotkey { color: #00dfdf }
/*
.darkgoldenrod, .darkgoldenrod a, .trust.extremely-terrified, .trust.extremely-terrified a { color: darkgoldenrod }
.darkred, .darkred a, .defiant.full, .defiant.full a { color: darkred }
.darkviolet, .darkviolet a, .devotion.hateful, .devotion.hateful a { color: darkviolet }
*/
.deeppink, .deeppink a, .devotion.devoted, .devotion.devoted a { color: #ff4dad }
/*
.steelblue, .steelblue a { color: steelblue }
*/
.deepskyblue, .deepskyblue a, .intelligent, .intelligent a { color: #26bdef }
/*
.dodgerblue, .dodgerblue a { color: dodgerblue }
.blue, .blue a { color: blue }
*/
.gold, .gold a, .trust.dec, .trust.dec a, .trust.frightened, .trust.frightened a { color: #ffd90c }
/*
.goldenrod, .goldenrod a, .trust.terrified, .trust.terrified a { color: goldenrod }
.gray, .gray a { color: gray }
*/
.green, .green a, .reputation.inc, .reputation.inc a, .improvement, .improvement a, .flaw.break, .flaw.break a, .skill.inc, .skill.inc a, .positive, .positive a { color: #18af18 }
.hotpink, .hotpink a, .devotion.inc, .devotion.inc a, .devotion.accept, .devotion.accept a { color: #ff86c3 }
/*
.lawngreen, .lawngreen a { color: lawngreen }
.lightblue, .lightblue a { color: lightblue }
*/
.lightcoral, .lightcoral a, .fetish.gain, .fetish.gain a { color: #e762e3 }
.lightgreen, .lightgreen a, .relationship, .relationship a { color: #00cb7a }
/*
.lightpink, .lightpink a { color: lightpink }
.lightsalmon, .lightsalmon a, .fetish.inc, .fetish.inc a { color: lightsalmon }
*/
.lime, .lime a, .change.positive, .change.positive a, .virginity.loss, .virginity.loss a, .pregnant, .pregnant a { color: #40ff40 }
.limegreen, .limegreen a { color: limegreen }
.magenta, .magenta a, .devotion.worship, .devotion.worship a { color: #ff36ff }
.mediumaquamarine, .mediumaquamarine a, .trust.inc, .trust.inc a, .trust.careful, .trust.careful a { color: #52d3d3 }

.mediumorchid, .mediumorchid a, .devotion.dec, .devotion.dec a, .devotion.resistant, .devotion.resistant a { color: #bc60d3 }
/*
.mediumseagreen, .mediumseagreen a, .trust.trusting, .trust.trusting a { color: mediumseagreen }
*/
.orange, .orange a, .stupid, .stupid a, .change.negative, .change.negative a, .defiant.careful, .defiant.careful a, .intro.question { color: #ffbb3e }
/*
.orangered, .orangered a, .defiant.inc, .defiant.inc a, .defiant.bold, .defiant.bold, .education.neg a { color: orangered }
.orchid, .orchid a { color: orchid }
*/
.pink, .pink a, .slave.name.simple, .slave-name { color: #ff9fc3 }
.red, .red a, .health.dec, .health.dec a, .cash.dec, .cash.dec a, .flaw.gain, .flaw.gain a, .error, .error a, .elites.loss, .elites.loss a, .reputation.dec, .reputation.dec a, .warning, .warning a { color: #ff3131 }
/*.seagreen, .seagreen a, .trust.prof-trusting, .trust.prof-trusting a { color: seagreen }
.springgreen, .springgreen a, .skill.player, .skill.player a { color: springgreen }
*/
.tan, .tan a { color: #d2b48c }
/*
.chocolate, .chocolate a { color: chocolate }
.saddlebrown, .saddlebrown a { color: saddlebrown }
.teal, .teal a { color: teal }
*/
.yellow, .yellow a, .noteworthy, .noteworthy a, .devotion.ambivalent, .devotion.ambivalent a, .trust.fearful, .trust.fearful a { color: #f7f754 }
.yellowgreen, .yellowgreen a, .cash.inc, .cash.inc a, .cash, .cash a { color: #9acd32 }


code:
#init-screen {
	color: var(--text-color);
	background-color: var(--background-color);
}


code:
body {
	color: var(--text-color);
	background-color: var(--background-color);
}

a {
	color: var(--link-color);
}

a:hover {
	color: var(--link-hover-color);
}

button {
	color: var(--text-color);
	background-color: var(--button-color);
	border: 1px solid var(--button-border-color);
}

button:hover {
	background-color: var(--button-hover-color);
	border-color: var(--button-border-color);
}

button:disabled {
	background-color: var(--button-disabled-color);
	border: 1px solid var(--button-border-color);
}

input, select, textarea {
	color: #e1e1e1;
	background-color: transparent;
	border: 1px solid var(--button-border-color);
}

input:not(:disabled):focus, select:not(:disabled):focus, textarea:not(:disabled):focus,
input:not(:disabled):hover, select:not(:disabled):hover, textarea:not(:disabled):hover {
	background-color: #2b2640;
	border-color: var(--button-selected-color);
}

hr {
	border-top: 1px solid var(--divider-color);
}


code:
#ui-dialog-titlebar {
	background-color: var(--sidebar-color);
}
#ui-dialog-body {
	background-color: var(--background-color);
	border: 1px solid var(--divider-color);
}
#ui-dialog-body hr {
	background-color: var(--divider-color);
}


code:
/* Saves dialog styling. */
#ui-dialog-body.saves > *:not(:first-child) {
	border-top-color: var(--divider-color);
}
#ui-dialog-body.saves tr:not(:first-child) {
	border-top-color: var(--divider-color);
}
#ui-dialog-body.saves .empty {
	color: var(--text-color);
}


code:
#ui-bar {
	background-color: var(--sidebar-color);
	border-right: 1px solid var(--divider-color);
}
#ui-bar a:hover {
	text-decoration: underline;
}
#ui-bar hr {
	border-color: var(--divider-color);
}
#ui-bar-toggle{
	color: #444;
	border: 1px solid var(--divider-color);
	border-right: none;
}
#ui-bar-toggle:hover {
	background-color: var(--sidebar-hover-color);
	border-color: var(--divider-color);
}
#menu ul {
	border: 1px solid var(--divider-color);
}
#menu li:not(:first-child) {
	border-top: 1px solid var(--divider-color);
}
#menu li a {
	color: var(--text-color);
	background-color: transparent;
	border: none;
}
#menu li a:hover {
	background-color: var(--sidebar-hover-color);
	text-decoration: none;
}


code:
div.building.basement {
	border-top-color: #8F8F8F;
	background-image: repeating-linear-gradient(135deg, transparent, transparent 20px, #8F8F8F 20px, #8F8F8F 30px);
}

div.building .row .penthouse{
	border-color: #5daad7;
}

div.building .row .apartments{
	border-color: #3eaf3e;
}

div.building .row .private{
	border-color: #cb1a1a;
}

div.building .row .pit{
	border-color: #d94c18;
}

div.building div.innerCell {
	background-color: var(--background-color);
}

div.building div.decorative {
	outline-color: black;
}

/* introduction special formatting */
/* makes all links unusable */
.intro div.building a {
	color: var(--text-color);
}

div.building div.row div.dairy {
	border-color: saddlebrown;
}


code:
div.card {
	/*border: none;*/
	border-color: #384678;
	background-color: #212534;
	box-shadow: 10px 10px 5px #282d40;
}


code:
.lrgRender > div.mask, .lrgVector > div.mask {
	background: linear-gradient(90deg, rgba(28, 29, 36, 1), rgba(28, 29, 36, 0.8) 60%, rgba(28, 29, 36, 0));
}

.clear-formatting {
	color: var(--text-color);
}


code:
div.options-group .comment {
	color: #575757;
}


code:
div.quick-links div.toggle {
	background-color: var(--button-color);
}

div.quick-links div.toggle:hover {
	background-color: var(--button-hover-color);
}

div.quick-links div.menu-link {
	background-color: var(--button-disabled-color);
	border-color: var(--button-border-color);
	box-shadow: var(--button-border-color);
}

div.quick-links div.notification:not(.category)::after,
div.quick-links div.category.notification.collapsed > div.toggle::after {
	content: "\e80b";
}


code:
:root {
    /* base */
    --background-color: #fff;
    --sidebar-color: #ccf;
    --sidebar-hover-color: #99f;
    --text-color: #000;
    --divider-color: #888;

    /* button */
    --button-color: #b38cd9;
    --button-border-color: #9966cc;
    --button-selected-color: #663399;
    --button-hover-color: #ccb3e6;
    --button-disabled-color: #8c70a9;

    --link-color: #2A52B7;
    --link-hover-color: #446DD5;
}

.aquamarine, .aquamarine a, .skill, .skill a { color: #008563 }
.coral, .coral a, .fetish.loss, .fetish.loss a { color: #8a5c2e }
.cyan, .cyan a, .hotkey { color: #008585 }
/*
.darkgoldenrod, .darkgoldenrod a, .trust.extremely-terrified, .trust.extremely-terrified a { color: darkgoldenrod }
.darkred, .darkred a, .defiant.full, .defiant.full a { color: darkred }
.darkviolet, .darkviolet a, .devotion.hateful, .devotion.hateful a { color: darkviolet }
.deeppink, .deeppink a, .devotion.devoted, .devotion.devoted a { color: deeppink }
.steelblue, .steelblue a { color: steelblue }
*/
.deepskyblue, .deepskyblue a, .intelligent, .intelligent a { color: #00B4F0 }
/*
.dodgerblue, .dodgerblue a { color: dodgerblue }
.blue, .blue a { color: blue }
*/
.gold, .gold a, .trust.dec, .trust.dec a, .trust.frightened, .trust.frightened a { color: #cc8d00 }
/*
.goldenrod, .goldenrod a, .trust.terrified, .trust.terrified a { color: goldenrod }
.gray, .gray a { color: gray }
*/
.green, .green a, .reputation.inc, .reputation.inc a, .improvement, .improvement a, .flaw.break, .flaw.break a, .skill.inc, .skill.inc a, .positive, .positive a { color: #006600 }
/*
.hotpink, .hotpink a, .devotion.inc, .devotion.inc a, .devotion.accept, .devotion.accept a { color: hotpink }
.lawngreen, .lawngreen a { color: lawngreen }
.lightblue, .lightblue a { color: lightblue }
*/
.lightcoral, .lightcoral a, .fetish.gain, .fetish.gain a { color: #B85C00 }
.lightgreen, .lightgreen a, .relationship, .relationship a { color: #008550 }
/*
.lightpink, .lightpink a { color: lightpink }
.lightsalmon, .lightsalmon a, .fetish.inc, .fetish.inc a { color: lightsalmon }
.lime, .lime a, .change.positive, .change.positive a, .virginity.loss, .virginity.loss a, .pregnant, .pregnant a { color: lime }
.limegreen, .limegreen a { color: limegreen }
.magenta, .magenta a, .devotion.worship, .devotion.worship a { color: magenta }
*/
.mediumaquamarine, .mediumaquamarine a, .trust.inc, .trust.inc a, .trust.careful, .trust.careful a { color: #0F6161 }
/*
.mediumorchid, .mediumorchid a, .devotion.dec, .devotion.dec a, .devotion.resistant, .devotion.resistant a { color: mediumorchid }
.mediumseagreen, .mediumseagreen a, .trust.trusting, .trust.trusting a { color: mediumseagreen }
*/
.orange, .orange a, .stupid, .stupid a, .change.negative, .change.negative a, .defiant.careful, .defiant.careful a, .intro.question { color: #B85C00 }
/*
.orangered, .orangered a, .defiant.inc, .defiant.inc a, .defiant.bold, .defiant.bold, .education.neg a { color: orangered }
.orchid, .orchid a { color: orchid }
*/
.pink, .pink a, .slave.name.simple, .slave-name { color: #E00070 }
/*
.red, .red a, .health.dec, .health.dec a, .cash.dec, .cash.dec a, .flaw.gain, .flaw.gain a, .error, .error a, .elites.loss, .elites.loss a, .reputation.dec, .reputation.dec a, .warning, .warning a { color: red }
.seagreen, .seagreen a, .trust.prof-trusting, .trust.prof-trusting a { color: seagreen }
.springgreen, .springgreen a, .skill.player, .skill.player a { color: springgreen }
*/
.tan, .tan a { color: #846E58 }
/*
.chocolate, .chocolate a { color: chocolate }
.saddlebrown, .saddlebrown a { color: saddlebrown }
.teal, .teal a { color: teal }
*/
.yellow, .yellow a, .noteworthy, .noteworthy a, .devotion.ambivalent, .devotion.ambivalent a, .trust.fearful, .trust.fearful a { color: #cc9600 }
.yellowgreen, .yellowgreen a, .cash.inc, .cash.inc a, .cash, .cash a { color: #677f1f }


code:
body {
	color: var(--text-color);
	background-color: var(--background-color);
}

a {
	color: var(--link-color);
}

a:hover {
	color: var(--link-hover-color);
}

button {
	color: var(--text-color);
	background-color: var(--button-color);
	border: 1px solid var(--button-border-color);
}

button:hover {
	background-color: var(--button-hover-color);
	border-color: var(--button-border-color);
}

button:disabled {
	background-color: var(--button-disabled-color);
	border: 1px solid var(--button-border-color);
}

input, select, textarea {
	color: #333;
	background-color: transparent;
	border: 1px solid var(--button-border-color);
}

input:not(:disabled):focus, select:not(:disabled):focus, textarea:not(:disabled):focus,
input:not(:disabled):hover, select:not(:disabled):hover, textarea:not(:disabled):hover {
	background-color: #e5d9f2;
	border-color: var(--button-selected-color);
}

hr {
	border-top: 1px solid var(--divider-color);
}


code:
#ui-dialog-titlebar {
	background-color: var(--divider-color);
}
#ui-dialog-body {
	background-color: var(--sidebar-color);
	border: 1px solid var(--divider-color);
}
#ui-dialog-body hr {
	background-color: var(--divider-color);
}


code:
div.building.basement {
	border-top-color: #8F8F8F;
	background-image: repeating-linear-gradient(135deg, transparent, transparent 20px, #8F8F8F 20px, #8F8F8F 30px);
}

div.building div.innerCell {
	background-color: var(--background-color);
}

div.building div.decorative {
	outline-color: black;
}

/* introduction special formatting */
/* makes all links unusable */
.intro div.building a {
	color: var(--text-color);
}

div.building div.row div.dairy {
	border-color: saddlebrown;
}


code:
div.card {
	/*border: none;*/
	border-color: #cccccc;
	background-color: #e6e6e6;
	box-shadow: 10px 10px 5px #b3b3b3;
}


code:
.lrgRender > div.mask, .lrgVector > div.mask {
	background: linear-gradient(90deg, rgba(255, 255, 255, 1), rgba(255, 255, 255, 0.8) 60%, rgba(255, 255, 255, 0));
}

.clear-formatting {
	color: var(--text-color);
}


code:
# Themes

Every subdirectory is a distinct theme. The compiled theme is placed in `bin/` and can be loaded ingame via the options
page.

## Creating new themes

Files inside these directories are combined into one CSS file based on
alphabetical ordering. The light theme is recommended as a base for creating new themes.

For quick testing run `App.UI.Theme.loadTheme("yourTheme.css")` in the browser console. This will unload the current
theme and load the new theme. If you modify the same file it is effectively reloading. Supplying no argument (eg.
`App.UI.Theme.loadTheme()`) will unload any theme.


code:
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto

###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs     diff=csharp

###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln       merge=binary
#*.csproj    merge=binary
#*.vbproj    merge=binary
#*.vcxproj   merge=binary
#*.vcproj    merge=binary
#*.dbproj    merge=binary
#*.fsproj    merge=binary
#*.lsproj    merge=binary
#*.wixproj   merge=binary
#*.modelproj merge=binary
#*.sqlproj   merge=binary
#*.wwaproj   merge=binary

###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg   binary
#*.png   binary
#*.gif   binary

###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc   diff=astextplain
#*.DOC   diff=astextplain
#*.docx  diff=astextplain
#*.DOCX  diff=astextplain
#*.dot   diff=astextplain
#*.DOT   diff=astextplain
#*.pdf   diff=astextplain
#*.PDF   diff=astextplain
#*.rtf   diff=astextplain
#*.RTF   diff=astextplain

repo_name: LamkasDev/rjw

path: .gitattributes


code:
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.

# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
*.csproj.user

# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs

# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/

# Visual Studio 2015 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/

# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*

# NUNIT
*.VisualState.xml
TestResult.xml

# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c

# DNX
project.lock.json
project.fragment.lock.json
artifacts/

*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc

# Chutzpah Test files
_Chutzpah*

# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb

# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap

# TFS 2012 Local Workspace
$tf/

# Guidance Automation Toolkit
*.gpState

# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user

# JustCode is a .NET coding add-in
.JustCode

# TeamCity is a build add-in
_TeamCity*

# DotCover is a Code Coverage Tool
*.dotCover

# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*

# MightyMoose
*.mm.*
AutoTest.Net/

# Web workbench (sass)
.sass-cache/

# Installshield output folder
[Ee]xpress/

# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html

# Click-Once directory
publish/

# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
#*.pubxml
*.publishproj

# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/

# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
# NuGet v3's project.json files produces more ignoreable files
*.nuget.props
*.nuget.targets

# Microsoft Azure Build Output
csx/
*.build.csdef

# Microsoft Azure Emulator
ecf/
rcf/

# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt

# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/

# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
node_modules/
orleans.codegen.cs

# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/

# RIA/Silverlight projects
Generated_Code/

# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm

# SQL Server files
*.mdf
*.ldf

# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings

# Microsoft Fakes
FakesAssemblies/

# GhostDoc plugin setting file
*.GhostDoc.xml

# Node.js Tools for Visual Studio
.ntvs_analysis.dat

# Visual Studio 6 build log
*.plg

# Visual Studio 6 workspace options file
*.opt

# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions

# Paket dependency manager
.paket/paket.exe
paket-files/

# FAKE - F# Make
.fake/

# JetBrains Rider
.idea/
*.sln.iml

# CodeRush
.cr/

# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
/Archived
/MyTo-Do.txt
/LL_post.txt
/creditsnew.txt
/Assemblies/UnityEngine.UI.dll
/Assemblies/UnityEngine.dll
/Assemblies/TextMeshPro-1.0.55.56.0b11.dll
/Assemblies/NVorbis.dll
/Assemblies/NAudio.dll
/Assemblies/HugsLib.dll
/Assemblies/Assembly-CSharp.dll
/Assemblies/Assembly-CSharp-firstpass.dll
/Assemblies/0Harmony.dll
/RimJobWorld.Main.sln
/.gitignore
/1.3/Assemblies/UnityEngine.SubsystemsModule.dll
/1.3/Assemblies/HarmonyMod.dll
/1.3/Assemblies/0Harmony.dll

path: .gitignore


code:
<?xml version="1.0" encoding="utf-8" ?>

<Defs>
	<rjw.BackstoryDef>
		<defName>rjw_childC</defName>
		<baseDescription>[PAWN_nameDef] was born in civil settlement.</baseDescription>
		<title>Child</title>
		<titleShort>Child</titleShort>
		<slot>Childhood</slot>
		<categoryName>Civil</categoryName>
	</rjw.BackstoryDef>

	<rjw.BackstoryDef>
		<defName>rjw_childT</defName>
		<baseDescription>[PAWN_nameDef] was born in a tribe.</baseDescription>
		<title>Child</title>
		<titleShort>Child</titleShort>
		<slot>Childhood</slot>
		<categoryName>Tribal</categoryName>
	</rjw.BackstoryDef>
</Defs>


code:
<?xml version="1.0" encoding="utf-8" ?>

<Defs>
	<BodyPartDef>
		<defName>Genitals</defName>
		genitals
		<hitPoints>12</hitPoints>
		<frostbiteVulnerability>0.01</frostbiteVulnerability>
		<skinCovered>true</skinCovered>
		<solid>false</solid>
		<canSuggestAmputation>false</canSuggestAmputation>
		<bleedRate>1.0</bleedRate>
		<tags>

RJW_Fertility

		</tags>
	</BodyPartDef>

	<BodyPartDef>
		<defName>Chest</defName>
		chest
		<hitPoints>24</hitPoints>
		<frostbiteVulnerability>0.01</frostbiteVulnerability>
		<skinCovered>true</skinCovered>
		<solid>false</solid>
		<canSuggestAmputation>false</canSuggestAmputation>
		<bleedRate>1.0</bleedRate>
	</BodyPartDef>

	<BodyPartDef>
		<defName>Anus</defName>
		anus
		<hitPoints>8</hitPoints>
		<frostbiteVulnerability>0.01</frostbiteVulnerability>
		<skinCovered>true</skinCovered>
		<solid>false</solid>
		<canSuggestAmputation>false</canSuggestAmputation>
		<bleedRate>1.0</bleedRate>
	</BodyPartDef>
</Defs>


code:
<?xml version="1.0" encoding="utf-8" ?>

<Defs>
	<BodyPartDef>
		<defName>MechGenitals</defName>
		mechanoid implanter
		<hitPoints>20</hitPoints>
		<skinCovered>true</skinCovered>
		<solid>true</solid>
		<canSuggestAmputation>false</canSuggestAmputation>
		<bleedRate>0</bleedRate>
		<frostbiteVulnerability>0.0</frostbiteVulnerability>
		<tags>

RJW_Fertility

		</tags>
	</BodyPartDef>
</Defs>


code:
<?xml version="1.0" encoding="utf-8" ?>

<Defs>
	<BodyPartTagDef>
		<defName>RJW_Fertility</defName>
		<label>fertility_source</label>
		<description>Neccessary for reproduction</description>
		<vital>false</vital>
	</BodyPartTagDef>
</Defs>


code:
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
	<DamageDef>
		<defName>ViralDamage</defName>
		<workerClass>DamageWorker_AddInjury</workerClass>
		<label>viral damage</label>
		<externalViolence>false</externalViolence>
		<deathMessage>{0} has died of a disease.</deathMessage>
		<hediff>VirusPerma</hediff>
		<hediffSkin>VirusPerma</hediffSkin>
		<hediffSolid>VirusPerma</hediffSolid>
		<makesBlood>false</makesBlood>
		<harmAllLayersUntilOutside>false</harmAllLayersUntilOutside>
	</DamageDef>
</Defs>


code:
<?xml version="1.0" encoding="utf-8" ?>

<Defs>
	<ThingDef ParentName="MakeableDrugBase">
		<defName>Aphrodisiac</defName>
		Aphrodisiac drug
		<description>A smokable drug made from humpshroom.</description>
		<graphicData>
			<texPath>Things/Item/Drug/Penoxycyline</texPath>
			<graphicClass>Graphic_StackCount</graphicClass>
		</graphicData>
		<rotatable>false</rotatable>
		<statBases>
			<WorkToMake>450</WorkToMake>
			<MarketValue>50</MarketValue>
			<Mass>0.05</Mass>
			<DeteriorationRate>6</DeteriorationRate>
			<Flammability>1.3</Flammability>
		</statBases>
		<techLevel>Medieval</techLevel>
		<ingestible>
			<foodType>Plant, Processed</foodType>
			<joyKind>Chemical</joyKind>
			<joy>0.80</joy>
			<baseIngestTicks>720</baseIngestTicks>
			<nurseable>true</nurseable>
			<drugCategory>Social</drugCategory>
			<ingestSound>Ingest_Smoke</ingestSound>
			<ingestEffect>Smoke_Joint</ingestEffect>
			<ingestEffectEat>EatVegetarian</ingestEffectEat>
			<ingestHoldOffsetStanding>
				<northDefault>
					<offset>(0.27,0,0.08)</offset>
					<behind>true</behind>
				</northDefault>
				<east>
					<offset>(0.45,0,0.08)</offset>
				</east>
				<south>
					<offset>(0.27,0,0.08)</offset>
				</south>
				<west>
					<offset>(-0.50,0,0.08)</offset>
					<flip>true</flip>
				</west>
			</ingestHoldOffsetStanding>
			<ingestHoldUsesTable>false</ingestHoldUsesTable>
			<ingestCommandString>Smoke {0}</ingestCommandString>
			<ingestReportString>Smoking {0}.</ingestReportString>
			<ingestReportStringEat>Consuming {0}.</ingestReportStringEat>
			<useEatingSpeedStat>false</useEatingSpeedStat>
			<outcomeDoers>

					<hediffDef>HumpShroomEffect</hediffDef>
					<severity>0.6</severity>
					<toleranceChemical>HumpShroom</toleranceChemical>

					<hediffDef>HumpShroomTolerance</hediffDef>
					<severity>0.042</severity>
					<divideByBodySize>true</divideByBodySize>

					<need>Sex</need>
					<offset>-0.5</offset>

			</outcomeDoers>
		</ingestible>
		<recipeMaker>
			<recipeUsers>

CraftingSpot

DrugLab

			</recipeUsers>
			<workSpeedStat>DrugCookingSpeed</workSpeedStat>
			<workSkill>Cooking</workSkill>
		</recipeMaker>
		<costList>
			<HumpShroom>4</HumpShroom>
		</costList>
		<comps>

				<chemical>HumpShroom</chemical>
				<addictiveness>0.030</addictiveness>
				<minToleranceToAddict>0.15</minToleranceToAddict>
				<existingAddictionSeverityOffset>0.1</existingAddictionSeverityOffset>
				<needLevelOffset>1</needLevelOffset>
				<listOrder>30</listOrder>

		</comps>
	</ThingDef>
</Defs>


code:
<?xml version="1.0" encoding="utf-8" ?>

<Defs>

	<ThingDef ParentName="MakeableDrugPillBase">
		<defName>RJW_Contraceptive</defName>
		Contraception pills
		<description>Deliver severe hit to fertility for one quadrum.</description>
		<graphicData>
			<texPath>Things/Item/Contraceptive</texPath>
			<graphicClass>Graphic_StackCount</graphicClass>
		</graphicData>
		<rotatable>false</rotatable>
		<statBases>
			<WorkToMake>120</WorkToMake>
			<MarketValue>15</MarketValue>
			<Mass>0.05</Mass>
		</statBases>
		<techLevel>Industrial</techLevel>
		<ingestible>
			<drugCategory>Medical</drugCategory>
			<outcomeDoers>

					<hediffDef>RJW_Contraceptive</hediffDef>
					<severity>1.0</severity>

			</outcomeDoers>
		</ingestible>
		<recipeMaker>
			<researchPrerequisite>DrugProduction</researchPrerequisite>
			<recipeUsers>

DrugLab

			</recipeUsers>
		</recipeMaker>
		<costList>
			<MedicineHerbal>1</MedicineHerbal>
		</costList>
		<comps>

			<addictiveness>0</addictiveness>
			<listOrder>1000</listOrder>
				<overdoseSeverityOffset>
					<min>0.08</min>
					<max>0.14</max>
				</overdoseSeverityOffset>

		</comps>
	</ThingDef>

	<HediffDef>
		<defName>RJW_Contraceptive</defName>
		<hediffClass>HediffWithComps</hediffClass>
		contraceptive
		<description>Contraception pills deliver severe hit to fertility for one quadrum.</description>
		<defaultLabelColor>(0.75, 0.75, 1.0)</defaultLabelColor>
		<isBad>false</isBad>
		<comps>

				<disappearsAfterTicks>
					<min>900000</min>
					<max>900000</max>
				</disappearsAfterTicks>

		</comps>
		<stages>

				Contracepted
				<capMods>

						<capacity>RJW_Fertility</capacity>
						<setMax>0.1</setMax>

				</capMods>

		</stages>
	</HediffDef>
</Defs>


code:
<?xml version="1.0" encoding="utf-8" ?>

<Defs>

	<ThingDef ParentName="MakeableDrugPillBase">
		<defName>RJW_FertPill</defName>
		Hyperfertility pills
		<description>Enhances fertility for 48 hours. Also works as a mild aphrodisiac.</description>
		<graphicData>
			<texPath>Things/Item/FertPill</texPath>
			<graphicClass>Graphic_StackCount</graphicClass>
		</graphicData>
		<rotatable>false</rotatable>
		<statBases>
			<WorkToMake>120</WorkToMake>
			<MarketValue>15</MarketValue>
			<Mass>0.05</Mass>
		</statBases>
		<techLevel>Industrial</techLevel>
		<ingestible>
			<drugCategory>Medical</drugCategory>
			<outcomeDoers>

					<hediffDef>RJW_FertPill</hediffDef>
					<severity>1.0</severity>

					<need>Sex</need>
					<offset>-0.25</offset>

			</outcomeDoers>
		</ingestible>
		<recipeMaker>
			<researchPrerequisite>DrugProduction</researchPrerequisite>
			<recipeUsers>

DrugLab

			</recipeUsers>
		</recipeMaker>
		<costList>
			<MedicineHerbal>1</MedicineHerbal>
			<HumpShroom>1</HumpShroom>
		</costList>
		<comps>

			<addictiveness>0</addictiveness>
			<listOrder>1000</listOrder>
				<overdoseSeverityOffset>
					<min>0.08</min>
					<max>0.14</max>
				</overdoseSeverityOffset>

		</comps>
	</ThingDef>

	<HediffDef>
		<defName>RJW_FertPill</defName>
		<hediffClass>HediffWithComps</hediffClass>
		fertpill
		<description>Enhanced fertility. A mild aphrodisiac.</description>
		<defaultLabelColor>(0.75, 0.75, 1.0)</defaultLabelColor>
		<isBad>false</isBad>
		<comps>

				<disappearsAfterTicks>
					<min>120000</min>
					<max>120000</max>
				</disappearsAfterTicks>

		</comps>
		<stages>

				Hyperfertility
				<capMods>

						<capacity>RJW_Fertility</capacity>
						<offset>4</offset>

				</capMods>

		</stages>
	</HediffDef>
</Defs>