fix(social): robot blocks
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 17s
CI / ui (pull_request) Successful in 52s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m21s

Blocking an auto-match opponent who is secretly a pooled robot is recorded  instead in a separate `robot_blocks` table.

Now blocking behaves the same in that game (struck name, hidden composer) and lists the blocked opponent under the name you saw, but is recorded only against that game — the disguise holds, the shared robot is never globally blocked, and the matchmaker keeps pairing you with robots (so you can never block yourself out of opponents).

- the shared robot account is never put in `blocks`
- the matchmaker keeps it free and it is not blocked under its other per-game names
- the blocked list and the in-game card still show it by joining that table; an unblock deletes the row
This commit is contained in:
Ilia Denisov
2026-06-18 13:12:19 +02:00
parent 81b9e1529e
commit 64be0572b3
29 changed files with 700 additions and 68 deletions
+22 -5
View File
@@ -9,6 +9,8 @@ import { indexForLetter, letterForIndex, setAlphabet, type AlphabetEntryWire } f
import type { PlacedTile } from './client';
import type {
AccountRef,
BlockList,
RobotBlockEntry,
Banner,
BannerCampaign,
BestMove,
@@ -576,11 +578,15 @@ export function decodeEvent(kind: string, payload: Uint8Array): PushEvent | null
// --- social encoders ---
export function encodeTarget(accountId: string): Uint8Array {
export function encodeTarget(accountId: string, gameId?: string): Uint8Array {
const b = new Builder(64);
const id = b.createString(accountId);
// game_id is set only by an in-game block, so a disguised-robot opponent is recorded
// per-game; every other target path omits it.
const gid = gameId ? b.createString(gameId) : 0;
fb.TargetRequest.startTargetRequest(b);
fb.TargetRequest.addAccountId(b, id);
if (gid) fb.TargetRequest.addGameId(b, gid);
return finish(b, fb.TargetRequest.endTargetRequest(b));
}
@@ -722,14 +728,25 @@ export function decodeOutgoingList(buf: Uint8Array): AccountRef[] {
return out;
}
export function decodeBlockList(buf: Uint8Array): AccountRef[] {
export function decodeBlockList(buf: Uint8Array): BlockList {
const l = fb.BlockList.getRootAsBlockList(new ByteBuffer(buf));
const out: AccountRef[] = [];
const blocked: AccountRef[] = [];
for (let i = 0; i < l.blockedLength(); i++) {
const r = l.blocked(i);
if (r) out.push(decodeAccountRef(r));
if (r) blocked.push(decodeAccountRef(r));
}
return out;
const robots: RobotBlockEntry[] = [];
for (let i = 0; i < l.robotsLength(); i++) {
const r = l.robots(i);
if (r)
robots.push({
id: r.id() ?? '',
displayName: r.displayName() ?? '',
gameId: r.gameId() ?? '',
seat: r.seat(),
});
}
return { blocked, robots };
}
export function decodeFriendCode(buf: Uint8Array): FriendCode {