Form EL-2 · submitted 1 August 2026

LOOK

by otis

Rating

66.5

No. 1

Per building

The Quiet Annexe

53.1

Morning Rush

84.4

Home Time

60.5

Lunch

62.4

The Tower

82.4

Bad Monday

56.0

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

84.4
How long people waited

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

0–15s: 208 people15–30s: 60 people30–45s: 7 people45–60s: 0 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+208
Table
WaitPeopleShare
0–15s20875.6%
15–30s6021.8%
30–45s72.5%
Wait time through the run

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

p50p90people waiting
p50p9036s0s870s
Table
Atp50p90Waiting
0s1.0s1.0s0
30s1.4s3.3s0
60s1.5s6.5s2
90s0.7s5.1s0
120s1.0s8.9s1
150s1.3s6.4s1
180s10.6s13.4s8
210s10.9s19.6s4
240s5.4s12.9s7
270s12.2s21.4s5
300s4.2s18.3s9
330s16.9s21.6s5
360s2.3s20.2s0
390s6.5s11.6s3
420s4.7s8.9s4
450s7.0s13.4s1
480s5.8s9.9s16
510s15.4s25.8s12
540s29.1s32.6s11
570s15.0s35.6s1
600s6.2s15.2s1
630s1.0s3.0s0
660s6.2s7.7s1
690s3.3s9.1s0
720s1.0s4.0s0
750s0.0s0.0s0
780s0.0s0.0s0
810s0.0s0.0s0
840s0.0s0.0s0
870s0.0s0.0s0

First seed in full

People
275
Under 30s
97.5%
Under 90s
100.0%
Median wait
7.4s
p90 wait
21.4s
p99 wait
35.0s
Longest wait
39.0s
Median journey
25.6s
Never served
0
Passed by a full car
66
Empty runs
23
Mean occupancy
2.2

Home Time

60.5
How long people waited

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

0–15s: 183 people15–30s: 38 people30–45s: 9 people45–60s: 21 people60–75s: 4 people75–90s: 1 people90–105s: 4 people105–120s: 1 people120–135s: 2 people135–150s: 10 people150–165s: 0 people165–180s: 0 people180–195s: 3 people195–210s: 1 people210–225s: 0 people225–240s: 1 people240–255s: 2 people255–270s: 1 people270–285s: 0 people285–300s: 2 peopleover 300s: 18 people30s90s0s300s+183
Table
WaitPeopleShare
0–15s18360.8%
15–30s3812.6%
30–45s93.0%
45–60s217.0%
60–75s41.3%
75–90s10.3%
90–105s41.3%
105–120s10.3%
120–135s20.7%
135–150s103.3%
180–195s31.0%
195–210s10.3%
225–240s10.3%
240–255s20.7%
255–270s10.3%
285–300s20.7%
300s+186.0%
Wait time through the run

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

p50p90people waiting
p50p90541s0s870s
Table
Atp50p90Waiting
0s2.9s2.9s1
30s6.3s9.2s2
60s5.8s28.2s1
90s3.2s3.7s0
120s5.0s11.9s2
150s4.7s12.5s3
180s5.6s14.7s12
210s5.2s7.5s18
240s8.3s54.5s21
270s9.7s26.3s25
300s6.4s11.6s30
330s7.6s56.3s29
360s20.5s184.2s26
390s7.9s33.9s27
420s12.1s49.1s28
450s7.2s25.1s27
480s14.0s15.4s37
510s27.6s339.6s33
540s19.5s50.7s36
570s27.5s67.9s34
600s4.8s41.2s31
630s20.6s141.8s28
660s8.2s10.7s32
690s43.9s431.7s27
720s9.5s59.3s21
750s254.0s540.8s0
780s0.0s0.0s0
810s0.0s0.0s0
840s0.0s0.0s0
870s0.0s0.0s0

First seed in full

People
301
Under 30s
73.4%
Under 90s
85.0%
Median wait
9.4s
p90 wait
144.3s
p99 wait
540.2s
Longest wait
560.4s
Median journey
23.3s
Never served
0
Passed by a full car
25
Empty runs
24
Mean occupancy
1.6

Lunch

62.4
How long people waited

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

0–15s: 183 people15–30s: 56 people30–45s: 43 people45–60s: 36 people60–75s: 18 people75–90s: 21 people90–105s: 7 people105–120s: 4 people120–135s: 1 people135–150s: 1 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+183
Table
WaitPeopleShare
0–15s18349.5%
15–30s5615.1%
30–45s4311.6%
45–60s369.7%
60–75s184.9%
75–90s215.7%
90–105s71.9%
105–120s41.1%
120–135s10.3%
135–150s10.3%
Wait time through the run

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

p50p90people waiting
p50p90120s0s1170s
Table
Atp50p90Waiting
0s4.3s6.8s1
30s6.4s14.6s2
60s7.8s11.7s1
90s3.4s6.4s5
120s7.8s32.4s5
150s14.2s34.9s2
180s4.5s14.4s2
210s6.9s33.8s6
240s7.1s67.4s2
270s9.5s21.0s1
300s2.9s14.2s7
330s9.7s25.7s6
360s17.0s18.0s8
390s9.5s73.2s9
420s14.1s46.6s0
450s6.3s10.0s7
480s7.8s20.6s2
510s3.4s41.4s36
540s9.8s23.2s29
570s46.2s55.4s39
600s77.0s82.5s32
630s46.7s102.3s22
660s81.1s96.2s12
690s4.9s41.0s21
720s81.9s119.5s20
750s48.2s69.2s15
780s23.8s46.7s20
810s50.1s73.9s10
840s20.6s46.9s14
870s33.0s40.5s7
900s12.9s52.9s5
930s2.2s22.7s2
960s4.6s14.0s4
990s14.9s25.7s1
1020s5.1s6.6s7
1050s14.7s32.8s0
1080s0.0s0.0s0
1110s0.0s0.0s0
1140s0.0s0.0s0
1170s0.0s0.0s0

First seed in full

People
370
Under 30s
64.6%
Under 90s
96.5%
Median wait
15.0s
p90 wait
73.8s
p99 wait
115.9s
Longest wait
144.6s
Median journey
30.2s
Never served
0
Passed by a full car
77
Empty runs
26
Mean occupancy
2.3

The Tower

82.4
How long people waited

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

0–15s: 383 people15–30s: 160 people30–45s: 40 people45–60s: 18 people60–75s: 7 people75–90s: 7 people90–105s: 4 people105–120s: 0 people120–135s: 2 people135–150s: 2 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+383
Table
WaitPeopleShare
0–15s38361.5%
15–30s16025.7%
30–45s406.4%
45–60s182.9%
60–75s71.1%
75–90s71.1%
90–105s40.6%
120–135s20.3%
135–150s20.3%
Wait time through the run

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

p50p90people waiting
p50p90125s0s1170s
Table
Atp50p90Waiting
0s2.8s13.0s2
30s3.4s13.5s10
60s12.6s27.4s4
90s3.9s16.8s8
120s6.1s12.0s8
150s11.2s25.6s13
180s9.0s76.8s10
210s13.4s25.5s17
240s12.7s125.0s23
270s40.9s92.3s18
300s30.8s43.0s12
330s6.9s16.9s21
360s19.8s65.0s10
390s16.5s79.1s12
420s10.8s27.1s4
450s5.4s19.2s6
480s7.7s29.4s0
510s6.7s16.4s10
540s10.6s24.4s2
570s10.7s21.5s14
600s19.9s27.6s6
630s5.5s26.8s6
660s13.0s39.0s2
690s4.3s18.5s7
720s10.2s38.1s3
750s1.4s11.1s11
780s9.1s17.8s12
810s15.9s63.1s13
840s7.2s15.2s6
870s15.3s39.2s15
900s14.6s26.8s7
930s7.3s29.4s13
960s13.0s30.6s12
990s14.1s49.9s10
1020s17.9s101.3s19
1050s18.9s28.7s0
1080s0.0s0.0s0
1110s0.0s0.0s0
1140s0.0s0.0s0
1170s0.0s0.0s0

First seed in full

People
623
Under 30s
87.2%
Under 90s
98.7%
Median wait
11.3s
p90 wait
38.1s
p99 wait
96.5s
Longest wait
142.9s
Median journey
35.5s
Never served
0
Passed by a full car
40
Empty runs
97
Mean occupancy
2.4

Bad Monday

56.0
How long people waited

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

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

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

p50p90people waiting
p50p90308s0s870s
Table
Atp50p90Waiting
0s1.0s1.0s0
30s1.4s1.6s3
60s8.5s15.0s1
90s3.9s6.5s0
120s1.0s5.1s0
150s7.7s12.1s3
180s5.4s9.4s0
210s2.4s15.6s0
240s1.0s1.6s2
270s5.8s10.6s6
300s9.4s16.1s5
330s9.0s28.5s16
360s39.3s54.5s1
390s8.7s30.4s5
420s19.0s44.8s2
450s11.2s11.8s3
480s6.4s18.6s4
510s20.7s26.6s9
540s27.4s32.9s6
570s27.9s27.9s13
600s13.6s13.6s17
630s0.0s0.0s23
660s0.0s0.0s29
690s0.0s0.0s32
720s0.0s0.0s34
750s0.0s0.0s34
780s0.0s0.0s34
810s144.8s164.2s32
840s290.8s307.9s22
870s260.8s282.4s14

First seed in full

People
163
Under 30s
66.9%
Under 90s
79.1%
Median wait
14.1s
p90 wait
255.6s
p99 wait
307.9s
Longest wait
308.9s
Median journey
37.2s
Never served
21
Passed by a full car
44
Empty runs
14
Mean occupancy
2.3

The algorithm

7966 bytes

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

// LOOK — the classic lift algorithm, and the one your office probably runs.//// Every car is sweeping in one direction. It serves each stop ahead of it on that// leg, then turns round and sweeps back. The name is the important part: unlike// SCAN, which runs to the end of the shaft whether or not anybody is up there, LOOK// only goes as far as the furthest outstanding request before reversing.//// The rule that makes it work — and the one people miss — is that a hall call is// only answered on the leg going the caller's way. Stopping for a down-call on the// way up just opens the doors at somebody who does not want to get in.//// Note what you are not given: how many people are waiting anywhere, and how many// are in any car. A lit button is a lit button. Working around that is the job.const SECONDS_PER_FLOOR = 0.6const SECONDS_PER_STOP = 5const CROWDING_PENALTY = 6export default {  setup(building) {    this.top = building.floors - 1    this.heading = new Map()  },  tick(world) {    const cars = world.elevators.filter((car) => !car.outOfService)    if (cars.length === 0) return    // Hand each lit button to whichever car reaches it soonest on the sweep it is    // already committed to.    const work = new Map()    for (const car of cars) work.set(car.id, [])    for (const call of world.calls) {      let best = null      let bestCost = Infinity      // If every car is over its cap the call still has to go somewhere, or in a      // one-lift building nothing would ever be assigned and the car would sit      // there while the lobby filled up.      let fallback = null      let fallbackCommitted = Infinity      for (const car of cars) {        // Do not keep piling work onto a car that already has plenty. Without a load        // reading this is the only brake there is, and without a brake one car ends        // up promising the whole building while the others sit idle.        const committed = this.busy(car) + work.get(car.id).length        if (committed < fallbackCommitted) {          fallbackCommitted = committed          fallback = car        }        if (committed >= car.capacity) continue        const cost =          this.reach(car.floor, this.heading.get(car.id), call.floor, call.direction) *            SECONDS_PER_FLOOR +          work.get(car.id).length * SECONDS_PER_STOP +          committed * CROWDING_PENALTY        if (cost < bestCost) {          bestCost = cost          best = car        }      }      const chosen = best || fallback      if (chosen) work.get(chosen.id).push(call)    }    for (const car of cars) this.drive(car, work.get(car.id), cars)  },  // How busy a car looks.  //  // Nothing anywhere tells you how many people are in a lift — that is the point of  // the game. The closest honest signal is how many different floors the people  // inside have already asked for: eight people all going to the fifth floor look  // like one stop, and they are. It is a lower bound, and it is enough to stop you  // loading every call onto the same car.  busy(car) {    return car.pressed.length  },  // 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      // We carry on up, turn round at the top of the run, and collect them coming back.      if (wants === 'down') return this.top - position + (this.top - floor)      // Behind us and going our way: they wait for the whole round trip.      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)  },  drive(car, calls, cars) {    const here = car.atFloor    const loading = car.doors !== 'closed'    // Two kinds of stop: somebody inside wants off here, or somebody outside wants    // on. Only the second kind cares which way the car is going.    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 })    // Never re-queue the floor the car is standing at with its doors open. It would    // shut them and immediately open them again for nobody.    const pending = stops.filter((s) => !(loading && here !== null && s.floor === here))    if (pending.length === 0) {      this.heading.delete(car.id)      car.indicate(null)      // An idle car is worth more at the lobby than wherever it happened to finish.      // One car covers it; the rest stay put rather than all stampeding downstairs.      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))  },  // Order the stops into legs: everything this way, then everything back, and so on.  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++) {      // This is the LOOK part: turn round at the furthest request, not at the end      // of the shaft.      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 }  },  // Which way to point the lantern: the direction the car will travel *from the stop  // it is about to make*, not the direction it is travelling now.  lantern(here, loading, plan, stops) {    const anchor = loading && here !== null ? here : plan.queue[0]    if (anchor === undefined) return plan.heading    // There is no down from the ground, and no up from the top.    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    // If the car is going there to answer a hall call, it has to show that call's    // direction — otherwise the people it came for will not get in, the call stays    // lit, and it will keep coming back and keep not collecting them.    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  },}