Place finishers by when they crossed
Goal
Two racers who cross the finish line on the same fixed step are placed by which of them crossed first within that step, not by their seat number. A step is a sixtieth of a second, and at running speed that is long enough for a close finish to go to the wrong racer every time it happens.
Files you will edit
src/finish.ts
Steps
Keep where each racer was on the previous step, and room for this step's finishers. Both are seat lanes, allocated once.
ts// src/finish.ts import { Transform, type GameContext } from 'gameable'; import { CONTACT, FINISH_Z, TOP, MAX_PLAYERS } from './course'; import { race } from './state'; import { emit } from './events'; /** Each racer's z on the previous step: where they came from when they cross. */ const lastZ = new Float32Array(MAX_PLAYERS); /** When, within this step, each finisher crossed. */ const crossedAt = new Float64Array(MAX_PLAYERS); /** This step's finishers, earliest first. */ const order = new Int8Array(MAX_PLAYERS);Work out when within the step each finisher crossed. The course runs along
-Z, so a racer who was atfromand is now pastFINISH_Zcrossed after(from - FINISH_Z) / (from - z)of the step. KeeplastZup to date in every phase, so the first racing step already knows where everyone came from.ts// src/finish.ts export function finish(ctx: GameContext): void { let count = 0; for (let seat = 0; seat < MAX_PLAYERS; seat += 1) { const e = race.entity[seat]; if (e === 0) continue; const from = lastZ[seat]; const z = Transform.z[e]; lastZ[seat] = z; if ( race.phase !== 'racing' || race.spectator[seat] !== 0 || race.place[seat] !== 0 || z > FINISH_Z || Math.abs(Transform.x[e]) > CONTACT.finishWidth || Transform.y[e] < TOP || Transform.y[e] > CONTACT.finishMaxY ) continue; const frac = from > FINISH_Z && from > z ? (from - FINISH_Z) / (from - z) : 1; crossedAt[seat] = ctx.elapsed - ctx.dt + frac * ctx.dt; // insert, earliest first; equal times keep seat order let i = count; while (i > 0 && crossedAt[order[i - 1]] > crossedAt[seat]) { order[i] = order[i - 1]; i -= 1; } order[i] = seat; count += 1; }Give the places in that order.
ts// src/finish.ts for (let i = 0; i < count; i += 1) { const seat = order[i]; race.place[seat] = race.next++; emit(seat, 'finish'); if (race.first < 0) race.first = ctx.elapsed; race.revision += 1; } }
Verify
sh
npm testAdd a test beside "awards places in order": on the same step seat 0 crosses from z = -66 to -68.5, which is late in the step, and seat 1 crosses from -67.9 to -69, which is early in it. Seat 1 is placed first:
ts
expect([...race.place.slice(0, 2)]).toEqual([2, 1]);See also
- Write a game system — the fixed step this works within, and why it allocates nothing
- Add a lobby — the round the finish belongs to
- Read player input — what moves the racers to the line