diff --git a/js/003-data/gameVariableData.js b/js/003-data/gameVariableData.js
index 2db7bf14ea496f33d67714dbfd44a9503f5724fa..0e20f0d364566ec3db6130a6b7a6467d19c2b0ce 100644
--- a/js/003-data/gameVariableData.js
+++ b/js/003-data/gameVariableData.js
@@ -371,6 +371,7 @@ App.Data.resetOnNGPlus = {
 	targetArcology: {fs: "New"},
 
 	plot: 1,
+	plotEventWeek: 0,
 	assignmentRecords: {},
 	marrying: [], // array of slave being married this week
 	organs: [],
diff --git a/src/002-config/fc-version.js b/src/002-config/fc-version.js
index bd99c90adf1abc94af343bc918c9ad4716973123..fac73455af3908898aee320c87ad7a7774e12d4a 100644
--- a/src/002-config/fc-version.js
+++ b/src/002-config/fc-version.js
@@ -2,5 +2,5 @@ App.Version = {
 	base: "0.10.7.1", // The vanilla version the mod is based off of, this should never be changed.
 	pmod: "3.9.6",
 	commitHash: null,
-	release: 1123 // When getting close to 2000,  please remove the check located within the onLoad() function defined at line five of src/js/eventHandlers.js.
+	release: 1124 // When getting close to 2000,  please remove the check located within the onLoad() function defined at line five of src/js/eventHandlers.js.
 };
diff --git a/src/data/backwardsCompatibility/backwardsCompatibility.js b/src/data/backwardsCompatibility/backwardsCompatibility.js
index f0f3d216e2d672b279b06c5699213975fea7e64f..b27815d8936356dc480ddac58f22ba6cd49823e7 100644
--- a/src/data/backwardsCompatibility/backwardsCompatibility.js
+++ b/src/data/backwardsCompatibility/backwardsCompatibility.js
@@ -2238,6 +2238,10 @@ App.Update.oldVersions = function(node) {
 	if (V.releaseID <= 1116 && V.pregnancyMonitoringUpgrade === 3) {
 		V.pregnancyMonitoringUpgrade = 1;
 	}
+	// assume we're caught up on plot events if we were running the old plot event system
+	if (V.releaseID <= 1123) {
+		V.plotEventWeek = App.Events.effectiveWeek();
+	}
 
 	node.append(`Done!`);
 };
diff --git a/src/events/nonRandom/arcologyNaming.js b/src/events/nonRandom/arcologyNaming.js
index bb078d002205531e325899f91b0450343ca38e27..18f2734818ce0a00b958c4e4bada3c1d49071877 100644
--- a/src/events/nonRandom/arcologyNaming.js
+++ b/src/events/nonRandom/arcologyNaming.js
@@ -5,9 +5,7 @@ App.Events.PArcologyNaming = class PArcologyNaming extends App.Events.BaseEvent
 
 	eventPrerequisites() {
 		return [
-			() => App.Events.effectiveWeek() === 4,
 			() => V.arcologies[0].name.indexOf("Arcology ") !== -1,
-			() => V.plotEventWeek !== 4
 		];
 	}
 
@@ -17,7 +15,6 @@ App.Events.PArcologyNaming = class PArcologyNaming extends App.Events.BaseEvent
 
 	execute(node) {
 		let r = [];
-		V.plotEventWeek = 4;
 		r.push(`As a society free of the encumbrance of governmental oversight, the arcologies of the Free Cities are places where societal evolution and corporate expansion can occur rapidly. Even so, the incredible speed with which the arcology has improved under your tenure as compared to that of your predecessor, after you obtained ownership through`);
 		if (V.PC.rumor === "wealth") {
 			r.push(`a leveraged buyout,`);
diff --git a/src/events/nonRandom/daughters/pBombing.tw b/src/events/nonRandom/daughters/pBombing.tw
index 85e5ed231cc884f7743a98eb69569911f2444c2a..79379414173c03e6eaebcf745aeaa3e97cf433ed 100644
--- a/src/events/nonRandom/daughters/pBombing.tw
+++ b/src/events/nonRandom/daughters/pBombing.tw
@@ -1,7 +1,6 @@
 :: P bombing [nobr]
 
 <<set $nextButton = "Continue">>
-<<set $nextLink = "Random Nonindividual Event">>
 
 <<if _S.Bodyguard>>
 	<<setLocalPronouns _S.Bodyguard>>
diff --git a/src/events/nonRandom/daughters/pCollaborationChoice.tw b/src/events/nonRandom/daughters/pCollaborationChoice.tw
index 6018eb635b955a75cca2d77bdfcc8b89ae7a955a..2f23d92e3c62891212cf6af4f69a9afd0f05910e 100644
--- a/src/events/nonRandom/daughters/pCollaborationChoice.tw
+++ b/src/events/nonRandom/daughters/pCollaborationChoice.tw
@@ -1,6 +1,6 @@
 :: P collaboration choice [nobr]
 
-<<set $nextButton = "Continue", $nextLink = "Random Nonindividual Event">>
+<<set $nextButton = "Continue">>
 
 <<setLocalPronouns $traitor>>
 <<run Enunciate($traitor)>>
diff --git a/src/events/nonRandom/daughters/pCoupAftermath.tw b/src/events/nonRandom/daughters/pCoupAftermath.tw
index 3d1d3baabbd79c5488faf6f46a3337eeb0692447..9e3f8c86d2248d0955867f82b3f55fb0450bb8dc 100644
--- a/src/events/nonRandom/daughters/pCoupAftermath.tw
+++ b/src/events/nonRandom/daughters/pCoupAftermath.tw
@@ -1,6 +1,6 @@
 :: P coup aftermath [nobr]
 
-<<set $nextButton = " ", $nextLink = "Random Nonindividual Event", $rivalOwner = 0, $rivalryPower = 0, _num = random(0,99)>> /* hide button until user makes a selection */
+<<set $nextButton = " ", $rivalOwner = 0, $rivalryPower = 0, _num = random(0,99)>> /* hide button until user makes a selection */
 <<if _num <= $seeDicks>>
 	<<set $rivalGender = 2>>
 <<else>>
diff --git a/src/events/nonRandom/daughters/pCoupAttempt.tw b/src/events/nonRandom/daughters/pCoupAttempt.tw
index 59092130f434d707e75faf534e92db0eca07522a..9f6ee5026521a3eb20f58622362ae5bece70c290 100644
--- a/src/events/nonRandom/daughters/pCoupAttempt.tw
+++ b/src/events/nonRandom/daughters/pCoupAttempt.tw
@@ -1,6 +1,6 @@
 :: P coup attempt [nobr]
 
-<<set $nextButton = "Continue", $nextLink = "Random Nonindividual Event", $daughtersVictory = 1>>
+<<set $nextButton = "Continue", $daughtersVictory = 1>>
 
 <<if $traitor != 0>>
 	<<set _weeks = $traitorWeeks-1, _pregWeeks = $traitorWeeks-1, $traitorWeeks = 0>>
diff --git a/src/events/nonRandom/daughters/pCoupBetrayal.tw b/src/events/nonRandom/daughters/pCoupBetrayal.tw
index b909a046674801b0cfb16ad20a25793cc28f1567..71b41cac5bc440b8f7f40ab95f68d22d0e182d80 100644
--- a/src/events/nonRandom/daughters/pCoupBetrayal.tw
+++ b/src/events/nonRandom/daughters/pCoupBetrayal.tw
@@ -1,6 +1,6 @@
 :: P coup betrayal [nobr]
 
-<<set $nextButton = "Continue", $nextLink = "Random Nonindividual Event", $daughtersVictory = 1>>
+<<set $nextButton = "Continue", $daughtersVictory = 1>>
 
 <<set _weeks = $traitorWeeks-1, _pregWeeks = $traitorWeeks-1, $traitorWeeks = 0>>
 
diff --git a/src/events/nonRandom/daughters/pHackerSupport.tw b/src/events/nonRandom/daughters/pHackerSupport.tw
index 86ad08e8016f807633073540782d6be045ce913f..3a368ef36f2ace02cbfc6fdb3243c2d3887a9ebd 100644
--- a/src/events/nonRandom/daughters/pHackerSupport.tw
+++ b/src/events/nonRandom/daughters/pHackerSupport.tw
@@ -1,6 +1,6 @@
 :: P hacker support [nobr]
 
-<<set $nextButton = "End Call", $nextLink = "Random Nonindividual Event", $hackerSupport = 0>>
+<<set $nextButton = "End Call", $hackerSupport = 0>>
 
 <<setAssistantPronouns>>
 <<set $fcnn.push("...the Daughters of Liberty as 'cyberterrorists', after an attempted...")>>
diff --git a/src/events/nonRandom/daughters/pTraitorMessage.tw b/src/events/nonRandom/daughters/pTraitorMessage.tw
index f4cb04d78a0871e2db0b990bdff09132690d107d..c3b72d9864a87f81021561a3bcd75802080cc302 100644
--- a/src/events/nonRandom/daughters/pTraitorMessage.tw
+++ b/src/events/nonRandom/daughters/pTraitorMessage.tw
@@ -1,6 +1,6 @@
 :: P traitor message [nobr]
 
-<<set $nextButton = "Continue", $nextLink = "Random Nonindividual Event">>
+<<set $nextButton = "Continue">>
 <<set _weeks = $traitorWeeks-1, _pregWeeks = $traitorWeeks-1, $traitorWeeks = 1>>
 <<setLocalPronouns $traitor>>
 
diff --git a/src/events/nonRandom/daughters/pUndergroundRailroad.tw b/src/events/nonRandom/daughters/pUndergroundRailroad.tw
index adab61e47cab1fade2ee2ae332d6f41eb5ff11ac..49e8c7df6b7f87fba207226c13e4b9a01f122073 100644
--- a/src/events/nonRandom/daughters/pUndergroundRailroad.tw
+++ b/src/events/nonRandom/daughters/pUndergroundRailroad.tw
@@ -1,6 +1,9 @@
 :: P underground railroad [nobr]
 
-<<set $nextButton = " ", $nextLink = "Random Nonindividual Event">> /* hide button until user makes a selection */
+<<set $nextButton = " ">> /* hide button until user makes a selection */
+<<set $returnTo = $nextLink>> /* we might have to call AS Dump, so make sure it goes back to wherever we were going next */
+
+<<set $collaboration = 0, $traitor = 0, $hackerSupport = 0>>
 
 <<set $activeSlave = $slaves.filter(function(s) { return s.fuckdoll == 0 && s.fetish != "mindbroken" && canWalk(s) && s.devotion < 75 && s.trust < 75 && s.indenture == -1 && canWalk(s) && canTalk(s) && canSee(s) && ["serve in the club", "serve the public", "whore", "work in the brothel"].includes(s.assignment); }).random()>>
 <<if (ndef $activeSlave)>> /* search again without assignments limitation */
@@ -190,7 +193,7 @@ This is disturbing, to say the least. After close investigation, it appears some
 		<</replace>>
 	<</link>>
 	<br><<link "Tell $him to ignore the Daughters in the future">>
-		<<set $nextButton = "Continue", $nextLink = "AS Dump", $returnTo = "Random Nonindividual Event">><<run App.Utils.updateUserButton()>> /* unlock Continue button */
+		<<set $nextButton = "Continue">><<run App.Utils.updateUserButton()>> /* unlock Continue button */
 		<<replace "#result">>
 		$activeSlave.slaveName nods $his head in acceptance. There is too much at stake to take such a risk. All attempts to use your monitoring systems to find the citizens who contacted $him fail; it seems their ability to corrupt your systems is considerable. $He keeps $his lips shut tight in an effort to prevent rumors from spreading, but @@.mediumorchid;whispers of freedom@@ still manage to run through your chattel.
 		<<run $slaves.forEach(function(s) { s.devotion -= 5; })>>
@@ -324,7 +327,7 @@ This is disturbing, to say the least. After close investigation, it appears some
 		<<run removeSlave($activeSlave)>>
 	<</link>>
 	<br><<link "Tell $him to ignore the Daughters in the future">>
-		<<set $nextButton = "Continue", $nextLink = "AS Dump", $returnTo = "Random Nonindividual Event">><<run App.Utils.updateUserButton()>> /* unlock Continue button */
+		<<set $nextButton = "Continue", $nextLink = "AS Dump">><<run App.Utils.updateUserButton()>> /* unlock Continue button */
 		<<replace "#result">>
 		<<if $traitorType != "defiant">>
 			$activeSlave.slaveName almost @@.mediumorchid;faints with relief@@ when it becomes apparent you aren't going to punish $him for speaking of freedom.
@@ -337,7 +340,7 @@ This is disturbing, to say the least. After close investigation, it appears some
 		<</replace>>
 	<</link>>
 	<br><<link "Publicly flog $him for treasonous conversation">>
-		<<set $nextButton = "Continue", $nextLink = "AS Dump", $returnTo = "Random Nonindividual Event">><<run App.Utils.updateUserButton()>> /* unlock Continue button */
+		<<set $nextButton = "Continue", $nextLink = "AS Dump">><<run App.Utils.updateUserButton()>> /* unlock Continue button */
 		<<replace "#result">>
 		You announce that a treasonous slave will be publicly flogged. Treason is understood in the Free Cities to be activity that tends to undermine slavery, and public interest is considerable when $activeSlave.slaveName is dragged out into a public atrium and secured to a post. You do your duty; the one that passes the sentence should swing the lash. The ordeal is long and bloody. The populace understand the necessity of the punishment, though they are @@.red;disturbed@@ that such a thing could happen in your penthouse of all places. The effect on $activeSlave.slaveName's health @@.health.dec;is serious,@@ and $he is @@.gold;terrified of failing you again.@@
 		<<set $activeSlave.trust -= 15>>
@@ -345,7 +348,7 @@ This is disturbing, to say the least. After close investigation, it appears some
 		<</replace>>
 	<</link>>
 	<br><<link "Chemically lobotomize $him to extirpate any record of the incident">>
-		<<set $nextButton = "Continue", $nextLink = "AS Dump", $returnTo = "Random Nonindividual Event">><<run App.Utils.updateUserButton()>> /* unlock Continue button */
+		<<set $nextButton = "Continue", $nextLink = "AS Dump">><<run App.Utils.updateUserButton()>> /* unlock Continue button */
 		<<replace "#result">>
 		You inform $activeSlave.slaveName that $he is to return to $his duties, which $he does, mystified. Later in the day $he finds $himself scheduled for surgery, and meekly goes. As $he feels the cool touch of the injector, loaded with powerful chemical lobotomizing agents, $he looks up at the camera above the autosurgery with a hint of <<if $traitorType != "defiant">>resigned apology<<else>>regret<</if>> in $his eyes. When $he is released from the surgery's care a few hours later, the hint is gone. @@.red;As is everything.@@
 		<<set $activeSlave.fetish = "mindbroken">>
@@ -353,7 +356,7 @@ This is disturbing, to say the least. After close investigation, it appears some
 	<</link>>
 	<<if $arcologies[0].FSPaternalist == "unset" && $seeExtreme == 1>>
 		<br><<link "Publicly execute $him">>
-			<<set $nextButton = "Continue", $returnTo = "Random Nonindividual Event">><<run App.Utils.updateUserButton()>> /* unlock Continue button */
+			<<set $nextButton = "Continue">><<run App.Utils.updateUserButton()>> /* unlock Continue button */
 			<<replace "#result">>
 			You announce that a treasonous slave will be publicly executed. Treason is understood in the Free Cities to be activity that tends to undermine slavery, and public interest is considerable when $activeSlave.slaveName is dragged out into a public atrium and <<if $arcologies[0].FSAztecRevivalist !== "unset">>has $his heart cut out of $his living body<<elseif $arcologies[0].FSEdoRevivalist !== "unset">>is boiled alive<<elseif $arcologies[0].FSRomanRevivalist !== "unset">>is crucified and left hanging until $he perishes<<else>>is summarily hanged<</if>>. The populace understands the necessity of the punishment, though they are @@.red;disturbed@@ that such a thing could happen in your penthouse of all places. The surviving slaves are @@.gold;terrified@@ at the display, but at least you can be sure they will remember the price of failing you.
 			<<run repX(-500, "event", $activeSlave)>>
diff --git a/src/events/nonRandom/mercs/pCitizensAndCivilians.tw b/src/events/nonRandom/mercs/pCitizensAndCivilians.tw
index b5018beaeaede8d62c0906b2fe61864f29a02e4f..f2ee9db5c0532ae810c8186091db8bb3c8d15318 100644
--- a/src/events/nonRandom/mercs/pCitizensAndCivilians.tw
+++ b/src/events/nonRandom/mercs/pCitizensAndCivilians.tw
@@ -7,7 +7,7 @@
 <</if>>
 <<setAssistantPronouns>>
 
-<<set $nextButton = "Continue", $nextLink = "Random Nonindividual Event">>
+<<set $nextButton = "Continue">>
 
 <<if $assistant.personality > 0>>
 	While working at your desk, you are accompanied by the luscious sound of $assistant.name humming to _himselfA, which _heA does to indicate _heA's working on a difficult task. After this goes on for a while,
diff --git a/src/events/nonRandom/mercs/pMercenaries.tw b/src/events/nonRandom/mercs/pMercenaries.tw
index c00dcffc947e5a641f1ab9772ff2a2885e827a48..93733b26a4cc460fa021bca2e21ca0524ac82771 100644
--- a/src/events/nonRandom/mercs/pMercenaries.tw
+++ b/src/events/nonRandom/mercs/pMercenaries.tw
@@ -1,6 +1,6 @@
 :: P mercenaries [nobr]
 
-<<set $nextButton = "Continue", $nextLink = "Random Nonindividual Event", $mercenaries = 0, $mercenariesTitle = "mercenaries">>
+<<set $nextButton = "Continue", $mercenaries = 0, $mercenariesTitle = "mercenaries">>
 
 <<if ($PC.skill.warfare >= 100)>>
 	<<set _price = 2500>>
diff --git a/src/events/nonRandom/mercs/pRaidInvitation.tw b/src/events/nonRandom/mercs/pRaidInvitation.tw
index 4af7af869814aa586db997d1e45e06dc0ff41da5..df14811aef9d73760dc997a0916f3f9531225c99 100644
--- a/src/events/nonRandom/mercs/pRaidInvitation.tw
+++ b/src/events/nonRandom/mercs/pRaidInvitation.tw
@@ -1,6 +1,6 @@
 :: P raid invitation [nobr]
 
-<<set $nextButton = "Continue", $nextLink = "Random Nonindividual Event">>
+<<set $nextButton = "Continue">>
 
 <<set $eventResults.raid = 0, $eventResults.raidTarget = 0>>
 
diff --git a/src/events/nonRandom/mercs/pSnatchAndGrab.tw b/src/events/nonRandom/mercs/pSnatchAndGrab.tw
index 4e9bb2b484fdce8efa0725626e3ee8c23bab070b..df7f2b0f84146748d5981b2d0b5704f19a0343db 100644
--- a/src/events/nonRandom/mercs/pSnatchAndGrab.tw
+++ b/src/events/nonRandom/mercs/pSnatchAndGrab.tw
@@ -1,7 +1,6 @@
 :: P snatch and grab [nobr]
 
 <<set $nextButton = "Continue">>
-<<set $nextLink = "Random Nonindividual Event">>
 
 <<set $eventResults.snatch = 0>>
 <<setAssistantPronouns>>
diff --git a/src/events/nonRandom/militia.js b/src/events/nonRandom/militia.js
index c2334216904d9fb19c4fbc9293f8bf456e60596d..a44f4ecb2c0609d0c03d118a5623c7bb60702091 100644
--- a/src/events/nonRandom/militia.js
+++ b/src/events/nonRandom/militia.js
@@ -3,19 +3,11 @@ App.Events.PMilitia = class PMilitia extends App.Events.BaseEvent {
 		super(actors, params);
 	}
 
-	eventPrerequisites() {
-		return [
-			() => App.Events.effectiveWeek() === 24,
-			() => V.plotEventWeek !== 24
-		];
-	}
-
 	get eventName() {
 		return "Militia";
 	}
 
 	execute(node) {
-		V.plotEventWeek = 24;
 		let r = [];
 		const dronesTooCost = 5000;
 		const selfAloneCost = 2000;
@@ -41,37 +33,31 @@ App.Events.PMilitia = class PMilitia extends App.Events.BaseEvent {
 		App.Events.addResponses(node, responses);
 
 		function armDrones() {
-			const el = new DocumentFragment();
 			let r = [];
 			r.push(`In a time of uncertainty, the public adores people who protect them. So, in addition to publicly procuring yourself the latest weapons and armor, you update the arcology's drone systems. The security drones' riot cannons can be replaced for easy maintenance, so it's rather easy to provide them with alternate, lethal weaponry that they can switch to if it becomes necessary. ${V.arcologies[0].name} becomes known as one of the best-protected in the Free Cities. <span class="green">Your reputation has greatly improved.</span>`);
 			unlock();
 			repX(4000, "event");
 			cashX(-dronesTooCost, "event");
 			V.personalArms = 3;
-			App.Events.addParagraph(node, r);
-			return el;
+			return r;
 		}
 
 		function armOnlySelf() {
-			const el = new DocumentFragment();
 			let r = [];
 			r.push(`In a time of uncertainty, the public looks up to people who project strength. So, you purchase yourself some of the latest armor and weapons, and make sure they are visible in a glass-walled cabinet in your office. Many of your wealthier tenants follow suit. A few of them even emulate your example and practice using these implements once a week. <span class="green">Your reputation has improved.</span>`);
 			unlock();
 			repX(1500, "event");
 			cashX(-selfAloneCost, "event");
 			V.personalArms = 1;
-			App.Events.addParagraph(node, r);
-			return el;
+			return r;
 		}
 
 		function ignore() {
-			const el = new DocumentFragment();
 			let r = [];
 			r.push(`No doubt this panicky fad will pass. You ignore the controversy. In a few days, the subject of a militia passes from the public mind.`);
 			unlock();
 			V.personalArms = 0;
-			App.Events.addParagraph(node, r);
-			return el;
+			return r;
 		}
 
 		function unlock() {
diff --git a/src/events/nonRandom/pDefenseFears.tw b/src/events/nonRandom/pDefenseFears.tw
index b5cb4572216b7364019e4d19fa49b18c042954da..c27a1edb7102ca7e771046e41e551565b6fc90de 100644
--- a/src/events/nonRandom/pDefenseFears.tw
+++ b/src/events/nonRandom/pDefenseFears.tw
@@ -1,6 +1,6 @@
 :: P defense fears [nobr]
 
-<<set $nextButton = " ", $nextLink = "Random Nonindividual Event">> /* hide button until user makes a selection */
+<<set $nextButton = " ">> /* hide button until user makes a selection */
 <<set $fcnn.push("...hiring mercenaries to act as a defensive force against the old world...")>>
 
 A deputation of slaveowning citizens comes to see you. Though they haven't experienced anything so disturbing as your dealings with the Daughters of Liberty, rumors of unrest and revolution are running through the Free Cities. They are upset with the lack of troops to protect the arcology. This is quite a development in the young history of Free Cities society; only a few months ago, the idea of collective defense would have been a bitterly controversial one. It's still an employer's market for mercenaries; you could easily hire some. Alternatively, your citizens would probably agree to fund them by subscription — the word "taxes" would be impolitic.
diff --git a/src/events/nonRandom/pInvasion.tw b/src/events/nonRandom/pInvasion.tw
index c7bf19ce67a3ee8dc26e45ec14d92f15c9525e39..d2d31484a55a7d7d7604e2fad5ba24bb3f89912c 100644
--- a/src/events/nonRandom/pInvasion.tw
+++ b/src/events/nonRandom/pInvasion.tw
@@ -1,6 +1,6 @@
 :: P invasion [nobr]
 
-<<set $nextButton = "Continue", $nextLink = "Random Nonindividual Event">>
+<<set $nextButton = "Continue">>
 
 <<set $invasionVictory = 1, $peacekeepers = 0>>
 <<setAssistantPronouns>>
diff --git a/src/events/nonRandom/shootInvitation.js b/src/events/nonRandom/shootInvitation.js
index df329419379d8fe61e15979e9e8a2b79c03b7df2..0fea7a533a67470e81e1dc1c07e58dea6b86efc4 100644
--- a/src/events/nonRandom/shootInvitation.js
+++ b/src/events/nonRandom/shootInvitation.js
@@ -3,19 +3,11 @@ App.Events.PShootInvitation = class PShootInvitation extends App.Events.BaseEven
 		super(actors, params);
 	}
 
-	eventPrerequisites() {
-		return [
-			() => App.Events.effectiveWeek() === 17,
-			() => V.plotEventWeek !== 17
-		];
-	}
-
 	get eventName() {
 		return "Shoot Invitation";
 	}
 
 	execute(node) {
-		V.plotEventWeek = 17;
 		let r = [];
 		r.push(`${capFirstChar(V.assistant.name)} usually delays message delivery when you're relieving your sexual needs with your property, but messages from other arcology owners have a special priority. This one is a brief but rather well-spoken audio invitation from one of your more notoriously wealthy peers.`);
 		App.Events.addParagraph(node, r);
diff --git a/src/events/nonRandom/slaveFood.js b/src/events/nonRandom/slaveFood.js
index e31fa22eb014ecd1dd018209906e7fe7ea8073ed..5ec7dd08f41c710a23c3400f76bbd7b0557847d9 100644
--- a/src/events/nonRandom/slaveFood.js
+++ b/src/events/nonRandom/slaveFood.js
@@ -3,19 +3,11 @@ App.Events.PSlaveFood = class PSlaveFood extends App.Events.BaseEvent {
 		super(actors, params);
 	}
 
-	eventPrerequisites() {
-		return [
-			() => App.Events.effectiveWeek() === 20,
-			() => V.plotEventWeek !== 20
-		];
-	}
-
 	get eventName() {
 		return "Slave Food";
 	}
 
 	execute(node) {
-		V.plotEventWeek = 20;
 		let r = [];
 		const {
 			HeA, HisA,
diff --git a/src/events/nonRandom/stripClubAftermath.js b/src/events/nonRandom/stripClubAftermath.js
index f7c6d904cf53456ee19b2da8a917724163144d1f..6e4e8db67274a3b767fb086c768c11f46d3d06bb 100644
--- a/src/events/nonRandom/stripClubAftermath.js
+++ b/src/events/nonRandom/stripClubAftermath.js
@@ -3,19 +3,11 @@ App.Events.PStripClubAftermath = class PStripClubAftermath extends App.Events.Ba
 		super(actors, params);
 	}
 
-	eventPrerequisites() {
-		return [
-			() => App.Events.effectiveWeek() === 8,
-			() => V.plotEventWeek !== 8
-		];
-	}
-
 	get eventName() {
 		return "Strip Club Aftermath";
 	}
 
 	execute(node) {
-		V.plotEventWeek = 8;
 		let r = [];
 		let contractCost = 1000;
 		let cost;
diff --git a/src/events/nonRandom/stripClubClosing.js b/src/events/nonRandom/stripClubClosing.js
index 88ae831bd67243b4bf48752ba350f0801b9bfa44..59171e5b467008a4bfe0e752fbda8c667289d7eb 100644
--- a/src/events/nonRandom/stripClubClosing.js
+++ b/src/events/nonRandom/stripClubClosing.js
@@ -3,19 +3,11 @@ App.Events.PStripClubClosing = class PStripClubClosing extends App.Events.BaseEv
 		super(actors, params);
 	}
 
-	eventPrerequisites() {
-		return [
-			() => App.Events.effectiveWeek() === 6,
-			() => V.plotEventWeek !== 6
-		];
-	}
-
 	get eventName() {
 		return "Strip Club Closing";
 	}
 
 	execute(node) {
-		V.plotEventWeek = 6;
 		let r = [];
 		V.eventResults.strip = 1;
 		r.push(`It's been a good few weeks, getting settled in as owner of ${V.arcologies[0].name}. The power of being overlord of this great building and everyone in it is incredible, but so is the responsibility. It's a good thing you have ample opportunities for stress relief. You're going to need them after today. There's a nasty disturbance on a business level of the arcology. Normally, the arcology's public safety drones would suppress this kind of nonsense, but ${V.assistant.name} program is suggesting that you resolve the dispute.`);
diff --git a/src/events/nonRandomEvent.js b/src/events/nonRandomEvent.js
index 49157292c9e2d40f79ba792f574998cbcb9584e0..be4b3d37cc3e2df59036c06948ab7bc1e56cb8bc 100644
--- a/src/events/nonRandomEvent.js
+++ b/src/events/nonRandomEvent.js
@@ -45,6 +45,7 @@ App.Events.getNonrandomEvents = function() {
 		new App.Events.SEDeath(),
 		new App.Events.SEBirth(),
 		new App.Events.SEfctv(),
+		new App.Events.TimeGatedPlotEvent(),
 		new App.Events.assistantAwakens(),
 		new App.Events.assistantSP(),
 		new App.Events.assistantFS(),
@@ -55,13 +56,7 @@ App.Events.getNonrandomEvents = function() {
 		new App.Events.pBadBreasts(),
 		new App.Events.pAidInvitation(),
 		new App.Events.pAidResult(),
-		new App.Events.PStripClubClosing(),
-		new App.Events.PStripClubAftermath(),
-		new App.Events.PMilitia(),
-		new App.Events.PShootInvitation(),
 		new App.Events.PShootResult(),
-		new App.Events.PArcologyNaming(),
-		new App.Events.PSlaveFood(),
 		new App.Events.TwineEvent().wrapPassage([
 			() => V.RecruiterID !== 0,
 			() => V.recruiterProgress >= (13 + (V.recruiterEugenics === 1 ? policies.countEugenicsSMRs() * 6 : 0))
@@ -237,13 +232,7 @@ globalThis.nonRandomEvent = function() {
 			V.assholeKnight = 1;
 			V.imperialEventWeek = effectiveWeek;
 			setTimeout(() => Engine.play("SE assholeknight"), Engine.minDomActionDelay);
-		} else if (effectiveWeek === 31 && V.mercenaries === 0) {
-			setTimeout(() => Engine.play("P mercenaries"), Engine.minDomActionDelay);
-		} else if (effectiveWeek === 35 && V.mercenaries > 0) {
-			setTimeout(() => Engine.play("P snatch and grab"), Engine.minDomActionDelay);
-		} else if (effectiveWeek === 43) {
-			setTimeout(() => Engine.play("P invasion"), Engine.minDomActionDelay);
-		} else if ((effectiveWeek === 44) && (V.mercenaries > 0) && V.mercRomeo !== 1) {
+		} else if ((effectiveWeek >= 44) && (V.mercenaries > 0) && V.mercRomeo !== 1) {
 			const valid = V.slaves.find(function(s) { return (["serve the public", "serve in the club", "whore", "work in the brothel"].includes(s.assignment) || s.counter.publicUse >= 50) && s.fetish !== "mindbroken" && s.fuckdoll === 0; });
 			V.mercRomeo = 1;
 			if (valid) {
@@ -251,8 +240,6 @@ globalThis.nonRandomEvent = function() {
 			} else {
 				setTimeout(() => Engine.play("Nonrandom Event"), Engine.minDomActionDelay);
 			}
-		} else if (effectiveWeek === 46 && V.mercenaries > 0) {
-			setTimeout(() => Engine.play("P raid invitation"), Engine.minDomActionDelay);
 		} else if (effectiveWeek === 48 && V.experimental.food === 1) {
 			V.foodCrisis = 1;
 			setTimeout(() => Engine.play("P food crisis"), Engine.minDomActionDelay);
@@ -261,39 +248,9 @@ globalThis.nonRandomEvent = function() {
 			setTimeout(() => Engine.play("P food crisis"), Engine.minDomActionDelay);
 		} else if (effectiveWeek === 54 && (V.peacekeepers) && V.peacekeepers.attitude >= 0) {
 			setTimeout(() => Engine.play("P peacekeepers deficit"), Engine.minDomActionDelay);
-		} else if (effectiveWeek === 56) {
-			V.collaboration = 0;
-			V.traitor = 0;
-			V.hackerSupport = 0;
-			setTimeout(() => Engine.play("P underground railroad"), Engine.minDomActionDelay);
-		} else if (effectiveWeek === 58 && V.traitor === 0) {
-			setTimeout(() => Engine.play("P bombing"), Engine.minDomActionDelay);
 		} else if (effectiveWeek === 60 && V.rations > 0) {
 			V.foodCrisis = 3;
 			setTimeout(() => Engine.play("P food crisis"), Engine.minDomActionDelay);
-		} else if (effectiveWeek === 61 && V.traitor !== 0) {
-			setTimeout(() => Engine.play("P traitor message"), Engine.minDomActionDelay);
-		} else if (effectiveWeek === 62 && V.mercenaries < 3) {
-			setTimeout(() => Engine.play("P defense fears"), Engine.minDomActionDelay);
-		} else if (effectiveWeek === 65 && V.mercenaries >= 3) {
-			setTimeout(() => Engine.play("P citizens and civilians"), Engine.minDomActionDelay);
-		} else if (effectiveWeek === 67 && V.traitor !== 0) {
-			setTimeout(() => Engine.play("P collaboration choice"), Engine.minDomActionDelay);
-		} else if (effectiveWeek === 69) {
-			setTimeout(() => Engine.play("P hacker support"), Engine.minDomActionDelay);
-		} else if (effectiveWeek === 70 && V.collaboration === 1 && V.traitorType !== "trapper") {
-			setTimeout(() => Engine.play("P coup collaboration"), Engine.minDomActionDelay);
-		} else if (effectiveWeek === 71) {
-			const doubleAgent = (V.traitorType !== "agent" && V.traitorType !== "trapper") ? 0 : 1;
-			if (V.traitorType === "trapper") {
-				setTimeout(() => Engine.play("P coup betrayal"), Engine.minDomActionDelay);
-			} else if (V.mercenaries + V.personalArms + V.hackerSupport + doubleAgent < 5) {
-				setTimeout(() => Engine.play("P coup loss"), Engine.minDomActionDelay);
-			} else {
-				setTimeout(() => Engine.play("P coup attempt"), Engine.minDomActionDelay);
-			}
-		} else if (effectiveWeek === 72) {
-			setTimeout(() => Engine.play("P coup aftermath"), Engine.minDomActionDelay);
 		} else if (V.SF.Toggle && V.SF.Active === -1 && effectiveWeek >= 72) {
 			setTimeout(() => Engine.play("Security Force Proposal"), Engine.minDomActionDelay);
 		} else if (V.arcologies[0].FSRestart !== "unset" && V.failedElite > 300 && V.eugenicsFullControl !== 1) {
diff --git a/src/events/timeGatedPlotEvent.js b/src/events/timeGatedPlotEvent.js
new file mode 100644
index 0000000000000000000000000000000000000000..86100e045204ab0fa3b2b728f9c483a2a45ee224
--- /dev/null
+++ b/src/events/timeGatedPlotEvent.js
@@ -0,0 +1,92 @@
+/** This event serves as a controller for the "main" plot event chain, which is designed to advance the plot based ONLY on the current (effective) week.
+ * These events don't have to keep their own gating code or results variables, because this chain controller tracks when they should be executed. */
+App.Events.TimeGatedPlotEvent = class TimeGatedPlotEvent extends App.Events.BaseEvent {
+	constructor(actors, params) {
+		super(actors, params);
+
+		// sparse array, indexed by effective week
+		// maybe mercs should be a separate chain?  Oh well, they're here for now.
+		this.events = [];
+		this.events[4] = new App.Events.PArcologyNaming();
+		this.events[6] = new App.Events.PStripClubClosing();
+		this.events[8] = new App.Events.PStripClubAftermath();
+		this.events[17] = new App.Events.PShootInvitation();
+		this.events[20] = new App.Events.PSlaveFood();
+		this.events[24] = new App.Events.PMilitia();
+		this.events[31] = new App.Events.TwineEvent().wrapPassage([
+			() => V.mercenaries === 0
+		], "P mercenaries");
+		this.events[35] = new App.Events.TwineEvent().wrapPassage([
+			() => V.mercenaries > 0
+		], "P snatch and grab");
+		this.events[43] = new App.Events.TwineEvent().wrapPassage([], "P invasion");
+		this.events[46] = new App.Events.TwineEvent().wrapPassage([
+			() => V.mercenaries > 0
+		], "P raid invitation");
+		this.events[54] = new App.Events.TwineEvent().wrapPassage([], "P underground railroad");
+		this.events[58] = new App.Events.TwineEvent().wrapPassage([
+			() => V.traitor === 0
+		], "P bombing");
+		this.events[61] = new App.Events.TwineEvent().wrapPassage([
+			() => V.traitor !== 0
+		], "P traitor message");
+		this.events[62] = new App.Events.TwineEvent().wrapPassage([
+			() => V.mercenaries < 3
+		], "P defense fears");
+		this.events[65] = new App.Events.TwineEvent().wrapPassage([
+			() => V.mercenaries >= 3
+		], "P citizens and civilians");
+		this.events[67] = new App.Events.TwineEvent().wrapPassage([
+			() => V.traitor !== 0
+		], "P collaboration choice");
+		this.events[69] = new App.Events.TwineEvent().wrapPassage([], "P hacker support");
+		this.events[70] = new App.Events.TwineEvent().wrapPassage([
+			() => V.collaboration === 1,
+			() => V.traitorType !== "trapper"
+		], "P coup collaboration");
+		// week 71 plot event is one of three possibilities, depending on game state
+		const doubleAgent = (V.traitorType !== "agent" && V.traitorType !== "trapper") ? 0 : 1;
+		if (V.traitorType === "trapper") {
+			this.events[71] = new App.Events.TwineEvent().wrapPassage([], "P coup betrayal");
+		} else if (V.mercenaries + V.personalArms + V.hackerSupport + doubleAgent < 5) {
+			this.events[71] = new App.Events.TwineEvent().wrapPassage([], "P coup loss");
+		} else {
+			this.events[71] = new App.Events.TwineEvent().wrapPassage([], "P coup attempt");
+		}
+		this.events[72] = new App.Events.TwineEvent().wrapPassage([], "P coup aftermath");
+
+		// events with complex prerequisites don't belong here...they're just normal scheduled events and need their own chain controller or result flags
+		for (let week = 0; week < this.events.length; ++week) {
+			const event = this.events[week];
+			if (event.actorPrerequisites().length > 0) {
+				throw new Error(`Time-gated plot events are not permitted to cast actors. Check event: ${event.eventName}`);
+			}
+			if (V.debugMode !== 0 && event.eventPrerequisites().length > 0) {
+				// sometimes this is intentional, but it could also be a mistake...unlike ordinary nonrandom events, events in the plot chain only get ONE chance to execute, so you have to be very careful with prereqs
+				console.log(`Time-gated plot event ${event.eventName} imposes prerequisites and WILL BE SKIPPED ENTIRELY if the prerequisites are not met on week ${week}!`);
+			}
+		}
+	}
+
+	eventPrerequisites() {
+		if (V.plot === 1) {
+			const effectiveWeek = App.Events.effectiveWeek();
+			// choose the earliest event that we need to run, within the window between the last event we ran and the current week, and run it
+			// this means that effectiveWeek can jump ahead and we still won't skip any events...we'll just run whatever was missed until we catch up
+			// but we also won't ever repeat any events, or play any new ones that should have occurred before the last event we played
+			for (let week = V.plotEventWeek + 1; week <= effectiveWeek; ++week) {
+				if (this.events[week]) {
+					this.params.event = this.events[week];
+					this.params.week = week;
+					break;
+				}
+			}
+		}
+		return [() => !!this.params.event];
+	}
+
+	execute(node) {
+		V.plotEventWeek = this.params.eventWeek;
+		this.params.event.execute(node);
+	}
+};
diff --git a/src/uncategorized/seNicaeaAnnouncement.tw b/src/uncategorized/seNicaeaAnnouncement.tw
index 4a4199989bfd6bbfc976e2aced419ac9e59ca795..530a0a29f42399e26b971048b660fe537689f09b 100644
--- a/src/uncategorized/seNicaeaAnnouncement.tw
+++ b/src/uncategorized/seNicaeaAnnouncement.tw
@@ -1,6 +1,5 @@
 :: SE nicaea announcement [nobr]
 
-<<if $plot == 1>><<set $nextLink = "Nonrandom Event">><<else>><<set $nextLink = "Random Nonindividual Event">><</if>>
 <<set $nextButton = "Continue">>
 <<set $nicaea.announced = 1, $nicaea.preparation = 1, $nicaea.involvement = 0, $nicaea.power = 1, $nicaea.eventWeek = $week>>
 <<set $nicaea.name = "Council of " + App.Data.ArcologyNames.ChattelReligionist.random()>>
diff --git a/src/uncategorized/seNicaeaPreperation.tw b/src/uncategorized/seNicaeaPreperation.tw
index aff6a5af9b0bd7522471d3a6ac35826764180b86..4f726f4b279e64e6bd2507eb0951d59d501e54d1 100644
--- a/src/uncategorized/seNicaeaPreperation.tw
+++ b/src/uncategorized/seNicaeaPreperation.tw
@@ -1,6 +1,5 @@
 :: SE nicaea preparation [nobr]
 
-<<if $plot == 1>><<set $nextLink = "Nonrandom Event">><<else>><<set $nextLink = "Random Nonindividual Event">><</if>>
 <<set $nextButton = "Continue">>
 <<set $nicaea.preparation = 0, $nicaea.eventWeek = $week>>