PUBG drop map: live squad map with WebSocket rooms
All checks were successful
Deploy production / deploy (push) Successful in 1m15s

- server.js wraps Bun.serve to add /ws/<channel> upgrades (same server and port) and
  serve the map tiles from the host mount; Dockerfile now runs bun ./server.js
- ws hub (channel registry, origin check) + pubg-map channel: rooms by unguessable
  code, validated + rate-limited messages, per-room write queue, presence
- games.pubg_rooms / pubg_matches / pubg_markers; per-match sessions with history
- /trackers/games/pubg/map hub (owner: rooms, landings over time, history) and
  /trackers/games/pubg/map/<code> fullscreen Leaflet map (all 11 maps)
- deploy-remote.sh bind-mounts ~/pubg-maps; scripts/build-pubg-tiles.py rebuilds it
This commit is contained in:
2026-10-25 22:46:24 +00:00
parent d93374bca2
commit 0fe0d36f87
26 changed files with 39201 additions and 3 deletions

View File

@@ -27,3 +27,4 @@ export * from './music';
export * from './hardware';
export * from './dataholders';
export * from './stats';
export * from './pubg-map';

View File

@@ -0,0 +1,62 @@
import { doublePrecision, index, integer, text, uniqueIndex } from 'drizzle-orm/pg-core';
import { gamesSchema } from './schemas';
import { id, timestamps, timestamptz } from './helpers';
// The live PUBG drop map (/trackers/games/pubg/map): rooms that friends join with an
// unguessable share code, one match per game played (map, landings, plane path, hotspots,
// vehicles, notes), and the markers themselves. Only the owner creates rooms and deletes
// history; anyone with a room's code edits that room's current match over the site WebSocket.
export const pubgRooms = gamesSchema.table('pubg_rooms', {
id: id(),
/** 20 characters of base62; the share link is /trackers/games/pubg/map/<code>. */
code: text('code').notNull().unique(),
name: text('name').notNull(),
...timestamps
});
export const pubgMatches = gamesSchema.table(
'pubg_matches',
{
id: id(),
roomId: integer('room_id')
.notNull()
.references(() => pubgRooms.id, { onDelete: 'cascade' }),
/** 1, 2, 3… within the room. */
number: integer('number').notNull(),
/** #lib/pubg-map/shared MAPS slug: erangel, miramar, sanhok, vikendi… */
map: text('map').notNull(),
startedAt: timestamptz('started_at').notNull().defaultNow(),
/** Set when "New match" archives it; null for the room's current match. */
endedAt: timestamptz('ended_at')
},
(t) => [
uniqueIndex('pubg_matches_room_number_idx').on(t.roomId, t.number),
index('pubg_matches_map_idx').on(t.map, t.startedAt)
]
);
export const pubgMarkers = gamesSchema.table(
'pubg_markers',
{
/** Made by the browser that placed it, so its own copy and the broadcast share an id. */
id: text('id').primaryKey(),
matchId: integer('match_id')
.notNull()
.references(() => pubgMatches.id, { onDelete: 'cascade' }),
/** landing, plane, hotspot, vehicle, poi, note. */
kind: text('kind').notNull(),
/** 0..1 across and down the map image. */
x: doublePrecision('x').notNull(),
y: doublePrecision('y').notNull(),
/** Plane path end point. */
x2: doublePrecision('x2'),
y2: doublePrecision('y2'),
label: text('label'),
/** Nickname and colour of whoever placed it (friends have no account). */
player: text('player').notNull(),
color: text('color').notNull(),
...timestamps
},
(t) => [index('pubg_markers_match_idx').on(t.matchId)]
);