diff --git a/client/src/MediaControl.css b/client/src/MediaControl.css
index 145e438..2d357fe 100644
--- a/client/src/MediaControl.css
+++ b/client/src/MediaControl.css
@@ -20,9 +20,9 @@
padding: 0;
margin: 0;
width: 5rem;
- min-width: 5rem;
height: 3.75rem;
min-height: 3.75rem;
+ min-width: 5rem;
background-color: #444;
border-radius: 0.25rem;
border: 2px dashed #666; /* Visual indicator for drop zone */
@@ -31,8 +31,8 @@
.MediaControlSpacer.Medium {
width: 11.5em;
height: 8.625em;
- min-width: 11.5em;
- min-height: 8.625em;
+ /* min-width: 11.5em;
+ min-height: 8.625em; */
}
.MediaControl {
@@ -42,8 +42,8 @@
left: 0; /* Start at left of container */
width: 5rem;
height: 3.75rem;
- min-width: 5rem;
- min-height: 3.75rem;
+ min-width: 1.25rem;
+ min-height: 0.9375rem;
z-index: 1200;
border-radius: 0.25rem;
}
@@ -63,8 +63,8 @@
.MediaControl.Medium {
width: 11.5em;
height: 8.625em;
- min-width: 11.5em;
- min-height: 8.625em;
+ /* min-width: 11.5em;
+ min-height: 8.625em; */
}
.MediaControl .Controls {
diff --git a/client/src/PlayerList.tsx b/client/src/PlayerList.tsx
index 1ae5e90..3680a64 100644
--- a/client/src/PlayerList.tsx
+++ b/client/src/PlayerList.tsx
@@ -193,15 +193,10 @@ const PlayerList: React.FC = () => {
Pick your color:
- {[
- { label: "Orange", value: "orange" },
- { label: "Red", value: "red" },
- { label: "White", value: "white" },
- { label: "Blue", value: "blue" },
- ].map((c) => (
-
+
+
))}
diff --git a/client/src/RoomView.css b/client/src/RoomView.css
index 3cade8f..7f47253 100644
--- a/client/src/RoomView.css
+++ b/client/src/RoomView.css
@@ -93,7 +93,6 @@
justify-content: space-between;
width: 25rem;
max-width: 25rem;
- overflow: hidden;
z-index: 5000;
}
diff --git a/server/routes/games.ts b/server/routes/games.ts
index a79d1b9..0b36466 100755
--- a/server/routes/games.ts
+++ b/server/routes/games.ts
@@ -99,7 +99,9 @@ const processTies = (players: Player[]): boolean => {
const rev = slots.slice().reverse();
for (const slot of rev) {
const s = slot || [];
- if (s.length !== 1) {
+ // Only consider actual populated slots. Empty slots (holes) should be skipped.
+ if (s.length > 1) {
+ // real tie among multiple players in this slot
ties = true;
s.forEach((player: Player) => {
player.orderRoll = 0; /* Ties have to be re-rolled */
@@ -107,12 +109,17 @@ const processTies = (players: Player[]): boolean => {
player.orderStatus = `Tied for ${irstify(position)}`;
player.tied = true;
});
- } else if (s[0]) {
+ position += s.length;
+ } else if (s.length === 1 && s[0]) {
+ // single player in this slot - clear tie and record position
s[0].tied = false;
s[0].position = irstify(position);
s[0].orderStatus = `Placed in ${irstify(position)}.`;
+ position += 1;
+ } else {
+ // empty slot - skip
+ continue;
}
- position += s.length;
}
return ties;
@@ -155,10 +162,16 @@ const processGameOrder = (game: Game, player: Player, dice: number): any => {
return B.order - A.order;
});
- console.log(`Pre process ties: `, players);
+ console.log(
+ `${info}: Pre process ties: `,
+ players.reduce((acc, p) => ({ ...acc, [p.color as string]: p.orderRoll }), {})
+ );
if (processTies(players)) {
- console.log(`${info}: There are ties in player rolls:`, players);
+ console.log(
+ `${info}: There are ties in player rolls:`,
+ players.reduce((acc, p) => ({ ...acc, [p.color as string]: p.orderRoll }), {})
+ );
sendUpdateToPlayers(game, {
players: getFilteredPlayers(game),
chat: game.chat,
@@ -3380,14 +3393,33 @@ const ping = (session: Session) => {
// Add new function to schedule recurring pings
const schedulePing = (session: Session) => {
+ // Diagnostic logging to help detect multiple intervals being created
+ try {
+ console.log(
+ `${session.short}: schedulePing called for ${getName(session)} - existing pingInterval? ${!!session.pingInterval}`
+ );
+ } catch (e) {
+ /* ignore logging errors */
+ }
+
if (session.pingInterval) {
- clearInterval(session.pingInterval);
+ // Clear any previous interval before creating a new one
+ try {
+ clearInterval(session.pingInterval);
+ } catch (e) {
+ console.warn(`${session.short}: Failed to clear previous pingInterval:`, e);
+ }
}
// Send ping every 10 seconds
session.pingInterval = setInterval(() => {
ping(session);
}, 10000);
+ try {
+ console.log(`${session.short}: pingInterval started for ${getName(session)}`);
+ } catch (e) {
+ /* ignore logging errors */
+ }
};
// wsInactive not present in this refactor; no-op placeholder removed
@@ -3764,6 +3796,7 @@ const sendError = (session: any, error: string): void => {
};
const sendWarning = (session: any, warning: string): void => {
+ console.warn(`${session.short}: Warning: ${warning}`);
try {
session?.ws?.send(JSON.stringify({ type: "warning", warning }));
} catch (e) {
@@ -4220,6 +4253,22 @@ router.ws("/ws/:id", async (ws, req) => {
// If there was a previous websocket and it's a different object, try to
// close it to avoid stale sockets lingering in memory.
if (previousWs && previousWs !== ws) {
+ // Clear any existing ping/keepAlive timers associated with the old socket
+ try {
+ if (session.pingInterval) {
+ clearInterval(session.pingInterval);
+ session.pingInterval = undefined;
+ console.log(`${short}: Cleared old pingInterval during reconnection for ${getName(session)}`);
+ }
+ if (session.keepAlive) {
+ clearTimeout(session.keepAlive);
+ session.keepAlive = undefined;
+ console.log(`${short}: Cleared old keepAlive during reconnection for ${getName(session)}`);
+ }
+ } catch (e) {
+ console.warn(`${short}: Error clearing old timers during reconnection:`, e);
+ }
+
// Clean up peer from audio registry before replacing WebSocket
if (gameId in audio) {
try {
@@ -4249,11 +4298,10 @@ router.ws("/ws/:id", async (ws, req) => {
let warning: string | void | undefined;
let processed = true;
- // If this is the first time the session attached a WebSocket, or if the
- // websocket was just replaced (reconnect), send an initial consolidated
- // snapshot so clients can render deterministically without needing to
- // wait for a flurry of incremental game-update events.
- sendInitialGameSnapshot(game, session);
+ // The initial-game snapshot is sent from the connection attach path to
+ // ensure it is only sent once per websocket lifecycle. Avoid sending it
+ // here from the message handler to prevent duplicate snapshots when a
+ // client sends messages during the attach/reconnect sequence.
switch (incoming.type) {
case "join":
diff --git a/server/routes/games/constants.ts b/server/routes/games/constants.ts
index 8b5e12c..b5b9169 100644
--- a/server/routes/games/constants.ts
+++ b/server/routes/games/constants.ts
@@ -15,6 +15,7 @@ export const debug = {
export const all = `[ all ]`;
export const info = `[ info ]`;
export const todo = `[ todo ]`;
+export const warn = `[ warn ]`;
export const SEND_THROTTLE_MS = 50;
export const INCOMING_GET_BATCH_MS = 20;