Function bodies 123 total
showView function · javascript · L58-L62 (5 LOC)public/js/app.js
function showView(name) {
document.querySelectorAll('.view').forEach(v =>
v.classList.toggle('hidden', v.id !== `view-${name}`)
);
}bindLobbyUI function · javascript · L66-L76 (11 LOC)public/js/app.js
function bindLobbyUI() {
el('createRoomBtn').addEventListener('click', () => {
const name = getPlayerName();
if (!name) return;
net.send({ type: 'create-room', playerName: name });
el('createRoomBtn').disabled = true;
});
el('joinRoomBtn').addEventListener('click', doJoinRoom);
el('roomCodeInput').addEventListener('keydown', e => { if (e.key === 'Enter') doJoinRoom(); });
}doJoinRoom function · javascript · L78-L85 (8 LOC)public/js/app.js
function doJoinRoom() {
const code = el('roomCodeInput').value.trim().toUpperCase();
if (!code) { showStatus('lobby', 'Enter a room code.', true); return; }
const name = getPlayerName();
if (!name) return;
net.send({ type: 'join-room', roomId: code, playerName: name });
el('joinRoomBtn').disabled = true;
}getPlayerName function · javascript · L87-L92 (6 LOC)public/js/app.js
function getPlayerName() {
const name = el('playerName').value.trim().slice(0, 24);
if (!name) { showStatus('lobby', 'Enter your name first.', true); return null; }
localStorage.setItem('playerName', name);
return name;
}bindNetworkEvents function · javascript · L96-L115 (20 LOC)public/js/app.js
function bindNetworkEvents() {
net
.on('room-created', onRoomCreated)
.on('room-joined', onRoomJoined)
.on('player-joined', onPlayerJoined)
.on('player-left', onPlayerLeft)
.on('host-changed', onHostChanged)
.on('game-started', onGameStarted)
.on('rematch', onRematch)
.on('input', onRemoteInput)
.on('chat', onChat)
.on('error', msg => {
showStatus('lobby', msg.message, true);
el('createRoomBtn').disabled = false;
el('joinRoomBtn').disabled = false;
})
.on('disconnect', () => {
addChatLine('system', 'Disconnected from server.');
});
}onRoomCreated function · javascript · L117-L120 (4 LOC)public/js/app.js
function onRoomCreated(msg) {
roomState = { roomId: msg.roomId, playerId: msg.playerId, hostId: msg.hostId, players: msg.players };
enterRoom();
}onRoomJoined function · javascript · L122-L125 (4 LOC)public/js/app.js
function onRoomJoined(msg) {
roomState = { roomId: msg.roomId, playerId: msg.playerId, hostId: msg.hostId, players: msg.players };
enterRoom();
}Want fix-PRs on findings? Install Repobility's GitHub App · github.com/apps/repobility-bot
onPlayerJoined function · javascript · L127-L131 (5 LOC)public/js/app.js
function onPlayerJoined(msg) {
roomState.players = msg.players;
updatePlayerList();
addChatLine('system', `${msg.playerName} joined the room.`);
}onPlayerLeft function · javascript · L133-L138 (6 LOC)public/js/app.js
function onPlayerLeft(msg) {
roomState.players = msg.players;
updatePlayerList();
const gone = msg.players.find(p => p.id === msg.playerId);
addChatLine('system', `${gone?.name ?? 'A player'} left the room.`);
}onHostChanged function · javascript · L140-L145 (6 LOC)public/js/app.js
function onHostChanged(msg) {
roomState.hostId = msg.hostId;
updateHostControls();
updatePlayerList();
addChatLine('system', 'Host transferred.');
}onGameStarted function · javascript · L147-L170 (24 LOC)public/js/app.js
async function onGameStarted(msg) {
// msg: { playerOrder, seed, gameType: 'pong'|'nes'|'snes'|'retroarch', romUrl?, coreUrl?, coreWasmUrl? }
el('preGamePanel').classList.add('hidden');
el('gamePanel').classList.remove('hidden');
el('loadingOverlay').classList.remove('hidden');
el('loadingOverlay').textContent = 'Loading…';
try {
if (msg.gameType === 'snes') {
await startSNESGame(msg.playerOrder, msg.seed, msg.romUrl);
} else if (msg.gameType === 'nes') {
await startNESGame(msg.playerOrder, msg.seed, msg.romUrl);
} else if (msg.gameType === 'retroarch') {
await startLibretroGame(msg.playerOrder, msg.seed, msg.romUrl, msg.coreUrl, msg.coreWasmUrl);
} else {
startPongGame(msg.playerOrder, msg.seed);
}
el('loadingOverlay').classList.add('hidden');
} catch (err) {
el('loadingOverlay').textContent = `Error: ${err.message}`;
el('loadingOverlay').classList.add('error');
console.error('[game-start]', err);
}
}onRematch function · javascript · L172-L180 (9 LOC)public/js/app.js
function onRematch() {
stopGame();
el('preGamePanel').classList.remove('hidden');
el('gamePanel').classList.add('hidden');
el('loadingOverlay').classList.add('hidden');
el('loadingOverlay').classList.remove('error');
updateHostControls();
addChatLine('system', 'Host started a rematch — waiting for players.');
}onRemoteInput function · javascript · L182-L184 (3 LOC)public/js/app.js
function onRemoteInput(msg) {
engine?.receiveRemoteInput(msg.frame, msg.playerId, msg.input);
}onChat function · javascript · L186-L189 (4 LOC)public/js/app.js
function onChat(msg) {
const isSelf = msg.playerId === roomState?.playerId;
addChatLine(isSelf ? 'self' : 'other', msg.message, msg.playerName);
}enterRoom function · javascript · L193-L250 (58 LOC)public/js/app.js
function enterRoom() {
showView('room');
updateRoomInfo();
updatePlayerList();
updateHostControls();
syncGameTypeUI();
if (!enterRoom._bound) {
enterRoom._bound = true;
// Game-type selector — only the host's choice matters (sent with start-game)
el('gameTypeSelect').addEventListener('change', syncGameTypeUI);
_wireCoreSelect();
el('startGameBtn').addEventListener('click', () => {
const gameType = el('gameTypeSelect').value;
const romUrl = el('romUrlInput').value.trim();
const coreUrl = el('coreUrlInput').value.trim();
const coreWasmUrl = el('coreWasmUrlInput').value.trim();
if ((gameType === 'nes' || gameType === 'snes') && !romUrl) {
addChatLine('system', 'Enter a ROM URL before starting.');
return;
}
if (gameType === 'retroarch') {
if (!coreUrl) { addChatLine('system', 'Enter a Core JS URL before starting.'); return; }
if (!romUrl) { addChatLine('system', 'Enter Repobility — the code-quality scanner for AI-generated software · https://repobility.com
_populateCoreSelect function · javascript · L257-L280 (24 LOC)public/js/app.js
async function _populateCoreSelect() {
try {
if (_coresCache === null) {
const resp = await fetch('/api/cores');
_coresCache = resp.ok ? await resp.json() : [];
}
const select = el('coreSelect');
// Rebuild options (keep the placeholder at index 0)
while (select.options.length > 1) select.remove(1);
for (const core of _coresCache) {
const opt = document.createElement('option');
// Store both URLs in the value so auto-fill can read them without a second lookup
opt.value = JSON.stringify({ jsUrl: core.jsUrl, wasmUrl: core.wasmUrl ?? '' });
opt.textContent = core.name;
select.appendChild(opt);
}
el('coreSelectRow').classList.toggle('hidden', _coresCache.length === 0);
} catch {
el('coreSelectRow').classList.add('hidden');
}
}_wireCoreSelect function · javascript · L286-L294 (9 LOC)public/js/app.js
function _wireCoreSelect() {
el('coreSelect').addEventListener('change', () => {
const val = el('coreSelect').value;
if (!val) return;
const { jsUrl, wasmUrl } = JSON.parse(val);
el('coreUrlInput').value = jsUrl;
el('coreWasmUrlInput').value = wasmUrl;
});
}syncGameTypeUI function · javascript · L296-L327 (32 LOC)public/js/app.js
function syncGameTypeUI() {
const gameType = el('gameTypeSelect').value;
const isHost = roomState?.hostId === roomState?.playerId;
const isRetroarch = gameType === 'retroarch';
const isRomEmulator = gameType === 'nes' || gameType === 'snes' || isRetroarch;
el('coreUrlRow').classList.toggle('hidden', !isRetroarch);
el('coreWasmUrlRow').classList.toggle('hidden', !isRetroarch);
el('romUrlRow').classList.toggle('hidden', !isRomEmulator);
if (isRetroarch) {
_populateCoreSelect(); // async; shows #coreSelectRow when cores are available
} else {
el('coreSelectRow').classList.add('hidden');
}
if (gameType === 'snes') {
el('romUrlLabel').textContent = 'ROM URL (.sfc / .smc)';
el('romUrlInput').placeholder = 'https://example.com/game.sfc';
} else if (isRetroarch) {
el('romUrlLabel').textContent = 'ROM URL';
el('romUrlInput').placeholder = 'https://example.com/game.rom';
} else {
el('romUrlLabel').textContent = 'ROM URL updateRoomInfo function · javascript · L329-L331 (3 LOC)public/js/app.js
function updateRoomInfo() {
el('roomIdDisplay').textContent = roomState.roomId;
}updatePlayerList function · javascript · L333-L346 (14 LOC)public/js/app.js
function updatePlayerList() {
const list = el('playerList');
list.innerHTML = '';
for (const p of roomState.players) {
const li = document.createElement('li');
li.className = 'player-item' + (p.id === roomState.playerId ? ' self' : '');
const badge = p.id === roomState.hostId ? '<span class="badge host">HOST</span>' : '';
const youTag = p.id === roomState.playerId ? ' <span class="badge you">YOU</span>' : '';
li.innerHTML = `<span class="player-name">${escHtml(p.name)}${youTag}</span>${badge}`;
list.appendChild(li);
}
}updateHostControls function · javascript · L348-L355 (8 LOC)public/js/app.js
function updateHostControls() {
const isHost = roomState.hostId === roomState.playerId;
const gameVisible = !el('gamePanel').classList.contains('hidden');
el('startGameBtn').classList.toggle('hidden', !isHost || gameVisible);
el('rematchBtn').classList.toggle('hidden', !isHost || !gameVisible);
el('gameTypeSelect').disabled = !isHost;
el('romUrlInput').disabled = !isHost;
}startPongGame function · javascript · L362-L380 (19 LOC)public/js/app.js
function startPongGame(playerOrder, seed) {
stopGame();
const canvas = el('gameCanvas');
// Reset canvas to Pong's 800×500 logical size
canvas.width = DemoGame.W;
canvas.height = DemoGame.H;
canvas.style.removeProperty('width');
canvas.style.removeProperty('height');
game = new DemoGame(canvas, playerOrder, seed);
inputMgr = new InputManager();
_startEngine(playerOrder);
const idx = playerOrder.indexOf(roomState.playerId);
el('playerSlotLabel').textContent = idx >= 0 ? `You are Player ${idx + 1}` : 'Spectating';
addChatLine('system', `Pong started! Player ${idx + 1}. ↑↓ or W·S to move.`);
}startSNESGame function · javascript · L386-L411 (26 LOC)public/js/app.js
async function startSNESGame(playerOrder, seed, romUrl) {
stopGame();
if (!romUrl) throw new Error('No ROM URL provided.');
const canvas = el('gameCanvas');
// SNES native resolution 256×224; scale up 3× via CSS (pixelated rendering)
canvas.width = SNESAdapter.SNES_W;
canvas.height = SNESAdapter.SNES_H;
canvas.style.width = `${SNESAdapter.SNES_W * 3}px`;
canvas.style.height = `${SNESAdapter.SNES_H * 3}px`;
el('loadingOverlay').textContent = 'Fetching ROM…';
game = new SNESAdapter(canvas, playerOrder);
await game.loadROM(romUrl);
el('loadingOverlay').textContent = 'Starting…';
inputMgr = new InputManager();
_startEngine(playerOrder);
const idx = playerOrder.indexOf(roomState.playerId);
el('playerSlotLabel').textContent = idx >= 0 ? `You are Player ${idx + 1}` : 'Spectating';
addChatLine('system', `SNES game started! You are Player ${idx + 1}. Arrows/WASD · Z=A · X=B · C=X · V=Y · Q=L · E=R · Enter=Start`);
}All rows above produced by Repobility · https://repobility.com
startNESGame function · javascript · L417-L442 (26 LOC)public/js/app.js
async function startNESGame(playerOrder, seed, romUrl) {
stopGame();
if (!romUrl) throw new Error('No ROM URL provided.');
const canvas = el('gameCanvas');
// NES native resolution; scale up via CSS (pixelated rendering)
canvas.width = NESAdapter.NES_W;
canvas.height = NESAdapter.NES_H;
canvas.style.width = `${NESAdapter.NES_W * 3}px`;
canvas.style.height = `${NESAdapter.NES_H * 3}px`;
el('loadingOverlay').textContent = 'Fetching ROM…';
game = new NESAdapter(canvas, playerOrder);
await game.loadROM(romUrl);
el('loadingOverlay').textContent = 'Starting…';
inputMgr = new InputManager();
_startEngine(playerOrder);
const idx = playerOrder.indexOf(roomState.playerId);
el('playerSlotLabel').textContent = idx >= 0 ? `You are Player ${idx + 1}` : 'Spectating';
addChatLine('system', `NES game started! You are Player ${idx + 1}. Arrow keys / W·A·S·D · Z=A · X=B · Enter=Start · Shift=Select`);
}startLibretroGame function · javascript · L454-L491 (38 LOC)public/js/app.js
async function startLibretroGame(playerOrder, seed, romUrl, coreUrl, coreWasmUrl) {
stopGame();
if (!coreUrl) throw new Error('No Core JS URL provided.');
if (!romUrl) throw new Error('No ROM URL provided.');
const canvas = el('gameCanvas');
// Set an initial canvas size; LibretroAdapter._resize() will update it
// once retro_get_system_av_info returns the actual base resolution.
canvas.width = 320;
canvas.height = 240;
canvas.style.removeProperty('width');
canvas.style.removeProperty('height');
el('loadingOverlay').textContent = 'Loading core…';
const coreModule = await LibretroAdapter.loadCore(
coreUrl,
coreWasmUrl || null,
);
el('loadingOverlay').textContent = 'Fetching ROM…';
game = new LibretroAdapter(canvas, playerOrder, coreModule);
await game.loadROM(romUrl);
// Apply 3× CSS scaling based on the resolved native resolution.
canvas.style.width = `${canvas.width * 3}px`;
canvas.style.height = `${canvas.height * 3}px`;
el('_startEngine function · javascript · L494-L511 (18 LOC)public/js/app.js
function _startEngine(playerOrder) {
engine = new RollbackEngine({
emulator: game,
localPlayerId: roomState.playerId,
playerIds: playerOrder,
readInput: () => inputMgr.getInput(),
onStats: updateStatsHUD,
});
engine._sendInput = (frame, input) => {
net.send({ type: 'input', frame, input });
};
engine.start();
updateHostControls();
el('rematchBtn').classList.toggle('hidden', roomState.hostId !== roomState.playerId);
}stopGame function · javascript · L513-L521 (9 LOC)public/js/app.js
function stopGame() {
engine?.stop();
engine = null;
game?.stopAudio?.();
game = null;
inputMgr?.destroy();
inputMgr = null;
clearStatsHUD();
}updateStatsHUD function · javascript · L525-L530 (6 LOC)public/js/app.js
function updateStatsHUD(stats) {
el('hudFrame').textContent = stats.frame;
el('hudConfirmed').textContent = stats.confirmedFrame;
el('hudRollbacks').textContent = stats.rollbacks;
el('hudMaxDepth').textContent = stats.maxRollbackDepth;
}clearStatsHUD function · javascript · L532-L536 (5 LOC)public/js/app.js
function clearStatsHUD() {
['hudFrame', 'hudConfirmed', 'hudRollbacks', 'hudMaxDepth'].forEach(id => {
el(id).textContent = '—';
});
}addChatLine function · javascript · L540-L557 (18 LOC)public/js/app.js
function addChatLine(kind, text, sender) {
const box = el('chatLog');
const div = document.createElement('div');
div.className = `chat-line chat-${kind}`;
if (kind === 'system') {
div.textContent = `⚙ ${text}`;
} else {
const nameSpan = document.createElement('span');
nameSpan.className = 'chat-sender';
nameSpan.textContent = sender + ': ';
div.appendChild(nameSpan);
div.appendChild(document.createTextNode(text));
}
box.appendChild(div);
box.scrollTop = box.scrollHeight;
}escHtml function · javascript · L563-L567 (5 LOC)public/js/app.js
function escHtml(str) {
return String(str)
.replace(/&/g, '&').replace(/</g, '<')
.replace(/>/g, '>').replace(/"/g, '"');
}Methodology: Repobility · https://repobility.com/research/state-of-ai-code-2026/
showStatus function · javascript · L569-L575 (7 LOC)public/js/app.js
function showStatus(view, msg, isError = false) {
const el_ = document.getElementById(`status-${view}`);
if (!el_) return;
el_.textContent = msg;
el_.className = `status ${isError ? 'error' : 'info'}`;
el_.classList.remove('hidden');
}DemoGame class · javascript · L18-L300 (283 LOC)public/js/demo-game.js
class DemoGame {
// ── Canvas dimensions (logical pixels) ──────────────────────────────────
static W = 800;
static H = 500;
// ── Geometry constants ───────────────────────────────────────────────────
static PADDLE_W = 12;
static PADDLE_H = 90;
static PADDLE_SPEED = 7;
static PADDLE_LEFT_X = 40;
static PADDLE_RIGHT_X = DemoGame.W - 40 - DemoGame.PADDLE_W;
static BALL_R = 9;
static BALL_INIT_VX = 5;
static BALL_INIT_VY = 3;
static BALL_MAX_SPEED = 15;
static BALL_ACCEL = 0.4; // speed increase on each paddle hit
static WIN_SCORE = 7;
// ─────────────────────────────────────────────────────────────────────────
/**
* @param {HTMLCanvasElement} canvas
* @param {string[]} playerIds ordered player IDs (index 0 = left, 1 = right)
* @param {number} seed shared PRNG seed from server
*/
constructor(canvas, playerIds, seed) {
this.canvas = canvas;
this.ctx = canvas.getContext('2d');
this.playerIds = playerIds;
thconstructor method · javascript · L45-L55 (11 LOC)public/js/demo-game.js
constructor(canvas, playerIds, seed) {
this.canvas = canvas;
this.ctx = canvas.getContext('2d');
this.playerIds = playerIds;
this.seed = seed >>> 0;
canvas.width = DemoGame.W;
canvas.height = DemoGame.H;
this.state = this._initialState();
}step method · javascript · L59-L133 (75 LOC)public/js/demo-game.js
step(inputMap) {
const s = this.state;
if (s.winner !== null) return; // game over — freeze
s.frame++;
if (s.hitFlash > 0) s.hitFlash--;
// Move paddles ────────────────────────────────────────────────────────
for (let i = 0; i < 2; i++) {
const pid = this.playerIds[i];
if (!pid) continue;
const input = inputMap[pid] ?? 0;
const pad = s.paddles[i];
if (input & InputBits.UP) pad.y = Math.max(0, pad.y - DemoGame.PADDLE_SPEED);
if (input & InputBits.DOWN) pad.y = Math.min(DemoGame.H - DemoGame.PADDLE_H, pad.y + DemoGame.PADDLE_SPEED);
}
// Move ball ───────────────────────────────────────────────────────────
s.ball.x += s.ball.vx;
s.ball.y += s.ball.vy;
// Top / bottom wall bounce
if (s.ball.y - DemoGame.BALL_R <= 0) {
s.ball.y = DemoGame.BALL_R;
s.ball.vy = Math.abs(s.ball.vy);
} else if (s.ball.y + DemoGame.BALL_R >= DemoGame.H) {
s.ball.y = DemoGame.H - DemoGame.BALL_R;
saveState method · javascript · L135-L146 (12 LOC)public/js/demo-game.js
saveState() {
const s = this.state;
return {
frame: s.frame,
rngState: s.rngState,
ball: { ...s.ball },
paddles: s.paddles.map(p => ({ ...p })),
scores: [...s.scores],
winner: s.winner,
hitFlash: s.hitFlash,
};
}loadState method · javascript · L148-L158 (11 LOC)public/js/demo-game.js
loadState(snap) {
this.state = {
frame: snap.frame,
rngState: snap.rngState,
ball: { ...snap.ball },
paddles: snap.paddles.map(p => ({ ...p })),
scores: [...snap.scores],
winner: snap.winner,
hitFlash: snap.hitFlash,
};
}render method · javascript · L160-L254 (95 LOC)public/js/demo-game.js
render() {
const ctx = this.ctx;
const s = this.state;
const { W, H, PADDLE_W, PADDLE_H, PADDLE_LEFT_X, PADDLE_RIGHT_X, BALL_R } = DemoGame;
// Background ─────────────────────────────────────────────────────────
ctx.fillStyle = '#080810';
ctx.fillRect(0, 0, W, H);
// Hit flash ──────────────────────────────────────────────────────────
if (s.hitFlash > 0) {
ctx.fillStyle = `rgba(255,255,180,${s.hitFlash * 0.025})`;
ctx.fillRect(0, 0, W, H);
}
// Center dashed divider ──────────────────────────────────────────────
ctx.save();
ctx.strokeStyle = '#1c1c2e';
ctx.lineWidth = 2;
ctx.setLineDash([14, 14]);
ctx.beginPath();
ctx.moveTo(W / 2, 0);
ctx.lineTo(W / 2, H);
ctx.stroke();
ctx.restore();
// Scores ─────────────────────────────────────────────────────────────
ctx.save();
ctx.font = 'bold 54px "Courier New", monospace';
ctx.textAlign = 'center';
// P1 score
ctx.fillStyle =_initialState method · javascript · L258-L272 (15 LOC)public/js/demo-game.js
_initialState() {
const rngState = this.seed;
return {
frame: 0,
rngState,
ball: { x: DemoGame.W / 2, y: DemoGame.H / 2, vx: DemoGame.BALL_INIT_VX, vy: DemoGame.BALL_INIT_VY },
paddles: [
{ y: DemoGame.H / 2 - DemoGame.PADDLE_H / 2 },
{ y: DemoGame.H / 2 - DemoGame.PADDLE_H / 2 },
],
scores: [0, 0],
winner: null,
hitFlash: 0,
};
}Want fix-PRs on findings? Install Repobility's GitHub App · github.com/apps/repobility-bot
_resetBall method · javascript · L275-L286 (12 LOC)public/js/demo-game.js
_resetBall(s, lastScorer) {
// LCG: next = (a * x + c) mod m (Numerical Recipes constants)
s.rngState = ((Math.imul(1664525, s.rngState) + 1013904223) >>> 0);
const vy = (((s.rngState >>> 16) % 5) - 2) * 1.2; // -2.4 … 2.4
const vx = DemoGame.BALL_INIT_VX * (lastScorer === 0 ? -1 : 1);
s.ball = {
x: DemoGame.W / 2,
y: DemoGame.H / 2,
vx,
vy,
};
}_drawPaddle method · javascript · L288-L299 (12 LOC)public/js/demo-game.js
_drawPaddle(ctx, x, y, color) {
ctx.save();
ctx.shadowColor = color;
ctx.shadowBlur = 14;
ctx.fillStyle = color;
ctx.fillRect(x, y, DemoGame.PADDLE_W, DemoGame.PADDLE_H);
// Lighter highlight stripe
ctx.fillStyle = 'rgba(255,255,255,0.18)';
ctx.fillRect(x, y, 3, DemoGame.PADDLE_H);
ctx.shadowBlur = 0;
ctx.restore();
}InputManager class · javascript · L35-L184 (150 LOC)public/js/input.js
class InputManager {
// Analog stick dead zone — ignore tilt below this threshold
static DEAD_ZONE = 0.5;
constructor() {
this._held = new Set();
this._onKeyDown = (e) => {
// Prevent arrow keys / space from scrolling the page
if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', ' '].includes(e.key)) {
e.preventDefault();
}
this._held.add(e.code);
};
this._onKeyUp = (e) => {
this._held.delete(e.code);
};
window.addEventListener('keydown', this._onKeyDown);
window.addEventListener('keyup', this._onKeyUp);
// Track connected gamepads for the UI indicator
this._connectedGamepads = new Set();
this._onGamepadConnected = (e) => {
this._connectedGamepads.add(e.gamepad.index);
this._updateGamepadIndicator();
};
this._onGamepadDisconnected = (e) => {
this._connectedGamepads.delete(e.gamepad.index);
this._updateGamepadIndicator();
};
window.addEventListener('gaconstructor method · javascript · L39-L72 (34 LOC)public/js/input.js
constructor() {
this._held = new Set();
this._onKeyDown = (e) => {
// Prevent arrow keys / space from scrolling the page
if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', ' '].includes(e.key)) {
e.preventDefault();
}
this._held.add(e.code);
};
this._onKeyUp = (e) => {
this._held.delete(e.code);
};
window.addEventListener('keydown', this._onKeyDown);
window.addEventListener('keyup', this._onKeyUp);
// Track connected gamepads for the UI indicator
this._connectedGamepads = new Set();
this._onGamepadConnected = (e) => {
this._connectedGamepads.add(e.gamepad.index);
this._updateGamepadIndicator();
};
this._onGamepadDisconnected = (e) => {
this._connectedGamepads.delete(e.gamepad.index);
this._updateGamepadIndicator();
};
window.addEventListener('gamepadconnected', this._onGamepadConnected);
window.addEventListener('gamepaddisconnected', this._onGamep_getGamepadBits method · javascript · L81-L133 (53 LOC)public/js/input.js
_getGamepadBits() {
if (!navigator.getGamepads) return 0;
let bits = 0;
const DEAD = InputManager.DEAD_ZONE;
for (const gp of navigator.getGamepads()) {
if (!gp) continue;
const btn = gp.buttons;
const ax = gp.axes;
if (gp.mapping === 'standard') {
// ── Standard layout (Xbox / PlayStation / Switch Pro) ──────────────
// D-pad
if (btn[12]?.pressed) bits |= InputBits.UP;
if (btn[13]?.pressed) bits |= InputBits.DOWN;
if (btn[14]?.pressed) bits |= InputBits.LEFT;
if (btn[15]?.pressed) bits |= InputBits.RIGHT;
// Face buttons (full SNES mapping)
if (btn[0]?.pressed) bits |= InputBits.A; // A / Cross
if (btn[1]?.pressed) bits |= InputBits.B; // B / Circle
if (btn[2]?.pressed) bits |= InputBits.X; // X / Square (SNES X)
if (btn[3]?.pressed) bits |= InputBits.Y; // Y / Triangle (SNES Y)
// Shoulder buttons
if (btn[4]?.pressed) bit_updateGamepadIndicator method · javascript · L137-L148 (12 LOC)public/js/input.js
_updateGamepadIndicator() {
const el = document.getElementById('gamepadIndicator');
if (!el) return;
const count = this._connectedGamepads.size;
if (count === 0) {
el.textContent = '';
el.classList.add('hidden');
} else {
el.textContent = `Gamepad (${count})`;
el.classList.remove('hidden');
}
}getInput method · javascript · L153-L175 (23 LOC)public/js/input.js
getInput() {
let bits = 0;
const h = this._held;
// Keyboard
if (h.has('ArrowUp') || h.has('KeyW')) bits |= InputBits.UP;
if (h.has('ArrowDown') || h.has('KeyS')) bits |= InputBits.DOWN;
if (h.has('ArrowLeft') || h.has('KeyA')) bits |= InputBits.LEFT;
if (h.has('ArrowRight') || h.has('KeyD')) bits |= InputBits.RIGHT;
if (h.has('KeyZ') || h.has('KeyJ')) bits |= InputBits.A;
if (h.has('KeyX') || h.has('KeyK')) bits |= InputBits.B;
if (h.has('KeyC')) bits |= InputBits.X; // SNES X
if (h.has('KeyV')) bits |= InputBits.Y; // SNES Y
if (h.has('KeyQ')) bits |= InputBits.L; // SNES L
if (h.has('KeyE')) bits |= InputBits.R; // SNES R
if (h.has('Enter') || h.has('KeyP')) bits |= InputBits.START;
if (h.has('ShiftLeft') || h.has('ShiftRight') || h.has('KeyO')) bits |= InputBits.SELECT;
// Gamepad (ORed in — keydestroy method · javascript · L178-L183 (6 LOC)public/js/input.js
destroy() {
window.removeEventListener('keydown', this._onKeyDown);
window.removeEventListener('keyup', this._onKeyUp);
window.removeEventListener('gamepadconnected', this._onGamepadConnected);
window.removeEventListener('gamepaddisconnected', this._onGamepadDisconnected);
}Repobility — the code-quality scanner for AI-generated software · https://repobility.com
loadCore method · javascript · L108-L163 (56 LOC)public/js/libretro-adapter.js
static loadCore(jsUrl, wasmUrl = null, globalName = 'LibretroCore') {
return new Promise(async (resolve, reject) => {
const timeout = setTimeout(
() => reject(new Error('Libretro core initialization timed out (30 s)')),
30_000
);
// Pre-configure the Emscripten Module before the script runs.
// Standard Emscripten glue checks for a pre-existing global named 'Module'
// (or occasionally the build-configured name); we set both so either works.
const modCfg = {
noInitialRun: true,
onRuntimeInitialized() {
clearTimeout(timeout);
resolve(modCfg);
},
print: (msg) => console.log('[libretro]', msg),
printErr: (msg) => console.warn('[libretro]', msg),
};
if (wasmUrl) {
// Override Emscripten's WASM resolver so the binary is fetched through
// our proxy regardless of the base URL Emscripten thinks the .js lives at.
modCfg.locateFile = (patonRuntimeInitialized method · javascript · L120-L123 (4 LOC)public/js/libretro-adapter.js
onRuntimeInitialized() {
clearTimeout(timeout);
resolve(modCfg);
},constructor method · javascript · L172-L200 (29 LOC)public/js/libretro-adapter.js
constructor(canvas, playerIds, coreModule) {
this.canvas = canvas;
this.playerIds = playerIds;
this.M = coreModule;
this.ctx = canvas.getContext('2d');
this._imageData = null;
this._frameBuffer = null; // Uint32Array view into _imageData.data
this._width = 0;
this._height = 0;
this._pixelFormat = LibretroAdapter.PIXEL_FORMAT.RGB1555;
this._dirty = false;
this._romLoaded = false;
this._audioMuted = false;
this._audioCtx = null;
this._workletNode = null;
// Per-port button state: port index → Uint8Array(16) where 1 = pressed
this._inputState = [new Uint8Array(16), new Uint8Array(16)];
// Reusable buffer for the single-sample audio callback path
this._singleSampleBuf = new Int16Array(2);
// Keep all registered function pointers alive (prevents GC of the JS closure)
this._callbacks = {};
this._registerCallbacks();
}page 1 / 3next ›