diff --git a/src/pregmod/FCTV/FCTV.js b/src/pregmod/FCTV/FCTV.js
index 729a102468cd30e9dbed44bc1a15037122a24fd5..2e084803adea618f17660346ba1ec6035388f329 100644
--- a/src/pregmod/FCTV/FCTV.js
+++ b/src/pregmod/FCTV/FCTV.js
@@ -26,12 +26,12 @@ globalThis.FCTV = (function() {
 		FinalTouches: FinalTouches,
 	};
 
-	/** Produce an array of numbers representing all available channels in App.Data.FCTV.shows
+	/** Produce an array of numbers representing all available channels in App.Data.FCTV.channels
 	 * @returns {Array}
 	 */
 	function channels() {
 		const array = [];
-		for (const channel in App.Data.FCTV.shows) {
+		for (const channel in App.Data.FCTV.channels) {
 			array.push(parseInt(channel, 10));
 		}
 		return array;
@@ -49,9 +49,9 @@ globalThis.FCTV = (function() {
 			canSelect: 1,
 			text: `A notification is shown: `
 		};
-		if (App.Data.FCTV.shows.hasOwnProperty(i)) {
-			if (App.Data.FCTV.shows[i].hasOwnProperty("tags")) {
-				for (const tag of App.Data.FCTV.shows[i].tags) {
+		if (App.Data.FCTV.channels.hasOwnProperty(i)) {
+			if (App.Data.FCTV.channels[i].hasOwnProperty("tags")) {
+				for (const tag of App.Data.FCTV.channels[i].tags) {
 					switch (tag) {
 						case "hyperPreg":
 							if (!V.seeHyperPreg) {
@@ -88,7 +88,7 @@ globalThis.FCTV = (function() {
 					}
 				}
 			}
-			if (App.Data.FCTV.shows[i].hasOwnProperty("disableSelection")) {
+			if (App.Data.FCTV.channels[i].hasOwnProperty("disableSelection")) {
 				x.canSelect = -1; x.text += `<i>This channel appears at random times</i>`;
 			}
 		}
@@ -153,14 +153,14 @@ globalThis.FCTV = (function() {
 	function showRange(i, operation = 'rand') {
 		// check the tags.
 		/*
-		for (const episode of App.Data.FCTV.shows[i].episode) {
-			App.Data.FCTV.shows[i].episode[episode].hasOwnProperty("tags") {
+		for (const episode of App.Data.FCTV.channels[i].episode) {
+			App.Data.FCTV.channels[i].episode[episode].hasOwnProperty("tags") {
 
 			}
 			if
 		}
 		*/
-		const epLength = App.Data.FCTV.shows[i].episode.length;
+		const epLength = App.Data.FCTV.channels[i].episode.length;
 
 		let low = 0;
 		let max = epLength;
@@ -320,7 +320,7 @@ globalThis.FctvDisplay = function({usedRemote = 0, seeAll = 0} = {}) {
 						buttons.push(span);
 					}
 				} else {
-					if (App.Data.FCTV.shows[i].disableSelection) {
+					if (App.Data.FCTV.channels[i].disableSelection) {
 						buttons.push(
 							App.UI.DOM.disabledLink(i, [FCTV.showChannel(i).text])
 						);
@@ -371,7 +371,7 @@ globalThis.FctvDisplay = function({usedRemote = 0, seeAll = 0} = {}) {
 	function displayShow() {
 		const frag = new DocumentFragment();
 		const sel = V.FCTV.channel.selected || 1;
-		const channel = App.Data.FCTV.shows[sel];
+		const channel = App.Data.FCTV.channels[sel];
 		const viewedCount = V.FCTV.channel[num(sel, true)];
 		const availableEp = channel.episode.length;
 		let epToShow;
diff --git a/src/pregmod/FCTV/FCTVshows.js b/src/pregmod/FCTV/FCTVshows.js
index e2ea4554e665375363955c7e124f3919f3f600b8..64606071e041063c8be7fafdeb9c4ccd39b8c04b 100644
--- a/src/pregmod/FCTV/FCTVshows.js
+++ b/src/pregmod/FCTV/FCTVshows.js
@@ -1,3 +1,13 @@
+/**
+ * @typedef {object<string, App.Entity.SlaveState>} FctvActor
+ * @property {string} slave name
+ */
+
+/**
+ * @typedef {object<string, FctvActor[]>} FctvActors
+ */
+
+/** @type {object<string, FctvActor>} */
 App.Data.FCTV.actors = {
 	get millie() {
 		const slave = BaseSlave();
@@ -508,9 +518,39 @@ App.Data.FCTV.actors = {
 		return slave;
 	}
 };
-App.Data.FCTV.shows = {
+
+/**
+ * @typedef {object<string, FctvChannel[]>} FctvChannels
+ */
+
+/**
+ * @typedef {object} FctvChannel
+ * @property {FctvTags} [tags]
+ * @property {boolean} loop After an initial viewing, should the episodes continue to play in order?
+ * @property {string} intro
+ * @property {array<FctvEpisode[]>} episode
+ * @property {Function(App.Entity.SlaveState):string} outro
+ */
+
+/**
+ * @typedef {object} FctvEpisode
+ * @property {FctvTags[]} [tags]
+ * @property {array<App.Entity.SlaveState>} [slaves]
+ * @property {string} text HTML embedded
+ */
+
+/**
+ * @typedef {object} FctvTags
+ * @property {boolean} [preg]
+ * @property {boolean} [hyperPreg]
+ * @property {boolean} [loli]
+ * @property {boolean} [extreme]
+ * @property {boolean} [dicks]
+ */
+
+App.Data.FCTV.channels = {
 	0: { // News
-		tags: [],
+		tags: {},
 		loop: true,
 		intro: `officially known as the FCNN stream channel and you've started watching the middle of a news segment.`,
 		episode: [
@@ -602,7 +642,7 @@ App.Data.FCTV.shows = {
 		]
 	},
 	1: { // Talk
-		tags: [],
+		tags: {},
 		loop: true,
 		get intro() {
 			const r = [];
@@ -653,7 +693,7 @@ App.Data.FCTV.shows = {
 		]
 	},
 	2: { // 'Home and Slave'
-		tags: [],
+		tags: {},
 		loop: true,
 		intro: `which is currently showing the 'Home and Slave' stream channel.The current show features a set of female twins wearing nothing but tool belts.Their assets aren't particularly noteworthy, but they have a great hourglass figure, toned muscles, and gorgeous girl-next-door faces. The girls are hosting a DIY show, and seem to be performing a lot of the work themselves. The occasional bead of sweat makes their smooth tan skin really stand out.`,
 		episode: [
@@ -739,7 +779,7 @@ App.Data.FCTV.shows = {
 	},
 	3: { // 'Home Slave Shopping'
 		// NOTE: These slaves are meant to be high quality and expensive, they are the product of the combined slave markets of all the Free Cities. Additionally, they won't follow the player's slave selling policies because they aren't being sold in the PC's arcology. Because they are purchased, it shouldn't be a balance issue or impact the game like a slave gift.
-		tags: [],
+		tags: {},
 		loop: true,
 		disableSelection: true,
 		intro: `which is currently streaming 'Home Slave Shopping'. It's a bit strange, shopping for slaves without inspecting them in person, but you have to admit it's kind of convenient. Plus, you might find something that'd be difficult to get in your own arcology's markets. You start watching at the end of one slave being displayed; the program goes into a lot of detail that isn't always available from shady salesmen at the market. Two hosts are displaying the merchandise and an older male reads details on each slave from a prompter, while a fit female works the slave for the camera to give viewers a good look at what they might purchase.`,
@@ -777,7 +817,7 @@ App.Data.FCTV.shows = {
 		},
 		episode: [
 			{// premium virgin
-				tags: [],
+				tags: {},
 				get slaves() { return [App.Data.FCTV.actors.premiumVirgin]; },
 				text: function(slave) {
 					const {He, he} = getPronouns(slave);
@@ -785,7 +825,7 @@ App.Data.FCTV.shows = {
 				}
 			},
 			{// hyperpregnant
-				tags: [],
+				tags: {},
 				get slaves() { return [App.Data.FCTV.actors.hyperPregnant]; },
 				text: function(slave) {
 					const r = [];
@@ -801,7 +841,7 @@ App.Data.FCTV.shows = {
 				}
 			},
 			{// superfetation
-				tags: ["preg"],
+				tags: {preg: true},
 				get slaves() { return [App.Data.FCTV.actors.superfetation]; },
 				text: function(slave) {
 					const r = [];
@@ -811,7 +851,7 @@ App.Data.FCTV.shows = {
 				}
 			},
 			{// MILF
-				tags: [],
+				tags: {},
 				get slaves() { return [App.Data.FCTV.actors.MILF]; },
 				text: function(slave) {
 					const r = [];
@@ -826,7 +866,7 @@ App.Data.FCTV.shows = {
 				}
 			},
 			{// discount young hottie
-				tags: [],
+				tags: {},
 				get slaves() { return [App.Data.FCTV.actors.youngHottie]; },
 				text: function() {
 					const r = [];
@@ -842,7 +882,7 @@ App.Data.FCTV.shows = {
 				}
 			},
 			{// huge balls
-				tags: ["dicks", ],
+				tags: {dicks: true},
 				get slaves() { return [App.Data.FCTV.actors.hugeBalls]; },
 				text: function(slave) {
 					const r = [];
@@ -853,7 +893,7 @@ App.Data.FCTV.shows = {
 				}
 			},
 			{// mpreg dickgirl
-				tags: ["dicks", "preg"],
+				tags: {dicks: true, preg:true},
 				get slaves() { return [App.Data.FCTV.actors.mpreg]; },
 				text: function(slave) {
 					const {girl, his, he, him} = getPronouns(slave);
@@ -864,7 +904,7 @@ App.Data.FCTV.shows = {
 	},
 	// 'Husbandry with Millie'
 	5: {
-		tags: ["preg"],
+		tags: {preg:true},
 		loop: true,
 		intro: `which is currently showing an episode of the slave-breeding for beginners series: 'Husbandry with Millie'. The show is hosted by the famous and charismatic Millie, a slave breeder from Arcturus who appears to be in her mid thirties. She's wearing something resembling a maternity dress over her large pregnant belly, but the loose fabric doesn't hide her enormous hips and complementary ass. The dress only comes part of the way up her chest, leaving her large milk-engorged breasts exposed as they rest atop the fabric. Millie begins the show the same way as always, by giving her viewers some encouragement. "Anyone can become a breeder, even you! Just be willing to learn, and as I always say..." she pats her full belly meaningfully "be ready to get your hands dirty!"`,
 		episode: [
@@ -1011,7 +1051,7 @@ App.Data.FCTV.shows = {
 		]
 	},
 	6: {// 'Modern Dairy'
-		tags: [],
+		tags: {},
 		loop: false,
 		get intro() {
 			const r = [];
@@ -1098,7 +1138,7 @@ App.Data.FCTV.shows = {
 		]
 	},
 	7: {// "Architecture + Ecology = Arcology"
-		tags: [],
+		tags: {},
 		loop: true,
 		get intro() {
 			const r = [];
@@ -1143,7 +1183,7 @@ App.Data.FCTV.shows = {
 		]
 	},
 	8: {// "Extreme Gestation for Fun and Profit"
-		tags: ["hyperPreg", "preg"],
+		tags: {hyperPreg:true, preg:true},
 		loop: true,
 		intro: `which is currently showing a preview of the how-to series "Extreme Gestation for Fun and Profit", hosted by Millie. It seems like the show's going to cover topics ranging from hyper-pregnancy to broodmother implants, and even hints and some sort of medical technique to allow anal pregnancy in males.`,
 		episode: [
@@ -1155,7 +1195,7 @@ App.Data.FCTV.shows = {
 		]
 	},
 	9: {// Channel  //TODO chain for shows a bit weird in original, would skip to next show if FS locked.  Maybe add a fallback that show is boring.  OOH!  Or else make each FS its own episode, and shuffle.
-		tags: [],
+		tags: {},
 		loop: true,
 		intro: ` which is currently showing a documentary on the `,
 		episode: [
@@ -1330,7 +1370,7 @@ App.Data.FCTV.shows = {
 		],
 	},
 	10: {// 'Cum and Cream Challenge'
-		tags: [],
+		tags: {},
 		loop: true,
 		get intro() {
 			const r = [];
@@ -1466,7 +1506,7 @@ App.Data.FCTV.shows = {
 		]
 	},
 	11: {// Channel
-		tags: [],
+		tags: {},
 		loop: true,
 		intro: ``,
 		episode: [
@@ -1596,7 +1636,7 @@ App.Data.FCTV.shows = {
 		]
 	},
 	12: {// Channel
-		tags: [],
+		tags: {},
 		loop: true,
 		get intro() {
 			if (FCTV.channelCount(1)) {
@@ -1800,7 +1840,7 @@ App.Data.FCTV.shows = {
 		]
 	},
 	13: {// Channel
-		tags: [],
+		tags: {},
 		loop: true,
 		episode: [
 			{
@@ -1909,7 +1949,7 @@ App.Data.FCTV.shows = {
 		]
 	},
 	14: {// Channel
-		tags: [],
+		tags: {},
 		loop: true,
 		intro: ``,
 		episode: [
@@ -2043,7 +2083,7 @@ App.Data.FCTV.shows = {
 		]
 	},
 	15: {// The Pirate Channel
-		tags: [],
+		tags: {},
 		loop: true,
 		intro: ``,
 		episode: [
@@ -2087,7 +2127,7 @@ App.Data.FCTV.shows = {
 		]
 	},
 	16: {// Channel
-		tags: [],
+		tags: {},
 		loop: true,
 		get intro() {
 			// All actors are at least 18