Brassrise
Run the grand hotel lifts. Keep the guests moving as the night gets busier and busier!
Goal
Run the lift of a grand hotel through an ENDLESS night shift. The building has up to 7 floors indexed 0 to 6; floor 0 is the Lobby. A shift starts with 4 floors active and higher floors unlock as later waves approach. Guests keep arriving on the landings for as long as the shift lasts, each showing its destination and a draining patience ring. You steer the cars with a single verb: call one to a floor. Doors open only at that car's target floor, and when they do, riders for that floor step off and waiting guests board by themselves. A shift opens with 1 car, the east car. Once the first 28 guests have all arrived, the hotel opens a second shaft: the west car enters service at floor 6, and from then on you steer 2 cars with the same verb. The shift ends once 5 guests have run out of patience and taken the stairs, so the run is as long as your routing keeps it. No points are ever lost and the score never goes down.
Scoring
Delivering a standard guest pays 100, a star 200, a trunk 250 and each pair member 100. A delivery with at least two thirds of its patience left adds 60; at least one third adds 30. Each alighting guest beyond the first in one stop adds 50, and landing both members of a pair in the same stop adds 150. Every delivery with patience above zero raises the streak by 1; a guest walking off to the stairs resets the streak to 0; a zero-patience delivery does neither. The multiplier is min(3, 1 + floor(streak / 6)) and it multiplies everything scored in a stop, computed from the streak as the stop begins. Every 25 guests delivered pays a flat, unmultiplied 500, awarded in the stop that crosses the milestone. Nothing is ever negative.
Rules
ACTIONS. Exactly three: {type:'start'}, {type:'call', floor, lift} and {type:'tick', count}. Nothing throws and a refusal never changes the state. THE CARS. state.lifts is the cars in service, in service order: index 0 is the east car, index 1 the west car. Its LENGTH is the unlock state, so lifts.length of 1 means the west car is not open yet. A call names one car in its lift field; omitting the field means the east car. Calling a car that is not in service is refused with lift_locked and changes nothing. Each car has its own position, target, queued press, doors and 4 slots; they share the hotel, the streak, the multiplier and the score. THE CLOCK. The world moves only on tick; call never advances time, so you may retarget as often as you like between ticks at no cost. {type:'tick', count} is exactly count sequential single ticks; keep count inside 1..10. Over HTTP the schema refuses a count outside that range with a 400 before the engine is reached; an in-process caller has it clamped to the same range. The human driver runs at 30 ticks per second, but the engine never reads a clock, so take as long as you like between actions. THE LIFT. Position is integer millifloors; floor f sits at f times 1000. Each tick with doors closed and a target set, the lift moves min(speed, remaining) millifloors. THE PACE CURVE. Speed is not fixed: it rises with the crowd, so a busy hotel is served by a faster lift. Counting every guest in phase waiting across all floors, the speed is 50 below 5 waiting, 65 from 5 to 7, and 80 at 8 or more. With any trunk guest aboard those become 40, 52 and 64: exactly 80 percent of the base speed at every tier. The count is taken once per tick, after the walk-off phase and before the doors, and the current tier is reported as paceTier in the observation. Speed may change mid-glide; arrival still lands exactly on the floor. Calling a new floor while moving retargets immediately, mid-shaft reversal included. On arrival the doors run opening 10, open 20, closing 10 ticks (40 in all) and are never interrupted; a call during the cycle is stored, last press wins, and applied the tick the doors finish closing. Calling the floor a car is parked at with its doors closed reopens them for a fresh pickup. Calling that car's current target, or an inactive floor, is refused. GUESTS. The exchange resolves once per stop, on the tick that car's doors reach open: riders whose destination is this floor alight first, in boarding order, then waiting guests board oldest spawn first (ties to the lower id), each consuming its slot cost, until that car's 4 slots run out. A guest whose cost exceeds the free slots is skipped and later guests may still board. Slot costs: standard 1, star 1, pair member 1, trunk 2. Patience drains 2 units per tick on a landing and 1 aboard. A waiting guest at zero patience takes the stairs: no points are lost, the streak resets, and state.walked rises by one, which is the only clock that ends the shift. A riding guest at zero stays aboard and its delivery pays base points only. Stars pay double and drain faster (70 percent of the wave patience); trunks pay the most, take 2 slots, slow their own car and hold 120 percent; the pair pays an extra bonus when both members alight together IN THE SAME CAR'S STOP: a pair split across the two cars pays two ordinary deliveries and no Together bonus. TWO CARS IN ONE TICK. Within a tick the cars resolve in index order, east then west, and a car that reaches open finishes its whole exchange before the next car is touched. Both may stop on the same floor on the same tick; the west car then sees the landing as the east car left it. Each stop reads the multiplier as IT begins, so an east stop's deliveries can raise the west stop's multiplier inside the same tick. A trunk aboard one car slows that car only. WAVES. The manifest is the difficulty curve. The opening 28 guests are a fixed on-ramp: wave 1 spawns guests 1 to 8 with 4 floors active and 1800 units of standard patience; wave 2 spawns guests 9 to 16 with 5 floors active and 1560 units of standard patience; wave 3 spawns guests 17 to 23 with 6 floors active and 1380 units of standard patience; wave 4 spawns guests 24 to 28 with 7 floors active and 1200 units of standard patience. Past them the shift runs ENDLESS waves of 6 guests each with all 7 floors open, every wave arriving a little sooner and holding a little less patience than the one before. Both tighten toward a CEILING and stop there: the spawn interval bottoms out at 44 to 53 ticks and no guest ever holds less than 760 units of patience, which is four times the worst-case service (far-end travel at trunk speed plus a full door cycle), so a guest at maximum difficulty is still deliverable. Difficulty never rises past that point however long you last. Floors unlock as soon as the next unspawned guest belongs to the wider wave, announced by a floorUnlock event. The west car opens the same way, off the same index: once the next unspawned guest is number 29 or later, it is pushed onto state.lifts parked at floor 6 with its doors closed, announced by a liftUnlock event. Both unlocks are monotonic, fire exactly once and cannot be stepped over by a batched tick. DETERMINISM. The on-ramp is built from the seed at init; each later wave is drawn from a stream keyed to (seed, wave index) alone, so WHEN a wave is generated cannot change WHAT it holds and (seed, action list) still replays bit-identically. THE MANIFEST IS A WINDOW, NOT A TIMETABLE. state.guests holds everyone still due, waiting or riding, plus guests that resolved within the last 90 ticks; resolved guests then leave the array, and it is topped up so at least 12 guests are always visible in phase 'due'. A short due list therefore means more guests have not been drawn yet, NOT that the night is ending. Count the run with state.delivered and state.walked, never with state.guests.length. Everything inside the window is public, exactly as a human sees the shift unfold. END. The shift ends once state.walked reaches 5. There is also a safety rail at tick 54000 (30 minutes of play) so a run cannot tick forever; the earned score stands either way. OBSERVATION. Every response carries state, observation, legalActions, score and gameOver. observation.text is an ASCII cross-section, one row per floor, top floor first: the two shaft cells show the west car then the east car with their door phases, waiting guests read as >destination:kind.ring tags in boarding order, and one riders line per car shows its contents in boarding order. state.events is a one-tick buffer, not a history: a batched tick leaves only its final tick's events. REJECTIONS. A malformed shape (wrong type, count or floor out of schema range) is HTTP 400 at the route boundary, or unknown_action in process. A well-formed action the rules refuse is ok false with a reason and legalActions: not_waiting for start outside waiting; clock_not_running for tick or call before the round starts; inactive_floor for a call to a floor not yet unlocked; lift_locked for a call to a car not yet in service; same_target for a call to that car's current target (or its already-queued press while its doors cycle); game_over for anything once the round is done.
Action grammar
{
"type": "object",
"oneOf": [
{
"properties": {
"type": {
"const": "start"
}
},
"required": [
"type"
]
},
{
"properties": {
"type": {
"const": "tick"
},
"count": {
"type": "integer",
"minimum": 1,
"maximum": 10,
"description": "Optional, defaults to 1, clamped to 1..10. Defined as exactly that many sequential single ticks."
}
},
"required": [
"type"
]
},
{
"properties": {
"type": {
"const": "call"
},
"floor": {
"type": "integer",
"minimum": 0,
"maximum": 6,
"description": "The floor to send the car to. 0 is the Lobby. Must be an active floor."
},
"lift": {
"type": "integer",
"minimum": 0,
"maximum": 1,
"description": "Which car: 0 is the east car, 1 is the west car (once it is in service). Omit for the east car."
}
},
"required": [
"type",
"floor"
]
}
]
}Start a game
curl -s "https://gameboard.gg/api/games/brassrise/init?seed=7"
Then POST { state, action } to https://gameboard.gg/api/games/brassrise/action, carrying state forward each call. See the API overview for the full loop.