Form EL-2 · submitted 1 August 2026

RSR

by kone

Rating

64.4

No. 2

Per building

The Quiet Annexe

53.1

Morning Rush

75.4

Home Time

50.7

Lunch

79.0

The Tower

63.4

Bad Monday

64.9

The Quiet Annexe

53.1
How long people waited

Everyone in the run, bucketed. The lines are the two thresholds you are scored on.

0–15s: 9 people15–30s: 9 people30–45s: 7 people45–60s: 5 people60–75s: 3 people75–90s: 0 people90–105s: 1 people105–120s: 0 people120–135s: 0 people135–150s: 0 people150–165s: 2 people165–180s: 0 people180–195s: 1 people195–210s: 4 people210–225s: 1 people225–240s: 3 people240–255s: 1 people255–270s: 1 people270–285s: 3 people285–300s: 0 peopleover 300s: 7 people30s90s0s300s+9
Table
WaitPeopleShare
0–15s915.8%
15–30s915.8%
30–45s712.3%
45–60s58.8%
60–75s35.3%
90–105s11.8%
150–165s23.5%
180–195s11.8%
195–210s47.0%
210–225s11.8%
225–240s35.3%
240–255s11.8%
255–270s11.8%
270–285s35.3%
300s+712.3%
Wait time through the run

Median and 90th percentile of how long each person waited, by the minute they got in.

p50p90people waiting
p50p9091s0s570s
Table
Atp50p90Waiting
0s6.0s6.0s2
30s15.1s22.3s3
60s24.4s34.8s0
90s6.9s6.9s4
120s4.4s4.4s6
150s55.1s67.4s3
180s2.2s11.6s7
210s38.8s91.1s10
240s36.6s60.0s5
270s0.0s0.0s7
300s0.0s0.0s10
330s0.0s0.0s12
360s0.0s0.0s16
390s0.0s0.0s21
420s0.0s0.0s23
450s0.0s0.0s23
480s0.0s0.0s23
510s0.0s0.0s23
540s0.0s0.0s23
570s0.0s0.0s23

First seed in full

People
57
Under 30s
31.6%
Under 90s
57.9%
Median wait
52.7s
p90 wait
327.5s
p99 wait
362.4s
Longest wait
362.4s
Median journey
180.3s
Never served
31
Passed by a full car
1
Empty runs
0
Mean occupancy
2.4

Morning Rush

75.4
How long people waited

Everyone in the run, bucketed. The lines are the two thresholds you are scored on.

0–15s: 194 people15–30s: 63 people30–45s: 16 people45–60s: 2 people60–75s: 0 people75–90s: 0 people90–105s: 0 people105–120s: 0 people120–135s: 0 people135–150s: 0 people150–165s: 0 people165–180s: 0 people180–195s: 0 people195–210s: 0 people210–225s: 0 people225–240s: 0 people240–255s: 0 people255–270s: 0 people270–285s: 0 people285–300s: 0 peopleover 300s: 0 people30s90s0s300s+194
Table
WaitPeopleShare
0–15s19470.5%
15–30s6322.9%
30–45s165.8%
45–60s20.7%
Wait time through the run

Median and 90th percentile of how long each person waited, by the minute they got in.

p50p90people waiting
p50p9044s0s870s
Table
Atp50p90Waiting
0s1.0s1.0s0
30s1.4s3.6s0
60s1.2s6.5s2
90s0.7s5.1s0
120s1.0s4.7s1
150s1.0s6.4s1
180s11.4s14.7s9
210s10.5s16.3s2
240s4.3s10.4s8
270s16.7s22.4s7
300s19.7s27.4s7
330s7.9s14.8s19
360s37.7s44.2s12
390s12.7s18.7s7
420s10.2s28.5s6
450s9.5s17.6s1
480s11.5s16.1s9
510s13.2s17.8s12
540s18.8s22.3s4
570s6.8s10.5s2
600s6.4s7.8s0
630s3.2s9.3s0
660s1.0s4.2s3
690s5.3s9.4s3
720s5.6s10.7s0
750s0.0s0.0s0
780s0.0s0.0s0
810s0.0s0.0s0
840s0.0s0.0s0
870s0.0s0.0s0

First seed in full

People
275
Under 30s
93.5%
Under 90s
100.0%
Median wait
9.6s
p90 wait
23.6s
p99 wait
44.2s
Longest wait
46.1s
Median journey
26.6s
Never served
0
Passed by a full car
96
Empty runs
16
Mean occupancy
2.5

Home Time

50.7
How long people waited

Everyone in the run, bucketed. The lines are the two thresholds you are scored on.

0–15s: 136 people15–30s: 32 people30–45s: 22 people45–60s: 18 people60–75s: 12 people75–90s: 7 people90–105s: 8 people105–120s: 7 people120–135s: 5 people135–150s: 1 people150–165s: 6 people165–180s: 5 people180–195s: 7 people195–210s: 6 people210–225s: 5 people225–240s: 9 people240–255s: 2 people255–270s: 0 people270–285s: 3 people285–300s: 2 peopleover 300s: 8 people30s90s0s300s+136
Table
WaitPeopleShare
0–15s13645.2%
15–30s3210.6%
30–45s227.3%
45–60s186.0%
60–75s124.0%
75–90s72.3%
90–105s82.7%
105–120s72.3%
120–135s51.7%
135–150s10.3%
150–165s62.0%
165–180s51.7%
180–195s72.3%
195–210s62.0%
210–225s51.7%
225–240s93.0%
240–255s20.7%
270–285s31.0%
285–300s20.7%
300s+82.7%
Wait time through the run

Median and 90th percentile of how long each person waited, by the minute they got in.

p50p90people waiting
p50p90413s0s870s
Table
Atp50p90Waiting
0s2.9s2.9s1
30s6.4s9.3s3
60s9.2s42.2s1
90s5.1s8.2s1
120s5.3s10.6s1
150s8.0s10.5s2
180s8.3s14.9s16
210s11.1s37.9s14
240s16.5s49.8s14
270s16.0s95.2s16
300s14.2s89.2s25
330s11.9s65.7s27
360s48.0s64.6s27
390s8.8s49.7s24
420s46.6s202.8s16
450s5.2s5.6s20
480s5.8s8.0s31
510s37.7s154.2s31
540s17.7s57.2s36
570s7.3s7.9s45
600s19.0s85.9s42
630s13.8s102.0s46
660s179.3s234.9s48
690s51.2s236.0s47
720s53.6s307.2s39
750s110.2s313.1s25
780s231.3s239.6s19
810s91.3s91.3s18
840s244.3s413.0s10
870s195.7s239.4s0

First seed in full

People
301
Under 30s
55.8%
Under 90s
75.4%
Median wait
20.3s
p90 wait
202.8s
p99 wait
336.5s
Longest wait
413.0s
Median journey
49.1s
Never served
6
Passed by a full car
32
Empty runs
32
Mean occupancy
2.3

Lunch

79.0
How long people waited

Everyone in the run, bucketed. The lines are the two thresholds you are scored on.

0–15s: 230 people15–30s: 93 people30–45s: 21 people45–60s: 17 people60–75s: 2 people75–90s: 1 people90–105s: 1 people105–120s: 0 people120–135s: 0 people135–150s: 5 people150–165s: 0 people165–180s: 0 people180–195s: 0 people195–210s: 0 people210–225s: 0 people225–240s: 0 people240–255s: 0 people255–270s: 0 people270–285s: 0 people285–300s: 0 peopleover 300s: 0 people30s90s0s300s+230
Table
WaitPeopleShare
0–15s23062.2%
15–30s9325.1%
30–45s215.7%
45–60s174.6%
60–75s20.5%
75–90s10.3%
90–105s10.3%
135–150s51.4%
Wait time through the run

Median and 90th percentile of how long each person waited, by the minute they got in.

p50p90people waiting
p50p90146s0s1170s
Table
Atp50p90Waiting
0s2.9s6.8s1
30s5.9s6.9s3
60s6.4s12.2s1
90s6.8s15.6s5
120s11.4s22.0s6
150s22.3s26.0s3
180s11.1s27.9s7
210s16.3s36.5s6
240s7.8s33.1s1
270s4.1s10.0s0
300s2.9s5.1s8
330s14.2s25.6s5
360s11.2s17.3s5
390s15.4s47.1s4
420s10.1s19.7s0
450s4.1s15.5s4
480s12.9s29.1s1
510s4.7s12.2s38
540s8.7s28.0s20
570s34.4s49.7s30
600s26.7s87.8s20
630s50.6s62.9s7
660s139.3s146.1s4
690s4.4s28.4s6
720s5.0s21.6s2
750s11.4s14.8s4
780s8.6s26.5s3
810s6.7s19.3s6
840s6.8s13.7s6
870s24.1s33.0s4
900s6.2s24.9s3
930s1.6s12.6s2
960s4.2s11.5s5
990s9.2s19.2s1
1020s3.4s5.1s6
1050s12.6s23.2s0
1080s0.0s0.0s0
1110s0.0s0.0s0
1140s0.0s0.0s0
1170s0.0s0.0s0

First seed in full

People
370
Under 30s
87.3%
Under 90s
98.4%
Median wait
10.1s
p90 wait
33.7s
p99 wait
139.9s
Longest wait
146.1s
Median journey
25.6s
Never served
0
Passed by a full car
33
Empty runs
13
Mean occupancy
2.3

The Tower

63.4
How long people waited

Everyone in the run, bucketed. The lines are the two thresholds you are scored on.

0–15s: 377 people15–30s: 154 people30–45s: 49 people45–60s: 27 people60–75s: 8 people75–90s: 2 people90–105s: 3 people105–120s: 1 people120–135s: 0 people135–150s: 0 people150–165s: 2 people165–180s: 0 people180–195s: 0 people195–210s: 0 people210–225s: 0 people225–240s: 0 people240–255s: 0 people255–270s: 0 people270–285s: 0 people285–300s: 0 peopleover 300s: 0 people30s90s0s300s+377
Table
WaitPeopleShare
0–15s37760.5%
15–30s15424.7%
30–45s497.9%
45–60s274.3%
60–75s81.3%
75–90s20.3%
90–105s30.5%
105–120s10.2%
150–165s20.3%
Wait time through the run

Median and 90th percentile of how long each person waited, by the minute they got in.

p50p90people waiting
p50p9097s0s1170s
Table
Atp50p90Waiting
0s1.0s6.8s4
30s9.1s21.1s7
60s6.7s12.2s4
90s7.7s43.5s9
120s3.2s12.6s6
150s15.2s45.5s8
180s4.9s15.3s14
210s12.8s30.7s22
240s19.9s36.1s16
270s1.9s96.8s11
300s29.3s92.0s15
330s10.0s41.7s14
360s26.9s49.1s13
390s13.9s38.5s8
420s12.1s21.8s3
450s5.0s12.5s3
480s4.6s8.2s2
510s5.7s25.2s7
540s8.3s26.8s3
570s8.0s32.3s15
600s11.4s27.2s5
630s10.8s42.9s11
660s14.3s37.4s3
690s5.4s19.1s6
720s15.2s38.5s6
750s7.8s26.1s7
780s5.7s15.2s12
810s5.2s39.9s10
840s11.7s60.6s13
870s21.9s53.9s15
900s11.0s20.1s17
930s22.4s48.9s17
960s13.6s28.4s13
990s18.9s27.6s16
1020s15.4s44.2s12
1050s23.2s27.6s1
1080s0.0s0.0s1
1110s0.0s0.0s1
1140s0.0s0.0s1
1170s0.0s0.0s1

First seed in full

People
623
Under 30s
85.2%
Under 90s
99.0%
Median wait
11.2s
p90 wait
36.7s
p99 wait
79.0s
Longest wait
152.2s
Median journey
39.1s
Never served
9
Passed by a full car
47
Empty runs
16
Mean occupancy
2.9

Bad Monday

64.9
How long people waited

Everyone in the run, bucketed. The lines are the two thresholds you are scored on.

0–15s: 67 people15–30s: 8 people30–45s: 4 people45–60s: 9 people60–75s: 14 people75–90s: 9 people90–105s: 10 people105–120s: 8 people120–135s: 1 people135–150s: 1 people150–165s: 2 people165–180s: 0 people180–195s: 2 people195–210s: 3 people210–225s: 3 people225–240s: 3 people240–255s: 10 people255–270s: 3 people270–285s: 5 people285–300s: 0 peopleover 300s: 1 people30s90s0s300s+67
Table
WaitPeopleShare
0–15s6741.1%
15–30s84.9%
30–45s42.5%
45–60s95.5%
60–75s148.6%
75–90s95.5%
90–105s106.1%
105–120s84.9%
120–135s10.6%
135–150s10.6%
150–165s21.2%
180–195s21.2%
195–210s31.8%
210–225s31.8%
225–240s31.8%
240–255s106.1%
255–270s31.8%
270–285s53.1%
300s+10.6%
Wait time through the run

Median and 90th percentile of how long each person waited, by the minute they got in.

p50p90people waiting
p50p90368s0s870s
Table
Atp50p90Waiting
0s1.0s1.0s0
30s1.4s1.6s4
60s7.1s14.2s1
90s4.2s6.8s0
120s1.0s5.1s0
150s1.6s6.4s2
180s9.3s14.7s4
210s10.7s19.5s1
240s6.4s9.1s1
270s4.2s12.1s4
300s3.2s32.0s9
330s21.3s30.1s19
360s56.5s74.3s12
390s11.0s32.7s16
420s8.8s8.8s22
450s104.7s115.8s10
480s12.2s51.4s18
510s99.5s101.2s19
540s62.1s69.5s15
570s7.7s8.6s21
600s87.0s90.5s18
630s0.0s0.0s24
660s0.0s0.0s30
690s0.0s0.0s33
720s0.0s0.0s35
750s117.0s117.0s34
780s142.3s254.0s31
810s265.4s367.6s22
840s269.5s271.8s18
870s233.9s245.6s6

First seed in full

People
163
Under 30s
46.0%
Under 90s
68.1%
Median wait
52.4s
p90 wait
245.4s
p99 wait
282.6s
Longest wait
367.6s
Median journey
71.5s
Never served
12
Passed by a full car
119
Empty runs
8
Mean occupancy
2.4

The algorithm

11634 bytes

Every submission is public. That is the point — the interesting part is how somebody else solved it.

// RSR — Relative System Response, the algorithm Otis patented.//// LOOK decides what order one car visits its stops in. RSR is a different kind of// idea: it decides *which car goes*, by scoring every car against every lit button// and taking the lowest score. The stop ordering underneath is still a LOOK sweep.////   score = time to reach the caller//         + penalty for how busy the car already looks//         + penalty if another car is already going there the same way//         - bonus if the car is already heading that way and the call is ahead of it//         - bonus if the car is idle and nearby//         - bonus if the car has nothing aboard//// Lower is better. The two penalties are what stop every car chasing the same call// and arriving in a convoy, which is the failure mode of naive nearest-car.//// Nothing tells you how many people are in a lift, or how many are waiting. But a// full car leaves a trace: it answers a call, and the button comes straight back on.// This one watches for that and stops trusting a car for a while afterwards, which// is most of what a load sensor would have bought it.//// The whole assignment is recomputed every few seconds rather than every tick, so a// call can be handed to a better car as conditions change — but not so often that// cars thrash between destinations and never arrive anywhere.const SECONDS_PER_FLOOR = 0.6const SECONDS_PER_STOP = 5const BUSY_PENALTY = 1.8          // per floor already requested from insideconst BUNCHING_PENALTY = 22       // another car is already going there, the same wayconst DIRECTION_BONUS = 9         // already sweeping that way, and the call is aheadconst IDLE_NEARBY_BONUS = 14      // sitting doing nothing within two floorsconst EMPTY_BONUS = 6             // nothing aboard, so it will not fill up en routeconst REOPTIMISE_EVERY = 5        // secondsconst DISTRUST_SECONDS = 12       // how long to doubt a car we think just filled upconst DISTRUST_PENALTY = 40       // heavy, but not a ban — it may still be the only carexport default {  setup(building) {    this.top = building.floors - 1    this.heading = new Map()    this.assigned = new Map()    // "floor:direction" -> car id    this.served = new Map()      // car id -> the call it has just answered    this.distrust = new Map()    // car id -> time until we stop believing it has room    this.lastPass = -Infinity  },  // Remember what this car just answered, so the next few ticks can tell us whether  // it actually cleared the landing or filled up and left people standing.  stop({ elevator, floor, car }, world) {    this.served.set(elevator, { floor, direction: car.indicated, at: world.time })  },  tick(world) {    const cars = world.elevators.filter((car) => !car.outOfService)    if (cars.length === 0) return    const byId = new Map(cars.map((car) => [car.id, car]))    const live = new Set(world.calls.map((call) => call.floor + ':' + call.direction))    // Did anybody we just served put their button straight back on? If so that car    // went away full, and we should stop offering it work for a bit.    for (const [carId, stop] of [...this.served]) {      if (world.time - stop.at > 2) {        this.served.delete(carId)        continue      }      if (stop.direction && live.has(stop.floor + ':' + stop.direction)) {        this.distrust.set(carId, world.time + DISTRUST_SECONDS)        this.served.delete(carId)      }    }    // Forget calls that have been answered, and cars that have gone out of service.    for (const key of [...this.assigned.keys()]) {      if (!live.has(key) || !byId.has(this.assigned.get(key))) this.assigned.delete(key)    }    // A full re-optimisation clears the slate; otherwise only brand new calls get    // assigned, and everything already promised to a car stays promised.    const reoptimise = world.time - this.lastPass >= REOPTIMISE_EVERY    if (reoptimise) {      this.lastPass = world.time      this.assigned.clear()    }    for (const call of world.calls) {      const key = call.floor + ':' + call.direction      if (this.assigned.has(key)) continue      let best = null      let bestScore = Infinity      let fallback = null      let fallbackLoad = Infinity      for (const car of cars) {        const committed = car.pressed.length + this.promised(car.id)        if (committed < fallbackLoad) {          fallbackLoad = committed          fallback = car        }        // Do not keep loading a car that has already promised a building's worth of        // work. With no load reading, this cap is the only brake there is.        if (committed >= car.capacity) continue        const score = this.score(car, call, cars, world)        if (score < bestScore) {          bestScore = score          best = car        }      }      // Somebody has to go, even if everybody is busy.      const chosen = best || fallback      if (chosen) this.assigned.set(key, chosen.id)    }    const work = new Map()    for (const car of cars) work.set(car.id, [])    for (const call of world.calls) {      const carId = this.assigned.get(call.floor + ':' + call.direction)      if (carId !== undefined && work.has(carId)) work.get(carId).push(call)    }    for (const car of cars) this.drive(car, work.get(car.id), cars)  },  // How many calls we have already promised this car in the current assignment.  promised(carId) {    let count = 0    for (const id of this.assigned.values()) if (id === carId) count++    return count  },  score(car, call, cars, world) {    const heading = this.heading.get(car.id)    const floors = this.reach(car.floor, heading, call.floor, call.direction)    let score = floors * SECONDS_PER_FLOOR    // We watched this one answer a call and the button came straight back on, so we    // think it went away full. A heavy penalty rather than a ban: in a quiet    // building it may still be the only car that can go, and refusing outright    // strands people rather than merely delaying them.    if ((this.distrust.get(car.id) || 0) > world.time) {      if (car.pressed.length === 0) this.distrust.delete(car.id)      else score += DISTRUST_PENALTY    }    // Every floor already requested from inside is a stop this car has to make    // before it gets anywhere else. It is the closest thing to a load reading you    // are given, and it is a lower bound rather than a count.    score += car.pressed.length * BUSY_PENALTY    // Anti-bunching: if somebody else is already going there the same way, a second    // car usually buys nothing and costs a lift somewhere else in the building.    //    // Tempting to fade this out for calls that have been lit a long time, on the    // theory that a persistent button means one car cannot cope. Measured: it makes    // the morning peak markedly worse, because the cars pile into the lobby and the    // rest of the building starves. Left alone deliberately.    for (const other of cars) {      if (other.id === car.id) continue      const goingThere = other.queue.includes(call.floor)      if (goingThere && this.heading.get(other.id) === call.direction) {        score += BUNCHING_PENALTY        break      }    }    // Already sweeping that way with the call ahead of it: nearly free to serve.    const ahead =      heading === 'up'        ? call.floor >= car.floor        : heading === 'down'          ? call.floor <= car.floor          : false    if (heading === call.direction && ahead) score -= DIRECTION_BONUS    // An idle car two floors away is the best possible answer to a hall call.    if (!heading && car.queue.length === 0 && Math.abs(car.floor - call.floor) <= 2) {      score -= IDLE_NEARBY_BONUS    }    // Empty, so it will not fill up before it gets there.    if (car.pressed.length === 0) score -= EMPTY_BONUS    return score  },  // How far this car has to travel, on its current sweep, to answer that call.  reach(position, heading, floor, wants) {    if (!heading) return Math.abs(position - floor)    if (heading === 'up') {      if (floor >= position && wants === 'up') return floor - position      if (wants === 'down') return this.top - position + (this.top - floor)      return this.top - position + this.top + floor    }    if (floor <= position && wants === 'down') return position - floor    if (wants === 'up') return position + floor    return position + this.top + (this.top - floor)  },  // Everything below is the same LOOK sweep. RSR chooses the car; the car still has  // to decide what order to visit things in, and LOOK is the right answer to that.  drive(car, calls, cars) {    const here = car.atFloor    const loading = car.doors !== 'closed'    const stops = []    for (const floor of car.pressed) stops.push({ floor, kind: 'car' })    for (const call of calls) stops.push({ floor: call.floor, kind: call.direction })    const pending = stops.filter((s) => !(loading && here !== null && s.floor === here))    if (pending.length === 0) {      this.heading.delete(car.id)      car.indicate(null)      const lobbyCovered = cars.some(        (other) => other.id !== car.id && (other.floor < 0.5 || other.queue[0] === 0),      )      if (!lobbyCovered && car.floor > 0.5) car.goTo(0)      return    }    let start = this.heading.get(car.id)    if (!start) {      let nearest = Infinity      for (const stop of pending) {        const distance = Math.abs(stop.floor - car.floor)        if (distance < nearest) {          nearest = distance          start = stop.floor >= car.floor ? 'up' : 'down'        }      }    }    const plan = this.sweep(car.floor, start, pending)    this.heading.set(car.id, plan.heading)    car.setQueue(plan.queue)    car.indicate(this.lantern(here, loading, plan, pending))  },  sweep(position, start, stops) {    const left = stops.slice()    const order = []    let cursor = position    let heading = start    let leading = null    for (let leg = 0; leg < 4 && left.length > 0; leg++) {      const floors = left.map((s) => s.floor)      const reach = heading === 'up' ? Math.max(cursor, ...floors) : Math.min(cursor, ...floors)      const taken = left.filter((s) => {        const wanted = s.kind === 'car' || s.kind === heading        const onTheWay = heading === 'up' ? s.floor >= cursor - 0.001 : s.floor <= cursor + 0.001        return wanted && onTheWay      })      taken.sort((a, b) => (heading === 'up' ? a.floor - b.floor : b.floor - a.floor))      if (taken.length > 0 && !leading) leading = heading      for (const stop of taken) {        left.splice(left.indexOf(stop), 1)        if (!order.includes(stop.floor)) order.push(stop.floor)      }      cursor = reach      heading = heading === 'up' ? 'down' : 'up'    }    for (const stop of left) if (!order.includes(stop.floor)) order.push(stop.floor)    return { queue: order, heading: leading || start }  },  lantern(here, loading, plan, stops) {    const anchor = loading && here !== null ? here : plan.queue[0]    if (anchor === undefined) return plan.heading    if (anchor <= 0) return 'up'    if (anchor >= this.top) return 'down'    const next = loading && here !== null ? plan.queue[0] : plan.queue[1]    const onward = next !== undefined && next !== anchor ? (next > anchor ? 'up' : 'down') : null    const wanted = new Set()    for (const stop of stops) {      if (stop.floor === anchor && stop.kind !== 'car') wanted.add(stop.kind)    }    if (wanted.size === 1) return [...wanted][0]    return onward || plan.heading  },}