0.99.4: weighted rotation on Orbit 1, Orbit 2, Gold, Paradox, Evidence, Heroes and Vortex
pauljd/davidson-ci: Davidson CI / quality (push) Failing after 2m11s
pauljd/davidson-ci: Davidson CI / ai-review (push) Failing after 1m31s

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-11-04 02:00:26 +00:00
co-authored by Claude Opus 5.5
parent 63d2505c61
commit e21c746a8e
10 changed files with 393 additions and 17 deletions
+2 -2
View File
@@ -9,8 +9,8 @@ android {
applicationId = "tv.orbit.classic"
minSdk = 26
targetSdk = 36
versionCode = 133
versionName = "0.99.3-adaptive-film-rest"
versionCode = 134
versionName = "0.99.4-weighted-seven"
}
buildFeatures { compose = true }
compileOptions { sourceCompatibility = JavaVersion.VERSION_17; targetCompatibility = JavaVersion.VERSION_17 }
@@ -373,3 +373,31 @@ internal val PpvSunday = listOf("WWE WrestleMania III", "WCW Capital Combat: The
"WWE WrestleMania X-Seven", "ECW Guilty as Charged 1999", "WWE In Your House 16: Canadian Stampede", "NJPW Dominion 6.9 in Osaka-jo Hall",
"IMPACT Wrestling: Bound for Glory 2019", "WCW Bash at the Beach 1996", "WWE WrestleMania XV", "NWA Chi-Town Rumble", "AEW Full Gear (2021)",
"WWE Survivor Series 1997", "ECW November To Remember 1998", "WCW Spring Stampede 1994", "WWE King of the Ring 1998")
// Weighted rotation for seven more channels (approved 4 November 2026). Rewind and Dark Files wait on a repeat investigation;
// Riot, Terra, Ring X and Junior keep equal turns.
/** Orbit 1's tiers, from docs/channels/orbit-1.md (approved 4 November 2026): core 3, surprises 1, everything else 2. */
internal val Orbit1Tiers: Map<String, Double> get() =
listOf("Boy Meets World", "Cheers", "Everybody Loves Raymond", "Family Matters", "Frasier", "Full House", "Miranda", "My Family", "Not Going Out", "Outnumbered", "Sabrina, The Teenage Witch", "Saved by the Bell", "The Big Bang Theory", "The Fresh Prince of Bel-Air", "The King of Queens", "The Wonder Years").associate { seriesKey(it) to TIER_CORE } +
listOf("Ballykissangel", "Doc Martin", "Heartbeat", "Little House on the Prairie", "The Waltons").associate { seriesKey(it) to TIER_SURPRISE }
/** Orbit 2's tiers, from docs/channels/orbit-2.md (approved 4 November 2026): core 3, surprises 1, everything else 2. */
internal val Orbit2Tiers: Map<String, Double> get() =
listOf("Airwolf", "Countdown", "Gladiators", "Have I Got News for You", "Knight Rider", "MacGyver", "Mastermind", "Pointless", "QI", "Robot Wars", "Takeshi's Castle", "The A-Team", "The Crystal Maze", "University Challenge", "Would I Lie to You?").associate { seriesKey(it) to TIER_CORE } +
listOf("Are You Afraid of the Dark?", "Eerie, Indiana", "Goosebumps", "Quantum Leap", "Tales from the Crypt").associate { seriesKey(it) to TIER_SURPRISE }
/** Gold's tiers, from docs/channels/orbit-gold.md (approved 4 November 2026): core 3, surprises 1, everything else 2. */
internal val GoldTiers: Map<String, Double> get() =
listOf("Blackadder", "Dad's Army", "Father Ted", "Fawlty Towers", "Keeping Up Appearances", "One Foot in the Grave", "Only Fools and Horses", "Open All Hours", "Porridge", "The Vicar of Dibley").associate { seriesKey(it) to TIER_CORE } +
listOf("Lovejoy", "Minder", "The Two Ronnies").associate { seriesKey(it) to TIER_SURPRISE }
/** Paradox's tiers, from docs/channels/paradox.md (approved 4 November 2026): core 3, surprises 1, everything else 2. */
internal val ParadoxTiers: Map<String, Double> get() =
listOf("Buffy the Vampire Slayer", "Doctor Who (2005)", "Doctor Who (2023)", "Star Trek: The Next Generation", "Supernatural", "The X-Files").associate { seriesKey(it) to TIER_CORE } +
listOf("Twin Peaks").associate { seriesKey(it) to TIER_SURPRISE }
/** Evidence's tiers, from docs/channels/evidence.md (approved 4 November 2026): core 3, surprises 1, everything else 2. */
internal val EvidenceTiers: Map<String, Double> get() =
listOf("A Touch of Frost", "Bones", "CSI: Crime Scene Investigation", "Columbo", "Criminal Minds", "Inspector Morse", "Law & Order", "Midsomer Murders", "NCIS", "Vera").associate { seriesKey(it) to TIER_CORE }
/** Heroes' tiers, from docs/channels/heroes.md (approved 4 November 2026): core 3, surprises 1, everything else 2. */
internal val HeroesTiers: Map<String, Double> get() =
listOf("Batman Beyond", "Batman: The Animated Series", "Justice League", "Spider-Man: The Animated Series", "Superman: The Animated Series", "Teen Titans", "The Batman", "X-Men: The Animated Series").associate { seriesKey(it) to TIER_CORE }
/** Vortex's tiers, from docs/channels/vortex.md (approved 4 November 2026): core 3, surprises 1, everything else 2. */
internal val VortexTiers: Map<String, Double> get() =
listOf("Batman Beyond", "Cardcaptor Sakura", "Dragon Ball", "Dragon Ball GT", "Dragon Ball Z", "Jonny Quest", "Gundam Wing", "Mobile Suit Gundam Wing", "Mortal Kombat: Defenders of the Realm", "Outlaw Star", "Pokémon", "Sailor Moon", "Samurai Jack", "Tenchi Muyo", "Tenchi Muyo! Ryo-Ohki", "Tenchi Universe", "Tenchi Muyo! Tenchi Universe", "The Big O", "ThunderCats", "Yu Yu Hakusho", "Yu-Gi-Oh! Duel Monsters").associate { seriesKey(it) to TIER_CORE }
@@ -88,7 +88,7 @@ internal val SmartChannels = listOf(
SuggestionRule("day", "Daytime", "Movie", listOf("Science Fiction", "Action"), 1975..2005, 6.5f, setOf("NC-17", "X"), 40, NotCultCinema + "Adventure", 70, 7.9f),
SuggestionRule("day", "Daytime", "Movie", listOf("Thriller", "Mystery"), 1975..2005, 6.5f, setOf("NC-17", "X"), 30, NotCultCinema + "Science Fiction", 70, 7.9f),
SuggestionRule("late", "Late night", "Movie", listOf("Horror"), 1970..2010, 6f, emptySet(), 90, NotCultCinema, 70))),
ChannelProgramming(VORTEX_NUMBER, "Vortex", VortexShows, seriesCount = 40, finishTogether = true, exactTitles = true, englishAudio = false,
ChannelProgramming(VORTEX_NUMBER, "Vortex", VortexShows, seriesCount = 40, finishTogether = true, exactTitles = true, englishAudio = false, tierWeights = VortexTiers,
dayParts = VortexDayParts,
suggestionRules = listOf(
SuggestionRule("movie", "Movie Night", "Movie", listOf("Animation"), 1985..2025, 5f, setOf("NC-17", "X"), 30, setOf("Documentary"), 30, titleContains = "Dragon Ball"),
@@ -99,7 +99,8 @@ internal val SmartChannels = listOf(
ChannelProgramming(201, "Terra", TerraShows, seriesCount = TerraShows.size, finishTogether = true, exactTitles = true, dayParts = TerraDayParts),
ChannelProgramming(TOTS_NUMBER, "Tots", TotsShows, seriesCount = 40, finishTogether = true, exactTitles = true, dayParts = TotsDayParts, shuffleEpisodes = true,
tierWeights = TotsTiers),
ChannelProgramming(106, "Paradox", ParadoxShows, seriesCount = 20, finishTogether = true, exactTitles = true, dayParts = ParadoxDayParts, shuffleTitles = ParadoxStandalones.toSet())) +
ChannelProgramming(106, "Paradox", ParadoxShows, seriesCount = 20, finishTogether = true, exactTitles = true, dayParts = ParadoxDayParts, shuffleTitles = ParadoxStandalones.toSet(),
tierWeights = ParadoxTiers)) +
ChannelPresets.filter { it.smartOnly }.map { ChannelProgramming(it.number, it.name, it.searches, seriesCount = if (it.films) it.searches.size else 60, finishTogether = !it.films, exactTitles = true,
filmRestMs = if (it.films) 7 * 24 * 60 * 60_000L else null,
suggestionRules = SmartChannelRules[it.number].orEmpty(), dayParts = SmartChannelParts[it.number].orEmpty(), shuffleTitles = SmartChannelMixed[it.number].orEmpty(),
@@ -108,8 +109,9 @@ internal val SmartChannels = listOf(
/** Film channels whose rest adapts to their pools: Premiere, whose pool runs well past a week (After Dark is set above). Proposed 4 November 2026. */
private val AdaptiveFilmRestChannels get() = setOf(301)
/** Editorially weighted rotation, per channel, once its tiers are approved: Orbit Three (0.97.0), then Odyssey, Wild, Orbit X and ToonZ (0.98.0). */
private val SmartChannelTiers: Map<Int, Map<String, Double>> get() = mapOf(103 to ThreeTiers, 202 to OdysseyTiers, 203 to WildTiers, 603 to OrbitXTiers, 605 to ToonZTiers)
/** Editorially weighted rotation, per channel, once its tiers are approved: Orbit Three (0.97.0), Odyssey, Wild, Orbit X and ToonZ (0.98.0), then Orbit 1, Orbit 2, Gold, Evidence and Heroes (0.99.4; Vortex and Paradox are set above). */
private val SmartChannelTiers: Map<Int, Map<String, Double>> get() = mapOf(103 to ThreeTiers, 202 to OdysseyTiers, 203 to WildTiers, 603 to OrbitXTiers, 605 to ToonZTiers,
101 to Orbit1Tiers, 102 to Orbit2Tiers, 104 to GoldTiers, 107 to EvidenceTiers, 602 to HeroesTiers)
/** Shows that play in a mixed order on channels whose identity is agreed. See ChannelIdentities.kt. */
private val SmartChannelMixed: Map<Int, Set<String>> get() = mapOf(101 to Orbit1Mixed, 102 to Orbit2Mixed, 103 to ThreeMixed, 104 to GoldMixed, 105 to RiotMixed,
@@ -404,7 +406,11 @@ internal fun extendWeighted(pool: List<PoolSeries>, cursor: ScheduleCursor, from
val series = eligible.minWith(compareBy<PoolSeries> { tally[it.id]!! }.thenBy { pool.indexOf(it) })
val episode = following(series)
val why = "$block; weighted: lowest tally for its tier (${"%.0f".format(tally[series.id])})" + (if (eligible.size < clear.size) ", others resting or just played" else "") +
if (unclashed.isEmpty()) "; fallback: every show here airs elsewhere then" else ""
when {
unclashed.isEmpty() -> "; fallback: every show here airs elsewhere then"
series.id == last && unclashed.size < available.size -> "; again, as the others here air elsewhere then"
else -> ""
}
val item = if (series.isFilm) SampleItem(series.title, "Movie", episode.durationMs / 60_000, "", episode.id, episode.durationMs) else scheduledItem(series.title, episode)
entries += ScheduleEntry(series.id, episode.mark, ScheduledMedia(item, episode.sourceId, episode.durationMs), start, why, if (series in available) part?.key.orEmpty() else "")
val weight = part?.takeIf { series in available }?.weights?.get(seriesKey(series.title)) ?: weightOf(series)
@@ -66,7 +66,7 @@ internal class HttpsEmbyTransport(internal val deviceId: String = UUID.randomUUI
connection.connectTimeout = 10_000
connection.readTimeout = request.readTimeoutMs ?: if (request.path == "/Sessions/Logout") 5_000 else 15_000
connection.setRequestProperty("Accept", "application/json")
connection.setRequestProperty("X-Emby-Authorization", "Emby Client=\"Orbit TV\", Device=\"Orbit TV\", DeviceId=\"$deviceId\", Version=\"0.99.3\"")
connection.setRequestProperty("X-Emby-Authorization", "Emby Client=\"Orbit TV\", Device=\"Orbit TV\", DeviceId=\"$deviceId\", Version=\"0.99.4\"")
request.token?.let { connection.setRequestProperty("X-Emby-Token", it) }
if (request.method == "POST") {
val bytes = request.body.orEmpty().toByteArray(Charsets.UTF_8)
@@ -33,7 +33,7 @@ internal class SecureMediaDataSource(private val session: EmbySampleSession) : B
next.instanceFollowRedirects = false
next.connectTimeout = 10_000
next.readTimeout = 20_000
next.setRequestProperty("User-Agent", "OrbitTV/0.99.3")
next.setRequestProperty("User-Agent", "OrbitTV/0.99.4")
next.setRequestProperty("Accept-Encoding", "identity")
session.mediaHeaders(address).forEach { (key, value) -> next.setRequestProperty(key, value) }
if (dataSpec.position != 0L || dataSpec.length != C.LENGTH_UNSET.toLong()) {
@@ -21,7 +21,9 @@ class WeightedRolloutReplayTest {
private val day = 24 * 60 * 60_000L
private val hour = 60 * 60_000L
private val channels = listOf(TOTS_NUMBER, 605, 202, 203, 603)
/** Channels weighted on the box as installed (0.99.3), for the other channels' plans that each one avoids. */
private val installedWeighted = setOf(103, 202, 203, 603, 605, 614)
private val channels = (System.getenv("ORBIT_ROLLOUT_CHANNELS") ?: "614,605,202,203,603").split(",").map { it.trim().toInt() }
private fun plan(file: File): ChannelPlan {
val root = JSONObject(file.readText()); val rows = root.getJSONArray("entries"); val t = root.getJSONObject("tail"); val marks = t.getJSONObject("marks")
@@ -57,7 +59,7 @@ class WeightedRolloutReplayTest {
while (now < end) {
if (restartEvery > 0 && ++step % restartEvery == 0) { ScheduleStore(folder).savePlan("user", programming.number, current); current = ScheduleStore(folder).loadPlan("user", programming.number)!! }
current = topUpPlan(current, current.tail, series, now, programming.filmRestMs, programming.finishTogether, programming.dayParts, zone, avoid,
shuffle = programming.shuffleEpisodes, mixed = programming::mixes, weightOf = weightOf) ?: break
shuffle = programming.shuffleEpisodes, mixed = programming::mixes, weightOf = weightOf, adaptiveRest = programming.adaptiveFilmRest) ?: break
current.entries.filter { e -> all.none { it.startMs == e.startMs } }.forEach { all += it }
now += 6 * hour
}
@@ -66,6 +68,7 @@ class WeightedRolloutReplayTest {
return if (keepCommitted) all.filter { it.startMs < end } else all.filter { it.startMs >= committed.endMs && it.startMs < end }
}
private fun local(ms: Long) = java.time.Instant.ofEpochMilli(ms).atZone(zone).toLocalDateTime().withNano(0)
private fun pct(x: Double) = "%.0f%%".format(100 * x)
private fun report(label: String, entries: List<ScheduleEntry>, series: List<PoolSeries>, programming: ChannelProgramming, weightOf: (PoolSeries) -> Double, others: ProgrammeClash, out: StringBuilder): Map<String, Int> {
@@ -94,7 +97,11 @@ class WeightedRolloutReplayTest {
val waits = inBlocks.associate { s -> s.title to (listOf(first) + entries.filter { it.seriesId == s.id }.map { it.startMs } + listOf(last)).zipWithNext().maxOf { (a, b) -> b - a } / day.toDouble() }
val never = inBlocks.filter { s -> entries.none { it.seriesId == s.id } }.map { it.title }
out.appendLine(" starvation: never aired ${never.ifEmpty { listOf("none") }.joinToString()}; longest waits " + waits.entries.sortedByDescending { it.value }.take(4).joinToString { "${it.key} ${"%.1f".format(it.value)} d" })
val twice = entries.zipWithNext().count { (a, b) -> a.seriesId == b.seriesId && weightedAt(b) }
// A show twice running is only allowed when every other show in its block is on another channel at that moment.
// A block of one show, such as Gold's Sunday Only Fools and Horses, plays it back to back by design.
fun choice(e: ScheduleEntry) = dayPartFor(series, programming.dayParts, e.startMs, zone).first.size > 1
val twice = entries.zipWithNext().count { (a, b) -> a.seriesId == b.seriesId && weightedAt(b) && choice(b) && "air elsewhere then" !in b.why && "airs elsewhere then" !in b.why }
val twiceForClash = entries.zipWithNext().count { (a, b) -> a.seriesId == b.seriesId && weightedAt(b) && choice(b) } - twice
val clashes = entries.count { others(byId.getValue(it.seriesId), it.startMs, it.endMs) }
val ordered = series.filter { !(programming.shuffleEpisodes || programming.mixes(it)) }.associateBy { it.id }
val back = entries.filter { it.seriesId in ordered }.groupBy { it.seriesId }.values.sumOf { es -> es.zipWithNext().count { (a, b) -> b.mark <= a.mark && b.mark != ordered.getValue(a.seriesId).episodes.first().mark } }
@@ -104,7 +111,7 @@ class WeightedRolloutReplayTest {
val outside = entries.count { e -> dayPartFor(series, programming.dayParts, e.startMs, zone).first.none { it.id == e.seriesId } }
val bursts = entries.indices.count { i -> val w = entries.drop(i).takeWhile { it.startMs < entries[i].startMs + 3 * hour }; w.size >= 4 && w.count { it.seriesId == entries[i].seriesId } * 2 > w.size }
out.appendLine(" on another channel at the same time: $clashes")
out.appendLine(" rules: same show twice running (weighted blocks) $twice; three-hour stretches over half one show $bursts; in-order stepping back $back; shuffled off its deck $offDeck; gaps $gaps; outside its block $outside")
out.appendLine(" rules: same show twice running (weighted blocks with a choice) $twice, plus $twiceForClash where every other show airs elsewhere; three-hour stretches over half one show $bursts; in-order stepping back $back; shuffled off its deck $offDeck; gaps $gaps; outside its block $outside")
return mapOf("sameDay" to sameDayWeighted, "twice" to twice, "back" to back, "offDeck" to offDeck, "gaps" to gaps, "outside" to outside, "never" to never.size, "clashes" to clashes)
}
@@ -114,7 +121,7 @@ class WeightedRolloutReplayTest {
"every other channel's programmes as the box does. Copies of the box's files from 3 November.\n\n")
// Every other channel's four weeks on today's rules, for clash avoidance.
val baseline = SmartChannels.filter { File(dir, "schedule-${it.number}.json").exists() && File(dir, "episodes-${it.number}.json").exists() }.associate { p ->
p.number to replay(plan(File(dir, "schedule-${p.number}.json")), pool(File(dir, "episodes-${p.number}.json")), p, p.tierWeights?.let { p::weightOf }.takeIf { p.number == 103 }, keepCommitted = true)
p.number to replay(plan(File(dir, "schedule-${p.number}.json")), pool(File(dir, "episodes-${p.number}.json")), p, p.tierWeights?.let { p::weightOf }.takeIf { p.number in installedWeighted }, keepCommitted = true)
}
for (number in channels) {
val programming = SmartChannels.first { it.number == number }
@@ -129,9 +136,10 @@ class WeightedRolloutReplayTest {
val weighted = replay(committed, series, programming, weightOf, others)
val restarted = replay(committed, series, programming, weightOf, others, restartEvery = 3)
report("Today", today, series, programming, weightOf, others, out)
val after = report("Weighted (0.98.0)", weighted, series, programming, weightOf, others, out)
val after = report("Weighted (as built)", weighted, series, programming, weightOf, others, out)
assertEquals("${programming.name}: a restart changes nothing", weighted, restarted)
out.appendLine(" a save and reload every 18 hours gives the identical four weeks: true")
File(dir, "weighted-rollout-replay.txt").writeText(out.toString())
for (key in listOf("sameDay", "twice", "back", "offDeck", "gaps", "outside", "never")) assertEquals("${programming.name}: $key", 0, after[key])
// Blocks left on equal turns keep today's rule. Their programmes start at slightly different times, as the day before
// them now ends at different moments, so the sequence shifts at block edges; the rule and the shares stay the same.
@@ -190,7 +190,13 @@ class WeightedRotationTest {
}
@Test fun theApprovedChannelsAreWeightedAndTotsBedtimeIsNot() {
assertEquals(listOf(103, 202, 203, 603, 605, 614), SmartChannels.filter { it.tierWeights != null }.map { it.number }.sorted())
assertEquals(listOf(101, 102, 103, 104, 106, 107, 202, 203, 601, 602, 603, 605, 614), SmartChannels.filter { it.tierWeights != null }.map { it.number }.sorted())
// Riot, Terra, Ring X and Junior keep equal turns; Rewind and Dark Files wait on their repeat investigation.
assertTrue(SmartChannels.filter { it.number in setOf(105, 201, 401, 611, 604, 204) }.all { it.tierWeights == null })
assertEquals(TIER_CORE, SmartChannels.first { it.number == 101 }.weightOf(show("The Wonder Years", 1)), 0.0)
assertEquals(TIER_SURPRISE, SmartChannels.first { it.number == 101 }.weightOf(show("Heartbeat", 1)), 0.0)
assertEquals(TIER_CORE, SmartChannels.first { it.number == 601 }.weightOf(show("Tenchi Muyo! Ryo-Ohki", 1)), 0.0)
assertEquals(TIER_CORE, SmartChannels.first { it.number == 106 }.weightOf(show("Doctor Who (2023)", 1)), 0.0)
val tots = SmartChannels.first { it.number == TOTS_NUMBER }
assertEquals(listOf(true, true, false, false), tots.dayParts.map { it.weighted })
val toonz = SmartChannels.first { it.number == 605 }
@@ -202,7 +208,7 @@ class WeightedRotationTest {
assertEquals(TIER_SURPRISE, SmartChannels.first { it.number == 203 }.weightOf(show("Dirty Jobs", 1)), 0.0)
assertEquals(TIER_CORE, SmartChannels.first { it.number == 603 }.weightOf(show("Mighty Morphin Power Rangers", 1)), 0.0)
assertEquals(TIER_CORE, SmartChannels.first { it.number == 202 }.weightOf(show("Cosmos", 1)), 0.0)
assertTrue(SmartChannels.filter { it.number !in setOf(103, 202, 203, 603, 605, 614) }.all { p -> p.dayParts.all { it.weighted && it.weights.isEmpty() } })
assertTrue(SmartChannels.filter { it.number != 614 && it.number != 605 }.all { p -> p.dayParts.all { it.weighted && it.weights.isEmpty() } })
}
@Test fun orbitThreesTiers() {
+52
View File
@@ -0,0 +1,52 @@
# Orbit TV 0.99.4: weighted rotation on seven more channels
Paul approved this release on 4 November 2026 at 01:31, for seven channels: Orbit 1, Orbit 2, Orbit Gold, Paradox, Evidence, Heroes and Vortex. The change is to scheduling only.
## What's in it
- **Tiers from each brief:** `Orbit1Tiers`, `Orbit2Tiers`, `GoldTiers`, `ParadoxTiers`, `EvidenceTiers`, `HeroesTiers` and `VortexTiers` in ChannelIdentities.kt. Core is 3, surprises 1, and everything else 2, all matched by exact title (docs/replays/tier-proposal-2026-11-04.md).
- The Wonder Years is core on Orbit 1, as Paul approved.
- On Vortex, the brief's "Gundam Wing", "Tenchi Muyo!" and "Mortal Kombat" are core under the library's names (Mobile Suit Gundam Wing, Tenchi Muyo! Ryo-Ohki and Tenchi Universe, Mortal Kombat: Defenders of the Realm), each beside its original.
- **Weighted rotation exactly as installed for the first six channels.** The tier decides how often a show is chosen; its own cursor or shuffle deck decides the episode. Every daypart, event, presentation key and order setting is unchanged.
- **Unchanged channels:** Rewind and Dark Files wait on the repeat investigation. Riot, Terra, Ring X and Junior keep equal turns.
- **New in the weighted rule:** when the show that just played has to play again because every other show in its block is on another channel at that moment, the reason now says so ("again, as the others here air elsewhere then"). The choice itself is unchanged.
## Checks before install
- **409 unit tests pass**, including the hand-run replays on box copies taken at 01:40. Lint has no errors.
- **WeightedRotationTest:** exactly 13 channels are weighted. Riot, Terra, Ring X, Junior, Rewind and Dark Files aren't. It also checks the key tiers (The Wonder Years, Heartbeat, Tenchi Muyo! Ryo-Ohki and Doctor Who (2023)).
- **CatalogueNamesTest:** each alias carries its original's tier.
- **Four-week real-pool replay** with clash avoidance, plus a save and reload every 18 hours (docs/replays/weighted-rollout-replay-0.99.4.txt):
| Channel | Core | Same file within 7 days | Never aired / longest wait | Twice running* | 3-hour bursts |
|---|---|---|---|---|---|
| Orbit 1 | 41% → 56% | 0 → 0 | Not Going Out, 28 d → none, 21 d (Heartbeat, Sunday drama only) | 2 → 0 | 1 → 0 |
| Orbit 2 | 34% → 34% | 0 → 0 | none, 25 d → none, 7 d | 1 → 0 | 18 → 0 |
| Gold | 31% → 38% | 70 → 0 | none, 28 d → none, 7 d | 11 → 0 | 173 → 16 |
| Paradox | 32% → 47% | 0 → 0 | none (Doctor Who (2023) waited 27 d) → none, 1.9 d | 1 → 0 | 0 → 0 |
| Evidence | 47% → 48% | 0 → 0 | none, 26 d → none, 6.9 d | 2 → 0 | 0 → 0 |
| Heroes | 62% → 60% | 123 → 88 | none, 20 d → none, 1.3 d | 21 → 0 | 157 → 0 |
| Vortex | 40% → 49% | 0 → 95 | 6 anime, 28 d → none, 10 d | 1 → 0 | 0 → 0 |
\* In blocks with more than one show to choose from. A one-show event such as Gold's Sunday Only Fools and Horses plays it back to back by design. Orbit 2 has one more case where its Friday Spooky event's only other show was on another channel at that moment, and it's named in the reason.
- **Every weighted run:**
- No file airs again within 24 hours.
- No in-order show steps back, and no shuffled show leaves its deck.
- There are no gaps, nothing outside its block and no clash with another channel.
- Committed programmes come through exactly.
- A save and reload gives the identical four weeks.
- **Fingerprints** (docs/replays/replay-fingerprints-0.99.4.txt), a four-week replay of all 30 channels against 0.99.3:
- Every committed fingerprint is identical.
- Only the seven approved channels differ.
- The presentation replay is unchanged (83 transitions, committedKeptExactly=true).
## Known limits
- **Vortex repeats:** Vortex's weekly same-file repeats rise from 0 to 95. Its short catalogues now get their fair share, and lifting core from 40% to 49% plays the core shows' episodes more often. Paul asked to keep an eye on this. The episode-rest investigation for Rewind and Dark Files will cover Vortex too, and any fix it finds would come to Vortex in its own release.
- **Paradox's pool cap:** Paradox's list reads at most 20 shows, and its pool has 18.
- **Coordinated cutover:** every channel's committed plan runs to about 10 November, and these rules book from there, in line with the coordinated cutover still to come.
## Rollback
Install 0.99.3. Tallies saved by 0.99.4 are ignored by channels 0.99.3 doesn't weight.
@@ -0,0 +1,89 @@
# Four-week replay fingerprints, 0.99.3 against 0.99.4, from box copies taken 4 November 01:40
## 0.99.3
101 committed=dda28d0abc733ef1 kept=true fourWeeks=6655ab1a27c8adcc programmes=2129
102 committed=06fdb544423630d5 kept=true fourWeeks=c144df512c1e2b22 programmes=1285
103 committed=b4fbb83246440278 kept=true fourWeeks=48df82a34fc9a95f programmes=1797
104 committed=bdb3a86e78815bad kept=true fourWeeks=f1654066f9f208af programmes=1706
105 committed=df711f9b7257041c kept=true fourWeeks=1ecd19c03115f7e9 programmes=2439
106 committed=c6e05fe7b6b8119e kept=true fourWeeks=26f095b2150baf74 programmes=1158
107 committed=9d18785a275d37ad kept=true fourWeeks=98d3ec7d2cc2452e programmes=942
201 committed=c46140eb0a7734fc kept=true fourWeeks=ea35589b8f9f961d programmes=945
202 committed=9e8f22bca9934da3 kept=true fourWeeks=c01cb292dd50c37b programmes=1077
203 committed=aa5ac90cbfbcefed kept=true fourWeeks=48f0b555a13a3877 programmes=1126
204 committed=71fcbfebd956f81b kept=true fourWeeks=715bf3297761d86e programmes=1165
301 committed=a366040ccc54b12f kept=true fourWeeks=8d65b9f292a378e8 programmes=357
302 committed=9369125185fab4ee kept=true fourWeeks=0ebddde50c9a0d2c programmes=437
303 committed=b6c2d88f2604a452 kept=true fourWeeks=366e574763b92fb8 programmes=416
304 committed=b70f04c56326e54f kept=true fourWeeks=78b73765fc5ce5f4 programmes=491
305 committed=43eb0b69d237ab50 kept=true fourWeeks=20ab6de564561f94 programmes=466
306 committed=cbc6ae14f4c14d65 kept=true fourWeeks=5b351d7577fc61f8 programmes=483
307 committed=f823275ad86da4ce kept=true fourWeeks=1ec9cc79f5ac16d1 programmes=433
308 committed=6b22d132fbf48e54 kept=true fourWeeks=3eea0d0b9e560225 programmes=507
309 committed=4fa48ef1b757c74f kept=true fourWeeks=ab49e8adf646d991 programmes=491
401 committed=39ee211c00f3a145 kept=true fourWeeks=d84e4c50005d367d programmes=873
601 committed=5b0c1e935cb09f4e kept=true fourWeeks=6f6e51ed5cb06e37 programmes=2130
602 committed=65ce37a3813345a4 kept=true fourWeeks=ec44aa9d1e810e8d programmes=2275
603 committed=c3dd997e17639240 kept=true fourWeeks=7be36734344b9ce1 programmes=2264
604 committed=ab6c1fbf8682ede1 kept=true fourWeeks=27001bcae0bbb102 programmes=2904
605 committed=8e05175ceb5faafc kept=true fourWeeks=f14b2de2a0a77c24 programmes=4288
611 committed=f6b86c8a984bbcec kept=true fourWeeks=21d88866596d5161 programmes=2950
612 committed=abc6bb844d11b367 kept=true fourWeeks=3ccdbd30f5827932 programmes=569
613 committed=a8a89df382ac4b33 kept=true fourWeeks=26ac416ffee96753 programmes=514
614 committed=dbecefc97db62739 kept=true fourWeeks=cc407886b4a28564 programmes=3846
## 0.99.4
101 committed=dda28d0abc733ef1 kept=true fourWeeks=4a930321d88473f7 programmes=2138
102 committed=06fdb544423630d5 kept=true fourWeeks=f50ad1a9917fe7ee programmes=1303
103 committed=b4fbb83246440278 kept=true fourWeeks=48df82a34fc9a95f programmes=1797
104 committed=bdb3a86e78815bad kept=true fourWeeks=52f20db1b15cbb70 programmes=1703
105 committed=df711f9b7257041c kept=true fourWeeks=1ecd19c03115f7e9 programmes=2439
106 committed=c6e05fe7b6b8119e kept=true fourWeeks=a4207fcc179d9c1a programmes=1159
107 committed=9d18785a275d37ad kept=true fourWeeks=6ddc7c8035888422 programmes=957
201 committed=c46140eb0a7734fc kept=true fourWeeks=ea35589b8f9f961d programmes=945
202 committed=9e8f22bca9934da3 kept=true fourWeeks=c01cb292dd50c37b programmes=1077
203 committed=aa5ac90cbfbcefed kept=true fourWeeks=48f0b555a13a3877 programmes=1126
204 committed=71fcbfebd956f81b kept=true fourWeeks=715bf3297761d86e programmes=1165
301 committed=a366040ccc54b12f kept=true fourWeeks=8d65b9f292a378e8 programmes=357
302 committed=9369125185fab4ee kept=true fourWeeks=0ebddde50c9a0d2c programmes=437
303 committed=b6c2d88f2604a452 kept=true fourWeeks=366e574763b92fb8 programmes=416
304 committed=b70f04c56326e54f kept=true fourWeeks=78b73765fc5ce5f4 programmes=491
305 committed=43eb0b69d237ab50 kept=true fourWeeks=20ab6de564561f94 programmes=466
306 committed=cbc6ae14f4c14d65 kept=true fourWeeks=5b351d7577fc61f8 programmes=483
307 committed=f823275ad86da4ce kept=true fourWeeks=1ec9cc79f5ac16d1 programmes=433
308 committed=6b22d132fbf48e54 kept=true fourWeeks=3eea0d0b9e560225 programmes=507
309 committed=4fa48ef1b757c74f kept=true fourWeeks=ab49e8adf646d991 programmes=491
401 committed=39ee211c00f3a145 kept=true fourWeeks=d84e4c50005d367d programmes=873
601 committed=5b0c1e935cb09f4e kept=true fourWeeks=1c408bb6c2f8afe2 programmes=2137
602 committed=65ce37a3813345a4 kept=true fourWeeks=458f6bb57ac1e6c2 programmes=2287
603 committed=c3dd997e17639240 kept=true fourWeeks=7be36734344b9ce1 programmes=2264
604 committed=ab6c1fbf8682ede1 kept=true fourWeeks=27001bcae0bbb102 programmes=2904
605 committed=8e05175ceb5faafc kept=true fourWeeks=f14b2de2a0a77c24 programmes=4288
611 committed=f6b86c8a984bbcec kept=true fourWeeks=21d88866596d5161 programmes=2950
612 committed=abc6bb844d11b367 kept=true fourWeeks=3ccdbd30f5827932 programmes=569
613 committed=a8a89df382ac4b33 kept=true fourWeeks=26ac416ffee96753 programmes=514
614 committed=dbecefc97db62739 kept=true fourWeeks=cc407886b4a28564 programmes=3846
## Differences
1,2c1,2
< 101 committed=dda28d0abc733ef1 kept=true fourWeeks=6655ab1a27c8adcc programmes=2129
< 102 committed=06fdb544423630d5 kept=true fourWeeks=c144df512c1e2b22 programmes=1285
---
> 101 committed=dda28d0abc733ef1 kept=true fourWeeks=4a930321d88473f7 programmes=2138
> 102 committed=06fdb544423630d5 kept=true fourWeeks=f50ad1a9917fe7ee programmes=1303
4c4
< 104 committed=bdb3a86e78815bad kept=true fourWeeks=f1654066f9f208af programmes=1706
---
> 104 committed=bdb3a86e78815bad kept=true fourWeeks=52f20db1b15cbb70 programmes=1703
6,7c6,7
< 106 committed=c6e05fe7b6b8119e kept=true fourWeeks=26f095b2150baf74 programmes=1158
< 107 committed=9d18785a275d37ad kept=true fourWeeks=98d3ec7d2cc2452e programmes=942
---
> 106 committed=c6e05fe7b6b8119e kept=true fourWeeks=a4207fcc179d9c1a programmes=1159
> 107 committed=9d18785a275d37ad kept=true fourWeeks=6ddc7c8035888422 programmes=957
22,23c22,23
< 601 committed=5b0c1e935cb09f4e kept=true fourWeeks=6f6e51ed5cb06e37 programmes=2130
< 602 committed=65ce37a3813345a4 kept=true fourWeeks=ec44aa9d1e810e8d programmes=2275
---
> 601 committed=5b0c1e935cb09f4e kept=true fourWeeks=1c408bb6c2f8afe2 programmes=2137
> 602 committed=65ce37a3813345a4 kept=true fourWeeks=458f6bb57ac1e6c2 programmes=2287
@@ -0,0 +1,187 @@
0.98.0: four weeks of new bookings after each committed plan, today's rule against the app's weighted rotation, avoiding
every other channel's programmes as the box does. Copies of the box's files from 3 November.
== Orbit 1 (101), committed plan ends 2026-11-10T13:25:28
in the pool: 46 shows; core 16, supporting 25, surprise 5
Today: 1716 programmes
airtime by tier: core 41%, supporting 58%, surprise 1%
block 0-6: core 67%, 7 shows, effectively 4.6 equal shows; top Frasier 28%, The Golden Girls 27%, Everybody Loves Raymond 24%
block 18-0: core 28%, 13 shows, effectively 10.7 equal shows; top Home Improvement 11%, The Golden Girls 10%, Frasier 10%
block 6-18: core 42%, 16 shows, effectively 14.7 equal shows; top Kenan & Kel 8%, The Wonder Years 7%, Full House 7%
event 18-20: core 0%, 5 shows, effectively 4.9 equal shows; top Doc Martin 26%, Heartbeat 20%, The Waltons 18%
event 6-18: core 15%, 11 shows, effectively 8.7 equal shows; top Red Dwarf 15%, Bread 14%, Last of the Summer Wine 14%
repeats: 1716 different episodes; same file again within 24 h 0, within 7 days 0
starvation: never aired Not Going Out; longest waits Not Going Out 28.0 d, My Family 23.9 d, Modern Family 23.8 d, The Big Bang Theory 19.6 d
on another channel at the same time: 0
rules: same show twice running (weighted blocks with a choice) 2, plus 0 where every other show airs elsewhere; three-hour stretches over half one show 1; in-order stepping back 0; shuffled off its deck 0; gaps 0; outside its block 0
Weighted (as built): 1725 programmes
airtime by tier: core 56%, supporting 42%, surprise 1%
block 0-6: core 79%, 7 shows, effectively 6.8 equal shows; top The Big Bang Theory 16%, Cheers 16%, Frasier 16%
block 18-0: core 48%, 13 shows, effectively 12.5 equal shows; top Everybody Loves Raymond 10%, The King of Queens 10%, Frasier 10%
block 6-18: core 54%, 16 shows, effectively 15.4 equal shows; top Family Matters 8%, Sabrina, The Teenage Witch 8%, The Wonder Years 8%
event 18-20: core 0%, 5 shows, effectively 4.9 equal shows; top Doc Martin 26%, Heartbeat 20%, The Waltons 18%
event 6-18: core 43%, 12 shows, effectively 11.5 equal shows; top My Family 11%, Not Going Out 11%, Miranda 11%
repeats: 1722 different episodes; same file again within 24 h 0, within 7 days 0, median 21.9 days between repeats
starvation: never aired none; longest waits Heartbeat 21.0 d, Doc Martin 14.0 d, Ballykissangel 14.0 d, The Waltons 14.0 d
on another channel at the same time: 0
rules: same show twice running (weighted blocks with a choice) 0, plus 0 where every other show airs elsewhere; three-hour stretches over half one show 0; in-order stepping back 0; shuffled off its deck 0; gaps 0; outside its block 0
a save and reload every 18 hours gives the identical four weeks: true
== Orbit 2 (102), committed plan ends 2026-11-10T13:31:34
in the pool: 53 shows; core 15, supporting 33, surprise 5
Today: 1028 programmes
airtime by tier: core 34%, supporting 62%, surprise 4%
block 0-6: core 26%, 17 shows, effectively 16.3 equal shows; top The Dukes of Hazzard 7%, CHiPs 7%, Miami Vice 7%
block 15-18: core 31%, 12 shows, effectively 11.3 equal shows; top Who Wants to Be a Millionaire? 12%, Family Fortunes 11%, Pointless 10%
block 18-21: core 53%, 8 shows, effectively 6.5 equal shows; top Total Wipeout 21%, Gladiators 17%, The Crystal Maze 17%
block 21-0: core 36%, 7 shows, effectively 6.5 equal shows; top 8 Out of 10 Cats Does Countdown 22%, Mock the Week 15%, QI 14%
block 6-15: core 36%, 7 shows, effectively 4.4 equal shows; top Robot Wars 36%, MythBusters 21%, How It's Made 18%
event 17-19: core 0%, 3 shows, effectively 2.9 equal shows; top Are You Afraid of the Dark? 39%, Goosebumps 36%, Eerie, Indiana 24%
repeats: 1024 different episodes; same file again within 24 h 0, within 7 days 0, median 27.6 days between repeats
starvation: never aired none; longest waits Time Team 25.0 d, Scrapheap Challenge 22.3 d, Takeshi's Castle 18.8 d, The Gadget Show 17.0 d
on another channel at the same time: 1
rules: same show twice running (weighted blocks with a choice) 1, plus 0 where every other show airs elsewhere; three-hour stretches over half one show 18; in-order stepping back 0; shuffled off its deck 0; gaps 0; outside its block 0
Weighted (as built): 1042 programmes
airtime by tier: core 34%, supporting 63%, surprise 3%
block 0-6: core 33%, 17 shows, effectively 15.9 equal shows; top The A-Team 8%, MacGyver 8%, Knight Rider 8%
block 15-18: core 42%, 12 shows, effectively 11.6 equal shows; top University Challenge 11%, Mastermind 11%, Countdown 11%
block 18-21: core 60%, 8 shows, effectively 7.7 equal shows; top Gladiators 15%, The Crystal Maze 15%, Robot Wars 15%
block 21-0: core 53%, 7 shows, effectively 6.7 equal shows; top Would I Lie to You? 18%, QI 18%, Have I Got News for You 17%
block 6-15: core 20%, 7 shows, effectively 6.8 equal shows; top Robot Wars 20%, Top Gear 13%, Time Team 13%
event 17-19: core 0%, 3 shows, effectively 3.0 equal shows; top Goosebumps 38%, Are You Afraid of the Dark? 31%, Eerie, Indiana 31%
repeats: 1025 different episodes; same file again within 24 h 0, within 7 days 0, median 20.9 days between repeats
starvation: never aired none; longest waits Eerie, Indiana 7.0 d, Are You Afraid of the Dark? 7.0 d, Goosebumps 7.0 d, The Chase 6.0 d
on another channel at the same time: 0
rules: same show twice running (weighted blocks with a choice) 0, plus 1 where every other show airs elsewhere; three-hour stretches over half one show 0; in-order stepping back 0; shuffled off its deck 0; gaps 0; outside its block 0
a save and reload every 18 hours gives the identical four weeks: true
== Orbit Gold (104), committed plan ends 2026-11-10T10:28:28
in the pool: 44 shows; core 10, supporting 31, surprise 3
Today: 1368 programmes
airtime by tier: core 31%, supporting 67%, surprise 1%
block 17-22: core 60%, 13 shows, effectively 9.1 equal shows; top Dad's Army 18%, One Foot in the Grave 16%, Ever Decreasing Circles 11%
block 2-6: core 93%, 7 shows, effectively 3.9 equal shows; top Dad's Army 40%, Only Fools and Horses 24%, Father Ted 18%
block 22-2: core 0%, 15 shows, effectively 12.2 equal shows; top Taskmaster 13%, 8 Out of 10 Cats 9%, Birds Of A Feather 9%
block 6-17: core 7%, 13 shows, effectively 9.4 equal shows; top Brush Strokes 15%, Waiting for God 14%, Never the Twain 13%
event 17-19: core 0%, 3 shows, effectively 3.0 equal shows; top Minder 35%, Lovejoy 34%, The Two Ronnies 32%
event 18-22: core 100%, 1 shows, effectively 1.0 equal shows; top Only Fools and Horses 100%
repeats: 1014 different episodes; same file again within 24 h 0, within 7 days 70, median 12.2 days between repeats
starvation: never aired none; longest waits Hi-de-Hi! 27.9 d, Men Behaving Badly 26.4 d, Fawlty Towers 25.6 d, Blackadder 25.3 d
on another channel at the same time: 0
rules: same show twice running (weighted blocks with a choice) 11, plus 0 where every other show airs elsewhere; three-hour stretches over half one show 173; in-order stepping back 0; shuffled off its deck 0; gaps 0; outside its block 0
Weighted (as built): 1365 programmes
airtime by tier: core 38%, supporting 61%, surprise 1%
block 17-22: core 70%, 13 shows, effectively 12.6 equal shows; top Only Fools and Horses 9%, One Foot in the Grave 9%, Father Ted 9%
block 2-6: core 82%, 8 shows, effectively 7.8 equal shows; top Porridge 14%, Keeping Up Appearances 14%, Only Fools and Horses 14%
block 22-2: core 0%, 15 shows, effectively 15.0 equal shows; top Taskmaster 7%, Steptoe and Son 7%, Men Behaving Badly 7%
block 6-17: core 21%, 13 shows, effectively 12.7 equal shows; top Keeping Up Appearances 11%, Open All Hours 11%, Some Mothers Do 'Ave 'Em 7%
event 17-19: core 0%, 3 shows, effectively 3.0 equal shows; top Minder 35%, Lovejoy 34%, The Two Ronnies 32%
event 18-22: core 100%, 1 shows, effectively 1.0 equal shows; top Only Fools and Horses 100%
repeats: 1024 different episodes; same file again within 24 h 0, within 7 days 0, median 11.2 days between repeats
starvation: never aired none; longest waits Minder 7.0 d, Lovejoy 7.0 d, The Two Ronnies 7.0 d, Just Good Friends 6.9 d
on another channel at the same time: 0
rules: same show twice running (weighted blocks with a choice) 0, plus 0 where every other show airs elsewhere; three-hour stretches over half one show 16; in-order stepping back 0; shuffled off its deck 0; gaps 0; outside its block 0
a save and reload every 18 hours gives the identical four weeks: true
== Paradox (106), committed plan ends 2026-11-10T12:20:27
in the pool: 18 shows; core 6, supporting 11, surprise 1
Today: 912 programmes
airtime by tier: core 32%, supporting 63%, surprise 5%
block 18-23: core 50%, 4 shows, effectively 4.0 equal shows; top Fringe 26%, Buffy the Vampire Slayer 26%, Angel 24%
block 23-6: core 10%, 6 shows, effectively 5.7 equal shows; top Farscape 21%, The Outer Limits 20%, The Outer Limits (1995) 20%
block 6-18: core 38%, 9 shows, effectively 7.7 equal shows; top Doctor Who (2005) 15%, Star Trek: The Next Generation 14%, Sliders 14%
repeats: 912 different episodes; same file again within 24 h 0, within 7 days 0
starvation: never aired none; longest waits Doctor Who (2023) 27.2 d, The X-Files 10.5 d, Torchwood 8.3 d, Warehouse 13 4.3 d
on another channel at the same time: 0
rules: same show twice running (weighted blocks with a choice) 1, plus 0 where every other show airs elsewhere; three-hour stretches over half one show 0; in-order stepping back 0; shuffled off its deck 0; gaps 0; outside its block 0
Weighted (as built): 913 programmes
airtime by tier: core 47%, supporting 50%, surprise 3%
block 18-23: core 60%, 4 shows, effectively 3.8 equal shows; top Supernatural 30%, Buffy the Vampire Slayer 30%, Angel 20%
block 23-6: core 25%, 6 shows, effectively 5.6 equal shows; top The X-Files 25%, Torchwood 17%, The Outer Limits (1995) 17%
block 6-18: core 55%, 9 shows, effectively 8.6 equal shows; top Star Trek: The Next Generation 14%, Doctor Who (2023) 14%, The X-Files 14%
repeats: 871 different episodes; same file again within 24 h 0, within 7 days 0, median 7.9 days between repeats
starvation: never aired none; longest waits Twin Peaks 1.9 d, Fringe 1.0 d, The Outer Limits 1.0 d, Torchwood 1.0 d
on another channel at the same time: 0
rules: same show twice running (weighted blocks with a choice) 0, plus 0 where every other show airs elsewhere; three-hour stretches over half one show 0; in-order stepping back 0; shuffled off its deck 0; gaps 0; outside its block 0
a save and reload every 18 hours gives the identical four weeks: true
== Evidence (107), committed plan ends 2026-11-10T10:25:10
in the pool: 30 shows; core 10, supporting 20, surprise 0
Today: 790 programmes
airtime by tier: core 47%, supporting 53%, surprise 0%
block 1-6: core 48%, 12 shows, effectively 11.0 equal shows; top Law & Order 10%, Criminal Minds 10%, CSI: Miami 10%
block 18-21: core 70%, 9 shows, effectively 7.9 equal shows; top Inspector Morse 16%, A Touch of Frost 15%, Midsomer Murders 14%
block 21-1: core 0%, 9 shows, effectively 8.7 equal shows; top Sherlock 16%, Line of Duty 12%, Happy Valley 12%
block all: core 54%, 13 shows, effectively 11.6 equal shows; top Columbo 14%, Law & Order 8%, CSI: Miami 8%
event 20-22: core 68%, 6 shows, effectively 6.0 equal shows; top Inspector Morse 19%, Midsomer Murders 17%, Lewis 16%
repeats: 790 different episodes; same file again within 24 h 0, within 7 days 0
starvation: never aired none; longest waits Dalziel and Pascoe 25.6 d, Hawaii Five-0 22.9 d, CSI: NY 10.9 d, Midsomer Murders 4.0 d
on another channel at the same time: 0
rules: same show twice running (weighted blocks with a choice) 2, plus 0 where every other show airs elsewhere; three-hour stretches over half one show 0; in-order stepping back 0; shuffled off its deck 0; gaps 0; outside its block 0
Weighted (as built): 806 programmes
airtime by tier: core 48%, supporting 52%, surprise 0%
block 1-6: core 51%, 12 shows, effectively 11.6 equal shows; top NCIS 11%, Bones 10%, Criminal Minds 10%
block 18-21: core 64%, 9 shows, effectively 8.8 equal shows; top A Touch of Frost 13%, Midsomer Murders 13%, Columbo 12%
block 21-1: core 0%, 9 shows, effectively 9.0 equal shows; top Happy Valley 11%, Dexter 11%, Sherlock 11%
block all: core 56%, 13 shows, effectively 12.5 equal shows; top Law & Order 9%, Bones 9%, Columbo 9%
event 20-22: core 91%, 6 shows, effectively 5.7 equal shows; top Inspector Morse 21%, A Touch of Frost 19%, Midsomer Murders 18%
repeats: 806 different episodes; same file again within 24 h 0, within 7 days 0
starvation: never aired none; longest waits Taggart 6.9 d, Lewis 6.1 d, Dalziel and Pascoe 6.0 d, Inspector Morse 5.9 d
on another channel at the same time: 0
rules: same show twice running (weighted blocks with a choice) 0, plus 0 where every other show airs elsewhere; three-hour stretches over half one show 0; in-order stepping back 0; shuffled off its deck 0; gaps 0; outside its block 0
a save and reload every 18 hours gives the identical four weeks: true
== Heroes (602), committed plan ends 2026-11-10T10:19:24
in the pool: 19 shows; core 8, supporting 11, surprise 0
Today: 1838 programmes
airtime by tier: core 62%, supporting 38%, surprise 0%
block 15-18: core 83%, 6 shows, effectively 5.6 equal shows; top Batman: The Animated Series 23%, Justice League 21%, Teen Titans 17%
block 18-22: core 77%, 10 shows, effectively 9.2 equal shows; top Batman: The Animated Series 16%, Teen Titans 13%, Justice League 12%
block 22-6: core 57%, 19 shows, effectively 14.7 equal shows; top Batman: The Animated Series 12%, Static Shock 9%, Teen Titans 9%
block 6-15: core 55%, 19 shows, effectively 15.4 equal shows; top Batman: The Animated Series 11%, Teen Titans 9%, Static Shock 8%
event 7-11: core 27%, 9 shows, effectively 6.9 equal shows; top Iron Man: Armored Adventures 21%, The Avengers: Earth's Mightiest Heroes 20%, X-Men: The Animated Series 15%
repeats: 957 different episodes; same file again within 24 h 0, within 7 days 123, median 10.9 days between repeats
starvation: never aired none; longest waits Batman: The Brave and the Bold 20.0 d, Ben 10 16.2 d, The Batman 16.0 d, The Spectacular Spider-Man 11.0 d
on another channel at the same time: 2
rules: same show twice running (weighted blocks with a choice) 21, plus 0 where every other show airs elsewhere; three-hour stretches over half one show 157; in-order stepping back 0; shuffled off its deck 0; gaps 0; outside its block 0
Weighted (as built): 1844 programmes
airtime by tier: core 60%, supporting 40%, surprise 0%
block 15-18: core 88%, 6 shows, effectively 5.9 equal shows; top Justice League 18%, Superman: The Animated Series 18%, Batman Beyond 18%
block 18-22: core 78%, 10 shows, effectively 9.7 equal shows; top Justice League 11%, Spider-Man: The Animated Series 11%, Teen Titans 11%
block 22-6: core 52%, 19 shows, effectively 18.3 equal shows; top Batman Beyond 7%, Justice League 7%, Teen Titans 7%
block 6-15: core 52%, 19 shows, effectively 18.2 equal shows; top Justice League 7%, Spider-Man: The Animated Series 7%, X-Men: The Animated Series 7%
event 7-11: core 31%, 9 shows, effectively 8.6 equal shows; top X-Men: The Animated Series 16%, Spider-Man: The Animated Series 15%, Silver Surfer 11%
repeats: 987 different episodes; same file again within 24 h 0, within 7 days 88, median 11.9 days between repeats
starvation: never aired none; longest waits Spider-Man Unlimited 1.3 d, Ben 10 1.1 d, Spider-Man and His Amazing Friends 1.1 d, Batman: The Brave and the Bold 1.0 d
on another channel at the same time: 0
rules: same show twice running (weighted blocks with a choice) 0, plus 0 where every other show airs elsewhere; three-hour stretches over half one show 0; in-order stepping back 0; shuffled off its deck 0; gaps 0; outside its block 0
a save and reload every 18 hours gives the identical four weeks: true
== Vortex (601), committed plan ends 2026-11-10T09:33:36
in the pool: 117 shows; core 17, supporting 100, surprise 0
Today: 1729 programmes
airtime by tier: core 40%, supporting 60%, surprise 0%
block 12-16: core 31%, 36 shows, effectively 32.8 equal shows; top Saint Seiya 4%, Avatar: The Last Airbender 4%, Mobile Fighter G Gundam 4%
block 6-12: core 51%, 18 shows, effectively 16.5 equal shows; top Cardcaptor Sakura 7%, Dragon Ball 7%, Sailor Moon 7%
block day: core 10%, 45 shows, effectively 39.8 equal shows; top Mobile Suit Gundam SEED 3%, Nobunaga the Fool 3%, Saint Seiya 3%
block night: core 39%, 41 shows, effectively 37.2 equal shows; top Cardcaptor Sakura 3%, Ronin Warriors 3%, Dragon Ball GT 3%
block toonami: core 100%, 17 shows, effectively 14.9 equal shows; top Dragon Ball Z 8%, Dragon Ball 8%, Cardcaptor Sakura 8%
event movie: core 0%, 19 shows, effectively 17.2 equal shows; top Dragon Ball Z: Battle of Gods 9%, Dragon Ball Super: Super Hero 9%, Dragon Ball Z: Resurrection 'F' 8%
repeats: 1729 different episodes; same file again within 24 h 0, within 7 days 0
starvation: never aired Murder Princess, Pokémon: Origins, Karas, Now and Then, Here and There, Zakuro, Legend of the Galactic Heroes Gaiden; longest waits Murder Princess 28.0 d, Pokémon: Origins 28.0 d, Karas 28.0 d, Now and Then, Here and There 28.0 d
on another channel at the same time: 0
rules: same show twice running (weighted blocks with a choice) 1, plus 0 where every other show airs elsewhere; three-hour stretches over half one show 0; in-order stepping back 0; shuffled off its deck 0; gaps 0; outside its block 0
Weighted (as built): 1737 programmes
airtime by tier: core 49%, supporting 51%, surprise 0%
block 12-16: core 44%, 37 shows, effectively 35.8 equal shows; top Dragon Ball Z 4%, Mobile Suit Gundam Wing 4%, Yu Yu Hakusho 3%
block 6-12: core 65%, 18 shows, effectively 17.4 equal shows; top Mortal Kombat: Defenders of the Realm 7%, Jonny Quest 7%, Pokémon 7%
block day: core 16%, 51 shows, effectively 49.9 equal shows; top Mobile Suit Gundam Wing 3%, Outlaw Star 3%, Mortal Kombat: Defenders of the Realm 3%
block night: core 47%, 42 shows, effectively 41.1 equal shows; top Pokémon 3%, Samurai Jack 3%, ThunderCats 3%
block toonami: core 100%, 17 shows, effectively 17.0 equal shows; top Dragon Ball 6%, Tenchi Muyo! Ryo-Ohki 6%, Dragon Ball GT 6%
event movie: core 0%, 19 shows, effectively 16.9 equal shows; top Dragon Ball Z: Battle of Gods 9%, Dragon Ball Super: Broly 9%, Dragon Ball Super: Super Hero 9%
repeats: 1541 different episodes; same file again within 24 h 0, within 7 days 95, median 8.4 days between repeats
starvation: never aired none; longest waits Ronin Warriors 10.0 d, S-CRY-Ed 9.0 d, Robotech 9.0 d, Vision of Escaflowne 8.9 d
on another channel at the same time: 0
rules: same show twice running (weighted blocks with a choice) 0, plus 0 where every other show airs elsewhere; three-hour stretches over half one show 0; in-order stepping back 0; shuffled off its deck 0; gaps 0; outside its block 0
a save and reload every 18 hours gives the identical four weeks: true