code:
// cSpell:ignore ibcoeffs

/** @returns {DocumentFragment} */
App.Intro.acquisition = function() {
	const el = new DocumentFragment();
	const valueOwed = (V.saveImported === 1) ? 5000 : 50000;
	const r = [];

	if (_.isArray(V.temp.careerBonusNeeded)) {
		getSlaves().filter(s => V.temp.careerBonusNeeded.includes(s.ID)).forEach(App.StartingGirls.applyCareerBonus);
		delete V.temp.careerBonusNeeded;
		delete V.temp.applyCareerBonus;
	}
	getSlaves().filter(s => s.origin === "$auto").forEach(s => App.StartingGirls.playerOrigin(s).apply());

	if (V.freshPC === 1 || V.saveImported === 0) {
		PCSetup();
	}
	if (V.policies.contraceptivesBan && V.PC.preg === -1) {
		V.PC.preg = 0;
	}
	SetBellySize(V.PC);

	parentSetup();
	PCChildrenCount();
	inbreedingCalc();
	if (V.plot === 1 && V.neighboringArcologies > 0) {
		V.arcologies.reduce((acc, val) => (val.direction !== 0 && val.prosperity > acc.prosperity) ? val : acc, V.arcologies[1]).rival = 1;
		V.rival.state = 2;
	}
	V.targetAgeNursery = V.minimumSlaveAge;
	resetFamilyCounters();

	delete V.temp.limitedCheatStart;

	App.UI.DOM.appendNewElement("p", el, "You've done it.");
	App.UI.DOM.appendNewElement("p", el, `You arrive at your new arcology, ${V.arcologies[0].name}, and head straight to the penthouse to enter the access codes that will tell the ${V.arcologies[0].name} systems to recognize you as their owner. The penthouse office is ready to receive the codes, and they authenticate. A voice activates in your earpiece.`);
	App.UI.DOM.appendNewElement("p", el, `Congratulations. I am a personal assistant program, and it is my pleasure to assist you, ${PlayerName()} the new owner of ${V.arcologies[0].name}. I will offer useful information whenever possible in italics. Your new arcology has some unusual equipment. The previous owner kept a small stable of sex slaves. The penthouse therefore has a body modification studio for tattooing, bleaching and piercing, and an auto salon for more prosaic things like hair care. It also has a remote surgery, a small surgical theater that can be operated remotely by a qualified surgeon if you can pay the fee. Finally, it has a slave nutrition system connected to the arcology's hydroponics bays. This system produces a tasty protein-rich drink that provides the physically active female body all its necessary nutrients while leaving the lower digestive tract extremely clean. It even causes a mild increase in sex drive.`, ["note"]);

	r.push(`The previous owner seems to have left in something of a hurry.`);
	let valueGiven = 0;
	if (V.cheatMode === 1) {
		r.push(`Since you've elected to take over an arcology with special advantages, you've acquired a very special group of slaves.`);
		r.push(App.Intro.cheatModeSlaves());
	} else if (V.saveImported === 1) {
		r.push(`Since it took some time for you to ensure that your existing stable of slaves were safely moved to ${V.arcologies[0].name}, the previous owner had the time to get most of their things away.`);
	} else if ((V.targetArcology.fs !== "New") && (V.targetArcology.fs !== "FSNull")) {
		for (let j = 0; j < 5; j++) {
			if (valueOwed - valueGiven <= 5000) {
				break;
			}
			const slave = generateFSSlave(V.targetArcology.fs);
			const slaveCostValue = slaveCost(slave);
			if (["FSAztecRevivalist", "FSChineseRevivalist", "FSNeoImperialist", "FSRestart", "FSSlaveProfessionalism"].includes(V.targetArcology.fs)) {
				valueGiven += slaveCostValue * 4;
			} else {
				valueGiven += slaveCostValue;
			}
			newSlave(slave);
		}
		const {He, his, girl} = getPronouns(getSlaves().randomValue());
		switch (V.targetArcology.fs) {
			case "FSSupremacist":
				r.push(`They kept a personal stable of fearful ${V.arcologies[0].FSSupremacistRace} sex slaves, but their sexual training is incomplete. Several of them are still here.`);
				break;
			case "FSSubjugationist":
				r.push(`They made it a special goal to enslave and whore out as many ${V.arcologies[0].FSSubjugationistRace} people as possible. Several of them are still here.`);
				break;
			case "FSGenderRadicalist":
				r.push(`They were in the process of building a stable of pretty young shemale whores. Several of them are still here. They're are all very attracted to men, and skilled at pleasing them.`);
				break;
			case "FSGenderFundamentalist":
				r.push(`They kept a personal stable of slaves for breeding purposes. Several of them are still here. They've been kept pregnant, and work as servants when they aren't being bred.`);
				break;
			case "FSPaternalist":
				r.push(`Their slaves were all very well treated. Several of them are still here. They were allowed to work as maids, and weren't even forced to have sex.`);
				break;
			case "FSDegradationist":
				r.push(`Their personal slaves were all Fuckdolls, slaves who have been permanently encased in advanced latex suits and converted into living sex toys. Several of them are still here.`);
				break;
			case "FSAssetExpansionist":
				r.push(`They kept a collection of bejeweled boobs for company, but they focused on breast expansion to the exclusion of the slaves' emotional training. Several of them are still here.`);
				break;
			case "FSSlimnessEnthusiast":
				r.push(`They kept a harem of slim, pretty girls, and treated them very well. Several of them are still here. They should be very trusting of a new owner.`);
				break;
			case "FSTransformationFetishist":
				r.push(`They were just putting the finishing touches on a planned brothel's worth of surgically enhanced whores. Several of them are still here. They are already used to prostitution.`);
				break;
			case "FSBodyPurist":
				r.push(`Their slaves were trained for sexual use, but their health, fitness, and natural appearance were the main priorities. Several of them are still here.`);
				break;
			case "FSMaturityPreferentialist":
				r.push(`They preferred to keep their MILFs as scantily clad servants. Several of them are still here. They aren't all happy to be sex objects, but they're used to it.`);
				break;
			case "FSYouthPreferentialist":
				r.push(`They treated their young slaves very well. Several of them are still here. Virgins have been carefully preserved, but have learned to use their mouths through experience.`);
				break;
			case "FSPastoralist":
				r.push(`Their herd of cow girls was progressing nicely, though more progress had been made on milk production than on training. Several of them are still here.`);
				break;
			case "FSPhysicalIdealist":
				r.push(`Their slaves worked as prostitutes, but mostly to fund a program of muscle building for all of them, which was nearing completion. Several of them are still here.`);
				break;
			case "FSChattelReligionist":
				r.push(`They were recruiting a stable of slave whores by targeting people with religious complexes that made them particularly vulnerable to recruitment. Several of them are still here.`);
				break;
			case "FSRomanRevivalist":
				r.push(`The only one of their slaves left is the bodyguard. ${He} should be very willing to transfer ${his} loyalty to you, as ${his} new owner.`);
				break;
			case "FSNeoImperialist":
				r.push(`This arcology kept their Knights as actively enslaved and docile servants; you find one of them remaining here. ${He} immediately pledges ${his} loyalty over to you.`);
				break;
			case "FSAztecRevivalist":
				r.push(`They maintained a combination of disobedient slaves, deemed incorrigible and blood priestesses. Since they offer themselves as slaves willingly, one has remained loyal to the owner of the arcology.`);
				break;
			case "FSEgyptianRevivalist":
				r.push(`They kept a harem of beautiful slave girls, who have been well-treated and well-trained. They should be very willing to serve you in turn.`);
				break;
			case "FSEdoRevivalist":
				r.push(`They put considerable effort into creating modern slave geisha, elegant Japanese slaves who were assigned to serve the public. Several of them are still here.`);
				break;
			case "FSArabianRevivalist":
				r.push(`They trained slaves personally, and never kept slaves for very long. The slaves they left are all partway through being trained.`);
				break;
			case "FSChineseRevivalist":
				r.push(`The only one of their slaves left is the Head Girl. ${He} should be willing to transfer ${his} loyalty to you, as ${his} proper superior.`);
				break;
			case "FSAntebellumRevivalist":
				r.push(`They kept a stable of servants, who the previous owner called 'house slaves'. They have hope that you'll be kinder to them, but do not trust you.`);
				break;
			case "FSRestart":
				r.push(`The only one of their slaves left is an absolute beauty of a ${girl}. ${He} is too smart for ${his} own good and will likely not trust you.`);
				break;
			case "FSRepopulationFocus":
				r.push(`They kept a personal stable of slaves for breeding purposes. Several of them are still here. They've been kept heavily pregnant, and are used to being used whilst so.`);
				break;
			case "FSHedonisticDecadence":
				r.push(`Their slaves were heavily pampered; free to lie about, fuck, and eat as much as they wanted. Several of them are still here, too lazy and spoiled to leave. They eagerly paw at you upon your arrival, begging for their bellies to be filled with food and for a good, hard fucking.`);
				break;
			case "FSIntellectualDependency":
				r.push(`They kept several vapid sluts, who are now desperately begging you for sex. It seems they've drained the life out of every toy left behind and have no idea how to recharge them.`);
				break;
			case "FSSlaveProfessionalism":
				r.push(`The only one of their slaves left has made it ${his} mission to have the penthouse ready for whomever should come to own it. ${He} greets you cordially, hands you a detailed summary of ${his} skills and information on your new arcology, and calmly awaits further orders.`);
				break;
			case "FSPetiteAdmiration":
				r.push(`They had quite the impish little harem. Several of them are still here and immediately swarm you as a welcome, eager to see someone taller again.`);
				break;
			case "FSStatuesqueGlorification":
				r.push(`They had quite the collection of towering slaves. Several of them are still here. They gaze down on you, evaluating how best to serve their new owner.`);
				break;
			default:
				r.push(`ERROR: bad arcology type`);
		}
	} else {
		r.push(`They could not get all of their personal effects away. Since they`);
		if (V.targetArcology.fs === "FSNull") {
			r.push(`tried to sample different kinds of sexual slavery,`);
		} else {
			r.push(`did not have the time in control of the arcology to develop a specific stable of sex slaves,`);
		}
		r.push(`their slaves were quite varied.`);
		const heroSlaves = App.Utils.buildHeroArray().shuffle();
		const families = App.Utils.getHeroFamilies();
		// If applicable, extract the incestuous families object from "families".
		let incestFamilies;
		if (V.seeIncest === 1) {
			incestFamilies = families.incest;
			delete families.incest;
		}
		/* Remove one member of each family from heroSlaves, and place her in a second list that we'll draw from to add her if the
			first member is acquired; leaving both in the list would essentially double the chance of acquiring them. Note that	the
			keys of "secondFamilyMembers" are the database ID's of the first members, while the values are the templates of the
			second family members. This reverses the order of keys and values in the "families" object, because it's easier to code
			it that way. (However, the keys and values for incestuous families remain are not reversed, because we need to be able
			to index by the second sibling in this case.)
		*/
		const secondFamilyMembers = {};
		for (let i = 0; i < heroSlaves.length; i++) {
			const heroSlave = heroSlaves[i];
			const heroSlaveID = heroSlave.ID;
			if (heroSlaveID in families) {
				const firstMemberID = families[heroSlaveID];
				secondFamilyMembers[firstMemberID] = heroSlave;
				heroSlaves.splice(i, 1);
				i--;
			}
		}
		let addFamilyMember = false;
		let secondMember = baseSlave();
		let slaveDatabaseID = 0;
		let secondMemberDatabaseID = 0; // We initialize this here so that we use it to set incestuous relationships.
		let isSecondMember = false;
		for (let j = 0; j < heroSlaves.length; j++) {
			let slave;
			if (addFamilyMember) {
				slave = secondMember;
				isSecondMember = true;
				addFamilyMember = false;
			} else if (valueOwed - valueGiven <= 5000) {
				break;
			} else {
				isSecondMember = false;
				const heroSlaveTemplate = heroSlaves[j];
				slaveDatabaseID = heroSlaveTemplate.ID;
				heroSlaves.splice(j, 1);
				j--;
				slave = App.Utils.getHeroSlave(heroSlaveTemplate);
				let slaveCostValue = slaveCost(slave);
				if (slaveDatabaseID in secondFamilyMembers) {
					const secondMemberTemplate = secondFamilyMembers[slaveDatabaseID];
					secondMemberDatabaseID = secondMemberTemplate.ID;
					secondMember = App.Utils.getHeroSlave(secondMemberTemplate);

					/* Check for identical twins to make them look identical. Note here that we're using the original age gap, not the lolified
						one, since there's a small chance that lolification could reduce a positive age gap to 0.
					*/
					if (secondMemberTemplate.actualAge === heroSlaveTemplate.actualAge && secondMemberTemplate.birthWeek === heroSlaveTemplate.birthWeek) {
						secondMember.natural.artSeed = slave.natural.artSeed;
					}

					/* Make sure siblings are far enough apart to allow for the younger one to gestate, or twins.

						If there were siblings in the database with skills for leadership positions dependent on age, we'd want to add code to
						make sure neither sibling gets lolified if at least one of them has such a skill, using App.Utils.oldIsGood to test
						both templates before calling getHeroSlave on either. That code hasn't been added right now because there are no
						slaves to test it on.
					*/
					const ageGap = secondMember.actualAge - slave.actualAge;
					if (ageGap === 1) {
						const minBirthWeek = slave.birthWeek - 10;
						if (secondMember.birthWeek < minBirthWeek) {
							secondMember.birthWeek = random(minBirthWeek, 51);
						}
					} else if (ageGap === 0) {
						secondMember.birthWeek = slave.birthWeek; // Fraternal twins
					} else if (ageGap === -1) {
						const maxBirthWeek = slave.birthWeek + 10;
						if (secondMember.birthWeek > maxBirthWeek) {
							secondMember.birthWeek = random(0, maxBirthWeek);
						}
					}
					slaveCostValue += slaveCost(secondMember);
					addFamilyMember = true;
				}
				if (valueGiven + slaveCostValue >= valueOwed * 2) {
					addFamilyMember = false;
					secondMemberDatabaseID = 0;
					continue; // Skip to the next slave in the list, until we find one cheap enough.
				}
				valueGiven += slaveCostValue; // For family members, cost is added for both at the same time, so doesn't need to be again in the next iteration.
			}
			const {him, he, his, himself} = getPronouns(slave);
			nationalityToAccent(slave);
			slave.oldDevotion = slave.devotion;
			slave.oldTrust = slave.trust;
			slave.health.tired = 0;
			if (isSecondMember) {
				V.heroSlavesPurchased.push(secondMemberDatabaseID);
			} else {
				V.heroSlavesPurchased.push(slaveDatabaseID);
			}

			newSlave(slave);

			// Set incestuous relationship. Checking "addFamilyMember" makes sure we only do this after both family members have been added.
			let incestTarget = undefined;
			if (V.seeIncest === 1 && !addFamilyMember && secondMemberDatabaseID in incestFamilies) {
				incestTarget = getSlaves().find(s => areSisters(s, slave) > 0)?.value;
				slave.relationship = 3;
				slave.relationshipTarget = incestTarget.ID;
				incestTarget.relationship = 3;
				incestTarget.relationshipTarget = slave.ID;
				secondMemberDatabaseID = 0;
			}

			r.push(slave.slaveName);
			if (slave.fetish === Fetish.MINDBROKEN) {
				r.push(`is, sadly, not mentally competent, and is wandering through the penthouse at the moment.`);
			} else if (isAmputee(slave)) {
				r.push(`is a quadruple amputee and is quite helpless, so you can attend to ${him} at your leisure.`);
			} else if (incestTarget) {
				const {sister} = getPronouns(incestTarget);
				r.push(`is engaged in an incestuous relationship with ${getSlave(slave.relationshipTarget).slaveName}, and has just begun to perform oral sex on ${him} ${sister}.`);
				incestTarget = undefined;
			} else if (slave.devotion < -50) {
				r.push(`is quite rebellious and was attempting to escape, so I have locked ${him} in the slave quarters.`);
			} else if (slave.devotion < -20) {
				r.push(`resists my orders and was considering escape, so I have locked ${him} in the slave quarters.`);
			} else if (slave.devotion <= 20) {
				r.push(`is reasonably obedient, and is waiting for you in the dormitory, I believe in the hope of making a good impression.`);
			} else if (slave.energy > 95) {
				r.push(`is a remarkable sexual addict, and I believe ${he} will be very happy to meet you.`);
			} else if (slave.fetish === "pregnancy") {
				if (slave.bellyPreg >= 500) {
					r.push(`is currently in the dormitory masturbating over ${his} growing pregnancy, and`);
					if (V.PC.belly >= 5000) {
						r.push(`will certainly be eager to meet you.`);
					} else {
						r.push(`I believe ${he} will be happy to show it to you.`);
					}
				} else {
					r.push(`is currently in the dormitory examining ${himself} to try to discern ${his} fertility, and I believe ${he} will be`);
					if (V.PC.belly >= 5000) {
						r.push(`eager to get acquainted with you.`);
					} else {
						r.push(`happy to meet you.`);
					}
				}
			} else if (slave.belly >= 5000) { // had to be placed after pregnancy or it would intercept
				r.push(`is currently in the dormitory massaging ${his} greatly distended belly.`);
			} else if (slave.fetish === "buttslut") {
				r.push(`is currently in the dormitory masturbating anally, and I believe ${he} will be happy to meet you.`);
			} else if (slave.fetish === "cumslut") {
				r.push(`is currently in the dormitory exhibiting oral fixation, and I believe ${he} will be happy to meet you.`);
			} else if (slave.fetish === "boobs") {
				r.push(`is currently in the dormitory playing with ${his} nipples, and I believe ${he} will be happy to meet you.`);
			} else if (slave.fetish === "humiliation") {
				r.push(`is currently in the entryway flashing passersby, and I believe ${he} will be happy to meet you.`);
			} else if (slave.fetish === Fetish.SUBMISSIVE) {
				r.push(`is currently in the dormitory, experimenting with self-bondage using the sheets; I believe ${he} will be happy to meet you.`);
			} else if (slave.fetish === "dom") {
				r.push(`is currently in the exercise area keeping fit; ${he} likes to take an active role sexually and is using this down time to work out.`);
			} else if (slave.fetish === "sadist") {
				r.push(`is currently outside your office; ${he} enjoys being superior to other slaves and I believe ${he} means to ingratiate ${himself} to you.`);
			} else if (slave.fetish === "masochist") {
				r.push(`is a sexual masochist; ${he} is currently in the bathroom, experimenting with auto-flagellation with a wet towel.`);
			} else {
				r.push(`is currently outside your office, and I believe ${he} is attempting to maintain sexual arousal to make a good first impression on you.`);
			}
		}
	}
	if (valueOwed - valueGiven > 0) {
		r.push(`There are some valuables present, worth ${cashFormat(valueOwed - valueGiven)}.`);
		cashX((valueOwed - valueGiven), "event");
	}
	App.Events.addNode(el, r, "p", "note");

	V.averageTrust = 0;
	V.averageDevotion = 0;
	let slavesContributing = 0;
	for (const slave of getSlaves().values()) {
		updateHealth(slave);
		slave.oldDevotion = slave.devotion;
		slave.oldTrust = slave.trust;
		// AVERAGE VALUES UPDATE
		if (assignmentVisible(slave)) {
			V.averageTrust += slave.trust;
			V.averageDevotion += slave.devotion;
			slavesContributing++;
		} else {
			if (slave.assignment !== Job.CELLBLOCK && slave.assignment !== Job.ARCADE) {
				if (slave.assignment !== Job.DAIRY || V.dairyRestraintsSetting < 2) {
					V.averageTrust += slave.trust * 0.5;
					V.averageDevotion += slave.devotion * 0.5;
					slavesContributing += 0.5;
				}
			}
		}
		if (V.policies.contraceptivesBan && slave.preg === -1) {
			slave.preg = 0;
		}
	}
	if (slavesContributing !== 0) {
		V.averageTrust = V.averageTrust / slavesContributing;
		V.averageDevotion = V.averageDevotion / slavesContributing;
	}
	V.enduringTrust = V.averageTrust;
	V.enduringDevotion = V.averageDevotion;
	App.UI.SlaveSummary.settingsChanged();

	el.append(
		App.UI.DOM.link(
			"Continue",
			() => {
				V.ui = "main";
				if (V.terrain === "urban") {
					V.slaveCostFactor = 0.85;
					V.menialSupplyFactor = 30000;
					V.menialDemandFactor = -30000;
				} else if (V.terrain === "marine") {
					V.slaveCostFactor = 1;
				} else {
					V.slaveCostFactor = 1.15;
					V.menialDemandFactor = 30000;
					V.menialSupplyFactor = -30000;
				}
				// @ts-ignore
				Save.browser.auto.save("Week Start Autosave");
			}
			, [], "Main"
		)
	);
	App.Verify.I.genePool(); // ensure the genePool records are correct
	return el;

	function PCSetup() {
		if (V.PC.vagina > 0) {
			V.PC.counter.birthsTotal = 0;
			if (isPCCareerInCategory("servant")) {
				if (V.PC.pregType !== 8) {
					if (V.PC.actualAge >= 50) {
						V.PC.counter.birthsTotal = 9;
						V.PC.counter.birthMaster = 9;
					} else if (V.PC.actualAge >= 35) {
						V.PC.counter.birthsTotal = 6;
						V.PC.counter.birthMaster = 6;
					} else if (V.PC.actualAge >= 16) {
						V.PC.counter.birthsTotal = 3;
						V.PC.counter.birthMaster = 3;
					} else if (V.PC.pubertyXX === 1) {
						V.PC.counter.birthsTotal = 2;
						V.PC.counter.birthMaster = 2;
					}
				} else { // Master kept you pregnant
					if (V.PC.actualAge >= 50) {
						V.PC.counter.birthsTotal = 70;
						V.PC.counter.birthMaster = 70;
					} else if (V.PC.actualAge >= 35) {
						V.PC.counter.birthsTotal = 40;
						V.PC.counter.birthMaster = 40;
					} else if (V.PC.actualAge >= 16) {
						V.PC.counter.birthsTotal = 16;
						V.PC.counter.birthMaster = 16;
					} else if (V.PC.pubertyXX === 1) {
						V.PC.counter.birthsTotal = 8;
						V.PC.counter.birthMaster = 8;
					}
				}
				for (const slave of getSlaves().values()) {
					if (slave.mother === SID.PC) {
						V.PC.counter.birthsTotal++;
						if (slave.father === SID.PC) {
							V.PC.counter.birthSelf++;
						} else {
							slave.father = SID.FORMERMASTER;
							V.PC.counter.birthMaster++;
						}
					}
				}
			} else if (isPCCareerInCategory("escort")) {
				for (const slave of getSlaves().values()) {
					if (slave.mother === SID.PC) {
						V.PC.counter.birthsTotal++;
						if (slave.father === SID.PC) {
							V.PC.counter.birthSelf++;
						} else {
							slave.father = SID.CLIENT;
							V.PC.counter.birthClient++;
						}
					} else if (slave.father === SID.PC) {
						slave.mother = SID.CLIENT;
					}
				}
			} else {
				for (const slave of getSlaves().values()) {
					if (slave.mother === SID.PC) {
						V.PC.counter.birthsTotal++;
						if (slave.father === SID.PC) {
							V.PC.counter.birthSelf++;
						} else {
							V.PC.counter.birthOther++;
						}
					}
				}
			}
			if (V.PC.preg > 0) {
				if (V.PC.pregType !== 8) {
					V.PC.pregType = 1;
				} else {
					V.PC.geneticQuirks.hyperFertility = 2;
				}
				if (isPCCareerInCategory("servant")) {
					V.PC.pregSource = SID.FORMERMASTER;
					if (V.PC.pregType !== 8) {
						V.PC.pregType += either(0, 0, 1);
					}
				} else if (isPCCareerInCategory("escort")) {
					V.PC.pregSource = SID.CLIENT;
				}
				V.PC.pregKnown = 1;
				WombForceFatherID(V.PC, V.PC.pregSource);
				WombUpdatePregVars(V.PC);
			}
		} else {
			V.PC.trueVirgin = 1;
		}
		if (V.PC.geneticQuirks.albinism === 2) {
			V.PC.skin = getGeneticSkinColor(V.PC);
			V.PC.hColor = getGeneticHairColor(V.PC);
			resetEyeColor(V.PC, "both");
		}
		if (isPCCareerInCategory("servant")) {
			if (V.PC.genes === "XX") {
				V.PC.boobsTat = "You have your Master's brand tattooed on your left breast.";
			} else {
				V.PC.shouldersTat = "You have your Master's brand tattooed on your left shoulder.";
			}
		} else if (V.PC.career === "prostitute" || V.PC.career === "child prostitute") {
			if (V.PC.genes === "XX") {
				V.PC.boobsTat = "You have your ID number from your days as a prostitute tattooed on your left breast.";
			} else {
				V.PC.buttTat = "You have your ID number from your days as a prostitute tattooed on your left buttock.";
			}
		} else if (V.PC.career === "hoodlum" || V.PC.career === "street urchin") {
			V.PC.shouldersTat = "You have a tattoo denoting your gang affiliation on your neck.";
		}
		if (V.plot) {
			if (V.PC.actualAge <= 13) {
				V.maximumRep = 5000;
			} else if (V.PC.actualAge <= 16) {
				V.maximumRep = 10500;
			}
		}
		// PC energy init
		if (V.PC.career === "child prostitute") {
			V.PC.aphrodisiacs = 1;
			V.PC.addict = 40;
		}
		if ((V.PC.physicalAge < V.PC.pubertyAgeXY && V.PC.physicalAge < V.PC.pubertyAgeXX) || V.PC.physicalAge < 13) {
			if (isPCCareerInCategory("escort")) {
				V.PC.energy = 45;
			} else if (canAchieveErection(V.PC) || V.PC.vagina > 0) {
				V.PC.energy = 15;
			} else {
				V.PC.energy = 0;
			}
		} else if (V.PC.physicalAge < 18) {
			if (isPCCareerInCategory("escort")) {
				V.PC.energy = 75;
			} else if (canAchieveErection(V.PC) || V.PC.vagina > 0) {
				V.PC.energy = 50;
			} else {
				V.PC.energy = 35;
			}
		} else {
			if (isPCCareerInCategory("escort")) {
				V.PC.energy = 90;
			} else if (canAchieveErection(V.PC) || V.PC.vagina > 0) {
				V.PC.energy = 65;
			} else {
				V.PC.energy = 50;
			}
		}
		if (V.PC.vagina === -1 && V.PC.preferredHole !== 0) {
			// if you don't have a vagina you can't prefer it
			V.PC.preferredHole = 0;
		}
		if (V.menstruation === 1) {
			V.PC.fertPeak = 1;
			if (V.PC.pubertyXX === 1) {
				// regardless of settings, the player is aware of their own menstrual cycle
				V.PC.fertKnown = 1;
			}
		}
	}

	function parentSetup() {
		/** @type {Map<number, number>} */
		const missingMap = new Map();

		/** @param {FC.HumanState} slave */
		function checkMissingParents(slave) {
			const missingMom = missingMap.get(slave.mother);
			if (missingMom) {
				slave.mother = missingMom;
			} else if (slave.mother > 0 && !getSlave(slave.mother)) {
				missingMap.set(slave.mother, generateMissingParentID());
			}

			const missingDad = missingMap.get(slave.father);
			if (missingDad) {
				slave.father = missingDad;
			} else if (slave.father > 0 && !getSlave(slave.father)) {
				missingMap.set(slave.father, generateMissingParentID());
			}
		}
		checkMissingParents(V.PC);
		getSlaves().forEach(checkMissingParents);
	}

	function PCChildrenCount() {
		let PCPregnancies = [];
		let SPregnancies = [];
		let birthData = "";
		getSlaves().forEach(function(s) {
			PCPregnancies = [];
			SPregnancies = [];
			getSlaves().filter(s0 => s0.newGamePlus === 0).forEach(function(s1) {
				if (s1.father === s.ID && s1.mother === SID.PC) {
					birthData = s1.actualAge + " " + s1.birthWeek + " " + s1.father;
					if (!PCPregnancies.includes(birthData)) {
						PCPregnancies.push(birthData);
					}
					s.counter.PCChildrenFathered++;
					V.PC.counter.birthDegenerate++;
				}
				if (s1.father === SID.PC && s1.mother === s.ID) {
					birthData = s1.actualAge + " " + s1.birthWeek + " " + s1.mother;
					if (!SPregnancies.includes(birthData)) {
						SPregnancies.push(birthData);
					}
					s.counter.PCChildrenBeared++;
					V.PC.counter.slavesFathered++;
				}
			});
			s.counter.PCKnockedUp += PCPregnancies.length;
			s.counter.birthsTotal += SPregnancies.length;
			s.counter.timesBred += SPregnancies.length;
			V.PC.counter.slavesKnockedUp += SPregnancies.length;
		});
	}

	function inbreedingCalc() {
		const coeffSlaves = [];
		for (const slave of getSlaves().values()) {
			if (slave.newGamePlus === 0) {
				slave.inbreedingCoeff = -1;
				getGenePoolRecordWriteMode(slave).inbreedingCoeff = -1;
				coeffSlaves.push(slave);
			}
		}
		const ibcoeffs = ibc.coeff_slaves(coeffSlaves);
		for (const slave of coeffSlaves) {
			slave.inbreedingCoeff = ibcoeffs[slave.ID];
			getGenePoolRecordWriteMode(slave).inbreedingCoeff = ibcoeffs[slave.ID];
		}
	}

	/** @param {FC.FutureSociety} FS */
	function generateFSSlave(FS) {
		let slave;
		switch (FS) {
			case "FSSupremacist":
				slave = GenerateNewSlave("XX", {race: V.arcologies[0].FSSupremacistRace, disableDisability: 1});
				slave.devotion = random(25, 45);
				slave.trust = random(-25, -45);
				setHealth(slave, jsRandom(0, 20), 0, 0, 0, 0);
				slave.face = random(15, 100);
				slave.skill.oral = random(15, 35);
				slave.skill.anal = random(15, 35);
				if (slave.vagina > -1) {
					if (slave.vagina === 0) {
						slave.vagina++;
					}
					slave.skill.vaginal = random(15, 35);
				}
				slave.skill.entertainment = random(15, 35);
				slave.clothes = "uncomfortable straps";
				slave.collar = "uncomfortable leather";
				slave.shoes = "flats";
				slave.assignment = "please you";
				break;
			case "FSSubjugationist":
				slave = GenerateNewSlave("XX", {race: V.arcologies[0].FSSubjugationistRace, disableDisability: 1});
				slave.devotion = random(25, 45);
				slave.trust = random(-25, -45);
				setHealth(slave, jsRandom(0, 20), 0, 0, 0, 0);
				slave.face = random(15, 100);
				slave.skill.oral = random(15, 35);
				slave.skill.anal = random(15, 35);
				if (slave.vagina > -1) {
					if (slave.vagina === 0) {
						slave.vagina++;
					}
					slave.skill.vaginal = random(15, 35);
				}
				slave.skill.whoring = random(15, 35);
				slave.clothes = "uncomfortable straps";
				slave.collar = "uncomfortable leather";
				slave.shoes = "flats";
				slave.assignment = "whore";
				break;
			case "FSGenderRadicalist":
				slave = GenerateNewSlave("XY", {disableDisability: 1, maxAge: 25});
				slave.devotion = random(25, 45);
				slave.trust = random(15, -15);
				setHealth(slave, 100, 0, 0, 0, jsRandom(0, 20));
				slave.face = random(0, 55);
				slave.boobs += 100 * random(2, 4);
				slave.butt += random(1, 2);
				slave.attrXY = random(70, 90);
				slave.attrXX = 0;
				slave.skill.oral = random(35, 65);
				slave.skill.penetrative = random(35, 65);
				slave.skill.anal = random(35, 65);
				slave.skill.whoring = random(35, 65);
				SoftenSexualFlaw(slave);
				slave.clothes = "uncomfortable straps";
				slave.collar = "stylish leather";
				slave.shoes = "heels";
				slave.assignment = "whore";
				break;
			case "FSGenderFundamentalist":
				slave = GenerateNewSlave("XX", {minAge: V.fertilityAge, maxAge: 25, disableDisability: 1});
				slave.devotion = random(25, 45);
				slave.trust = random(-25, -45);
				setHealth(slave, jsRandom(55, 65), 0, 0, 0, 0);
				slave.face = random(15, 100);
				slave.preg = random(1, 40);
				slave.lactation = 1;
				slave.lactationDuration = 2;
				SetBellySize(slave);
				if (slave.vagina > -1) {
					if (slave.vagina === 0) {
						slave.vagina++;
					}
					slave.skill.vaginal = random(15, 35);
				}
				slave.clothes = "a nice maid outfit";
				slave.collar = "tight steel";
				slave.shoes = "flats";
				slave.assignment = "be a servant";
				break;
			case "FSPaternalist":
				slave = GenerateNewSlave("XX", {disableDisability: 1});
				slave.devotion = random(25, 45);
				slave.trust = random(55, 65);
				setHealth(slave, jsRandom(55, 65), 0, 0, 0, 0);
				slave.face = random(15, 100);
				slave.intelligence = random(0, 100);
				slave.intelligenceImplant = 30;
				slave.skill.entertainment = random(15, 35);
				slave.clothes = "conservative clothing";
				slave.collar = "none";
				slave.shoes = "flats";
				slave.assignment = "be a servant";
				break;
			case "FSDegradationist":
				slave = GenerateNewSlave("XX");
				setHealth(slave, jsRandom(0, 15), 0, 0, 0, 0);
				slave.fuckdoll = 100;
				slave.career = "a Fuckdoll";
				applyMindbroken(slave);
				slave.boobs += 100 * random(10, 20);
				slave.butt += random(2, 3);
				slave.lips = random(2, 4);
				slave.weight = random(-15, 15);
				slave.clothes = "a Fuckdoll suit";
				slave.assignment = "please you";
				break;
			case "FSAssetExpansionist":
				slave = GenerateNewSlave("XX", {disableDisability: 1});
				slave.devotion = random(25, 45);
				slave.trust = random(-15, 15);
				setHealth(slave, jsRandom(25, 45), 0, 0, 0, 0);
				slave.chem = 50;
				slave.face = random(15, 100);
				slave.boobs += 100 * random(10, 20);
				slave.butt += random(2, 3);
				slave.lips += random(0, 1);
				if (slave.balls > 0) {
					slave.balls++;
				}
				if (slave.dick > 0) {
					slave.dick++;
				}
				slave.weight = random(15, 90);
				slave.skill.oral = random(15, 35);
				slave.skill.anal = 0;
				slave.anus = 0;
				if (slave.vagina > -1) {
					if (slave.vagina === 0) {
						slave.vagina++;
					}
					slave.skill.vaginal = random(15, 35);
				}
				slave.skill.entertainment = random(15, 35);
				slave.clothes = "slutty jewelry";
				slave.collar = "pretty jewelry";
				slave.shoes = "heels";
				slave.assignment = "please you";
				break;
			case "FSSlimnessEnthusiast":
				slave = GenerateNewSlave("XX", {disableDisability: 1});
				slave.devotion = random(25, 45);
				slave.trust = random(55, 65);
				setHealth(slave, jsRandom(55, 65), 0, 0, 0, 0);
				slave.face = random(15, 100);
				slave.boobs = 100 * random(1, 4);
				slave.butt = random(1, 2);
				slave.weight = random(-25, -15);
				slave.skill.oral = random(15, 35);
				slave.skill.anal = 0;
				slave.anus = 0;
				if (slave.vagina > -1) {
					if (slave.vagina === 0) {
						slave.vagina++;
					}
					slave.skill.vaginal = random(15, 35);
				}
				slave.skill.entertainment = random(15, 35);
				slave.clothes = "a leotard";
				slave.collar = "pretty jewelry";
				slave.shoes = "flats";
				slave.assignment = "please you";
				break;
			case "FSTransformationFetishist":
				slave = GenerateNewSlave("XX", {disableDisability: 1});
				slave.devotion = random(25, 45);
				slave.trust = random(-15, 15);
				setHealth(slave, jsRandom(-15, 0), Math.max(normalRandInt(5, 3), 0), Math.max(normalRandInt(5, 3), 0), 0, 0);
				slave.faceImplant = random(40, 70);
				slave.face = Math.clamp(Math.trunc(slave.face + slave.faceImplant / 2), -100, 100);
				slave.boobsImplant = 200 * random(4, 8);
				slave.boobs += slave.boobsImplant;
				slave.boobsImplantType = "normal";
				slave.buttImplant = random(2, 4);
				slave.butt += slave.buttImplant;
				slave.buttImplantType = "normal";
				slave.lipsImplant = random(1, 2);
				slave.lips = Math.clamp(Math.trunc(slave.lipsImplant + 2), -3, 3);
				slave.weight = random(-25, -15);
				slave.skill.oral = random(15, 35);
				slave.skill.anal = random(15, 35);
				if (slave.vagina > -1) {
					if (slave.vagina === 0) {
						slave.vagina++;
					}
					slave.skill.vaginal = random(15, 35);
				}
				slave.clothes = "a string bikini";
				slave.collar = "shock punishment";
				slave.shoes = "extreme heels";
				slave.assignment = "whore";
				break;
			case "FSBodyPurist":
				slave = GenerateNewSlave("XX", {disableDisability: 1});
				slave.devotion = random(55, 65);
				slave.trust = random(25, 45);
				setHealth(slave, 100, 0, 0, 0, jsRandom(0, 20));
				slave.face = random(15, 100);
				slave.weight = random(-5, 5);
				slave.muscles = random(10, 25);
				slave.skill.oral = random(15, 35);
				slave.skill.anal = random(15, 35);
				if (slave.vagina > -1) {
					if (slave.vagina === 0) {
						slave.vagina++;
					}
					slave.skill.vaginal = random(15, 35);
				}
				slave.clothes = "a nice maid outfit";
				slave.collar = "pretty jewelry";
				slave.shoes = "heels";
				slave.assignment = "be a servant";
				break;
			case "FSMaturityPreferentialist":
				slave = GenerateNewSlave("XX", {
					minAge: 36, maxAge: 39, ageOverridesPedoMode: 1, disableDisability: 1
				});
				slave.devotion = random(55, 65);
				slave.trust = random(-15, 15);
				setHealth(slave, jsRandom(25, 45), 0, 0, 0, 0);
				slave.face = random(15, 100);
				slave.boobs += 100 * random(1, 4);
				slave.butt += random(1, 2);
				slave.weight = random(-5, 90);
				slave.skill.oral = random(15, 35);
				slave.skill.anal = random(15, 35);
				if (slave.vagina > -1) {
					if (slave.vagina === 0) {
						slave.vagina++;
					}
					slave.skill.vaginal = random(15, 35);
				}
				SoftenBehavioralFlaw(slave);
				slave.clothes = "a slutty maid outfit";
				slave.collar = "pretty jewelry";
				slave.shoes = "heels";
				slave.assignment = "be a servant";
				break;
			case "FSYouthPreferentialist":
				slave = GenerateNewSlave("XX", {maxAge: 19, disableDisability: 1});
				slave.devotion = random(25, 45);
				slave.trust = random(55, 65);
				setHealth(slave, jsRandom(25, 45), 0, 0, 0, 0);
				slave.face = random(15, 100);
				slave.boobs = 100 * random(1, 4);
				slave.natural.boobs = slave.boobs;
				slave.butt = random(1, 3);
				slave.weight = random(-25, 25);
				slave.skill.oral = random(15, 35);
				slave.skill.anal = 0;
				slave.anus = 0;
				if (slave.vagina > -1) {
					slave.skill.vaginal = 0;
					slave.vagina = 0;
				}
				slave.skill.entertainment = random(15, 35);
				slave.clothes = "a schoolgirl outfit";
				slave.collar = "pretty jewelry";
				slave.shoes = "heels";
				slave.assignment = "be a servant";
				break;
			case "FSPastoralist":
				slave = GenerateNewSlave("XX", {disableDisability: 1});
				slave.devotion = random(25, 45);
				slave.trust = random(-25, -45);
				setHealth(slave, jsRandom(25, 45), 0, 0, 0, 0);
				slave.boobs += 100 * random(10, 20);
				if (slave.balls > 0) {
					slave.balls++;
				}
				slave.lactation = 2;
				slave.lactationDuration = 2;
				slave.clothes = "Western clothing";
				slave.collar = "leather with cowbell";
				slave.shoes = "flats";
				slave.assignment = "get milked";
				break;
			case "FSPhysicalIdealist":
				slave = GenerateNewSlave("XX", {disableDisability: 1});
				slave.devotion = random(25, 45);
				slave.trust = random(25, 45);
				setHealth(slave, 100, 0, 0, 0, jsRandom(10, 40));
				slave.muscles = random(50, 100);
				slave.skill.oral = random(15, 35);
				slave.skill.anal = random(15, 35);
				if (slave.vagina > -1) {
					if (slave.vagina === 0) {
						slave.vagina++;
					}
					slave.skill.vaginal = random(15, 35);
				}
				slave.skill.whoring = random(15, 35);
				slave.clothes = "body oil";
				slave.collar = "none";
				slave.shoes = "none";
				slave.assignment = "whore";
				break;
			case "FSChattelReligionist":
				slave = GenerateNewSlave("XX", {disableDisability: 1});
				slave.devotion = random(55, 65);
				slave.trust = random(55, 65);
				setHealth(slave, jsRandom(0, 15), 0, 0, 0, 0);
				if (slave.vagina === 0) {
					slave.vagina++;
				}
				slave.skill.whoring = random(10, 20);
				slave.behavioralFlaw = "none";
				slave.behavioralQuirk = "sinful";
				slave.clothes = "a chattel habit";
				slave.collar = "heavy gold";
				slave.shoes = "flats";
				slave.assignment = "whore";
				break;
			case "FSRomanRevivalist":
				slave = GenerateNewSlave("XX", {maxAge: 19, disableDisability: 1});
				slave.devotion = 100;
				slave.trust = random(55, 65);
				setHealth(slave, 100, 0, 0, 0, jsRandom(10, 30));
				slave.face = random(0, 55);
				slave.muscles = random(25, 50);
				slave.skill.combat = 70;
				slave.behavioralFlaw = "none";
				slave.behavioralQuirk = "fitness";
				slave.clothes = "a toga";
				slave.collar = "pretty jewelry";
				slave.shoes = "flats";
				slave.assignment = "guard you";
				V.BodyguardID = slave.ID;
				break;
			case "FSNeoImperialist":
				slave = GenerateNewSlave("XX", {
					minAge: 16, maxAge: 28, race: "white", disableDisability: 1
				});
				slave.devotion = 100;
				slave.trust = random(55, 65);
				setHealth(slave, 100, 0, 0, 0, jsRandom(10, 30));
				slave.face = random(20, 75);
				slave.muscles = random(25, 60);
				slave.skill.combat = 70;
				slave.behavioralFlaw = "none";
				slave.behavioralQuirk = "fitness";
				slave.skill.entertainment = random(15, 35);
				slave.clothes = "a tight Imperial bodysuit";
				slave.shoes = "flats";
				slave.assignment = "guard you";
				V.BodyguardID = slave.ID;
				break;
			case "FSAztecRevivalist":
				slave = GenerateNewSlave(null, {race: "latina", nationality: "Mexican", disableDisability: 1});
				slave.accent = 0;
				slave.devotion = 75;
				slave.trust = 75;
				setHealth(slave, jsRandom(-20, 20), 0, 0, 0, 0);
				slave.muscles = random(50, 75);
				slave.skill.combat = 70;
				slave.sexualFlaw = "malicious";
				slave.behavioralQuirk = "none";
				slave.clothes = "a huipil";
				slave.collar = "pretty jewelry";
				slave.shoes = "none";
				slave.assignment = "be your Head Girl";
				V.HeadGirlID = slave.ID;
				break;
			case "FSEgyptianRevivalist":
				slave = GenerateNewSlave("XX", {disableDisability: 1});
				slave.devotion = random(25, 45);
				slave.trust = random(25, 45);
				setHealth(slave, jsRandom(25, 45), 0, 0, 0, 0);
				slave.face = random(15, 100);
				slave.skill.oral = random(15, 35);
				slave.skill.anal = random(15, 35);
				if (slave.vagina > -1) {
					if (slave.vagina === 0) {
						slave.vagina++;
					}
					slave.skill.vaginal = random(15, 35);
				}
				slave.skill.entertainment = random(15, 35);
				SoftenSexualFlaw(slave);
				slave.clothes = "slutty jewelry";
				slave.collar = "ancient Egyptian";
				slave.shoes = "flats";
				slave.assignment = "please you";
				break;
			case "FSEdoRevivalist":
				slave = GenerateNewSlave(null, {race: "asian", nationality: "Japanese", disableDisability: 1});
				slave.accent = 0;
				slave.devotion = random(25, 45);
				slave.trust = random(25, 45);
				setHealth(slave, jsRandom(25, 45), 0, 0, 0, 0);
				slave.face = random(15, 100);
				slave.intelligence = random(0, 100);
				slave.intelligenceImplant = 30;
				slave.skill.entertainment = 100;
				slave.clothes = "a kimono";
				slave.collar = "satin choker";
				slave.shoes = "heels";
				slave.assignment = "serve the public";
				break;
			case "FSArabianRevivalist":
				slave = GenerateNewSlave("XX", {disableDisability: 1});
				slave.devotion = random(25, 45);
				slave.trust = random(-15, 15);
				setHealth(slave, jsRandom(25, 45), 0, 0, 0, 0);
				slave.face = random(15, 100);
				slave.intelligence = random(-15, 80);
				slave.intelligenceImplant = 0;
				slave.clothes = "harem gauze";
				slave.collar = "uncomfortable leather";
				slave.shoes = "flats";
				slave.assignment = "take classes";
				break;
			case "FSChineseRevivalist":
				slave = GenerateNewSlave(null, {
					race: "asian", nationality: "Chinese", minAge: 36, maxAge: 38, ageOverridesPedoMode: 1, disableDisability: 1
				});
				slave.devotion = random(55, 65);
				slave.trust = random(25, 45);
				setHealth(slave, jsRandom(25, 45), 0, 0, 0, 0);
				slave.face = random(0, 55);
				slave.accent = 0;
				slave.intelligence = 100;
				slave.intelligenceImplant = 30;
				slave.skill.oral = 100;
				slave.skill.anal = 100;
				if (slave.vagina > -1) {
					if (slave.vagina === 0) {
						slave.vagina++;
					}
					slave.skill.vaginal = 100;
				}
				if (canAchieveErection(slave)) {
					slave.skill.penetrative = 100;
				}
				slave.skill.entertainment = 100;
				slave.skill.whoring = 100;
				SoftenBehavioralFlaw(slave);
				SoftenSexualFlaw(slave);
				slave.clothes = "a slutty qipao";
				slave.collar = "pretty jewelry";
				slave.shoes = "heels";
				slave.assignment = "be your Head Girl";
				V.HeadGirlID = slave.ID;
				break;
			case "FSAntebellumRevivalist":
				slave = GenerateNewSlave("XX", {
					race: "black", nationality: "American", minAge: 18, maxAge: 24, ageOverridesPedoMode: 1, disableDisability: 1
				});
				slave.devotion = random(-20, 25);
				slave.trust = random(-55, -20);
				setHealth(slave, jsRandom(25, 45), 0, 0, 0, 0);
				slave.accent = 1;
				slave.face = random(10, 45);
				slave.accent = 2;
				slave.intelligence = jsRandom(-50, 25);
				slave.skill.oral = jsRandom(20, 40);
				slave.skill.anal = jsRandom(20, 40);
				if (slave.vagina > -1) {
					if (slave.vagina === 0) {
						slave.vagina++;
					}
					slave.skill.vaginal = 15;
				}
				slave.skill.entertainment = 15;
				slave.skill.whoring = 15;
				SoftenBehavioralFlaw(slave);
				SoftenSexualFlaw(slave);
				slave.collar = "uncomfortable leather";
				slave.clothes = "a slutty maid outfit";
				slave.armAccessory = "elbow gloves";
				slave.legAccessory = "long stockings";
				slave.shoes = "heels";
				slave.assignment = "be a servant";
				slave.custom.desc = "$He speaks with the demeaning accent of slaves from the Old South.";
				slave.slaveName = App.Data.misc.antebellumSlaveNames.random();
				slave.slaveSurname = App.Data.misc.antebellumSlaveSurnames.random();
				break;
			case "FSRestart":
				slave = GenerateNewSlave("XX", {disableDisability: 1});
				slave.devotion = -100;
				slave.trust = -100;
				setHealth(slave, jsRandom(80, 90), 0, 0, 0, 0);
				slave.intelligence = 100;
				slave.intelligenceImplant = 30;
				slave.face = 100;
				slave.faceShape = "sensual";
				slave.skill.oral = random(35, 75);
				slave.skill.anal = random(35, 75);
				if (slave.vagina > -1) {
					if (slave.vagina === 0) {
						slave.vagina++;
					}
					slave.skill.vaginal = random(35, 75);
				}
				slave.skill.entertainment = random(15, 35);
				slave.skill.whoring = 0;
				SoftenSexualFlaw(slave);
				slave.clothes = "a ball gown";
				slave.shoes = "flats";
				slave.assignment = Job.REST;
				break;
			case "FSRepopulationFocus":
				slave = GenerateNewSlave("XX", {
					minAge: V.fertilityAge + 3, maxAge: 25, ageOverridesPedoMode: 1, disableDisability: 1
				});
				slave.devotion = random(25, 45);
				slave.trust = random(-25, -45);
				setHealth(slave, jsRandom(55, 65), 0, 0, 0, 0);
				slave.face = random(15, 100);
				slave.preg = random(10, 40);
				slave.pregType = random(3, 8);
				slave.lactation = 1;
				slave.lactationDuration = 2;
				SetBellySize(slave);
				slave.counter.birthsTotal = 5;
				slave.bellySag = 20;
				slave.bellySagPreg = 20;
				if (slave.vagina > -1) {
					slave.vagina = 4;
					slave.skill.vaginal = random(15, 35);
				}
				slave.clothes = "a nice maid outfit";
				slave.shoes = "flats";
				slave.assignment = "please you";
				break;
			case "FSHedonisticDecadence":
				slave = GenerateNewSlave("XX", {maxAge: 25, disableDisability: 1});
				slave.devotion = random(25, 45);
				slave.trust = random(-25, -45);
				setHealth(slave, jsRandom(-20, 20), 0, 0, 0, 0);
				slave.face = random(15, 40);
				slave.boobs += 100 * random(3, 6);
				slave.butt += random(2, 5);
				slave.weight = random(100, 200);
				slave.skill.oral = random(15, 35);
				slave.skill.anal = random(15, 35);
				slave.anus = 2;
				if (slave.vagina > -1) {
					slave.skill.vaginal = random(15, 35);
					slave.vagina = 3;
				}
				if (canAchieveErection(slave)) {
					slave.skill.penetrative = random(15, 35);
				}
				slave.skill.entertainment = 0;
				slave.energy = random(60, 80);
				slave.behavioralFlaw = "gluttonous";
				slave.clothes = "attractive lingerie";
				slave.shoes = "flats";
				slave.diet = "fattening";
				slave.rules.living = "luxurious";
				slave.assignment = Job.REST;
				break;
			case "FSIntellectualDependency":
				slave = GenerateNewSlave("XX", {minAge: 14, maxAge: 18, disableDisability: 1});
				setHealth(slave, jsRandom(55, 65), 0, 0, 0, 0);
				slave.devotion = random(45, 65);
				slave.trust = random(-15, 45);
				slave.face = random(30, 100);
				slave.energy = 100;
				slave.weight = random(-25, -15);
				slave.skill.oral = 0;
				slave.skill.anal = 0;
				slave.skill.vaginal = 0;
				slave.skill.entertainment = 0;
				slave.skill.whoring = 0;
				slave.intelligence = -100;
				slave.intelligenceImplant = 0;
				if (slave.vagina === 0) {
					slave.vagina++;
				}
				slave.anus++;
				break;
			case "FSSlaveProfessionalism":
				slave = GenerateNewSlave("XX", {minAge: 18, maxAge: 25, disableDisability: 1});
				slave.devotion = 100;
				slave.trust = 20;
				setHealth(slave, 80, 0, 0, 0, jsRandom(10, 30));
				slave.face = random(30, 100);
				slave.energy = 10;
				slave.weight = random(-15, 5);
				slave.skill.oral = 100;
				slave.skill.anal = 100;
				slave.skill.vaginal = 100;
				slave.skill.penetrative = 100;
				slave.skill.entertainment = 100;
				slave.skill.whoring = 100;
				slave.intelligence = 100;
				slave.intelligenceImplant = 30;
				slave.accent = 0;
				SoftenBehavioralFlaw(slave);
				SoftenSexualFlaw(slave);
				slave.clothes = "a nice maid outfit";
				slave.collar = "pretty jewelry";
				slave.assignment = "be your Head Girl";
				V.HeadGirlID = slave.ID;
				break;
			case "FSPetiteAdmiration":
				slave = GenerateNewSlave("XX", {minAge: 14, maxAge: 18, disableDisability: 1});
				slave.devotion = random(55, 65);
				slave.trust = random(-15, 15);
				setHealth(slave, jsRandom(25, 45), 0, 0, 0, 0);
				slave.face = random(15, 100);
				if (slave.height >= 150) {
					slave.height = Height.random(slave, {limitMult: [-3, -1]});
					if (slave.height >= 150) {
						slave.height = Height.random(slave, {limitMult: [-4, -2]});
						if (slave.height >= 150) {
							slave.height = random(90, 130);
							slave.geneticQuirks.dwarfism = 2;
						}
					}
					slave.natural.height = slave.height;
				}
				slave.skill.oral = random(35, 65);
				slave.skill.anal = random(15, 35);
				if (slave.vagina > -1) {
					if (slave.vagina === 0) {
						slave.vagina++;
					}
					slave.skill.vaginal = random(15, 35);
				}
				slave.clothes = "a succubus outfit";
				slave.legAccessory = "long stockings";
				slave.assignment = "please you";
				break;
			case "FSStatuesqueGlorification":
				slave = GenerateNewSlave("XX", {
					minAge: 18, maxAge: 30, ageOverridesPedoMode: 1, disableDisability: 1
				});
				slave.devotion = random(55, 65);
				slave.trust = random(-15, 15);
				setHealth(slave, jsRandom(25, 45), 0, 0, 0, 0);
				slave.face = random(15, 100);
				slave.weight = random(-5, 5);
				slave.muscles = random(20, 40);
				if (slave.height < 185) {
					slave.height = Height.random(slave, {limitMult: [1, 3]});
					if (slave.height < 185) {
						slave.height = Height.random(slave, {limitMult: [2, 4]});
						if (slave.height < 185) {
							slave.height = random(200, 264);
							slave.geneticQuirks.gigantism = 2;
						}
					}
					slave.natural.height = slave.height;
				}
				slave.skill.oral = random(15, 35);
				slave.skill.anal = random(15, 35);
				if (slave.vagina > -1) {
					if (slave.vagina === 0) {
						slave.vagina++;
					}
					slave.skill.vaginal = random(15, 35);
				}
				slave.clothes = "slutty business attire";
				slave.shoes = "heels";
				slave.assignment = "please you";
				break;
			default:
				r.push(`ERROR: bad arcology type`);
		}
		slave.origin = "You acquired $him along with the arcology.";
		slave.career = "a slave";
		return slave;
	}
};


code:
/** @param {boolean} inIntro */
App.Intro.descriptionDivForArcology = function(arcology, acceptArcology = () => {}, inIntro = false) {
	const div = document.createElement("div");
	div.classList.add("card");
	div.append(
		App.UI.DOM.makeElement("span", inIntro ? App.UI.DOM.passageLink(arcology.name, "Intro Summary", () => acceptArcology()) : arcology.name, ['bold']),
		` is an established arcology located in a Free City `,
	);
	if (arcology.terrain === "urban") {
		div.append(`carved out of an urban area of ${arcology.continent}.`);
	} else if (arcology.terrain === "rural") {
		div.append(`built in a rural area of ${arcology.continent}.`);
	} else if (arcology.terrain === "marine") {
		div.append(`constructed just offshore of ${arcology.continent}.`);
	} else if (arcology.terrain === "ravine") {
		div.append(`constructed in a large canyon of ${arcology.continent}.`);
	} else {
		div.append(`in the middle of the ocean.`);
	}

	function newLine(...content) {
		const line = document.createElement("div");
		line.classList.add("indent");
		line.append(...content);
		div.append(line);
	}

	if (arcology.prosperity >= 60) {
		newLine("It is unusually prosperous for a vulnerable arcology.");
	} else if (arcology.prosperity <= 40) {
		newLine("It has little economic prosperity and is vulnerable.");
	}

	if (arcology.citizens > 0) {
		newLine("It has an unusually high ratio of citizens to sex slaves, increasing demand for sexual services.");
	} else if (arcology.citizens < 0) {
		newLine("It has an unusually low ratio of citizens to sex slaves, reducing demand for sexual services.");
	}

	let innerDiv = document.createElement("div");
	innerDiv.classList.add("indent");
	div.append(innerDiv);

	innerDiv.append("Its society ");
	if (arcology.FSProgress >= 50) {
		innerDiv.append("has advanced towards");
	} else if (arcology.FSProgress >= 30) {
		innerDiv.append("has devoted resources to");
	} else {
		innerDiv.append("has just begun to adopt");
	}
	innerDiv.append(" ");
	switch (arcology.fs) {
		case "FSSupremacist":
			innerDiv.append(App.UI.DOM.makeElement("span", `Supremacy of the ${arcology.race} ${arcology.race !== "mixed race" ? "race" : ""}.`, ["intro", "question"]));
			break;
		case "FSSubjugationist":
			innerDiv.append(App.UI.DOM.makeElement("span", `Subjugation of the ${arcology.race} ${arcology.race !== "mixed race" ? "race" : ""}.`, ["intro", "question"]));
			break;
		case "FSGenderRadicalist":
			innerDiv.append(App.UI.DOM.makeElement("span", `Gender Radicalism,`, ["intro", "question"]), " a movement that supports feminization of slavegirls with dicks.");
			break;
		case "FSGenderFundamentalist":
			innerDiv.append(App.UI.DOM.makeElement("span", `Gender Fundamentalism,`, ["intro", "question"]), " a reaction to modern libertinism that seeks to reinforce gender roles.");
			break;
		case "FSPaternalist":
			innerDiv.append(App.UI.DOM.makeElement("span", `Paternalism,`, ["intro", "question"]), " an optimistic strain of slavery that protects and improves slaves.");
			break;
		case "FSDegradationist":
			innerDiv.append(App.UI.DOM.makeElement("span", `Degradationism,`, ["intro", "question"]), " an extreme branch of modern slavery that treats slaves as subhuman.");
			break;
		case "FSAssetExpansionist":
			innerDiv.append(App.UI.DOM.makeElement("span", `Asset Expansionism,`, ["intro", "question"]), " a societal preoccupation with expansion of body parts, especially breasts.");
			break;
		case "FSSlimnessEnthusiast":
			innerDiv.append(App.UI.DOM.makeElement("span", `Slimness Enthusiasm,`, ["intro", "question"]), " an aesthetic movement that fetishizes the lithe female form.");
			break;
		case "FSTransformationFetishist":
			innerDiv.append(App.UI.DOM.makeElement("span", `Transformation Fetishism,`, ["intro", "question"]), " a focus on implants and other kinds of surgical alteration.");
			break;
		case "FSBodyPurist":
			innerDiv.append(App.UI.DOM.makeElement("span", `Body Purism,`, ["intro", "question"]), " a reaction to extreme surgical fetishism that prefers bodies grown biologically.");
			break;
		case "FSMaturityPreferentialist":
			innerDiv.append(App.UI.DOM.makeElement("span", `Maturity Preferentialism,`, ["intro", "question"]), " an appetite for mature slaves based on MILF fetishism.");
			break;
		case "FSYouthPreferentialist":
			innerDiv.append(App.UI.DOM.makeElement("span", `Youth Preferentialism,`, ["intro", "question"]), " which focuses on youth and virginity in slaves.");
			break;
		case "FSPastoralist":
			innerDiv.append(App.UI.DOM.makeElement("span", `Pastoralism,`, ["intro", "question"]), " an appetite for products of the human body, especially milk.");
			break;
		case "FSPhysicalIdealist":
			innerDiv.append(App.UI.DOM.makeElement("span", `Physical Idealism,`, ["intro", "question"]), " an aspirational movement which fetishizes muscle and physical fitness.");
			break;
		case "FSChattelReligionist":
			innerDiv.append(App.UI.DOM.makeElement("span", `Chattel Religionism,`, ["intro", "question"]), " a religious revival in the context of modern slavery.");
			break;
		case "FSRomanRevivalist":
			innerDiv.append(App.UI.DOM.makeElement("span", `Roman Revivalism,`, ["intro", "question"]), " which seeks to recreate the glory that was ancient Rome.");
			innerDiv.append(App.UI.DOM.makeElement("div", "It has an established lingua franca: Latin."));
			break;
		case "FSNeoImperialist":
			innerDiv.append(App.UI.DOM.makeElement("span", `Neo-Imperialism,`, ["intro", "question"]), " adopting old world customs and remodeling them under an absolutist Imperial fist.");
			break;
		case "FSAztecRevivalist":
			innerDiv.append(App.UI.DOM.makeElement("span", `Aztec Revivalism,`, ["intro", "question"]), " which aspires to reach the heights of the Aztec Empire at its peak.");
			innerDiv.append(App.UI.DOM.makeElement("div", "It has an established lingua franca: Nahuatl."));
			break;
		case "FSEgyptianRevivalist":
			innerDiv.append(App.UI.DOM.makeElement("span", `Egyptian Revivalism,`, ["intro", "question"]), " a movement to rebuild the monuments and greatness of ancient Egypt.");
			innerDiv.append(App.UI.DOM.makeElement("div", "It has an established lingua franca: Ancient Egyptian."));
			break;
		case "FSEdoRevivalist":
			innerDiv.append(App.UI.DOM.makeElement("span", `Edo Revivalism,`, ["intro", "question"]), " an insular movement with a focus on the cultural superiority of old Japan.");
			innerDiv.append(App.UI.DOM.makeElement("div", "It has an established lingua franca: Japanese."));
			break;
		case "FSArabianRevivalist":
			innerDiv.append(App.UI.DOM.makeElement("span", `Arabian Revivalism,`, ["intro", "question"]), " a melding of Arabian history and recent mythology of the Near East.");
			innerDiv.append(App.UI.DOM.makeElement("div", "It has an established lingua franca: Arabic."));
			break;
		case "FSChineseRevivalist":
			innerDiv.append(App.UI.DOM.makeElement("span", `Chinese Revivalism,`, ["intro", "question"]), " which modernizes the assumed superiority of the Middle Kingdom.");
			innerDiv.append(App.UI.DOM.makeElement("div", "It has an established lingua franca: Chinese."));
			break;
		case "FSAntebellumRevivalist":
			innerDiv.append(App.UI.DOM.makeElement("span", `Antebellum Revivalism,`, ["intro", "question"]), " which attempts to revive the romance and chivalry of the Antebellum South.");
			innerDiv.append(App.UI.DOM.makeElement("div", "It has an established lingua franca: English."));
			break;
		case "FSRepopulationFocus":
			innerDiv.append(App.UI.DOM.makeElement("span", `Repopulationism,`, ["intro", "question"]), " the belief that the key to humanity's survival is a child in every fertile womb.");
			break;
		case "FSRestart":
			innerDiv.append(App.UI.DOM.makeElement("span", `Eugenics,`, ["intro", "question"]), " the belief that the world's failings were caused by rampant breeding of the inferior, and as such, only society's best should reproduce.");
			break;
		case "FSHedonisticDecadence":
			innerDiv.append(App.UI.DOM.makeElement("span", `Decadent Hedonism,`, ["intro", "question"]), " a movement to embody life's pleasures, particularly eating and sex.");
			break;
		case "FSIntellectualDependency":
			innerDiv.append(App.UI.DOM.makeElement("span", `Intellectual Dependency,`, ["intro", "question"]), " an appetite for horny, stupid slaves based on bimbo fetishism.");
			break;
		case "FSSlaveProfessionalism":
			innerDiv.append(App.UI.DOM.makeElement("span", `Slave Professionalism,`, ["intro", "question"]), " a strain of slavery that seeks smart, skilled, elegant slaves to hone to perfection.");
			break;
		case "FSPetiteAdmiration":
			innerDiv.append(App.UI.DOM.makeElement("span", `Petite Admiration,`, ["intro", "question"]), " which prefers its slaves to stand heads and shoulders shorter than their masters.");
			break;
		case "FSStatuesqueGlorification":
			innerDiv.append(App.UI.DOM.makeElement("span", `Statuesque Glorification,`, ["intro", "question"]), " an obsession, both sexual and otherwise, over height.");
			break;
		default:
			innerDiv.append(App.UI.DOM.makeElement("span", `Multiculturalism,`, ["intro", "question"]), " a celebration of the total liberty that was the original purpose of the Free Cities.");
	}

	div.append(
		innerDiv,
		App.UI.DOM.makeElement("div", arcology.building.render(), ["intro"]),
	);

	return div;
};

App.Intro.getLanguageForArcology = function(arcology) {
	const language = App.Data.FutureSociety.records[arcology.fs].language;
	if (language) {
		return language;
	} else {
		switch (arcology.terrain) {
			case "South America":
				return "Spanish";
			case "Brazil":
				return "Portuguese";
			case "the Middle East":
			case "Africa": /* shouldn't that be Portuguese, Spanish or something? */
				return "Arabic";
			case "Asia":
				return "Chinese";
			case "Central Europe":
				return "German";
			case "Eastern Europe":
				return "Russian";
			case "Western Europe":
				return "English";
			case "Southern Europe":
				return "Italian";
			case "Scandinavia":
				return "Norwegian";
			case "Japan":
				return "Japanese";
			case "oceanic":
			case "North America":
			case "Australia":
			default:
				return "English";
		}
	}
};

App.Intro.getNameForArcology = function(arcology) {
	switch (arcology.fs) {
		case "FSSupremacist":
			switch (arcology.race) {
				case "white":
					return App.Data.ArcologyNames.SupremacistWhite.random();
				case "asian":
					return App.Data.ArcologyNames.SupremacistAsian.random();
				case "latina":
					return App.Data.ArcologyNames.SupremacistLatina.random();
				case "middle eastern":
					return App.Data.ArcologyNames.SupremacistMiddleEastern.random();
				case "black":
					return App.Data.ArcologyNames.SupremacistBlack.random();
				case "indo-aryan":
					return App.Data.ArcologyNames.SupremacistIndoAryan.random();
				case "pacific islander":
					return App.Data.ArcologyNames.SupremacistPacificIslander.random();
				case "malay":
					return App.Data.ArcologyNames.SupremacistMalay.random();
				case "amerindian":
					return App.Data.ArcologyNames.SupremacistAmerindian.random();
				case "southern european":
					return App.Data.ArcologyNames.SupremacistSouthernEuropean.random();
				case "semitic":
					return App.Data.ArcologyNames.SupremacistSemitic.random();
				default:
					return App.Data.ArcologyNames.SupremacistMixedRace.random();
			}
		case "FSSubjugationist":
			switch (arcology.race) {
				case "white":
					return App.Data.ArcologyNames.SubjugationistWhite.random();
				case "asian":
					return App.Data.ArcologyNames.SubjugationistAsian.random();
				case "latina":
					return App.Data.ArcologyNames.SubjugationistLatina.random();
				case "middle eastern":
					return App.Data.ArcologyNames.SubjugationistMiddleEastern.random();
				case "black":
					return App.Data.ArcologyNames.SubjugationistBlack.random();
				case "indo-aryan":
					return App.Data.ArcologyNames.SubjugationistIndoAryan.random();
				case "pacific islander":
					return App.Data.ArcologyNames.SubjugationistPacificIslander.random();
				case "malay":
					return App.Data.ArcologyNames.SubjugationistMalay.random();
				case "amerindian":
					return App.Data.ArcologyNames.SubjugationistAmerindian.random();
				case "southern european":
					return App.Data.ArcologyNames.SubjugationistSouthernEuropean.random();
				case "semitic":
					return App.Data.ArcologyNames.SubjugationistSemitic.random();
				default:
					return App.Data.ArcologyNames.SubjugationistMixedRace.random();
			}
		case "FSGenderRadicalist":
			return App.Data.ArcologyNames.GenderRadicalist.random();
		case "FSGenderFundamentalist":
			return App.Data.ArcologyNames.GenderFundamentalist.random();
		case "FSPaternalist":
			return App.Data.ArcologyNames.Paternalist.random();
		case "FSDegradationist":
			return App.Data.ArcologyNames.Degradationist.random();
		case "FSAssetExpansionist":
			return App.Data.ArcologyNames.AssetExpansionist.random();
		case "FSSlimnessEnthusiast":
			return App.Data.ArcologyNames.SlimnessEnthusiast.random();
		case "FSTransformationFetishist":
			return App.Data.ArcologyNames.TransformationFetishist.random();
		case "FSBodyPurist":
			return App.Data.ArcologyNames.BodyPurist.random();
		case "FSMaturityPreferentialist":
			return App.Data.ArcologyNames.MaturityPreferentialist.random();
		case "FSYouthPreferentialist":
			if (V.pedoMode === 1 || V.minimumSlaveAge < 6) {
				return App.Data.ArcologyNames.YouthPreferentialistLow.random();
			} else if (V.minimumSlaveAge < 14) {
				return App.Data.ArcologyNames.YouthPreferentialist.concat(App.Data.ArcologyNames.YouthPreferentialistLow).random();
			} else {
				return App.Data.ArcologyNames.YouthPreferentialist.random();
			}
		case "FSPastoralist":
			return App.Data.ArcologyNames.Pastoralist.random();
		case "FSPhysicalIdealist":
			return App.Data.ArcologyNames.PhysicalIdealist.random();
		case "FSChattelReligionist":
			return App.Data.ArcologyNames.ChattelReligionist.random();
		case "FSRomanRevivalist":
			return App.Data.ArcologyNames.RomanRevivalist.random();
		case "FSNeoImperialist":
			return App.Data.ArcologyNames.NeoImperialist.random();
		case "FSAztecRevivalist":
			return App.Data.ArcologyNames.AztecRevivalist.random();
		case "FSEgyptianRevivalist":
			return App.Data.ArcologyNames.EgyptianRevivalist.random();
		case "FSEdoRevivalist":
			return App.Data.ArcologyNames.EdoRevivalist.random();
		case "FSArabianRevivalist":
			return App.Data.ArcologyNames.ArabianRevivalist.random();
		case "FSChineseRevivalist":
			return App.Data.ArcologyNames.ChineseRevivalist.random();
		case "FSAntebellumRevivalist":
			return App.Data.ArcologyNames.AntebellumRevivalist.random();
		case "FSRepopulationFocus":
			return App.Data.ArcologyNames.Repopulationist.random();
		case "FSRestart":
			return App.Data.ArcologyNames.Eugenics.random();
		case "FSHedonisticDecadence":
			return App.Data.ArcologyNames.HedonisticDecadence.random();
		case "FSIntellectualDependency":
			return App.Data.ArcologyNames.IntellectualDependency.random();
		case "FSSlaveProfessionalism":
			return App.Data.ArcologyNames.SlaveProfessionalism.random();
		case "FSPetiteAdmiration":
			return App.Data.ArcologyNames.PetiteAdmiration.random();
		case "FSStatuesqueGlorification":
			return App.Data.ArcologyNames.StatuesqueGlorification.random();
		default:
			return "Arcology X-4";
	}
};

/** @param {boolean} inIntro */
App.Intro.generateEstablishedArcologies = function(inIntro = false) {
	/* setup */
	const fsAllowed = {
		FSGenderRadicalist: () => V.seeDicks !== 0,
		FSGenderFundamentalist: () => V.seeDicks !== 100,
		FSDegradationist: () => V.seeExtreme !== 0
	};
	const allowedFS = App.Data.FutureSociety.playerFSNames.filter(fs => !(fs in fsAllowed) || fsAllowed[fs]());

	let targets = 4;
	if (V.PC.career === "arcology owner") {
		targets += 2;
	}

	/* generation */
	const div = document.createElement("div");
	for (let i = 0; i < targets; i++) {
		div.append(arcologyCard());
	}
	return div;

	function arcologyCard() {
		const arcology = generateArcology();
		const div = App.Intro.descriptionDivForArcology(arcology, acceptArcology, inIntro);

		if (!inIntro) {
			// for some reason we put the acquisition link at the bottom instead of the top when moving arcologies
			const sectionPrice = {
				"spire": 2_500_000,
				"penthouse": 10_000_000,
				"shops": 1_000_000,
				"fountain": 5_000_000,
				"apartments": 500_000,
				"markets": 250_000,
				"ravine-markets": 250_000,
				"manufacturing": 100_000,
			};
			const rows = (/** @type {App.Arcology.Section} */ section) => section.rows
				.reduce((acc, cur) => acc + cur.length * sectionPrice[section.id], 0);
			const price = arcology.building.sections.reduce((acc, cur) => acc + rows(cur), 10_000_000) || 25_000_000; // base price of ¤10,000,000

			div.append(App.UI.DOM.makeElement("div", makePurchase(`Purchase ${arcology.name}`, price, "capEx", {
				handler: () => {
					acceptArcology();
					updatePlayerArcology();
					Engine.play("Main");
				},
				prereqs: [
					[
						V.rival.state < 2 || V.rival.state > 3 || App.Events.effectiveWeek() <= 72,
						`Your inter-arcology war is preventing you from leaving ${V.arcologies[0].name}.`
					],
					[
						App.Events.effectiveWeek() < 58 || V.daughtersVictory !== 1,
						`You cannot leave ${V.arcologies[0].name} behind while the Daughters of Liberty are still a threat.`
					],
				],
			}), ['center']));
		}

		return div;

		function acceptArcology() {
			V.targetArcology = arcology;
			V.terrain = arcology.terrain;
			V.continent = arcology.continent;
			V.language = arcology.language;
			V.building = arcology.building;

			arcology.apply();
		}

		function updatePlayerArcology() {
			V.arcologies[0].name = arcology.name;
			V.arcologies[0].weeks = 1;

			V.weatherCladding = 0;
			V.rep = 0;
			if (V.secExpEnabled && V.SecExp.core) {
				V.SecExp.core.authority = 0;
			}

			updateFS();
		}

		function updateFS() {
			const {fs} = arcology;

			App.Data.FutureSociety.fsNames.forEach(f => {
				FutureSocieties.remove(f);
			});

			if (fs) {
				V.arcologies[0][fs] = arcology.FSProgress;
			}
		}
	}

	function generateArcology() {
		const arcology = {};
		arcology.fs = getFS();
		arcology.name = App.Intro.getNameForArcology(arcology);
		arcology.FSProgress = either(10, 30, 50);
		arcology.prosperity = either(40, 50, 60);
		arcology.citizens = random(-1, 1);
		arcology.terrain = App.Data.Arcology.Terrain.random();
		arcology.continent = App.Data.Arcology.Continents.random();
		arcology.language = App.Intro.getLanguageForArcology(arcology);

		const env = {terrain: arcology.terrain, established: true, fs: arcology.fs};
		const preset = App.Arcology.randomPreset(env).construct(env);
		arcology.building = preset.building;
		arcology.apply = preset.apply;

		return arcology;

		function getFS() {
			const type = allowedFS.pluck();
			if (type === "FSSupremacist" || type === "FSSubjugationist") {
				arcology.race = App.Data.Slave.Races.random();
			}
			return type;
		}
	}
};

repo_name: Nithhogg/fc

license: mit


code:
App.Intro.CustomSlaveTrade = function() {
	let baseControlsFilter = "all";
	/**
	 * @typedef  {"c1"|"c2"|"c3"|"percent"|"10th percent"} adjustModeValue
	 */
	/** @type {adjustModeValue} */
	let adjustMode = "c2";

	const dynamicModeSettings = {
		travelFrictionExponent: App.Data.World.TravelFrictionExponent,
		popScaleFactor: App.Data.World.PopScaleFactor,
		debugView: 0,
	};

	const outerContainer = new DocumentFragment();
	App.UI.DOM.appendNewElement("p", outerContainer, `When civilization turned upon itself, some countries readily took to enslaving their own. Others were raided by their neighbors for their desirable, and profitable, citizens. Which nationalities were most affected by the booming slave trade, and thus, likely to appear in your local slave markets?`);

	let dynamicDiv = document.createElement("div");
	dynamicDiv.append(dynamicContent());
	outerContainer.append(dynamicDiv);

	return outerContainer;

	function dynamicContent() {
		const f = new DocumentFragment();

		if (hashSum(V.nationalities) < 1) {
			App.UI.DOM.appendNewElement("p", f, `You cannot be a slaveowner without a slave trade. Please add nationalities to continue.`, ["note"]);
		} else {
			App.UI.DOM.appendNewElement("p", f, App.UI.DOM.passageLink(
				"Confirm customization",
				// @ts-ignore
				V.customWA ? "Extreme Intro" : "Intro Summary",
				// @ts-ignore
				() => delete V.customWA
			));
		}

		f.append(App.UI.nationalitiesDisplay());

		const tabBar = new App.UI.Tabs.TabBar("customST");
		tabBar.addTab("Adjust slave populations", "custom", customControls());
		tabBar.addTab("Presets", "presets", presetControls());
		tabBar.addTab("Dynamic World", "dynamic", worldMap());
		tabBar.addTab("Import/Export", "import-export", importExport());
		f.append(tabBar.render());

		return f;
	}

	function customControls() {
		const f = new DocumentFragment();
		f.append(filters());
		f.append(adjustSelector());
		f.append(resetOptions());
		f.append(App.UI.sectionBreak());
		f.append(popControls());
		return f;
	}

	/**
	 * @returns {DocumentFragment}
	 */
	function filters() {
		const frag = new DocumentFragment();

		/* Filter controls */
		const raceDiv = document.createElement("div");
		raceDiv.append("Filter by Race: ");
		const raceLinks = [];
		for (const [race, capRace] of App.Data.misc.filterRaces) {
			if (baseControlsFilter === race) {
				raceLinks.push(App.UI.DOM.disabledLink(capRace, ["Currently selected race"]));
			} else {
				raceLinks.push(App.UI.DOM.link(capRace,
					() => {
						baseControlsFilter = race;
						refresh();
					}
				));
			}
		}
		raceDiv.append(App.UI.DOM.generateLinksStrip(raceLinks));
		frag.append(raceDiv);

		const regionDiv = document.createElement("div");
		regionDiv.append("Filter by Region: ");
		const regionLinks = [];
		for (const region of App.Data.misc.filterRegions) {
			if (baseControlsFilter === uncapFirstChar(region).replace(/[ -]/g, '')) {
				regionLinks.push(App.UI.DOM.disabledLink(region, ["Currently selected region"]));
			} else {
				regionLinks.push(App.UI.DOM.link(region,
					() => {
						baseControlsFilter = uncapFirstChar(region).replace(/[ -]/g, '');
						refresh();
					}
				));
			}
		}
		regionDiv.append(App.UI.DOM.generateLinksStrip(regionLinks));
		frag.append(regionDiv);
		return frag;
	}

	/**
	 * @returns {HTMLDivElement}
	 */
	function adjustSelector() {
		const div = document.createElement("div");
		div.append("Adjustment amount: ");
		/**
		 * @type {Array<[string, adjustModeValue]>}
		 */
		const options = [["Constant Small", "c1"], ["Constant Medium", "c2"], ["Constant Large", "c3"], ["Single Percent", "percent"], ["A 10th percent", "10th percent"]];
		const links = [];
		for (const o of options) {
			if (adjustMode === o[1]) {
				links.push(App.UI.DOM.disabledLink(o[0], ["Mode selected"]));
			} else {
				links.push(App.UI.DOM.link(o[0], () => {
					adjustMode = o[1];
					refresh();
				}));
			}
		}
		div.append(App.UI.DOM.generateLinksStrip(links));
		return div;
	}

	/**
	 * @returns {HTMLDivElement}
	 */
	function resetOptions() {
		const div = document.createElement("div");
		div.append(
			App.UI.DOM.link(
				"Reset filters",
				() => {
					baseControlsFilter = "all";
					refresh();
				}
			)
		);
		div.append(" | ");
		div.append(
			App.UI.DOM.link(
				"Clear all nationalities",
				() => {
					V.nationalities = {};
					refresh();
				}
			)
		);
		return div;
	}

	/**
	 * Fine control tweaking of populations
	 * @returns {DocumentFragment}
	 */
	function popControls() {
		const frag = new DocumentFragment();
		const nationalitiesCheck = App.UI.nationalitiesCheck();

		const grid = document.createElement("p");
		grid.classList.add("customize-slave-trade-grid");

		let nationalities = App.Data.misc.baseNationalities;
		if (baseControlsFilter !== "all") {
			const controlsNationality = App.Data.misc.nationalitiesByRace[baseControlsFilter] || App.Data.misc[baseControlsFilter + 'Nationalities'];
			nationalities = Object.keys(controlsNationality);
		}
		for (const nation of nationalities) {
			const div = document.createElement("div");
			div.append(nation);

			const span = document.createElement("span");
			span.classList.add("controls-container");

			const plusButton = App.UI.DOM.appendNewElement("button", span, "+", ["plus-button"]);
			plusButton.onclick = () => {
				addNationality(nation, adjustmentStep(nation, true));
				refresh();
			};

			if (nationalitiesCheck[nation]) {
				const minusButton = App.UI.DOM.appendNewElement("button", span, "-", ["minus-button"]);
				minusButton.onclick = () => {
					addNationality(nation, adjustmentStep(nation, false));
					refresh();
				};
			}

			if (V.nationalities[nation] > 1) {
				const zeroButton = App.UI.DOM.appendNewElement("button", span, "0", ["zero-button"]);
				zeroButton.onclick = () => {
					delete V.nationalities[nation];
					refresh();
				};
			}

			div.append(span);
			grid.append(div);
		}
		frag.append(grid);

		if (baseControlsFilter === "all") {
			frag.append(ethnicityControls());
		}

		return frag;
	}

	function ethnicityControls() {
		const frag = new DocumentFragment();
		App.UI.DOM.appendNewElement("p", frag, `By dominant race/ethnicity (hover over the name to see the nationalities affected):`);
		const grid = document.createElement("p");
		grid.classList.add("customize-slave-trade-grid");
		for (const race of App.Data.misc.filterRaces.keys()) {
			const racialNationalities = App.Data.misc.baseNationalities.filter(function(n) {
				let races = App.Data.misc.raceSelector[n] || App.Data.misc.raceSelector[''];
				return races[race] * 3.5 > hashSum(races);
			});

			if (racialNationalities.length > 0) {
				const div = document.createElement("div");
				div.append(App.UI.DOM.spanWithTooltip(race, racialNationalities.length > 0 ? racialNationalities.join(", ") : "(none)"));

				const span = document.createElement("span");
				span.classList.add("controls-container");

				const plusButton = App.UI.DOM.appendNewElement("button", span, "+", ["plus-button"]);
				plusButton.onclick = () => {
					racialNationalities.forEach(nation => {
						addNationality(nation, adjustmentStep(nation, true));
					});
					refresh();
				};

				const minusButton = App.UI.DOM.appendNewElement("button", span, "0", ["zero-button"]);
				minusButton.onclick = () => {
					racialNationalities.forEach(n => delete V.nationalities[n]);
					refresh();
				};

				div.append(span);
				grid.append(div);
			}
		}
		frag.append(grid);
		return frag;
	}

	/**
	 * How much a single +/- action should affect
	 * @param {string} nation
	 * @param {boolean} up
	 * @returns {number}
	 */
	function adjustmentStep(nation, up) {
		if (adjustMode === "c1") {
			return up ? 1 : -1;
		} else if (adjustMode === "c2") {
			return up ? 100 : -100;
		} else if (adjustMode === "c3") {
			return up ? 1000 : -1000;
		}

		const total = hashSum(V.nationalities);
		if (total === 0) {
			return 1;
		}

		const nationValue = V.nationalities[nation] ? V.nationalities[nation] : 0;

		if (nationValue === 0) {
			if (adjustMode === "percent") {
				return total / 99;
			} else if (adjustMode === "10th percent") {
				return total / 999;
			}
		}

		let fractionChange = 0;
		if (adjustMode === "percent") {
			fractionChange = 0.01;
		} else if (adjustMode === "10th percent") {
			fractionChange = 0.001;
		}

		const nonNationTotal = (total - nationValue);
		const nationFraction = nationValue / total;
		const nonNationFraction = nonNationTotal / total;

		if (!up) {
			// When going down we want to change by the same ratio, but in the opposite direction
			fractionChange *= -1;
		}

		// Arrive at this function from:
		// Target ratio = Value ratio with adjusted nationValue
		// <=>
		// (nationFraction + fractionChange) / (nonNationFraction - fractionChange) = (nationValue + change) / nonNationTotal
		// // Edit both fractions, because we want a behavior like this: 50% / 50% -> 51% / 49%
		// <=>
		// change = ... (See below)
		return nonNationTotal * ((nationFraction + fractionChange) / (nonNationFraction - fractionChange)) - nationValue;
	}

	function presetControls() {
		const f = new DocumentFragment();
		f.append(`Vanilla presets: `, generatePresetLinks(App.Data.NationalityPresets.Vanilla));
		f.append(`Mod presets: `, generatePresetLinks(App.Data.NationalityPresets.Mod));
		return f;
	}

	/**
	 * @param {Map<string, Record<string,number>>} presets
	 * @returns {HTMLDivElement}
	 */
	function generatePresetLinks(presets) {
		const grid = document.createElement("div");
		grid.classList.add("customize-slave-trade-grid", "presets");
		for (const [name, nationalities] of presets) {
			const div = document.createElement("div");
			div.append(name);

			const span = document.createElement("span");
			span.classList.add("controls-container");

			const setButton = App.UI.DOM.appendNewElement("button", span, "Set", ["set-button"]);
			setButton.onclick = () => {
				V.nationalities = clone(nationalities);
				refresh();
			};

			const plusButton = App.UI.DOM.appendNewElement("button", span, "+", ["plus-button"]);
			plusButton.onclick = () => {
				for (const nat in nationalities) {
					addNationality(nat, nationalities[nat]);
				}
				refresh();
			};

			div.append(span);
			grid.append(div);
		}
		return grid;
	}

	/**
	 * @param {string} nation
	 * @param {number} amount
	 */
	function addNationality(nation, amount) {
		if (V.nationalities.hasOwnProperty(nation)) {
			V.nationalities[nation] += amount;
			if (V.nationalities[nation] <= 0) {
				delete V.nationalities[nation];
			}
		} else if (amount > 0) {
			V.nationalities[nation] = amount;
		}
	}

	/**
	 * @returns {DocumentFragment}
	 */
	function worldMap() {
		const f = new DocumentFragment();
		App.UI.DOM.appendNewElement("p", f, `Slave trade is global, yet local trade is dominated by local availability. Where is your arcology located?`);
		App.UI.DOM.appendNewElement("p", f, "Click anywhere on the map to create a new slave trade distribution.", ["note"]);
		addMapSelector(f);

		let options = new App.UI.OptionsGroup();
		options.customRefresh(refresh);

		options.addSlider("Travel Friction Exponent", -2, 1, "travelFrictionExponent", dynamicModeSettings)
			.setStep(0.01)
			.addEndLabels("Global", "Local").addTextBox();
		options.addSlider("Population Scale Factor", 1, 50, "popScaleFactor", dynamicModeSettings)
			.setStep(0.1)
			.addEndLabels("Homogenous", "Diverse").addTextBox();

		/** @type {Array<[string,number,number]>} */
		const presets = [["Default (Balanced)", App.Data.World.TravelFrictionExponent, App.Data.World.PopScaleFactor],
			["Greenland (Local)", 0.55, 10],
			["Hyper Global Realistic", -2, 50], ["Hyper Global Equal", -2, 1]];

		const o = options.addCustomOption("Presets");
		for (const p of presets) {
			o.addButton(p[0], () => {
				dynamicModeSettings.travelFrictionExponent = p[1];
				dynamicModeSettings.popScaleFactor = p[2];
				refresh();
			});
		}

		options.addOption("Debug View", "debugView", dynamicModeSettings)
			.addValue("Off", 0).off()
			.addValue("A", 1).on()
			.addValue("B", 2).on();

		f.append(options.render());
		return f;
	}

	/**
	 * @param {DocumentFragment} container
	 */
	function addMapSelector(container) {
		// SVG size
		const width = 5760;
		const height = 2880;

		// TODO: it may make sense to create a new toplevel svg instead of cloning the entire svg
		const svg = App.Data.Art.OtherSVG.get("world").cloneNode(true);
		d3.select(svg)
			.style("width", null)
			.style("height", null)
			.style("width", "100%")
			.style("height", "100%")
			.on("click", listen);

		if (dynamicModeSettings.debugView > 0) {
			const g = d3.select(svg)
				.append("g")
				.attr("transform", `scale(${width / 360}, -${height / 180}) translate(180, -90)`);

			for (const p of App.Data.World.gridPoints()) {
				const pop = App.Data.World.populationAt(p);

				if (dynamicModeSettings.debugView === 1) {
					if (pop > 0) {
						const [lat, lon] = App.Data.World.gridPointToCoordinate(p);
						const r = 10 * Math.log(pop / 10) / 100;
						const nation = App.Data.World.nationIdAt(p);
						let color = nation / 230 * (255 * 255 * 255);
						g.append("circle").attr("cx", lon).attr("cy", lat).attr("r", r)
							.attr("fill", "#" + Math.round(color).toString(16))
							.attr("opacity", "0.5");
					}
				} else if (dynamicModeSettings.debugView === 2) {
					if (p[0] === -1) {
						const [lat, lon] = App.Data.World.gridPointToCoordinate(p);
						g.append("circle").attr("cx", lon).attr("cy", lat).attr("r", 0.5).attr("fill", "red");
					}
				}
			}
		}

		container.append(svg);

		function listen(ev) {
			let [x, y] = d3.pointer(ev);
			let lon = (x / width) * 360 - 180;
			let lat = ((y / height) * 180 - 90) * -1;
			console.log(lon, lat);
			const popScaleCache = createPopScaleCache();
			populateFromCoordinates(lat, lon, popScaleCache);
			normalizePopulation();
			refresh();
		}
	}

	/**
	 * Create scaling factors for each nationality based on total population
	 * @returns {{[key:string]:number}}
	 */
	function createPopScaleCache() {
		/** @type {{[key:string]:number}} */
		const cache = {};

		// Build pop cache
		for (const p of App.Data.World.gridPoints()) {
			const nation = App.Data.World.nationAt(p);
			const pop = App.Data.World.populationAt(p);
			if (cache.hasOwnProperty(nation)) {
				cache[nation] += pop;
			} else {
				cache[nation] = pop;
			}
		}

		// Build pop scale cache
		for (const p in cache) {
			const pop = cache[p];
			const a = Math.exp(dynamicModeSettings.popScaleFactor);
			const scaled = a * Math.log1p(pop / a);
			cache[p] = scaled / pop;
		}

		return cache;
	}

	/**
	 * Normalize population numbers and round them.
	 */
	function normalizePopulation() {
		// Find max
		let max = 0;
		for (const n in V.nationalities) {
			if (V.nationalities[n] > max) {
				max = V.nationalities[n];
			}
		}
		// Scale everything relative to max
		// relMax controls at which point small populations are cut off. The larger, the smaller allowed populations
		// will be. This does not change their relative likelihood.
		const relMax = 1000.0;
		const scale = relMax / max;
		for (const n in V.nationalities) {
			const val = Math.round(V.nationalities[n] * scale);
			if (val > 0) {
				V.nationalities[n] = val;
			} else {
				delete V.nationalities[n];
			}
		}
	}

	/**
	 * The distribution of population within the grid square must be uniform, but the data is essentially concentrated at the center point
	 * When the user clicks within a populated grid square, provide an alternative "distance" for that grid square's population
	 * Basically this stabilizes the weight of the grid you clicked inside, so clicking at the center or the edge of the cell doesn't affect it
	 * @param {number} lat Latitude of grid cell center
	 * @param {number} lon Longitude of grid cell center
	 */
	function altDist(lat, lon) {
		const lonCell = 360 / App.Data.World.GridDimensions.width;
		const latCell = 180 / App.Data.World.GridDimensions.height;

		// take one third of the corner-to-corner measurement of the grid cell as our alternate distance.
		// ideally we want the average distance between coordinates within the grid cell and the grid cell's center, but that's hard to calculate, and it's about the same.
		// the actual value for a square is around 0.3826, and for an equilateral triangle is 0.289. grid cells are almost square near the equator and almost triangular near the poles.
		return distanceInKmBetweenEarthCoordinates(lat - 0.5 * latCell, lon - 0.5 * lonCell, lat + 0.5 * latCell, lon + 0.5 * lonCell) / 3;
	}

	/**
	 * @param {number} lat
	 * @param {number} lon
	 * @param {{[key:string]:number}} popScaleData
	 */
	function populateFromCoordinates(lat, lon, popScaleData) {
		V.nationalities = {};
		const thisGP = App.Data.World.coordinateToGridPoint(lat, lon);
		for (const p of App.Data.World.gridPoints()) {
			const pop1 = 1; // or App.Data.World.populationAt(thisGP);
			const pop2 = App.Data.World.populationAt(p);
			if (pop2 > 0) {
				const coords = App.Data.World.gridPointToCoordinate(p);
				// is this the grid square we clicked inside of? use alternate distance if so
				const clickedGrid = (p[0] === thisGP[0] && p[1] === thisGP[1]);
				const dist = clickedGrid ? altDist(coords[0], coords[1]) : distanceInKmBetweenEarthCoordinates(lat, lon, coords[0], coords[1]);
				let gravity = (pop1 * pop2) / (dist * dist * frictionForDistance(dist));
				if (gravity < 1 && clickedGrid) {
					gravity = 1; // always give _some_ population for the grid cell you clicked on, if it's inhabited
				}
				const nation = App.Data.World.nationAt(p);
				// console.log(nation, gravity, Math.round(gravity));
				addNationality(nation, popScaleData[nation] * gravity);
			}
		}
	}

	/**
	 * Compute travel friction factor for a given distance. More friction means less influence for distant grid points
	 * Ideally we'd also do some pathing between the grid points (traveling over an unpopulated ocean/desert should be more expensive), but skip that for now
	 * @param {number} distance In km
	 * @returns {number} greater 0
	 */
	function frictionForDistance(distance) {
		// theoretically there should be a constant factor here too but it gets cancelled out during normalization, so we ignore it
		const e = dynamicModeSettings.travelFrictionExponent; // 0 is no scaling
		return Math.pow(distance, e);
	}

	// https://stackoverflow.com/questions/365826/calculate-distance-between-2-gps-coordinates
	/**
	 * @param {number} lat1
	 * @param {number} lon1
	 * @param {number} lat2
	 * @param {number} lon2
	 */
	function distanceInKmBetweenEarthCoordinates(lat1, lon1, lat2, lon2) {
		const earthRadiusKm = 6371;

		const dLat = degreesToRadians(lat2 - lat1);
		const dLon = degreesToRadians(lon2 - lon1);

		lat1 = degreesToRadians(lat1);
		lat2 = degreesToRadians(lat2);

		const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
			Math.sin(dLon / 2) * Math.sin(dLon / 2) * Math.cos(lat1) * Math.cos(lat2);
		const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
		return earthRadiusKm * c;
	}

	/**
	 * @param {number} degrees
	 */
	function degreesToRadians(degrees) {
		return degrees * Math.PI / 180;
	}

	function importExport() {
		const f = new DocumentFragment();
		const span = document.createElement("p");
		const importExportContainer = document.createElement("div");
		App.UI.DOM.appendNewElement(
			"span",
			span,
			App.UI.DOM.link(
				"Export Settings",
				() => {
					settingsExport(importExportContainer);
				}
			)
		);
		span.append(" | ");
		App.UI.DOM.appendNewElement(
			"span",
			span,
			App.UI.DOM.link(
				"Import Settings",
				() => {
					settingsImport(importExportContainer);
				}
			)
		);
		f.append(span);

		f.append(importExportContainer);
		return f;
	}

	/**
	 * @param {HTMLDivElement} container
	 */
	function settingsExport(container) {
		let textArea = document.createElement("textarea");
		textArea.value = Serial.stringify(V.nationalities);
		$(container).empty().append(textArea);
	}

	/**
	 * @param {HTMLDivElement} container
	 */
	function settingsImport(container) {
		let textArea = document.createElement("textarea");
		let button = document.createElement("button");
		button.append("Load");
		button.onclick = () => {
			try {
				V.nationalities = Serial.parse(textArea.value);
			} catch (SyntaxError) {
				Dialog.create("Invalid Input");
				Dialog.append("The input is not a valid nationalities object.");
				Dialog.open();
				return;
			}
			refresh();
		};

		$(container).empty().append(textArea, button);
	}

	function refresh() {
		return $(dynamicDiv).empty().append(dynamicContent());
	}
};

/**
 * @returns {HTMLElement}
 */
App.UI.nationalitiesDisplay = function() {
	const p = document.createElement("p");

	/* Generates cloned array of V.nationalities, removing duplicates and then sorting */
	const nationalitiesCheck = App.UI.nationalitiesCheck();
	const nationalities = [];
	for (const nat in nationalitiesCheck) {
		nationalities.push(nat);
	}
	nationalities.sort((a, b) => nationalitiesCheck[b] - nationalitiesCheck[a]);

	/* Prints distribution of V.nationalities, using nationalitiesCheck to render array */
	let percentPerPoint = 100.0 / hashSum(V.nationalities);
	let len = Object.keys(nationalitiesCheck).length;
	let j = 0;
	for (const nation of nationalities) {
		const span = document.createElement("span");
		span.append(`${nation} `);
		let percent = (V.nationalities[nation] * percentPerPoint).toFixed(2);
		if (percent === "0.00") {
			percent = "<0.01";
		}
		App.UI.DOM.appendNewElement("span", span, percent + "%", ["orange"]);
		j++;
		if (j < len) {
			span.append(` | `);
		}
		p.append(span);
	}
	return p;
};

/**
 * @returns {object}
 */
App.UI.nationalitiesCheck = function() {
	return Object.assign(
		{
			// Player can add custom nations here.
		},
		V.nationalities);
};


code:
App.Intro.economyIntro = function() {
	const node = new DocumentFragment();

	App.Events.addParagraph(node, [
		`It is the year 2037, and the past 21 years have not been kind. The world is starting to fall apart. The climate is deteriorating, resources are being exhausted, and there are more people to feed every year. Technology is advancing, but not fast enough to save everyone. `,
	]);

	if (V.PC.career === "arcology owner" || V.saveImported === 1) {
		setTimeout(() => Engine.play("Takeover Target"), Engine.minDomActionDelay);
	}

	const options = new App.UI.OptionsGroup();

	const difficultyComment = new Map([
		[1, `Not truly dire. Not yet.`],
		[2, `Getting a touch dire.`],
		[3, `Very serious.`],
		[4, `It won't be pretty.`],
		[5, `This is the last dance.`],
	]);

	options.addOption(`Exactly how bad is the situation?`, "baseDifficulty")
		.addValue(`Very Easy`, 1).addCallback(() => V.economy = 200)
		.addValue(`Easy`, 2).addCallback(() => V.economy = 125)
		.addValue(`Very serious (default)`, 3).addCallback(() => V.economy = 100)
		.addValue(`Hard`, 4).addCallback(() => V.economy = 80)
		.addValue(`Very Hard`, 5).addCallback(() => V.economy = 67)
		.addComment(difficultyComment.get(V.baseDifficulty));

	const difficultyComment2 = new Map([
		[0, `Barely noticeable, for now.`],
		[1, `A slow decline.`],
		[2, `It's visibly deteriorating before your eyes.`],
		[4, `It's going to hell in a handbasket.`],
	]);

	if (V.difficultySwitch === 0) {
		options.addOption(`How fast is it crumbling?`, "difficultySwitch")
			.addValue(`Vanilla`, 0)
			.addValue(`Harder`, 1).addCallback(()=>{ V.econRate = 2; })
			.addComment(`Some economic content requires this to be set to harder than vanilla`);
	} else {
		options.addOption(`How fast is it crumbling?`, "econRate")
			.addValue(`Return to vanilla`, 0).addCallback(() => {
				V.difficultySwitch = 0;
			})
			.addValue(`Easy`, 1)
			.addValue(`Medium`, 2)
			.addValue(`Hard`, 4)
			.addComment(difficultyComment2.get(V.econRate));
	}
	node.append(options.render());

	/* Not functional yet, place V.incomeRate = 1 in storyInit when ready for use
	//

		r.push(`All the things you need to run your arcology are getting more expensive`);
		if (V.incomeMod === 0) {
			r.push(`while all forms of income`);
			//remain static. [[Easier|Economy Intro][V.incomeRate = 1]]
		} else if (V.incomeMod === 1) {
			r.push(`while all forms of income <span style="font-weight:Bold">rise but cannot keep pace.</span> [[Easier|Economy Intro][${V.incomeRate} = 2]] | [[Harder|Economy Intro][V.incomeRate = 0]]`);
		} else {
			r.push(`but luckily all forms of income <span style="font-weight:Bold">rise in lockstep.</span> [[Harder|Economy Intro][${V.incomeRate} = 1]]`);
		}
	//

	//

		[[Play with static economy|Economy Intro][V.difficultySwitch = 0]]
	//

	*/
	//

	//

	App.Events.addResponses(node, [
		new App.Events.Result(`Next`, next),
		new App.Events.Result(`Skip Intro`, skip, `This will preclude you from taking over an established arcology.`),
	]);

	return node;

	function next() {
		Engine.play("Trade Intro");
		return ``;
	}
	function skip() {
		Engine.play("Intro Summary");
		return ``;
	}
};


code:
App.Intro.extremeIntro = function() {
	const node = new DocumentFragment();

	App.Events.addParagraph(node, [
		`The early Free Cities were wild places where the writ of law did not run. In some of the most depraved, slaves' bodies, minds and even lives were playthings of the wealthy and powerful. Though modern Free Cities are tremendously varied, a majority of the new communities made a choice about whether extreme practices were a flaw in a lawless society or one of its benefits.`,
		App.UI.DOM.makeElement("span", `How did most Free Cities react to the excesses of the early days?`, ["intro", "question"])
	]);

	App.Events.addResponses(node, [
		new App.Events.Result(`They drew back from them.`, drewBack, `Extreme content such as amputation and castration will not appear.`),
		new App.Events.Result(`They reveled in them.`, reveled, `Extreme content will appear.`),
		new App.Events.Result(`They drew back from them, but remained creative.`, creative, `Extreme content will not appear, but hyper-pregnancy related content might appear.`),
		new App.Events.Result(`They reveled in them and were particularly inventive.`, inventive, `Extreme content will appear, including hyper-pregnancy related content`),
	]);

	return node;

	function drewBack() {
		V.seeExtreme = 0;
		Engine.play("Gender Intro");
		return ``;
	}

	function reveled() {
		V.seeExtreme = 1;
		Engine.play("Gender Intro");
		return ``;
	}

	function creative() {
		V.seeExtreme = 0;
		V.seeHyperPreg = 1;
		Engine.play("Gender Intro");
		return ``;
	}

	function inventive() {
		V.seeExtreme = 1;
		V.seeHyperPreg = 1;
		Engine.play("Gender Intro");
		return ``;
	}
};


code:
App.Intro.genderIntro = function() {
	const node = new DocumentFragment();

	App.Events.addParagraph(node, [
		`The Free Cities are sexually libertine places, and sexual slavery is ubiquitous. Some of the early Free Cities upheld or even strengthened traditional gender roles, expecting men to be men and women to be women. Others subscribed to an interesting refinement of those gender roles, considering any sex slave female, regardless of her biology. A small minority even went so far as to strongly favor societal feminization of slaves born male; in these, biologically female slaves were a rare sight.`,
		App.UI.DOM.makeElement("span", `Which kind of Free City came to predominate?`, ["intro", "question"])
	]);

	App.Events.addResponses(node, [
		new App.Events.Result(`Free Cities that were open-minded about who could be a slave girl.`, open, `Default setting. The majority of slaves will be biologically female, and all content will be available.`),
		new App.Events.Result(`Free Cities that understood that girls are girls.`, girlIsGirl, `Almost all slaves will be biologically female, restricting some content.`),
		new App.Events.Result(`Free Cities that understood that girls are girls with some exceptions.`, exceptions, `This option will make almost all generated slaves female, but will allow for the attachment of a certain organ through surgery.`),
		new App.Events.Result(`Free Cities that preferred girls with dicks.`, slaveIsBoy, `Almost all slaves will be biologically male, restricting some content.`),
	]);

	return node;

	function open() {
		V.seeDicks = 25;
		Engine.play("Slave Age Intro");
		return ``;
	}

	function girlIsGirl() {
		V.seeDicks = 0;
		Engine.play("Slave Age Intro");
		return ``;
	}

	function exceptions() {
		V.seeDicks = 0;
		V.makeDicks = 1;
		Engine.play("Slave Age Intro");
		return ``;
	}

	function slaveIsBoy() {
		V.seeDicks = 100;
		Engine.play("Slave Age Intro");
		return ``;
	}
};