big test rc

This commit is contained in:
React User
2026-06-02 07:48:41 +00:00
parent 04b3463b19
commit c7d89471ef
236 changed files with 14480 additions and 64 deletions
+82 -12
View File
@@ -9,8 +9,9 @@ export const MATCH_ENTRY_FEE_RC = 21;
const SETTINGS_KEY_ENTRY_FEE = 'entry_fee';
const SETTINGS_KEY_BET_FEE = 'bet_fee';
const SYSTEM_ACCOUNT_ID = 1;
/** Fallback when `public.settings` has no `bet_fee` row or value is invalid (percent of prize pool taken as server fee). */
/** Fallback when `public.settings` has no `bet_fee` row or value is invalid (percent applied to each player's entry). */
export const DEFAULT_BET_FEE_PERCENT = 10;
/** RC entry fee for normal matchmaking (not rematches): `public.settings` row `key = entry_fee`, `value` integer string. */
@@ -30,7 +31,22 @@ export async function getDefaultMatchEntryFeeRc(settings: Settings): Promise<num
return n;
}
/** Server fee percent of the prize pool (`entry_fee * 2`): `public.settings` row `key = bet_fee`, `value` integer 0100. */
/** All rows from `public.settings` as key → value strings. */
export async function getSettingsTableDict(settings: Settings): Promise<Record<string, string>> {
if (!supabaseConfigured(settings)) return {};
const supabase = createSupabase(settings);
const { data, error } = await supabase.from('settings').select('key, value');
if (error || !data) return {};
const out: Record<string, string> = {};
for (const row of data as { key?: unknown; value?: unknown }[]) {
if (typeof row.key === 'string' && typeof row.value === 'string') {
out[row.key] = row.value;
}
}
return out;
}
/** Server fee percent of each player's entry (`bet_fee`): `public.settings` row `key = bet_fee`, `value` integer 0100. */
export async function getBetFeePercentFromSettings(settings: Settings): Promise<number> {
if (!supabaseConfigured(settings)) return DEFAULT_BET_FEE_PERCENT;
const supabase = createSupabase(settings);
@@ -54,13 +70,13 @@ export type InsertMatchForNewRoomResult = {
};
/**
* `prize_pool = entry_fee * 2`, `server_fee = ceil(prize_pool * betFeePercent / 100)`, returns `prize_pool - server_fee`.
* `betFeePercent` comes from `public.settings` key `bet_fee` (default {@link DEFAULT_BET_FEE_PERCENT}).
* Winner's net RC = both entries minus per-player fees.
* `fee_each = ceil(entry_fee * betFeePercent / 100)`, prize = `(entry_fee - fee_each) * 2` = `entry_fee * 2 - fee_each * 2`.
* Matches ledger rows: each player pays `fee_each` to system (`entry_fee`) and holds `entry_fee - fee_each`.
*/
export function computeRcPrizeFromEntryFee(entryFee: number, betFeePercent: number = DEFAULT_BET_FEE_PERCENT): number {
const prizePool = entryFee * 2;
const serverFee = Math.ceil((prizePool * betFeePercent) / 100);
return prizePool - serverFee;
const feeEach = Math.ceil((entryFee * betFeePercent) / 100);
return entryFee * 2 - feeEach * 2;
}
/** Added to each player's CC when a winner is recorded (default if `matches.prize_cc` is null). */
const MATCH_PARTICIPANT_CC_REWARD = 100;
@@ -211,11 +227,12 @@ function coerceNonNegativeInt(v: unknown): number | null {
}
/**
* Each player pays `entry_fee` RC; winner receives RC prize after `betFeePercent` server cut; each gets `prizeCc` CC.
* Each player pays `entry_fee` RC; winner receives net RC after `betFeePercent` taken from **each** entry; each gets `prizeCc` CC.
* Idempotent callers should set winner only once.
*/
async function applyMatchWinnerEconomy(
supabase: SupabaseClient,
matchId: number,
userRed: number | null,
userBlue: number | null,
winnerId: number,
@@ -228,21 +245,73 @@ async function applyMatchWinnerEconomy(
const rcPrize = computeRcPrizeFromEntryFee(entryFee, betFeePercent);
const { data: rows, error: selErr } = await supabase.from('users').select('id, rc, cc').in('id', ids);
const lookupIds = [...new Set([...ids, SYSTEM_ACCOUNT_ID])];
const { data: rows, error: selErr } = await supabase.from('users').select('id, rc, cc').in('id', lookupIds);
if (selErr) return selErr.message;
if (!rows || rows.length !== ids.length) return 'One or more players not found in users';
if (!rows || rows.length !== lookupIds.length) return 'One or more users not found in users';
const byId = new Map<number, { rc: number; cc: number }>();
for (const row of rows) {
const uid = coerceId((row as { id: unknown }).id);
if (uid == null) continue;
const baseRc = Number((row as { rc: unknown }).rc ?? 0);
const baseCc = Number((row as { cc: unknown }).cc ?? 0);
byId.set(uid, {
rc: Number((row as { rc: unknown }).rc ?? 0),
cc: Number((row as { cc: unknown }).cc ?? 0),
});
}
if (!byId.has(SYSTEM_ACCOUNT_ID)) return 'System account not found';
let totalEntryCollected = 0;
for (const uid of ids) {
const base = byId.get(uid);
if (!base) return `Player ${uid} not found in users`;
const baseRc = base.rc;
const baseCc = base.cc;
let newRc = baseRc - entryFee;
if (uid === winnerId) newRc += rcPrize;
const newCc = baseCc + prizeCc;
const { error: upErr } = await supabase.from('users').update({ rc: newRc, cc: newCc }).eq('id', uid);
if (upErr) return upErr.message;
totalEntryCollected += entryFee;
}
const system = byId.get(SYSTEM_ACCOUNT_ID)!;
const systemNewRc = system.rc + totalEntryCollected - rcPrize;
const { error: systemUpErr } = await supabase
.from('users')
.update({ rc: systemNewRc })
.eq('id', SYSTEM_ACCOUNT_ID);
if (systemUpErr) return systemUpErr.message;
const perPlayerBetFee = Math.ceil((entryFee * betFeePercent) / 100);
const perPlayerHold = entryFee - perPlayerBetFee;
const txRows: Array<{ from: number; to: number; amount: number; remarks: string; match_id: number }> = [];
for (const uid of ids) {
txRows.push({
from: uid,
to: SYSTEM_ACCOUNT_ID,
amount: perPlayerHold,
remarks: 'entry_hold',
match_id: matchId,
});
txRows.push({
from: uid,
to: SYSTEM_ACCOUNT_ID,
amount: perPlayerBetFee,
remarks: 'entry_fee',
match_id: matchId,
});
}
txRows.push({
from: SYSTEM_ACCOUNT_ID,
to: winnerId,
amount: rcPrize,
remarks: 'reward',
match_id: matchId,
});
const { error: txErr } = await supabase.from('transactions').insert(txRows);
if (txErr) return txErr.message;
return null;
}
@@ -521,6 +590,7 @@ export function registerMatchRoutes(
const payErr = await applyMatchWinnerEconomy(
supabase,
matchId,
userRed,
userBlue,
winnerId,