Find an empty classroom near you at Ohio State, free for as long as you need it.
It is a web page. No account, no search box, nothing to sign up for. It finds you, asks one question, and hands you rooms you can walk to, nearest first.
![]() |
![]() |
![]() |
| One question. | The answer. | Where it is. |
Every other tool answers "is this room free right now". That is the wrong question, because the room is not where you are standing.
Vacant answers how long the room is yours once you get there. It takes the gap in the room's schedule, subtracts the walk, and leaves ten minutes at the end so you are not packing up while the next class files in. Walk time is straight line distance times 1.3 for the fact that campus paths bend, at 78 metres a minute.
Read the second row of the middle screenshot. Townshend Hall 245 says free till 2:35pm. The next class in that room starts at 2:45pm, you are two minutes away, and 2:35pm is when you have to be packed up.
Tap it and the room screen says the same minute, then spells the rest out: Yours for 4h13 once you get there. The two screens agreeing is the point. They did not for a while: the room screen printed 2:45pm, the raw class start, handing back the ten minutes the row had already taken off (#77).
A row that says no class rest of today instead of a time means no class is coming at all, and the timeline behind it ends where the building locks.
![]() |
![]() |
| Drag the sheet up for the rest. | Tap a room twice for its whole day, doors included. |
You do not have to. But it is built to be installed, and installing it is what makes it answer in a stairwell.
On iPhone, open it in Safari and tap Share, or ... then Share if your tab bar sits at the bottom, then Add to Home Screen. iOS 26 defaults to that Compact layout, which is why the app offers you both. On Android, Chrome has Install app in its menu.
Installed, the whole app is 69 KB of shell and 66 KB of schedule, gzipped, and none of it is fetched again to answer a question. Turn the network off, open it, and it still ranks rooms. That matters because the moment you want it most is the moment you are in a basement with one bar.
Vacant reads the class schedule. A class schedule is not a reservation calendar and it is not a door. Three things are true and the app says all three on screen:
Doors get locked. This is the failure every other tool in this category ships with, and at Ohio State it is avoidable, because the Registrar publishes an open and close time per building per weekday. Measured against the committed index, 578 of the 581 rooms have no Saturday class at all this term. A tool that stops at the schedule calls all 578 of them free on a Saturday. 398 of those sit in a building the Registrar publishes as closed that day, and 123 more sit in a building nobody publishes hours for at all. 57 are in a building that is genuinely open.
Hours are not published everywhere. The Registrar's table covers 47
buildings. The index touches 78. So 125 of the 581 rooms, 22%, are in a building
whose door nobody documents. Those rooms say hours not published and rank below
every room that has real hours. They are never given an assumed window, because
"usually open" is a guess wearing the clothes of a fact.
Clubs book rooms. A club meeting, a review session or a departmental event is invisible to every public source, so it is invisible to Vacant too. Every screen carries that caveat, and #26 is the plan to go and measure how often it bites.
Ohio State publishes, for every class, the room it meets in and the minutes it occupies. Nobody publishes which rooms are empty. But empty is the complement of busy, so the whole product is one inversion of a public dataset. A script walks the class API, drops everything that is not a real Columbus room, and writes a room-keyed list of busy intervals. The page downloads that file, subtracts today's intervals from the building's published opening hours, and ranks what is left by walk time. There is no server, no database, no build step and no dependencies.
Everything the page fetches to produce a ranked list is ten files, 470 KB, or
102 KB over the wire once gzipped. Measured with node:zlib over the tree as
committed.
| Where | What is in it |
|---|---|
index.html |
The whole shell. Markup and CSS, no framework. |
js/app.js |
Three screens in one sheet: the question, the list, one room. |
js/engine.js |
The ranking, and the formula that decides how long a room is yours. |
js/map.js |
The campus map, drawn as vectors on a canvas. No tiles, no key. |
js/campus.js |
Latitude and longitude into map grid space. |
scripts/ |
The harvest, the inversion, and the screenshots. |
data/ |
The committed artefacts the page reads. |
git clone https://github.com/EnesYilmazcode/Vacant.git
cd Vacant
python3 -m http.server 8000 # or any static file serverThen open http://localhost:8000. It has to be served rather than opened as a
file, because the page is ES modules and it fetches JSON.
npm test # node --test, 224 tests, no network
node scripts/shoot.mjs # redraw docs/media from the real appNothing here needs a key or an account. Each of these takes --dry-run.
node scripts/fetch-buildings.mjs # OSU's GIS layer -> data/buildings.json
node scripts/fetch-campus.mjs # campus polygons -> data/campus.json
node scripts/fetch-building-hours.mjs # Registrar table -> data/buildings-hours.json
node scripts/fetch-rooms.mjs 1268 # the class schedule -> data/harvest-1268.json.gz
node scripts/build-index.mjs 1268 # invert it -> data/rooms-1268.jsonThe harvest itself is not committed, because it is regenerated weekly, so a fresh
clone has to run fetch-rooms before build-index. One pass over the schedule is
136 requests against content.osu.edu, sent at about three a second. The
committed Autumn 2026 harvest took four passes and 545 requests, because paging
that API is not deterministic: the harvester repeats until two passes in a row
turn up no meeting in a room it has not already seen. Pass two found thirteen of
them. data/harvest-1268.manifest.json records what every pass saw.
data/raw/1262 and data/raw/1264 are committed and must stay. Those terms have
already left the API and cannot be refetched at any price.
- The list is a room list where it should be a place list. Forty rows came back across thirteen buildings, and one of them took six of them. #62.
- Nobody has walked to a room the app called free and checked. That is the
measurement the whole thing rests on and it has not been taken.
#26 is the walk, and
spikes/walk.htmlis the checklist to walk it with.
Roomix is the incumbent at Ohio State and deserves an honest description. It is a maintained three platform product: a Flutter app with iOS and Android builds, a backend, accounts, bookmarks, themes, a nearest facility button and a vacancy search. Anyone calling it a toy has not looked at it.
Two differences are measurable rather than matters of taste. Both were taken off its own API and its own compiled bundle on 2026-08-26.
It downloads about 3.3 MB on first launch. Its API is eleven static JSON
files and courses.json alone is 2.4 MB.
It has no building hours. Grepping the bundle for "building hours", "holiday" and
"final exam" returns nothing, and api.roomix.app/hours.json is a 404. Its
vacancy search also needs a building selected first and then stops at a 200 metre
radius from it, and it prints raw metres rather than walk time.
The full teardown, with the commands, is in docs/research/prior-art.md. It also corrects an earlier draft of this README, which said Roomix was organised building by building and ignored the clock. Both halves were wrong.
- docs/BACKLOG.md is the thirty issues, in order.
- docs/DECISIONS.md is append only, and records what each decision was made against and which measurement settled it.
- docs/research/ is the measurement itself: the live API, OSU's GIS server, the Registrar's table, Roomix's bundle.
- docs/BLUEPRINT.md is the design note this README used to be, kept whole, with the places it turned out wrong marked at the top.
- scripts/shoot.mjs regenerates every screenshot above from
the real app, on a pinned clock and a pinned location, and refuses to write a
frame of a screen that has not stopped moving. Re-running it on an unchanged
tree writes the same five files. It also writes down what each screen said, in
docs/media/frames.json, and a test holds the alt text above to it.
The code is MIT, in LICENSE.
The data is derived from Ohio State's own public services: the class schedule at
content.osu.edu, the building layer published by OSU Facilities Information and
Technology Services GIS, and the classroom pool schedule from the Office of the
University Registrar. Each file in data/ carries its source and attribution
inside it. Vacant is not affiliated with Ohio State. How that gets stated on the
site is #25.




