diff --git a/src/js/assayJS.js b/src/js/assayJS.js
index 443245cab40d35fdf0d9db577e6925db32b0b014..4d5f872fafd615f30ba04f19dc56da07cf9f3ff2 100644
--- a/src/js/assayJS.js
+++ b/src/js/assayJS.js
@@ -1826,6 +1826,27 @@ window.retirementReady = function RetirementReady(slave) {
 	return false;
 };
 
+/** advances the age of a slave by one year, incurring all aging side effects
+ * @param {App.Entity.SlaveState} slave
+ */
+window.ageSlave = function ageSlave(slave) {
+	slave.physicalAge++;
+	slave.actualAge++;
+	if (slave.geneMods.NCS === 0) {
+		slave.visualAge++;
+		slave.ovaryAge += either(0.8, 0.9, 0.9, 1.0, 1.0, 1.0, 1.1);
+	} else {
+		/* Induced NCS completely takes over visual aging. Additionally, because of the neoteny aspects of NCS, ovaries don't age quite as fast. */
+		slave.ovaryAge += either(0.5, 0.6, 0.7, 0.7, 0.8, 0.9, 1.0);
+	}
+	if (slave.broodmother === 1) {
+		slave.ovaryAge += 0.2;
+	}
+	if (slave.physicalAge <= 18 && V.loliGrow > 0) {
+		physicalDevelopment(slave);
+	}
+};
+
 /** Is the slave a shelter slave?
  * @param {App.Entity.SlaveState} slave
  * @returns {boolean}
diff --git a/src/js/generateRelatedSlave.js b/src/js/generateRelatedSlave.js
index fef0c3948699998b04530dbd8351a919d7fa6253..191f3bab1e5ee93ef445aa4a32d5a806dc5da4fb 100644
--- a/src/js/generateRelatedSlave.js
+++ b/src/js/generateRelatedSlave.js
@@ -3,8 +3,8 @@ window.generateRelatedSlave = (function() {
 
 	/**
 	 * Generate a very similar relative for an existing slave (for use in Household Liquidators, for example).
-	 * @param {App.Entity.SlaveState} slave - the source relative
-	 * @param {string} relationship - the relationship that the new relative has with the source. Currently supports "daughter", "sibling", "twin".
+	 * @param {App.Entity.SlaveState} slave - the source relative. Note: this slave is NOT changed, calling code is responsible for setting up the source end of the relationship!
+	 * @param {string} relationship - the relationship that the new relative has with the source. Currently supports "parent", "child", "older sibling", "younger sibling", "twin".
 	 * @param {bool} oppositeSex - set to true if the new relative should be the opposite sex of the old one (otherwise it will be the same sex).
 	 * @returns {SlaveState} - new relative
 	 */
@@ -12,10 +12,14 @@ window.generateRelatedSlave = (function() {
 		let relative = prepareClone(slave);
 		if (relationship === "twin") {
 			makeTwin(relative);
-		} else if (relationship === "daughter") {
-			makeDaughter(relative);
-		} else if (relationship === "sibling") {
-			makeSibling(relative);
+		} else if (relationship === "child") {
+			makeChild(relative);
+		} else if (relationship === "parent") {
+			makeParent(relative);
+		} else if (relationship === "younger sibling") {
+			makeYoungerSibling(relative);
+		} else if (relationship === "older sibling") {
+			makeOlderSibling(relative);
 		}
 		if (oppositeSex) {
 			if (slave.genes === "XX") {
@@ -26,9 +30,12 @@ window.generateRelatedSlave = (function() {
 				// we'll assume futa are their own opposites and don't need tweaking
 			}
 		}
-		if (relative.actualAge < slave.actualAge || oppositeSex) { // not used for same-sex twins
+		// perform age-related adjustment for all relatives *except* same-sex twins (preserve identicality)
+		if (relative.actualAge !== slave.actualAge || oppositeSex) {
 			ageFixup(relative);
 		}
+		setHealth(slave, slave.health.condition);
+
 		return relative;
 	}
 
@@ -85,7 +92,7 @@ window.generateRelatedSlave = (function() {
 	 * Finish configuring a sibling
 	 * @param {App.Entity.SlaveState} slave - the new sibling
 	 */
-	function makeSibling(slave) {
+	function makeYoungerSibling(slave) {
 		if (!V.familyTesting) {
 			slave.relation = "sister";
 			slave.relationTarget = sourceID;
@@ -99,22 +106,37 @@ window.generateRelatedSlave = (function() {
 		slave.ovaryAge = slave.actualAge;
 		slave.birthWeek = random(0, 51);
 
-		// fuzz boobs/butt
-		if (slave.boobs > 200) {
-			slave.boobs += either(-100, 0, 100);
-		}
-		if (slave.butt > 1) {
-			slave.butt += random(-1, 1);
+		fuzzPhysicalTraits(slave);
+
+		randomiseFetishFlaws(slave);
+	}
+
+	/**
+	 * Finish configuring a sibling
+	 * @param {App.Entity.SlaveState} slave - the new sibling
+	 */
+	function makeOlderSibling(slave) {
+		if (!V.familyTesting) {
+			slave.relation = "sister";
+			slave.relationTarget = sourceID;
 		}
 
+		// increase age
+		const maxDifference = (V.retirementAge - 1) - slave.actualAge;
+		const ageDifference = Math.min(random(2, 6), maxDifference);
+		fastForward(slave, ageDifference);
+		slave.birthWeek = random(0, 51);
+
+		fuzzPhysicalTraits(slave);
+
 		randomiseFetishFlaws(slave);
 	}
 
 	/**
-	 * Finish configuring a daughter
-	 * @param {App.Entity.SlaveState} slave - the new daughter
+	 * Finish configuring a child
+	 * @param {App.Entity.SlaveState} slave - the new child
 	 */
-	function makeDaughter(slave) {
+	function makeChild(slave) {
 		if (!V.familyTesting) {
 			slave.relation = "daughter";
 			slave.relationTarget = sourceID;
@@ -144,13 +166,7 @@ window.generateRelatedSlave = (function() {
 		slave.boobs -= 100;
 		slave.butt -= 1;
 
-		// fuzz boobs/butt
-		if (slave.boobs > 200) {
-			slave.boobs += either(-100, 100);
-		}
-		if (slave.butt > 1) {
-			slave.butt += random(-1, 1);
-		}
+		fuzzPhysicalTraits(slave);
 
 		// daughter has never had children and is likely a virgin
 		slave.vagina = either(0, 0, 0, 1);
@@ -159,6 +175,67 @@ window.generateRelatedSlave = (function() {
 		randomiseFetishFlaws(slave);
 	}
 
+	/**
+	 * Finish configuring a parent
+	 * @param {App.Entity.SlaveState} slave - the new parent
+	 */
+	function makeParent(slave) {
+		if (!V.familyTesting) {
+			slave.relation = "mother"; // no fathers without family testing
+			slave.relationTarget = sourceID;
+		} else {
+			slave.mother = 0;
+			slave.father = 0;
+		}
+
+		// select age
+		const childAge = slave.actualAge;
+		let minAge = childAge + Math.max(11, V.minimumSlaveAge - 2);
+		let maxAge = Math.min(V.retirementAge - 1, childAge + 42);
+		if (maxAge < minAge) {
+			throw "Cannot generate parent (slave too old)";
+		}
+		slave.actualAge = random(minAge, maxAge);
+		slave.visualAge = slave.actualAge;
+		slave.physicalAge = slave.actualAge;
+		slave.ovaryAge = slave.actualAge;
+		slave.birthWeek = random(0, 51);
+
+		// parent always has less devotion/trust
+		slave.devotion -= 10;
+		slave.trust -= 10;
+
+		// mother always has more boobs/butt
+		if (slave.genes === "XX") {
+			slave.boobs += 100;
+			slave.butt += 1;
+		}
+
+		fuzzPhysicalTraits(slave);
+
+		// mother has had one child (at least)
+		if (slave.genes === "XX") {
+			slave.vagina = Math.max(slave.vagina, 1);
+			slave.counter.birthsTotal = 1;
+		}
+
+		randomiseFetishFlaws(slave);
+	}
+
+	/**
+	 * Fuzz some physical traits so we don't start out identical
+	 * @param {App.Entity.SlaveState} slave
+	 */
+	function fuzzPhysicalTraits(slave) {
+		// fuzz boobs/butt
+		if (slave.boobs > 200) {
+			slave.boobs += either(-100, 0, 100);
+		}
+		if (slave.butt > 1) {
+			slave.butt += random(-1, 0, 1);
+		}
+	}
+
 	/**
 	 * Randomize fetish and flaws
 	 * @param {App.Entity.SlaveState} slave
@@ -198,9 +275,6 @@ window.generateRelatedSlave = (function() {
 			}
 		}
 		SetBellySize(slave);
-
-		// reset health
-		setHealth(slave, slave.health.condition);
 	}
 
 	/**
@@ -306,6 +380,15 @@ window.generateRelatedSlave = (function() {
 		generatePuberty(slave);
 	}
 
+	function fastForward(slave, years) {
+		const _loliGrow = V.loliGrow;
+		V.loliGrow = 1; // force physical development
+		for (let i = 0; i < years; ++i) {
+			ageSlave(slave);
+		}
+		V.loliGrow = _loliGrow; // reset physical development flag
+	}
+
 	/**
 	 * Give a slave a realistic chance to activate a sexlinked genetic quirk which her opposite-sex relative was only a carrier for.
 	 * @param {App.Entity.SlaveState} slave - the slave to adjust
diff --git a/src/uncategorized/householdLiquidator.tw b/src/uncategorized/householdLiquidator.tw
index 23e5fb99a49328b1d0aa17c6872041c6e29d56c4..8b9dcdc86242c09de94316ee9e53301b4ac51c2a 100644
--- a/src/uncategorized/householdLiquidator.tw
+++ b/src/uncategorized/householdLiquidator.tw
@@ -24,7 +24,7 @@
 The household liquidator is offering a set of siblings for sale. As usual, you will only be permitted to inspect the older, but there is a guarantee that the younger will be similar.
 <br><br>
 
-<<set _relativeSlave = generateRelatedSlave($activeSlave, "sibling")>>
+<<set _relativeSlave = generateRelatedSlave($activeSlave, "younger sibling")>>
 <<if $familyTesting != 1>>
 	<<set $activeSlave.relation = "sister">>
 	<<set $activeSlave.relationTarget = _relativeSlave.ID>>
@@ -62,7 +62,7 @@ The household liquidator is offering a set of siblings for sale. As usual, you w
 The household liquidator is offering a mother and $his daughter for sale. As usual, you will only be permitted to inspect the mother, but there is a guarantee that the daughter will be similar.
 <br><br>
 
-<<set _relativeSlave = generateRelatedSlave($activeSlave, "daughter")>>
+<<set _relativeSlave = generateRelatedSlave($activeSlave, "child")>>
 <<if $familyTesting != 1>>
 	<<set $activeSlave.relation = "mother">>
 	<<set $activeSlave.relationTarget = _relativeSlave.ID>>
diff --git a/src/uncategorized/nextWeek.tw b/src/uncategorized/nextWeek.tw
index 112ea69338e931615e0359497a9357b0b3256bfc..9f4727009d04da0c447ebf128d82b5d034406936 100644
--- a/src/uncategorized/nextWeek.tw
+++ b/src/uncategorized/nextWeek.tw
@@ -116,23 +116,7 @@
 		<<set $slaves[_i].birthWeek++>>
 		<<if $slaves[_i].birthWeek >= 52>>
 			<<set $slaves[_i].birthWeek = 0>>
-			<<if $seeAge == 1>>
-				<<set $slaves[_i].physicalAge += 1, $slaves[_i].actualAge += 1>>
-				/* Note Induced NCS completely takes over visual aging, so the increment from pre-existing code simply is trapped behind a !NCS test. Additionally, because of the neoteny aspects of NCS, ovaries don't age quite as fast. */
-				<<if $slaves[_i].geneMods.NCS == 0>>
-					<<set $slaves[_i].visualAge += 1>>
-					/* (prev comment) Hopefully this works. It is intended, over a slave's lifetime, to cause her menopause to shift. */
-					<<set $slaves[_i].ovaryAge += either(.8, .9, .9, 1, 1, 1, 1.1)>>
-				<<else>>
-					<<set $slaves[_i].ovaryAge += either(.5, .6, .7, .7, .8, .9, 1)>>
-				<</if>>
-				<<if $slaves[_i].broodmother == 1>>
-					<<set $slaves[_i].ovaryAge += .2>>
-				<</if>>
-				<<if $slaves[_i].physicalAge <= 18 && $loliGrow > 0>>
-					<<run physicalDevelopment($slaves[_i])>>
-				<</if>>
-			<</if>>
+			<<run ageSlave($slaves[_i])>>
 		<</if>>
 	<</if>>
 	<<if $slaves[_i].indenture > 0>>
diff --git a/src/uncategorized/pCoupAttempt.tw b/src/uncategorized/pCoupAttempt.tw
index 71db6f8479e6d6325f3584fbbf72dcde0b764169..a60504571cfbb4ee8ec48ab24e2e26ccb9322fbe 100644
--- a/src/uncategorized/pCoupAttempt.tw
+++ b/src/uncategorized/pCoupAttempt.tw
@@ -26,13 +26,7 @@
 			<<set $traitor.birthWeek++>>
 			<<if $traitor.birthWeek >= 52>>
 				<<set $traitor.birthWeek = 0>>
-				<<if $seeAge == 1>>
-					<<set $traitor.physicalAge += 1, $traitor.actualAge += 1, $traitor.visualAge += 1>>
-					<<set $traitor.ovaryAge += either(.8, .9, .9, 1, 1, 1, 1.1)>>
-					<<if $traitor.physicalAge <= 18 && $loliGrow > 0>>
-						<<run physicalDevelopment($traitor)>>
-					<</if>>
-				<</if>>
+				<<run ageSlave($traitor)>>
 			<</if>>
 			<<set _weeks-->>
 		<</for>>
diff --git a/src/uncategorized/pCoupBetrayal.tw b/src/uncategorized/pCoupBetrayal.tw
index 4cc634a8be6630fa0320cb474089d57e050f71e4..1258d68f7fce9d36552a3d6cdac1a874c74059dd 100644
--- a/src/uncategorized/pCoupBetrayal.tw
+++ b/src/uncategorized/pCoupBetrayal.tw
@@ -25,13 +25,7 @@
 		<<set $traitor.birthWeek++>>
 		<<if $traitor.birthWeek >= 52>>
 			<<set $traitor.birthWeek = 0>>
-			<<if $seeAge == 1>>
-				<<set $traitor.physicalAge += 1, $traitor.actualAge += 1, $traitor.visualAge += 1>>
-				<<set $traitor.ovaryAge += either(.8, .9, .9, 1, 1, 1, 1.1)>>
-				<<if $traitor.physicalAge <= 18 && $loliGrow > 0>>
-					<<run physicalDevelopment($traitor)>>
-				<</if>>
-			<</if>>
+			<<run ageSlave($traitor)>>
 		<</if>>
 		<<set _weeks-->>
 	<</for>>
diff --git a/src/uncategorized/pTraitorMessage.tw b/src/uncategorized/pTraitorMessage.tw
index 3ba720727e70dd85f108657612b1a7c321abd571..e3bbedb530dcf3e7b0fcc3a2292171f438945483 100644
--- a/src/uncategorized/pTraitorMessage.tw
+++ b/src/uncategorized/pTraitorMessage.tw
@@ -49,13 +49,7 @@
 		<<set $traitor.birthWeek++>>
 		<<if $traitor.birthWeek >= 52>>
 			<<set $traitor.birthWeek = 0>>
-			<<if $seeAge == 1>>
-				<<set $traitor.physicalAge += 1, $traitor.actualAge += 1, $traitor.visualAge += 1>>
-				<<set $traitor.ovaryAge += either(.8, .9, .9, 1, 1, 1, 1.1)>>
-				<<if $traitor.physicalAge <= 18 && $loliGrow > 0>>
-					<<run physicalDevelopment($traitor)>>
-				<</if>>
-			<</if>>
+			<<run ageSlave($traitor)>>
 		<</if>>
 		<<set _weeks-->>
 	<</for>>
diff --git a/src/uncategorized/reBoomerang.tw b/src/uncategorized/reBoomerang.tw
index e19a7b4ecd666551d517c096db639b7a84fa4480..a8c1d886d6889311062195e4380dbed240c57d51 100644
--- a/src/uncategorized/reBoomerang.tw
+++ b/src/uncategorized/reBoomerang.tw
@@ -51,13 +51,7 @@ brings up the relevant feeds. There's a naked body crumpled pathetically against
 		<<set $activeSlave.birthWeek++>>
 		<<if $activeSlave.birthWeek >= 52>>
 			<<set $activeSlave.birthWeek = 0>>
-			<<if $seeAge == 1>>
-				<<set $activeSlave.physicalAge += 1, $activeSlave.actualAge += 1, $activeSlave.visualAge += 1>>
-				<<set $activeSlave.ovaryAge += either(.8, .9, .9, 1, 1, 1, 1.1)>>
-				<<if $activeSlave.physicalAge <= 18 && $loliGrow > 0>>
-					<<run physicalDevelopment($activeSlave)>>
-				<</if>>
-			<</if>>
+			<<run ageSlave($activeSlave)>>
 		<</if>>
 		<<set _weeks-->>
 	<</for>>
diff --git a/src/uncategorized/reFSEgyptianRevivalistAcquisition.tw b/src/uncategorized/reFSEgyptianRevivalistAcquisition.tw
index 45405d5a43fbedff34f11ca0b79b6226938400db..68e1c672f553cd45f43d806e8e712df1c76a4daf 100644
--- a/src/uncategorized/reFSEgyptianRevivalistAcquisition.tw
+++ b/src/uncategorized/reFSEgyptianRevivalistAcquisition.tw
@@ -25,7 +25,7 @@
 <<if ($activeSlave.dick > 0) && ($activeSlave.balls == 0)>><<set $activeSlave.balls = random(1,5)>><</if>>
 
 <<set _oppositeSex = $seeDicks > 0 && $seeDicks < 100 && (random(1, 4) <= 3)>>
-<<set _secondSlave = generateRelatedSlave($activeSlave, "sibling", _oppositeSex)>>
+<<set _secondSlave = generateRelatedSlave($activeSlave, "younger sibling", _oppositeSex)>>
 
 <<set _secondSlave.relationship = 4>>
 <<set _secondSlave.relationshipTarget = $activeSlave.ID>>