0.99.0: Ring X weeknight flagships are appointments, so they start near 18:00
pauljd/davidson-ci: Davidson CI / quality (push) Failing after 1m45s
pauljd/davidson-ci: Davidson CI / ai-review (push) Failing after 1m27s

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-11-04 01:00:29 +00:00
co-authored by Claude Opus 5.5
parent 837f97a3bf
commit e0d600bc3d
7 changed files with 159 additions and 7 deletions
+2 -2
View File
@@ -9,8 +9,8 @@ android {
applicationId = "tv.orbit.classic"
minSdk = 26
targetSdk = 36
versionCode = 129
versionName = "0.98.0-weighted-rollout"
versionCode = 130
versionName = "0.99.0-ringx-weeknights"
}
buildFeatures { compose = true }
compileOptions { sourceCompatibility = JavaVersion.VERSION_17; targetCompatibility = JavaVersion.VERSION_17 }
@@ -350,10 +350,11 @@ private val RingXSaturday = listOf("Ring of Honor (2002) [imdb-] [tvdb-386985]",
private val RingXLate = listOf("Dark Side of the Ring", "The Monday Night War", "WWE Untold", "WWE Rivals", "Biography: WWE Legends", "WWE Icons", "WWE Ruthless Aggression",
"Who Killed WCW?", "The Wrestlers", "Wrestlers", "WWE Legends House", "WWE NXT")
internal val RingXShows get() = (RingXMonday + RingXTuesday + RingXWednesday + RingXThursday + RingXFriday + RingXSaturday + RingXDaytimeVault + RingXLate + RingXVault).distinct()
// The weeknight flagships are appointments (4 November 2026): the vault before 18:00 is chosen to finish in time, so the night starts near its hour.
internal val RingXDayParts get() = listOf(
DayPart(18, 23, RingXMonday, days = setOf(DayOfWeek.MONDAY), key = "ringx.monday"), DayPart(18, 23, RingXTuesday, days = setOf(DayOfWeek.TUESDAY), key = "ringx.tuesday"),
DayPart(18, 23, RingXWednesday, days = setOf(DayOfWeek.WEDNESDAY), key = "ringx.wednesday"), DayPart(18, 23, RingXThursday, days = setOf(DayOfWeek.THURSDAY), key = "ringx.thursday"),
DayPart(18, 23, RingXFriday, days = setOf(DayOfWeek.FRIDAY), key = "ringx.friday"), DayPart(18, 23, RingXSaturday, days = setOf(DayOfWeek.SATURDAY), key = "ringx.saturday"),
DayPart(18, 23, RingXMonday, days = setOf(DayOfWeek.MONDAY), onTime = true, key = "ringx.monday"), DayPart(18, 23, RingXTuesday, days = setOf(DayOfWeek.TUESDAY), onTime = true, key = "ringx.tuesday"),
DayPart(18, 23, RingXWednesday, days = setOf(DayOfWeek.WEDNESDAY), onTime = true, key = "ringx.wednesday"), DayPart(18, 23, RingXThursday, days = setOf(DayOfWeek.THURSDAY), onTime = true, key = "ringx.thursday"),
DayPart(18, 23, RingXFriday, days = setOf(DayOfWeek.FRIDAY), onTime = true, key = "ringx.friday"), DayPart(18, 23, RingXSaturday, days = setOf(DayOfWeek.SATURDAY), onTime = true, key = "ringx.saturday"),
// PPV Sunday: one event, starting near 19:00 and running to its natural end, however late.
DayPart(19, 20, PpvSunday, days = setOf(DayOfWeek.SUNDAY), onTime = true, once = true, key = "ringx.ppv"),
DayPart(6, 23, RingXDaytimeVault), DayPart(23, 6, RingXLate))
@@ -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.98.0\"")
connection.setRequestProperty("X-Emby-Authorization", "Emby Client=\"Orbit TV\", Device=\"Orbit TV\", DeviceId=\"$deviceId\", Version=\"0.99.0\"")
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.98.0")
next.setRequestProperty("User-Agent", "OrbitTV/0.99.0")
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()) {
@@ -0,0 +1,36 @@
package tv.orbit.classic
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
import java.time.DayOfWeek
class RingXWeeknightsTest {
private val zone = java.time.ZoneId.of("Europe/London")
private val day = 24 * 60 * 60_000L
private val ring get() = SmartChannels.first { it.number == 401 }
@Test fun theSixWeeknightsAreAppointmentsAndNothingElseChanged() {
val nights = ring.dayParts.filter { it.fromHour == 18 && it.toHour == 23 }
assertEquals(DayOfWeek.entries.filter { it != DayOfWeek.SUNDAY }.toSet(), nights.flatMap { it.days }.toSet())
assertTrue(nights.all { it.onTime && !it.once && it.key.startsWith("ringx.") })
val ppv = ring.dayParts.single { it.key == "ringx.ppv" }
assertTrue(ppv.onTime && ppv.once && ppv.fromHour == 19)
assertTrue(ring.dayParts.filter { it.days.isEmpty() }.none { it.onTime })
}
@Test fun theNightStartsNearItsHourWithNothingCut() {
// Vault episodes of awkward lengths, so the daytime never lands on 18:00 by itself.
fun show(title: String, minutes: Int) = PoolSeries(title, title, (1..300).map { PoolEpisode("$title-$it", "src", EpisodeMark(1, it), "E$it", minutes * 60_000L) })
val titles = ring.dayParts.flatMap { it.titles }.distinct()
val pool = titles.filter { it in listOf("WWF Prime Time Wrestling", "WWE Heat", "WCW Saturday Night", "WWE Raw", "WCW Monday Nitro", "IMPACT! Wrestling", "All Elite Wrestling: Dynamite",
"WWE SmackDown", "ECW Hardcore TV", "NJPW Strong", "Dark Side of the Ring", "WWE Untold") }.mapIndexed { i, t -> show(t, listOf(47, 89, 63, 41, 118, 52)[i % 6]) }
val start = java.time.LocalDate.of(2026, 11, 16).atStartOfDay(zone).toInstant().toEpochMilli()
val plan = extendSchedule(pool, ScheduleCursor(), start, start + 14 * day, finishTogether = true, dayParts = ring.dayParts, zone = zone)
plan.entries.zipWithNext().forEach { (a, b) -> assertEquals(a.endMs, b.startMs) }
val firsts = plan.entries.filter { "event 18-23" in it.why }.groupBy { java.time.Instant.ofEpochMilli(it.startMs).atZone(zone).toLocalDate() }
.map { (date, es) -> (es.minOf { it.startMs } - date.atTime(18, 0).atZone(zone).toInstant().toEpochMilli()) / 60_000 }
assertEquals(12, firsts.size)
assertTrue("late by $firsts", firsts.all { it in 0..20 })
}
}
+44
View File
@@ -0,0 +1,44 @@
# Orbit TV 0.99.0: Ring X weeknight flagships start on time
This is the first of the isolated editorial corrections, approved as the next task by Paul on 4 November 2026 at 00:55. The change is to scheduling only, for Ring X (401) only.
## What's in it
The six weeknight blocks (Monday to Saturday, 18:00 to 23:00) are now appointments (`onTime = true` in `RingXDayParts`). This is the mechanism PPV Sunday has used since 0.94.0:
- In the 90 minutes before 18:00, the vault programmes are picked, in their normal turn where possible, so one ends at the appointment or within its first 20 minutes.
- Nothing is cut, no gap is left, and nothing extra is added.
Unchanged:
- the weeknight line-ups and their presentation keys and idents
- PPV Sunday
- the daytime vault and Late blocks
- equal-turns rotation with finish-together, since Ring X isn't weighted
- every other channel
## Checks before install
- **400 unit tests pass.** Lint has no errors.
- **New RingXWeeknightsTest:**
- The six weeknights are on time, with PPV Sunday still on time and once a night, and no everyday block changed.
- On a pool with awkward episode lengths, every weeknight over two weeks starts 0 to 20 minutes after 18:00, with no gaps.
- **Four-week replay on Ring X's real pool, from box copies taken at 00:40** (docs/replays/editorial-corrections-2026-11-04.txt, EditorialCorrectionsAuditTest). It covers 19 weeknights booked after the committed plan, with every channel booking together:
| | Median late | Worst | Within 10 min | Gaps |
|---|---|---|---|---|
| 0.98.0 | 34 min | 105 min | 6 of 19 | 0 |
| 0.99.0 | 8 min | 37 min | 12 of 19 | 0 |
The two worst nights (Saturday 37 minutes, Monday 30 minutes) are when no vault programme in its turn fits the run-in. The grace of the existing rule then lets the night start late rather than cut anything.
- **Fingerprints** (docs/replays/replay-fingerprints-0.99.0.txt): a four-week replay of all 30 channels on 0.98.0 and 0.99.0.
- Every committed fingerprint is identical.
- Only 401 differs.
- The presentation replay is unchanged (83 transitions, committedKeptExactly=true), and Ring X's weeknight idents still mark their nights.
## Known limits
- Ring X's committed plan runs to about 10 November, so new bookings follow this rule from then, in line with Paul's plan for one coordinated cutover later.
- Tested here as scheduling logic only.
## Rollback
Install 0.98.0. No saved data changes.
@@ -0,0 +1,71 @@
# Four-week replay fingerprints, 0.98.0 (837f97a) against 0.99.0, from box copies taken 4 November 00:40
## 0.98.0
101 committed=e2a56999e8a008ea kept=true fourWeeks=3de1a3331b6a294b programmes=2129
102 committed=06fdb544423630d5 kept=true fourWeeks=c144df512c1e2b22 programmes=1285
103 committed=17078bdc852aefc4 kept=true fourWeeks=c85d48a217a10c94 programmes=1797
104 committed=811d1bee8e3abe5e kept=true fourWeeks=a2236f9d16315f19 programmes=1706
105 committed=d8131ecf0e387195 kept=true fourWeeks=ffe3db054189fe0a programmes=2439
106 committed=c6e05fe7b6b8119e kept=true fourWeeks=26f095b2150baf74 programmes=1158
107 committed=9d18785a275d37ad kept=true fourWeeks=98d3ec7d2cc2452e programmes=942
201 committed=34acd633c3136235 kept=true fourWeeks=4149eaa0e26e856b programmes=945
202 committed=9e8f22bca9934da3 kept=true fourWeeks=c01cb292dd50c37b programmes=1077
203 committed=aa5ac90cbfbcefed kept=true fourWeeks=48f0b555a13a3877 programmes=1126
204 committed=1eb08bd1eea4dc27 kept=true fourWeeks=36e7d32d6fb69942 programmes=1170
301 committed=a366040ccc54b12f kept=true fourWeeks=12c5b036edae175e programmes=356
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=0e3ecca6fdb30bf6 programmes=466
306 committed=cbc6ae14f4c14d65 kept=true fourWeeks=5b351d7577fc61f8 programmes=483
307 committed=f823275ad86da4ce kept=true fourWeeks=1ec9cc79f5ac16d1 programmes=433
308 committed=0eeae8fbb0089be5 kept=true fourWeeks=737bc88204988895 programmes=507
309 committed=ffda2938276695fe kept=true fourWeeks=eb95121ca85adbe2 programmes=490
401 committed=39ee211c00f3a145 kept=true fourWeeks=1614f476babcf3c1 programmes=872
601 committed=67bd343aeea9b2b0 kept=true fourWeeks=0832e576914da5ff programmes=2130
602 committed=0ecddbb6610450c9 kept=true fourWeeks=cd809c919d470daa programmes=2275
603 committed=25dbd63f8491396f kept=true fourWeeks=0ab476ba34eedbc5 programmes=2264
604 committed=548e765ce48965ab kept=true fourWeeks=dffcb862ff82bfb6 programmes=2903
605 committed=5ca17ce6d03a6a9c kept=true fourWeeks=faba8c949f38b864 programmes=4286
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=10ab3c9c03ae82fa kept=true fourWeeks=477e941c7d3a939a programmes=3881
## 0.99.0
101 committed=e2a56999e8a008ea kept=true fourWeeks=3de1a3331b6a294b programmes=2129
102 committed=06fdb544423630d5 kept=true fourWeeks=c144df512c1e2b22 programmes=1285
103 committed=17078bdc852aefc4 kept=true fourWeeks=c85d48a217a10c94 programmes=1797
104 committed=811d1bee8e3abe5e kept=true fourWeeks=a2236f9d16315f19 programmes=1706
105 committed=d8131ecf0e387195 kept=true fourWeeks=ffe3db054189fe0a programmes=2439
106 committed=c6e05fe7b6b8119e kept=true fourWeeks=26f095b2150baf74 programmes=1158
107 committed=9d18785a275d37ad kept=true fourWeeks=98d3ec7d2cc2452e programmes=942
201 committed=34acd633c3136235 kept=true fourWeeks=4149eaa0e26e856b programmes=945
202 committed=9e8f22bca9934da3 kept=true fourWeeks=c01cb292dd50c37b programmes=1077
203 committed=aa5ac90cbfbcefed kept=true fourWeeks=48f0b555a13a3877 programmes=1126
204 committed=1eb08bd1eea4dc27 kept=true fourWeeks=36e7d32d6fb69942 programmes=1170
301 committed=a366040ccc54b12f kept=true fourWeeks=12c5b036edae175e programmes=356
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=0e3ecca6fdb30bf6 programmes=466
306 committed=cbc6ae14f4c14d65 kept=true fourWeeks=5b351d7577fc61f8 programmes=483
307 committed=f823275ad86da4ce kept=true fourWeeks=1ec9cc79f5ac16d1 programmes=433
308 committed=0eeae8fbb0089be5 kept=true fourWeeks=737bc88204988895 programmes=507
309 committed=ffda2938276695fe kept=true fourWeeks=eb95121ca85adbe2 programmes=490
401 committed=39ee211c00f3a145 kept=true fourWeeks=d84e4c50005d367d programmes=873
601 committed=67bd343aeea9b2b0 kept=true fourWeeks=0832e576914da5ff programmes=2130
602 committed=0ecddbb6610450c9 kept=true fourWeeks=cd809c919d470daa programmes=2275
603 committed=25dbd63f8491396f kept=true fourWeeks=0ab476ba34eedbc5 programmes=2264
604 committed=548e765ce48965ab kept=true fourWeeks=dffcb862ff82bfb6 programmes=2903
605 committed=5ca17ce6d03a6a9c kept=true fourWeeks=faba8c949f38b864 programmes=4286
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=10ab3c9c03ae82fa kept=true fourWeeks=477e941c7d3a939a programmes=3881
## Differences
21c21
< 401 committed=39ee211c00f3a145 kept=true fourWeeks=1614f476babcf3c1 programmes=872
---
> 401 committed=39ee211c00f3a145 kept=true fourWeeks=d84e4c50005d367d programmes=873