<|start_filename|>run.js<|end_filename|>
const fs = require("fs");
const pm2 = require("pm2");
const path = require("path");

/** @type {pm2.StartOptions[]} */
const DefaultProcesses = [
    {
        name: "Server1",
        env: {
            OGARX_MODE: "default/mega",
            OGARX_PORT: 3000,
            OGARX_SERVER: "Megasplit",
            OGARX_ENDPOINT: "mega",
            OGARX_TOKEN: process.env.OGARX_TOKEN
        }
    }
];

/** @type {pm2.StartOptions[]} */
let config = null;

if (!fs.existsSync("config.json")) {
    fs.writeFileSync("config.json", JSON.stringify(DefaultProcesses, null, 4));
    config = DefaultProcesses;
} else {
    config = require("./config.json");
}

const validateMode = mode => fs.existsSync(path.resolve(__dirname, "src", "modes", `${mode}.js`));

const { GATEWAY_PORT, GATEWAY_ORIGIN } = process.env;

/** @type {pm2.StartOptions[]} */
const procToStart = [];

for (const proc of config) {
    proc.cwd = __dirname;
    proc.script = "./src/index.js";
    proc.max_memory_restart = "300M";
    proc.kill_timeout = 3000;

    if (validateMode(proc.env.OGARX_MODE)) {
        procToStart.push(proc);
    } else {
        console.warn(`Unable to find mode "${proc.env.OGARX_MODE}", aborting process "${proc.name}"`);
    }
}

pm2.connect(err => {
    if (err) return console.error("Failed to connect to pm2", err);
    pm2.start({
        name: "Gateway",
        cwd: __dirname,
        script: "./src/gateway.js",
        wait_ready: true,
        env: { GATEWAY_PORT, GATEWAY_ORIGIN }
    }, e => {
        if (e) console.error(e);
        else console.log("Gateway Process started");

        Promise.all(procToStart.map(proc => new Promise(res => {        
            pm2.start(proc, e => {
                if (e) console.error(e);
                else  console.log(`PM2 Process "${proc.name}" ` +
                    `(${proc.env.OGARX_MODE}-${proc.env.OGARX_SERVER}) mounted on ` +
                    `:${proc.env.OGARX_PORT || 443}/${proc.env.OGARX_ENDPOINT}`);
                res();
            });
        }))).then(() => process.exit(0));
    });
});

<|start_filename|>public/css/index.css<|end_filename|>
@font-face {
    font-family: "Lato";
    src: url("/static/font/Lato-Bold.ttf") format("truetype");
}
:root {
    --border-color-1: #5779dd;
    --border-color-2: #e64ca6;
}
body {
    font-family: "Lato";
    color: #eee;
    background-color: black;
    position: absolute!important;
    top: 0px;
    left: 0px;
    width: 100%;
    height: 100%;
    margin: 0px;
    overflow: hidden;
    user-select: none;
    z-index: -2;
}
#settings-bar {
    position: absolute;
    top:  15px;
    left: 15px;
    z-index: 1;
    height: 50px;
    width: auto;
}
.settings-icon {
    margin: 5px 5px 5px 5px;
    width: 30px;
    height: 30px;
    outline: none;
}
.settings-icon svg {
    width: 30px;
    height: 30px;
    opacity: 1;
}
.play-icon {
    border-radius: 50%;
    background-color: #00000044;
}
.ogarx-icon {
    outline: none;
}
.ogarx-icon svg {
    color: rgba(238, 238, 238, 0.5)!important;
    fill: rgba(238, 238, 238, 0.5)!important;
    transition: fill 0.25s linear, color 0.25s linear;
    cursor: pointer;
}
.ogarx-icon svg:hover {
    color: rgba(238, 238, 238, 0.9)!important;
    fill: rgba(238, 238, 238, 0.9)!important;
}
.key-value {
    display: grid;
    grid-template-columns: auto auto;
    column-gap: 15px;
}
#skins {
    padding: 20px;
    overflow-y: scroll;
    display: grid;
    grid-template-columns: auto auto;
    row-gap: 15px;
    column-gap: 15px;
}
.skin-item {
    width: 100px;
    height: 100px;
    cursor: pointer;
}
.skin-item img {
    width: 100%;
    height: 100%;
    border-radius: 50%;
}
.skin-item .selected-1, #skin-1 {
    border: solid 5px var(--border-color-1);
}
.skin-item .selected-2, #skin-2 {
    border: solid 5px var(--border-color-2);
}
.skin-item .selected-1.selected-2 {
    border: solid 5px #ffffff;
}
.key-value * {
    padding: 8px;
    margin-top: 10px;
    font-size: 18px;
}
.key-value *:nth-child(2n-1) {
    text-align: right;
}
h1, h2, h3, h4, h5, h6 {
    font-weight: bold;
    color: #93a7e1!important;
}
#offcanvas-menu {
    background-color: rgba(14, 14, 14, 0.8);
    align-items: center;
    display: flex;
    flex-direction: column;
}
#replay-list {
    max-height: calc(100vh - 320px);
    overflow-y: scroll;
}
#prev-clip, #next-clip {
    cursor: pointer;
    outline: none;
}
.disabled * {
    cursor: not-allowed!important;
    color:rgba(136, 136, 136, 0.5)!important;
}
.replay-item {
    padding: 10px;
}
.replay-item * {
    transition: opacity 0.25s linear;
}
.replay-thumbnail {
    width: 100%;
    aspect-ratio: 16/9;
    object-fit: cover;
    transition: box-shadow 0.25s linear;
    box-shadow: 0px 0px 2px #93a7e1;
}
.replay-thumbnail:hover {
    box-shadow: 0px 0px 5px #93a7e1;
}
.stats-pie {
    margin: auto;
    width: 60%;
    aspect-ratio: 1/1;
}
.stats-pie circle {
    transition: stroke-dasharray 1s linear, stroke-dashoffset 1s linear;
}
.stats-pie text {
    stroke: none;
    fill: #eee;
    text-anchor: middle;
    alignment-baseline: central;
    font-size: 8px;
}
#minimap {
    position: absolute;
    bottom: 0px;
    right: 0px;
    width: 200px;
    height: 200px;
    background-color: rgba(14, 14, 14, 0.8);
    border: solid 2px#ffffff77;
}
.minimap-node {
    transition: cx 0.25s linear, cy 0.25s linear, r 0.25s linear;
}
.minimap-text {
    text-anchor: middle;
    font-family: Arial, Helvetica, sans-serif;
    font-size: 40px;
    color: #eee;
    transition: x 0.25s linear, y 0.25s linear, r 0.25s linear;
}
.selectable {
    color: rgba(238, 238, 238, 0.8);
    transition: color 0.1s linear;
    cursor: pointer;
}
.selectable:hover {
    color: #93a7e1;
}
#canvas {
    position: fixed;
    min-width: 100vw;
    min-height: 100vh;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    z-index: -1;
    object-fit: cover;
}
#hud, #game-over {
    display: flex;
    flex-direction: column;
    justify-content: center;
    width: 100vw;
    height: 100vh;
    position: absolute;
    text-align: center;
    background: radial-gradient(ellipse at center, rgba(0,0,0,0.5) 0%,rgba(125,185,232,0) 100%);
}

#game-over span {
    font-size: 1.2em;
}

.fade-in {
    animation: fadeIn ease 0.5s;
}
@keyframes fadeIn {
    0% { opacity:0; }
    100% { opacity:1; }
}

.fade-out {
    animation: fadeOut ease 0.5s;
}
@keyframes fadeOut {
    0% { opacity:1; }
    100% { opacity:0; }
}

#leaderboard {
    padding: 10px;
    width: 15%;
    height: auto;
    margin: 0;
    position: absolute;
    top: 30px;
    right: 0;
    z-index: 1;
    color: #eee;
    background-color: #00000044;
}
#stats1 {
    position: absolute;
    top: 10px;
    right: 40px;
    z-index: 1;
    color: #eee;
    background-color: #00000044;
}
#stats2 {
    position: absolute;
    top: 10px;
    left: 50%;
    z-index: 1;
    color: #eee;
    transform: translateX(-50%);
    background-color: #00000044;
}
#stats3 {
    position: absolute;
    top: 10px;
    left: 160px;
    z-index: 1;
    color: #eee;
    background-color: #00000044;
}
.uk-modal-dialog {
    background-color: #000000ee;
}
#ping, #fps, #bandwidth {
    display: inline-block;
    width: 1.5em;
    text-align: left;
}
#linelock {
    display: inline-block;
    width: 6em;
    text-align: left;
}
#mycells {
    display: inline-block;
    width: 2em;
    text-align: right;
}
.uk-notification-message {
    background-color: #111;
}
.text-danger {
    color: red;
}
.text-success {
    color: greenyellow;
}
#leaderboard p {
    margin: 5px;
}
#server-name {
    margin: 0px;
    text-align: left;
}
#chat {
    overflow-y: scroll;
    background-color: #0000007a;
    color: #eee;
    position: absolute;
    bottom: 0px;
    left: 0px;
    width: 25vw;
    height: 40vh;
    z-index: 1;
}
#chat p {
    margin: 4px;
}
#skin-1, #skin-2 {
    width: 200px;
    height: 200px;
    border-radius: 50%;
    margin: auto;
}
#swap-skin {
    padding: 5px;
    background-color: #00000044;
}
#skin-1 {
    transform: translateX(-150px);
}
#skin-2 {
    transform: translateX(150px);
}
#skin-tab {
    min-width: 300px;
}
#skin-input {
    text-align: center;
    width: 300px;
}
#chat-input {
    position: absolute;
    bottom: 20px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 1;
}
.uk-overlay-default {
    background-color: transparent;
}
.servers {
    width: 100%;
}
.servers span {
    color: #bbb;
}
.servers:hover span {
    color: #eee;
}
input[type="text"], #server-input, .servers {
    margin: auto;
    padding: 5px;
    font-size: 20px;
    background-color: rgba(0, 0, 0, 0.8);
    color: #eee;
    outline: none;
    border: none;
    border-bottom: solid 3px rgba(255, 255, 255, 0.25);
    transition: 0.2s linear;
    appearance: none;
    -moz-appearance: none;
    -webkit-appearance: none;
}
input[type="text"]:focus, #server-input:hover, .servers:hover {
    color: #eee;
    background-color: #22222277;
    border-bottom: solid 3px #93a7e1;
}
input[type="color"] {
    -webkit-appearance: none;
    margin-top: 0px;
    padding: 2px;
    width: 15px;
    height: 15px;
    border: none;
    outline: none;
    cursor: pointer;
}
input[type="color"]::-webkit-color-swatch-wrapper {
	padding: 0;
}
input[type="color"]::-webkit-color-swatch {
	border: none;
}
.server-log, .me {
    color:  #93a7e1;
}
#server-input:hover { cursor: pointer; }
button {
    min-width: 100px;
    outline: none;
    padding: 8px;
    font-size: 20px;
    background-color: rgba(0, 0, 0, 0.8);
    color: #eee;
    border: solid 3px rgba(255, 255, 255, 0.25);
    appearance: none;
    -moz-appearance: none;
    -webkit-appearance: none;
    cursor: pointer;
}
button:hover {
    border: solid 3px #93a7e1;
}
button:disabled {
    cursor: not-allowed;
    border: solid 3px #444;
}
::-webkit-scrollbar {
    width: 10px;
}
::-webkit-scrollbar-track {
    background: #00000055; 
}
::-webkit-scrollbar-thumb {
    background: rgba(136, 136, 136, 0.5); 
}
::-webkit-scrollbar-thumb:hover {
    background: rgba(238, 238, 238, 0.6); 
}
select {
    margin: 0;
    padding: 5px 35px 5px 5px;
    -webkit-appearance: none;
    -moz-appearance: none;
    appearance: none;
}

#render-delay-display {
    pointer-events: none;
    margin-top: -3px;
}

/* The slider itself */
.slider {
    -webkit-appearance: none;  /* Override default CSS styles */
    appearance: none;
    padding: 0;
    margin: 0;
    width: 100%; /* Full-width */
    height: 25px; /* Specified height */
    background:rgba(136, 136, 136, 0.25); /* Grey background */
    outline: none; /* Remove outline */
    -webkit-transition: .2s; /* 0.2 seconds transition on hover */
    transition: opacity .2s;
}
  
/* Mouse-over effects */
.slider:hover {
    background: rgba(238, 238, 238, 0.3); 
}
  
/* The slider handle (use -webkit- (Chrome, Opera, Safari, Edge) and -moz- (Firefox) to override default look) */
.slider::-webkit-slider-thumb {
    -webkit-appearance: none; /* Override default look */
    appearance: none;
    width: 8px; /* Set a specific slider handle width */
    height: 25px; /* Slider handle height */
    background: #93a7e1;
    cursor: pointer; /* Cursor on hover */
}
  
.slider::-moz-range-thumb {
    width: 8px; /* Set a specific slider handle width */
    height: 25px; /* Slider handle height */
    background: #93a7e1;
    cursor: pointer; /* Cursor on hover */
}

.uk-subnav span {
    font-size: 20px;
}

.signal-bars {  
    height: 26px;
    width: 30px;  
    margin: auto;
    padding: 0px!important;
    display: flex;
    align-items: flex-end;
}

.signal-bars div {
    width: 6px;  
    background-color: #777;
    margin-left: 0px;
    margin-right: 2px;
}

.signal-bars > :nth-child(1) { height: 20%; }
.signal-bars > :nth-child(2) { height: 40%; }
.signal-bars > :nth-child(3) { height: 60%; }
.signal-bars > :nth-child(4) { height: 80%; margin-right: 0px; }

.signal-good .signal-bars > div {
    background-color: #16a085;
    border: thin solid darken(#16a085, 7%);
}

.signal-ok .signal-bars > :nth-child(1), 
.signal-ok .signal-bars > :nth-child(2), 
.signal-ok .signal-bars > :nth-child(3) {
    background-color: #f1c40f;
    border: thin solid darken(#f1c40f, 7%);
}

.signal-bad .signal-bars > :nth-child(1), 
.signal-bad .signal-bars > :nth-child(2) {
    background-color: #e74c3c;
    border: thin solid darken(#e74c3c, 20%);
}

.signal-worse .signal-bars > :nth-child(1) {
    background-color: #a01303;
    border: thin solid darken( #a01303, 20%);
}


<|start_filename|>src/game/handle.js<|end_filename|>
module.exports = class Handle {
    /** @param {import(".")} game */
    constructor(game) {
        this.game = game;
        /** @type {import("./controller")} */
        this.controller = null; 
        /** @type {Set<number>} */
        this.pids = new Set();
        /** @type {Handle} */
        this.spectate = null;
        
        this.onLog = this.onLog.bind(this);
        this.onTick = this.onTick.bind(this);
        this.onJoin = this.onJoin.bind(this);
        this.onChat = this.onChat.bind(this);
        this.onInfo = this.onInfo.bind(this);
        this.onLeave = this.onLeave.bind(this);
        this.onSpawn = this.onSpawn.bind(this);
        this.onError = this.onError.bind(this);
        this.onMinimap = this.onMinimap.bind(this);
        this.onLeaderboard = this.onLeaderboard.bind(this);

        this.game
            .on("log", this.onLog)
            .on("tick", this.onTick)
            .on("join", this.onJoin)
            .on("chat", this.onChat)
            .on("info", this.onInfo)
            .on("leave", this.onLeave)
            .on("spawn", this.onSpawn)
            .on("error", this.onError)
            .on("minimap", this.onMinimap)
            .on("leaderboard", this.onLeaderboard);

        /** @type {[string, Function][]} */
        this.extraEvents = [];

        this.showonMinimap = false;
        this.showonLeaderboard = false;
        this.wasAlive = false;
        this.actualSpawnTick = 0;
    };

    get alive() { return this.controller.alive; }
    get score() { return this.controller.score; }
    
    join() { 
        this.game.addHandler(this); 
        this.pids.add(this.controller.id);
    }

    remove() { 
        this.game.removeHandler(this);
    }

    calculateViewport() {
        const g = this.game;
        const e = g.engine;
        const o = g.options;
        const cells = g.engine.cells;

        let size = 0, size_x = 0, size_y = 0;
        let x = 0, y = 0, factor = 0;
        let min_x = o.MAP_HW, max_x = -o.MAP_HW;
        let min_y = o.MAP_HH, max_y = -o.MAP_HH;

        let cell_count = 0;
        let total_score = 0;

        for (const id of this.pids) {
            let score = 0;

            cell_count += g.engine.counters[id].size;
            for (const cell_id of e.counters[id]) {
                const cell = cells[cell_id];
                const r = cell.r;
                const sqr = r * r;
                const cell_x = cell.x, cell_y = cell.y;
                x += cell_x * sqr;
                y += cell_y * sqr;
                min_x = Math.min(min_x, cell_x - r);
                max_x = Math.max(max_x, cell_x + r);
                min_y = Math.min(min_y, cell_y - r);
                max_y = Math.max(max_y, cell_y + r);
                score += sqr * 0.01;
                size += sqr;
            }
            g.controls[id].score = score;
            total_score += score;
        }

        if (!cell_count) return;

        size = size || 1;
        factor = Math.pow(cell_count + 100, 0.05);
        const factored_size = (factor + 1) * Math.sqrt(total_score * 100);
        const vx = x / size, vy = y / size;
        size_x = size_y = Math.max(factored_size, o.PLAYER_VIEW_MIN) * o.PLAYER_VIEW_SCALE;
        size_x = Math.max(size_x, (vx - min_x) * 1.5);
        size_x = Math.max(size_x, (max_x - vx) * 1.5);
        size_y = Math.max(size_y, (vy - min_y) * 1.5);
        size_y = Math.max(size_y, (max_y - vy) * 1.5);

        for (const id of this.pids) {
            const c = this.game.controls[id];
            c.box.set([min_x, max_x, min_y, max_y]);
            c.viewportX = vx;
            c.viewportY = vy;
            c.viewportHW = size_x;
            c.viewportHH = size_y;
            c.maxScore = Math.max(c.maxScore, total_score);

            if (c.score > o.MAP_HH * o.MAP_HW / 100 * o.WORLD_RESTART_MULT) {
                g.emit("oversize", c);
                if (o.WORLD_KILL_OVERSIZE) {
                    e.delayKill(id);
                } else {
                    e.shouldRestart = true;
                }
            }
        }
    }


    register(event, callback) {
        this.game.on(event, callback);
        this.extraEvents.push([event, callback]);
    }

    off() {
        this.remove();
        this.game
            .off("log", this.onLog)
            .off("tick", this.onTick)
            .off("join", this.onJoin)
            .off("chat", this.onChat)
            .off("info", this.onInfo)
            .off("leave", this.onLeave)
            .off("spawn", this.onSpawn)
            .off("error", this.onError)
            .off("minimap", this.onMinimap)
            .off("leaderboard", this.onLeaderboard);
        for (const [event, cb] of this.extraEvents)
            this.game.off(event, cb);
        this.extraEvents = [];
        this.wasAlive = false;
    }

    // Virtual methods
    onLog() {};
    onTick() {};
    onJoin(controller) {};
    onChat(message) {};
    onLeave(controller) {};
    onSpawn(controller) {};
    onInfo(controller) {};
    onError(err) {};
    onMinimap(handles) {};
    onLeaderboard(handles) {};
}

<|start_filename|>webgl/game/state.js<|end_filename|>
module.exports = class State {
    constructor () {
        this.setBuffer();
    }

    setBuffer(buf) {
        if (!buf) {
            if (self.SharedArrayBuffer) buf = new SharedArrayBuffer(200);
            else buf = new ArrayBuffer(200);
        }

        this.sharedBuffer = buf;
        this.buffer = new Int32Array(this.sharedBuffer);
    }

    get resolution() { return [[1920, 1080], [1280, 720], [854, 480]][this.quality] || [1920, 1080]; }

    get spectate() { return Atomics.load(this.buffer, 0); }
    set spectate(v) { Atomics.store(this.buffer, 0, v); }

    get splits() { return Atomics.load(this.buffer, 1); }
    set splits(v) { Atomics.add(this.buffer, 1, v); }

    get ejects() { return Atomics.load(this.buffer, 2); }
    set ejects(v) { Atomics.add(this.buffer, 2, v); }

    get macro() { return Atomics.load(this.buffer, 3); }
    set macro(v) { Atomics.store(this.buffer, 3, v); }

    get respawn() { return Atomics.load(this.buffer, 4); }
    set respawn(v) { Atomics.store(this.buffer, 4, v); }

    get visible() { return Atomics.load(this.buffer, 5); }
    set visible(v) { Atomics.store(this.buffer, 5, v); }

    get lineLock() { return Atomics.load(this.buffer, 6); }
    set lineLock(v) { Atomics.store(this.buffer, 6, v); }

    get skin() { return Atomics.load(this.buffer, 7); }
    set skin(v) { Atomics.store(this.buffer, 7, v) }

    get name() { return Atomics.load(this.buffer, 8); }
    set name(v) { Atomics.store(this.buffer, 8, v); }

    get mass() { return Atomics.load(this.buffer, 9); }
    set mass(v) { Atomics.store(this.buffer, 9, v); }

    get draw() { return Atomics.load(this.buffer, 10); }
    set draw(v) { Atomics.store(this.buffer, 10, v); }

    get zoom() { return Atomics.load(this.buffer, 11); }
    set zoom(v) { Atomics.store(this.buffer, 11, v); }

    get auto_respawn() { return Atomics.load(this.buffer, 12); }
    set auto_respawn(v) { Atomics.store(this.buffer, 12, v); }

    get clip() { return Atomics.load(this.buffer, 13); }
    set clip(v) { Atomics.store(this.buffer, 13, v); }
    
    get skin_quality() { return Atomics.load(this.buffer, 14); }
    set skin_quality(v) { Atomics.store(this.buffer, 14, v); }
    
    get text_quality() { return Atomics.load(this.buffer, 15); }
    set text_quality(v) { Atomics.store(this.buffer, 15, v); }
    
    get circle_quality() { return Atomics.load(this.buffer, 16); }
    set circle_quality(v) { Atomics.store(this.buffer, 16, v); }

    get circle_radius() { return [1024, 512, 256, 128][this.circle_quality]; }
    get skin_dim() { return [1024, 512, 256, 128][this.skin_quality]; }
    
    get ignore_skin() { return Atomics.load(this.buffer, 17); }
    set ignore_skin(v) { Atomics.store(this.buffer, 17, v); }
    
    get mouse_sync() { return Atomics.load(this.buffer, 18); }
    set mouse_sync(v) { Atomics.store(this.buffer, 18, v); }

    get quality() { return Atomics.load(this.buffer, 19); }
    set quality(v) { Atomics.store(this.buffer, 19, v); }

    set clicked(v) { Atomics.store(this.buffer, 20, v); }

    set s_tab(v) { Atomics.store(this.buffer, 21, v); }

    exchange() {
        return {
            splits: Atomics.exchange(this.buffer, 1, 0),
            ejects: Atomics.exchange(this.buffer, 2, 0),
            macro: this.macro,
            respawn: Atomics.exchange(this.buffer, 4, 0),
            lineLock: Atomics.exchange(this.buffer, 6, 0),
            clip: Atomics.exchange(this.buffer, 13, 0),
            clicked: Atomics.exchange(this.buffer, 20, 0),
            switch_tab: Atomics.exchange(this.buffer, 21, 0)
        }
    }
}

<|start_filename|>ipc-test/node-ipc/server.js<|end_filename|>
const { IPC } = require("node-ipc");

const ipc = new IPC();

ipc.config.id = "server";
ipc.config.retry = 3000;
ipc.config.rawBuffer = true;
ipc.config.silent = true;

const pong = new ArrayBuffer(1);
new Uint8Array(pong)[0] = 69;

const memory = new WebAssembly.Memory({ initial: 200 });

ipc.serve(() => {
    ipc.server.on("connect", socket => {
        console.log("Server received connection");
        ipc.server.emit(socket, pong);
    });

    ipc.server.on("data", (data, socket) => {
        ipc.server.emit(socket, memory.buffer);
    });
});

ipc.server.start();

<|start_filename|>src/bot/index.js<|end_filename|>
const Handle = require("../game/handle");
const BOTS = require("../../public/static/data/bots.json");

/** @template T @param {T[]} array */
const pick = array => array[~~(Math.random() * array.length)];

module.exports = class Bot extends Handle {

    /** @param {import(".")} game */
    constructor(game) {
        super(game);
        this.join();
        this.controller.name = `[BOT] ${pick(BOTS.names)}`;
        this.controller.skin = BOTS.skins.length ? pick(BOTS.skins) : "";

        this.__nextActionTick = 0;

        this.game.emit("join", this.controller);
    };

    get myCellIDs() { return this.game.engine.counters[this.controller.id]; }
    
    set nextAction(v) { this.__nextActionTick = this.game.engine.__now + 1000 * v; }

    onTick() {

        const c = this.controller;
        const g = this.game;
        const e = g.engine;
        const cells = e.cells;
        const s = e.options.PLAYER_SPAWN_SIZE;
        
        if (e.__now < this.__nextActionTick) return;
        
        // if (!this.controller.alive || this.controller.score < s * s / 500) 
        //     this.controller.spawn = true;
        
        // this.controller.ejectMarco = true;
        // this.controller.splitAttempts = 7;
        // this.controller.mouseX = this.controller.viewportX;
        // this.controller.mouseY = this.controller.viewportY;

        // Less than 20% of or spawn mass and 3 second spawn cooldown
        if (!c.alive || c.score < e.options.BOT_SPAWN_SIZE * e.options.BOT_SPAWN_SIZE * 0.002) {
            c.requestSpawn();
            this.nextAction = 3;
            // console.log("BOT SPAWN");
        } else {
            let canEjectCount = 0;
            for (const cell_id of this.myCellIDs)
                if (cells[cell_id].r > e.options.PLAYER_MIN_EJECT_SIZE) canEjectCount++;

            if (canEjectCount > 3) {
                c.ejectMarco = true;
                c.mouseX = c.viewportX;
                c.mouseY = c.viewportY;

                this.nextAction = 1; // 1 sec
            } else {
                
                if (Math.random() < 0.1) {
                    if (e.options.PLAYER_MAX_CELLS > 16 && this.myCellIDs.size < 20 && this.controller.score < 40000) {
                        // Solotrick to random direction
                        c.ejectMarco = true;
                        c.splitAttempts = 7;
                        this.nextAction = 5;
                    } else {
                        c.splitAttempts = ~~(1 + Math.random() * 3);
                        this.nextAction = 3;
                    }
                    
                    const a = Math.random() * Math.PI * 2;
                    c.mouseX = c.viewportX + 5000 * Math.sin(a);
                    c.mouseY = c.viewportY + 5000 * Math.cos(a);
                } else {
                    // Idle
                    c.ejectMarco = false;
                    c.mouseX = c.viewportX;
                    c.mouseY = c.viewportY;
                    this.nextAction = 1;
                    // console.log("BOT IDEL");
                }
            }
        }
    };

    /** @param {string} err */
    onError(err) {};
    /** @param {string} message */
    onChat(message) {};
}

<|start_filename|>src/network/sw-server.js<|end_filename|>
const FakeSocket = require("./fake-socket");

const Protocols = require("./protocols");
// Register console protocol
Protocols.push(require("./protocols/web-console"));

const Game = require("../game");

module.exports = class SharedWorkerServer {

    constructor() {
        this.game = new Game();
        /** @type {Set<MessagePort & { ws: FakeSocket }>} */
        this.ports = new Set();
    }

    open() {
        self.onconnect = e => {
            
            /** @type {MessagePort} */
            const port = e.source;  
            const ws = new FakeSocket(port);
            
            this.ports.add(port);

            ws.onmessage = message => {
                if (!ws.p) {
                    const Protocol = Protocols.find(p => p.handshake(new DataView(message)));
                    if (!Protocol) {
                        console.log(message);
                        ws.end(1003, "Ambiguous protocol");
                    }
                    else ws.p = new Protocol(this.game, ws, message);
                } else {
                    port.lastMessage = Date.now();
                    try {
                        ws.p.onMessage(new DataView(message));
                    } catch(e) {};
                }
            }

            ws.onclose = (code, reason) => {

                if (ws.p && ws.p.controller) {
                    console.log(`Disconnected: (handle#${ws.p.controller.id}) code: ${code}, message: ${reason}`);
                } else {
                    console.log(`Disconnected: code: ${code}, message: ${reason}`);
                }

                this.ports.delete(port);
                ws.p && ws.p.off();
                if (!this.ports.size) self.close();
            }

            // Wait for server to start running (load wasm modules)
            const onopen = () => {
                if (!this.game.engine.running) {
                    setTimeout(() => onopen(), 1000);
                } else {
                    port.postMessage({ event: "open" });
                }
            }
            onopen();
        }

        // Mimic uWS idleTimeout behavior
        setInterval(() => {
            for (const port of this.ports)
                if (Date.now() - port.lastMessage > 3000) 
                    port.ws.end(1001, "No PING received");
        }, 1000);
    }

    close() {
        for (const port of this.ports) port.close();
    }
}

<|start_filename|>webgl/game/replay-db.js<|end_filename|>
/** @returns {Promise<IDBDatabase>} */
module.exports = () => new Promise((resolve, reject) => {
    const req = indexedDB.open("ogar69-replay");
    req.onupgradeneeded = e => {
        console.log("Creating replay object store");
        /** @type {IDBDatabase} */
        const db = e.target.result;
        db.createObjectStore("replay-meta");
        db.createObjectStore("replay-data");
    }
    req.onsuccess = _ => resolve(req.result);
    req.onerror = reject;
});

<|start_filename|>src/index.js<|end_filename|>
const fs = require("fs");
const path = require("path");
const CORE_PATH  = path.resolve(__dirname, "..", "public", "static", "wasm", "server.wasm");
const PROTOCOL_PATH = path.resolve(__dirname, "..", "public", "static", "wasm", "ogarx.wasm");
const SSL_FOLDER_PATH = path.resolve(__dirname, "..", "ssl");
const SSL_PATH = path.resolve(SSL_FOLDER_PATH, "options.json");

const Server = require("./network/ws-server");
const OgarXProtocol = require("./network/protocols/ogarx");

const server = new Server(process.env.OGARX_SERVER);
const engine = server.game.engine;

server.setGameMode(process.env.OGARX_MODE || "default");

process.on("SIGINT", async () => {
    engine.stop();
    await server.close();
    process.exit(0);
});

let sslOptions = null;
if (!fs.existsSync(SSL_FOLDER_PATH)) fs.mkdirSync(SSL_FOLDER_PATH);
if (fs.existsSync(SSL_PATH)) sslOptions = require(SSL_PATH);

(async () => {
    await engine.init(fs.readFileSync(CORE_PATH));
    await OgarXProtocol.init(fs.readFileSync(PROTOCOL_PATH), 100); // 100mb

    const opened = await server.open({ 
        sslOptions, 
        port: process.env.OGARX_PORT, 
        endpoint: process.env.OGARX_ENDPOINT,
        token: process.env.OGARX_TOKEN
    });

    if (!opened) process.exit(1);
    engine.start();
})();


<|start_filename|>ipc-test/node-ipc/client.js<|end_filename|>
const { IPC } = require("node-ipc");

const ipc = new IPC();

ipc.config.id = `client#${Date.now()}`;
ipc.config.retry = 3000;
ipc.config.rawBuffer = true;
ipc.config.silent = true;

let count = 0;

const buffer = new ArrayBuffer(10);

ipc.connectTo("server", () => {
    ipc.of.server.on("connect", () => {
        console.log("Client connected");
        ipc.of.server.emit(buffer);
    });

    ipc.of.server.on("data", data => {
        ipc.of.server.emit(buffer);
        count++;
    });
});

setInterval(() => {
    console.log(`Message per second: ${count}`);
    count = 0;
}, 1000);

<|start_filename|>webgl/game/loader.js<|end_filename|>
let loaded = false;
/** @type {IDBDatabase} */
let db;
const ReplayDB = require("./replay-db");
const GIFEncoder = require("gif-encoder");
const { serialize } = require("./custom-bson");

(async() => {
    const font = new FontFace("Lato", "url(/static/font/Lato-Bold.ttf)");
    fonts.add(font);
    await font.load();
    db = await ReplayDB();
    loaded = true;
})();

const temp = new OffscreenCanvas(10, 10);
const temp_ctx = temp.getContext("2d");

/** @type {SharedArrayBuffer} */
let pool;

onmessage = async evt => {

    if (!loaded) return setTimeout(() => onmessage(evt), 3000);

    /** @type {{ data: { id: number, skin: string, name: string, skin_dim: number }}} */
    const { data } = evt;
    if (!data) return;

    if (data.name) {
        try {
            const name_canvas = new OffscreenCanvas(0, 500);
            const name_ctx = name_canvas.getContext("2d");
            name_ctx.font = "bold 320px Lato";
            name_ctx.fillStyle = "white";
            name_ctx.strokeStyle = "black";
            name_ctx.textAlign = "center";
            name_ctx.lineWidth = 26;
            name_ctx.textBaseline = "middle";
        
            const m = name_ctx.measureText(data.name);
            
            if (m.width) {
                name_canvas.width = Math.ceil(m.width + 40);
                name_ctx.font = "bold 320px Lato";
                name_ctx.fillStyle = "white";
                name_ctx.strokeStyle = "black";
                name_ctx.textAlign = "center";
                name_ctx.lineWidth = 26;
                name_ctx.textBaseline = "middle";
                name_ctx.clearRect(0, 0, name_canvas.width, name_canvas.height);
                name_ctx.strokeText(data.name, name_canvas.width >> 1, name_canvas.height >> 1);
                name_ctx.fillText(data.name, name_canvas.width >> 1, name_canvas.height >> 1);
                const name_bitmap = name_canvas.transferToImageBitmap();
                // Send name bitmap back to renderer worker
                self.postMessage({ id: data.id, name: name_bitmap }, [name_bitmap]);
            }
        } catch (e) {
            console.log(`Failed to load name: "${data.name}" (pid: ${data.id})`, e);
        }
    }

    if (data.skin) {
        try {
            if (!/https?:\/\//.test(data.skin) && 
                !/^\/static\/img\/virus-\d+.png/.test(data.skin)) 
                throw new Error("Invalid skin");
    
            const res = await fetch(data.skin);
            const blob = await res.blob();
            const skin_bitmap = await createImageBitmap(blob);
            
            const Q = ~~data.quality || 512;

            temp.width = Q;
            temp.height = Q;

            temp_ctx.clearRect(0, 0, Q, Q);
            temp_ctx.imageSmoothingEnabled = true;
            temp_ctx.imageSmoothingQuality = "high";
            
            temp_ctx.beginPath();
            temp_ctx.arc(Q >> 1, Q >> 1, Q >> 1, 0, 2 * Math.PI, false);
            temp_ctx.clip();

            temp_ctx.drawImage(skin_bitmap, 0, 0, Q, Q);

            const result = await createImageBitmap(temp);

            postMessage({ id: data.id, skin: result }, [result]);
        } catch (e) {
            console.log(`Failed to load skin: "${data.skin}" (pid: ${data.id})`, e);
        }
    }

    if (data.replay) {
        const id = Date.now();

        const w = data.replay.PREVIEW_WIDTH, h = data.replay.PREVIEW_HEIGHT;
        const bytes = w * h * 4;
        const gif = new GIFEncoder(w, h);
        gif.writeHeader();
        gif.setDelay(1000 / data.replay.REPLAY_PREVIEW_FPS / 2);
        gif.setRepeat(0);
        gif.setQuality(5);

        const buffers = [];
        gif.on("data", chunk => buffers.push(chunk.buffer));
        gif.on("end", () => {
            console.log(`GIF took ${((Date.now() - id) / 1000).toFixed(1)} seconds to encode`);
            const s = serialize(data.replay);

            const thumbnail = new Blob(buffers, { type: "image/gif" });
            const meta = { thumbnail, size: s.byteLength + thumbnail.size };

            const tx = db.transaction(["replay-meta", "replay-data"], "readwrite");
            const metaStore = tx.objectStore("replay-meta");
            const dataStore = tx.objectStore("replay-data");

            metaStore.add(meta, id);
            dataStore.add(s, id);
            tx.oncomplete = () => self.postMessage({ event: "replay", id, state: "success" });
            tx.onerror = () => self.postMessage({ event: "replay", state: "failed" });
        });

        for (let offset = 0; offset < data.replay.PREVIEW_LENGTH * bytes && 
                offset < pool.byteLength; offset += bytes)
            gif.addFrame(new Uint8Array(pool, offset, bytes));

        gif.finish();
    }

    if (data.pool) pool = data.pool;
};

<|start_filename|>webgl/game/main.js<|end_filename|>
const HUD = require("./hud");
window.onload = () => window.app = new HUD();

<|start_filename|>src/game/dual.js<|end_filename|>
const Handle = require("./handle");

module.exports = class DualHandle extends Handle {
    /** 
     * @param {Handle} owner
     */
    constructor(owner) {
        super(owner.game);
        this.owner = owner;
        this.join();
    }

    get score() { return this.controller.score + this.owner.controller.score; }

    // Do nothing
    calculateViewport() {};
};

<|start_filename|>webgl/game/mouse.js<|end_filename|>
module.exports = class Mouse {
    constructor () {
        this.setBuffer();
    }

    setBuffer(buf) {
        if (!buf) {
            if (self.SharedArrayBuffer) buf = new SharedArrayBuffer(12);
            else buf = new ArrayBuffer(12);
        }

        this.sharedBuffer = buf;
        this.buffer = new Int32Array(this.sharedBuffer);
    }

    get x() { return Atomics.load(this.buffer, 0); }
    set x(v) { Atomics.store(this.buffer, 0, v) }

    get y() { return Atomics.load(this.buffer, 1); }
    set y(v) { Atomics.store(this.buffer, 1, v); }

    get scroll() { return Atomics.load(this.buffer, 2); }
    set scroll(v) { Atomics.store(this.buffer, 2, v); }
    
    updateScroll(v) { Atomics.add(this.buffer, 2, v); }
    resetScroll() { return Atomics.exchange(this.buffer, 2, 0); }
}

<|start_filename|>src/game/index.js<|end_filename|>
const { EventEmitter } = require("events");

const Controller = require("./controller");
const Engine = require("../physics/engine");
const OgarXProtocol = require("../network/protocols/ogarx");

const MAX_PLAYER = 250;

module.exports = class Game extends EventEmitter {

    constructor(name = "Server") {
        super();
        this.setMaxListeners(MAX_PLAYER + 1);
        this.name = name;
        this.engine = new Engine(this);
        this.controls = Array.from({ length: MAX_PLAYER }, (_, i) => new Controller(this.engine, i));
        this.handles = 0;

        this.on("oversize", /** @param {import("./controller")} c */ c => {
            try {
                this.emit("log", eval(`\`${this.engine.options.WORLD_OVERSIZE_MESSAGE}\``));
            } catch (e) {
                console.error(`Eval of "${this.engine.options.WORLD_OVERSIZE_MESSAGE}" failed in "oversize" listener: `, e);
            }
        });

        this.on("restart", () => this.emit("log", "Restarting Server..."));

        /** @type {{ message: string, isServer: boolean }[]} */
        this.history = [];
        this.on("chat", message => this.addChatHistory(message));
        this.on("log", message => this.addChatHistory(message, true));
    }

    addChatHistory(message, isServer = false) {
        this.history.push({ message, isServer });
        while (this.history.length > this.engine.options.CHAT_HISTORY) this.history.shift();
    }

    get playerCount() {
        return this.controls.reduce((prev, c) => (c.handle instanceof OgarXProtocol ? 1 : 0) + prev, 0);
    }

    get options() { return this.engine.options; }
    get isFull() { return this.handles >= MAX_PLAYER; }

    /** @param {import("./handle")} handle */
    addHandler(handle) {
        if (this.handles + (this.engine.options.DUAL_ENABLED ? 2 : 1) >= MAX_PLAYER) 
            return handle.onError("Server full");
        if (handle.controller) return;
        let id = 1; // 0 is occupied ig
        while (this.controls[id].handle) id++;
        this.controls[id].handle = handle;
        handle.controller = this.controls[id];
        // Prevent connection spam (can be done in client AND with kernel)?
        // handle.controller.lastSpawnTick = this.engine.__now;
        this.handles++;
    }

    /** @param {import("./handle")} handle */
    removeHandler(handle) {
        if (!handle.controller) return;
        const c = handle.controller;
        this.engine.delayKill(c.id, true);
        this.emit("leave", c);
        c.reset();
        handle.controller = null;
        this.handles--;
    }
}

<|start_filename|>src/modes/default/omega.js<|end_filename|>
/** @type {typeof import("../../physics/engine").DefaultSettings} */
module.exports = {
    TIME_SCALE: 1.2, // magic that make everything work like a certain ball game
    PLAYER_MAX_CELLS: 369,
    PLAYER_MERGE_NEW_VER: true,
    PLAYER_AUTOSPLIT_SIZE: 0,
    PLAYER_MERGE_TIME: 5,
    VIRUS_COUNT: 20,
    VIRUS_SIZE: 200,
    VIRUS_PUSH: true,
    VIRUS_MONOTONE_POP: true,
    EJECT_SIZE: 38,
    EJECT_LOSS: 38.4,
    EJECT_DELAY: 100,
    BOTS: 50,
    BOT_SPAWN_SIZE: 1000,
    PELLET_COUNT: 5000,
    PLAYER_VIEW_SCALE: 1.1,
    PLAYER_SPAWN_SIZE: 500,
    PLAYER_SPAWN_DELAY: 1500,
    PLAYER_MIN_SPLIT_SIZE: 150,
    PLAYER_NO_COLLI_DELAY: 550,
    PLAYER_NO_EJECT_DELAY: 250,
    MAP_HW: 26000,
    MAP_HH: 26000,
    STATIC_DECAY: 1,
    DYNAMIC_DECAY: 1,
    DECAY_MIN: 800,
    NORMALIZE_THRESH_MASS: 100000,
    DUAL_ENABLED: true,
    PLAYER_SAFE_SPAWN_RADIUS: 1.2
};

<|start_filename|>src/worker.js<|end_filename|>
const Server = require("./network/sw-server");
const OgarXProtocol = require("./network/protocols/ogarx");

const server = new Server();
const engine = server.game.engine;

const modes = {
    omega: require("./modes/default/omega"),
    mega: require("./modes/default/mega"),
    selfeed: require("./modes/default/selfeed"),
    ffa: require("./modes/default/ffa"),
    virus: require("./modes/default/virus")
}

const s = new URLSearchParams(self.location.search);
const options = modes[s.get("mode")] || modes.mega;

// Scale down the local server from omega since browser probably can't handle it
options.BOTS >>= 2;
options.MAP_HH >>= 1;
options.MAP_HW >>= 1;
options.VIRUS_COUNT >>= 2;
options.PELLET_COUNT >>= 2;

engine.setOptions(options);
server.open();

(async () => {
    let res = await fetch("/static/wasm/server.wasm");
    let buffer = await res.arrayBuffer();

    await engine.init(buffer);
    
    res = await fetch("/static/wasm/ogarx.wasm");
    buffer = await res.arrayBuffer();

    await OgarXProtocol.init(buffer, 4);

    engine.start();
    
    console.log("Shared worker server running");
})();


<|start_filename|>webgl/game/shaders.js<|end_filename|>
module.exports.SPRITE_VERT_SHADER_SOURCE = 
`#version 300 es
precision highp float;

uniform mat4 u_proj;
uniform vec2 u_uvs[6];

layout(location=0) in vec2 pos;

out vec2 uv;

void main() {
    gl_Position = u_proj * vec4(pos, 0.0f, 1.0f);
    uv = u_uvs[gl_VertexID % 6];
}
`;

module.exports.SPRITE_FRAG_SHADER_SOURCE =
`#version 300 es
precision highp float;

uniform sampler2D u_texture;

in vec2 uv;
out vec4 color;

void main() {
    color = texture(u_texture, uv);
}
`;

module.exports.PARTICLE_VERT_SHADER_SOURCE = 
`#version 300 es
precision highp float;

uniform mat4 u_proj;
uniform vec2 u_uvs[6];
uniform vec3 u_colors[$colors$];

layout(location=0) in vec3 pos;

out vec2 uv;
out vec3 fill;

void main() {
    gl_Position = u_proj * vec4(pos.xy, 0.0f, 1.0f);
    fill = u_colors[int(pos.z) % $colors$];
    uv = u_uvs[gl_VertexID % 6];
}
`;

module.exports.PARTICLE_FRAG_SHADER_SOURCE =
`#version 300 es
precision highp float;

uniform sampler2D u_texture;

in vec2 uv;
in vec3 fill;

out vec4 color;

void main() {
    color = vec4(fill, texture(u_texture, uv).a);
    // color = vec4(uv, 1.0f, 1.0f);
}
`;

module.exports.MASS_VERT_SHADER_SOURCE = 
`#version 300 es
precision highp float;
precision highp int;

uniform mat4 u_proj;
uniform vec2 u_uvs[48];

layout(location=0) in vec3 a_position;

out vec2 v_texcoord;
flat out int character;

void main() {
    vec4 world_pos = vec4(a_position.xy, 0.0, 1.0);
    gl_Position = u_proj * world_pos;
    character = int(a_position.z) >> 2;
    v_texcoord = u_uvs[int(a_position.z)];
}
`;

module.exports.MASS_FRAG_PEELING_SHADER_SOURCE = 
`#version 300 es

precision highp float;
precision highp int;
precision highp sampler2DArray;

uniform sampler2DArray u_mass_char;

in vec2 v_texcoord;
flat in int character;

out vec4 color;

void main() {
    color = texture(u_mass_char, vec3(v_texcoord, character));
}
`;

module.exports.BORDER_VERT_SHADER_SOURCE = 
`#version 300 es
precision highp float;

layout(location=0) in vec2 a_position;
uniform mat4 u_proj;
uniform vec2 u_map;
void main() {
    gl_Position = u_proj * vec4(a_position * u_map, 0.0, 1.0);
}
`;

module.exports.BORDER_FRAG_SHADER_SOURCE =
`#version 300 es
precision highp float;

uniform vec4 u_color;
out vec4 color;
void main() {
    color = u_color;
}
`;

<|start_filename|>build.js<|end_filename|>
const fs = require("fs");
const path = require("path");
const yargs = require("yargs");
const { hideBin } = require('yargs/helpers')
const browserify = require("browserify");
const minifier = require("babel-minify");

const MAIN_IN  = path.resolve(__dirname, "webgl", "game", "main.js");
const MAIN_OUT = path.resolve(__dirname, "public", "js", "main.min.js");

const RENDERER_IN  = path.resolve(__dirname, "webgl", "game", "renderer.js");
const RENDERER_OUT = path.resolve(__dirname, "public", "js", "renderer.min.js");

const LOADER_IN  = path.resolve(__dirname, "webgl", "game", "loader.js");
const LOADER_OUT = path.resolve(__dirname, "public", "js", "loader.min.js");

const CONTROL_IN  = path.resolve(__dirname, "webgl", "control", "control.js");
const CONTROL_OUT = path.resolve(__dirname, "public", "js", "control.min.js");

const SW_IN  = path.resolve(__dirname, "src", "worker.js");
const SW_OUT = path.resolve(__dirname, "public", "js", "sw.min.js");

/** @returns {Promise<string>} */
const streamToString = stream => {
    const chunks = [];
    return new Promise((resolve, reject) => {
        stream.on('data', chunk => chunks.push(chunk))
        stream.on('error', reject)
        stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')))
    });
}

const argv = yargs(hideBin(process.argv))
    .option('renderer', {
        alias: 'r',
        type: 'boolean',
        description: 'Build renderer'
    })
    .option('sharedworker', {
        alias: 's',
        type: 'boolean',
        description: 'Build sharedworker'
    })
    .option('main', {
        alias: 'm',
        type: 'boolean',
        description: 'Build entry point'
    })
    .option('control', {
        alias: 'c',
        type: 'boolean',
        description: 'Build controller'
    })
    .option('loader', {
        alias: 'l',
        type: 'boolean',
        description: 'Build loader'
    })
    .option('all', {
        alias: 'a',
        type: 'boolean',
        description: 'Build all js files'
    })
    .argv;

(async () => {
    let code;
    let bundled = 0;
    
    if (argv.renderer || argv.all) {
        console.log("Building renderer"); bundled++;
        code = await streamToString(browserify(RENDERER_IN).bundle());
        fs.writeFileSync(RENDERER_OUT, minifier(code).code);
    }

    if (argv.main || argv.all) {
        console.log("Building main"); bundled++;
        code = await streamToString(browserify(MAIN_IN).bundle());
        fs.writeFileSync(MAIN_OUT, minifier(code).code);
    }

    if (argv.control || argv.all) {
        console.log("Building web console"); bundled++;
        code = await streamToString(browserify(CONTROL_IN).bundle());
        fs.writeFileSync(CONTROL_OUT, minifier(code).code);
    }

    if (argv.loader || argv.all) {
        console.log("Building loader"); bundled++;
        code = await streamToString(browserify(LOADER_IN).bundle());
        fs.writeFileSync(LOADER_OUT, minifier(code).code);
    }

    if (argv.sharedworker || argv.all) {
        console.log("Building sharedworker"); bundled++;
        code = await streamToString(browserify(SW_IN).bundle());
        fs.writeFileSync(SW_OUT, minifier(code, {
            mangle: false,
            evaluate: false
        }).code);
    }

    if (!bundled) console.log("Nothing was bundled");
})();


<|start_filename|>public/css/control.css<|end_filename|>
html, body {
    width: 100%;
    height: 100%;
    margin: 0;
    background-color: black;
    color: #eeeeee;
}

#container {
    display: flex;
    width: 90%;
    max-width: 1000px;
    height: 75%;
    margin: 0;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    background-color: #111111;
}

.card {
    margin: 10px;
    border: solid 2px #93a7e1;
}

.column {
    width: 50%;
    display: flex;
    flex-direction: column;
}

.row {
    height: 50%;
}

.text-center {
    text-align: center;
}

.info-field {
    padding: 10px;
}

.margin-small {
    margin: 5px;
}

#player-list p {
    margin: 3px;
}

<|start_filename|>src/gateway.js<|end_filename|>
const pm2 = require("pm2");
const { existsSync, unlinkSync } = require("fs");
const { execSync } = require("child_process");

const net = require("net");
const path = require("path");
const uWS = require("uWebSockets.js");

const pipename = str => process.platform == "win32" ? `\\\\.\\pipe\\${str.replace(/^\//, "").replace(/\//g, "-")}` : str;
const SOCKET_FILE = path.resolve(__dirname, "unix.sock");

if (existsSync(SOCKET_FILE)) unlinkSync(SOCKET_FILE);

const SSL_FOLDER_PATH = path.resolve(__dirname, "..", "ssl");
const SSL_PATH = path.resolve(SSL_FOLDER_PATH, "options.json");
let sslOptions = null;
if (!existsSync(SSL_FOLDER_PATH)) fs.mkdirSync(SSL_FOLDER_PATH);
if (existsSync(SSL_PATH)) sslOptions = require(SSL_PATH);

const server = net.createServer();

/** @type {Set<net.Socket>} */
const sockets = new Set();

server.on("connection", sock => {
    sockets.add(sock);
    sock.on("data", buffer => {
            const data = JSON.parse(buffer.toString());
            sock.pid = data.pid;
            delete data.pid;
            sock.data = data;
        })
        .on("close", () => sockets.delete(sock));
});

server.listen(pipename(SOCKET_FILE));

let port = process.env.GATEWAY_PORT || 443;
if (process.platform === "win32") port = 6969;

let timeout = null;
/** @type {Set<uWS.HttpResponse>} */
const connections = new Set();

const broadcast = async () => {
    /** @type {pm2.ProcessDescription[]} */
    const procList = await new Promise(resolve => pm2.list((err, list) => resolve(err ? [] : list)));
    const data = "event: servers\ndata: " + JSON.stringify([...sockets].map(s => {
        const data = Object.assign({}, s.data);
        const proc = procList.find(p => p.pid == s.pid);
        data.load = proc ? proc.monit.cpu * 0.01 : 1 / sockets.size;
        return data;
    })) + "\n\n";
    for (const res of connections) res.write(data);
    timeout = setTimeout(broadcast, 500);
}

let token = process.env.OGARX_TOKEN;

(sslOptions ? uWS.SSLApp(sslOptions) : uWS.App())
    .get(`/gateway`, (res, _) => {
        res.writeStatus("200 OK");
        res.writeHeader("Content-type", "text/event-stream");
        res.writeHeader("Cache-Control", "no-cache");
        res.writeHeader("Access-Control-Allow-Origin", process.env.GATEWAY_ORIGIN || "*");
        res.onAborted(() => connections.delete(res));
        connections.add(res);
    })
    .get("/ping", (res, _) => {
        res.writeStatus("200 OK");
        res.writeHeader("Cache-Control", "max-age=0");
        res.writeHeader("Access-Control-Allow-Origin", process.env.GATEWAY_ORIGIN || "*");
        res.end(Date.now().toString());
    })
    .get("/update/:token", (res, req) => {
        const authorization = req.getParameter(0);
        if (token) {
            if (token == authorization) {
                const result = execSync("git pull origin master", 
                    { stdio: ['ignore', 'pipe', 'ignore'] }).toString("utf-8");
                if (result == "Already up to date.\n") {
                    res.end("Already updated");
                } else {
                    res.end(result);
                }
            } else {
                res.writeStatus("401 Unauthorized");
                res.end();
            }
        } else {
            res.writeStatus("302");
            res.writeHeader("location", "/");
            res.end();
        }
    })
    .get("/restart/:token", (res, req) => {
        const authorization = req.getParameter(0);
        if (token) {
            if (token == authorization) {
                res.end("Restarting all processes");
                setTimeout(() => pm2.restart("all"), 1000);
            } else {
                res.writeStatus("401 Unauthorized");
                res.end();
            }
        } else {
            res.writeStatus("302");
            res.writeHeader("location", "/");
            res.end();
        }
    })
    .get("/*", (res, _) => res.end("Hello OGARX Gateway"))
    .listen("0.0.0.0", port, sock => {
        process.on("SIGINT", () => {
            server.close();
            uWS.us_listen_socket_close(sock);
            clearTimeout(timeout);
            process.exit(0);
        });
        console.log((sock ? "Gateway Server listening" : "Gateway Server failed to listen") + 
            ` on port ${port} ` + (token ? "WITH token" : "WITHOUT token"));
        broadcast();
        process.send && process.send("ready");
    });

<|start_filename|>src/modes/default/selfeed.js<|end_filename|>
/** @type {typeof import("../../physics/engine").DefaultSettings} */
module.exports = {
    TIME_SCALE: 1.2, // magic that make everything work like a certain ball game
    PLAYER_MAX_CELLS: 128,
    PLAYER_MERGE_NEW_VER: true,
    PLAYER_AUTOSPLIT_SIZE: 0,
    PLAYER_MERGE_TIME: 0,
    PLAYER_NO_MERGE_DELAY: 900,
    PLAYER_SPEED: 2,
    VIRUS_COUNT: 0,
    VIRUS_SIZE: 200,
    VIRUS_PUSH: true,
    VIRUS_MONOTONE_POP: true,
    EJECT_SIZE: 85,
    EJECT_LOSS: 80,
    EJECT_DELAY: 100,
    BOTS: 40,
    BOT_SPAWN_SIZE: 1000,
    PELLET_COUNT: 5000,
    PLAYER_VIEW_SCALE: 1.2,
    PLAYER_SPAWN_SIZE: 1500,
    PLAYER_SPAWN_DELAY: 1500,
    PLAYER_SPLIT_DIST: 80,
    PLAYER_MIN_SPLIT_SIZE: 150,
    PLAYER_MIN_EJECT_SIZE: 100,
    MAP_HW: 26000,
    MAP_HH: 26000,
    NORMALIZE_THRESH_MASS: 100000,
    PLAYER_SAFE_SPAWN_RADIUS: 1.2
};

<|start_filename|>src/physics/quadtree.js<|end_filename|>
/**
 * @param {import("./cell")} cell
 * @param {QuadNode} node
 */
const getQuadrant = (cell, node) => {
    if (cell.y - cell.r > node.y) {
        if (cell.x + cell.r < node.x) return 0;
        else if (cell.x - cell.r > node.x) return 1;
    } else if (cell.y + cell.r < node.y) {
        if (cell.x + cell.r < node.x) return 2;
        else if (cell.x - cell.r > node.x) return 3;
    }
    return -1;
}

/**
 * @param {import("./cell")} cell
 * @param {QuadNode} node
 */
const insideQuad = (cell, node) => {
    return cell.x - cell.r > node.l &&
           cell.x + cell.r < node.r &&
           cell.y + cell.r < node.t &&
           cell.y - cell.r > node.b;
}

/**
 * QuadNode serialize to:
 * x, y, hw, hw (4 * 4 = 8 bytes)
 * 4 childpointers (4 * 4 = 16 bytes)
 * count (2 bytes)
 * Total = 34 + 2 * items
 */

class QuadNode {

    /**
     * @param {QuadTree} tree
     * @param {number} x
     * @param {number} y
     * @param {number} hw
     * @param {number} hh
     * @param {QuadNode} root
     */
    constructor(tree, x, y, hw, hh, root) {
        this.__ptr = 0;
        this.tree = tree;
        this.root = root;
        this.level = root ? root.level + 1 : 1;
        this.x = x;
        this.y = y;
        this.hw = hw;
        this.hh = hh;
        this.t = y + hh;
        this.b = y - hh;
        this.l = x - hw;
        this.r = x + hw;
        /** @type {[QuadNode, QuadNode, QuadNode, QuadNode]} */
        this.branches = null;
        /** @type {Set<number>} */
        this.items = new Set();
    }

    split() {
        if (this.branches || 
            this.items.size < this.tree.maxItems ||
            this.level > this.tree.maxLevel) return;
        const qw = this.hw / 2;
        const qh = this.hh / 2;
        this.branches = [
            new QuadNode(this.tree, this.x - qw, this.y + qh, qw, qh, this),
            new QuadNode(this.tree, this.x + qw, this.y + qh, qw, qh, this),
            new QuadNode(this.tree, this.x - qw, this.y - qh, qw, qh, this),
            new QuadNode(this.tree, this.x + qw, this.y - qh, qw, qh, this),
        ];
        for (const cell_id of this.items) {
            const cell = this.tree.cells[cell_id];
            const quadrant = getQuadrant(cell, this);
            if (quadrant < 0) continue;
            this.branches[quadrant].items.add(cell_id);
            cell.__root = this.branches[quadrant];
            this.items.delete(cell_id);
        }
    }

    merge() {
        let node = this;
        while (node != null) {
            if (!node.branches) { node = node.root; continue; }
            if (node.branches[0].branches || node.branches[0].items.size ||
                node.branches[1].branches || node.branches[1].items.size ||
                node.branches[2].branches || node.branches[2].items.size ||
                node.branches[3].branches || node.branches[3].items.size) return;
            node.branches = null;
        }
    }

    __serialize() {
        let ptr = this.__ptr = this.tree.__offset;
        this.tree.__offset += 34 + 2 * this.items.size;
        
        const v = this.tree.__view;
        v.setFloat32(ptr, this.x, true);
        ptr += 4;
        v.setFloat32(ptr, this.y, true);
        ptr += 4;
        v.setFloat32(ptr, this.hw, true);
        ptr += 4;
        v.setFloat32(ptr, this.hh, true);
        ptr += 4;

        if (this.branches) {
            this.branches[0].__serialize();
            this.branches[1].__serialize();
            this.branches[2].__serialize();
            this.branches[3].__serialize();

            v.setUint32(ptr, v.byteOffset + this.branches[0].__ptr, true);
            ptr += 4;
            v.setUint32(ptr, v.byteOffset + this.branches[1].__ptr, true);
            ptr += 4;
            v.setUint32(ptr, v.byteOffset + this.branches[2].__ptr, true);
            ptr += 4;
            v.setUint32(ptr, v.byteOffset + this.branches[3].__ptr, true);
            ptr += 4;
        } else {
            v.setUint32(ptr, 0, true);
            ptr += 16;
        }

        v.setUint16(ptr, this.items.size, true);
        ptr += 2;

        for (const cell_id of this.items) {
            v.setUint16(ptr, cell_id, true);
            ptr += 2;
        }
    }

    countItems() {
        if (this.branches) {
            return this.items.size + this.branches[0].countItems() +
                this.branches[1].countItems() +
                this.branches[2].countItems() +
                this.branches[3].countItems();
        } else return this.items.size;
    }

    print() {
        console.log(`QuadNode at ${this.__ptr} has ${this.items.size} items: ${[...this.items].join(", ")}`)
        if (this.branches) {
            this.branches[0].print();
            this.branches[1].print();
            this.branches[2].print();
            this.branches[3].print();
        }
    }
}

class QuadTree {

    /**
     * @param {import("./cell")[]} cells
     * @param {number} x
     * @param {number} y
     * @param {number} hw
     * @param {number} hh
     * @param {number} maxLevel 
     * @param {number} maxItems
     */
    constructor(cells, x, y, hw, hh, maxLevel, maxItems) {
        this.__offset = 0;
        this.cells = cells;
        this.root = new QuadNode(this, x, y, hw, hh, null);
        this.maxLevel = maxLevel;
        this.maxItems = maxItems;
    }

    /** @param {import("./cell")} cell */
    insert(cell) {
        if (cell.__root) console.log("INSERTING CELL ALREADY IN QUADTREE");
        let node = this.root;
        while (true) {
            if (!node.branches) break;
            const quadrant = getQuadrant(cell, node);
            if (quadrant < 0) break;
            node = node.branches[quadrant];
        }
        cell.__root = node;
        node.items.add(cell.id);
        node.split();
    }

    /** @param {import("./cell")} cell */
    remove(cell) {
        if (!cell.__root) return console.log("REMOVING CELL NOT IN QUADTREE");
        if (!cell.__root.items.delete(cell.id)) console.log("ITEM NOT IN QUAD??", cell.__root.items);
        cell.__root.merge();
        cell.__root = null;
    }

    /** @param {import("./cell")} cell */
    update(cell) {
        if (!cell.__root) {
            console.log(cell.toString());
            throw new Error("UPDATING CELL NOT IN QUADTREE");
        }
        const oldNode = cell.__root;
        let newNode = cell.__root;
        while (true) {
            if (!newNode.root) break;
            newNode = newNode.root;
            if (insideQuad(cell, newNode)) break;
        }
        while (true) {
            if (!newNode.branches) break;
            const quadrant = getQuadrant(cell, newNode);
            if (quadrant < 0) break;
            newNode = newNode.branches[quadrant];
        }
        if (oldNode === newNode) return;
        oldNode.items.delete(cell.id);
        newNode.items.add(cell.id);
        cell.__root = newNode;
        oldNode.merge();
        newNode.split();
    }

    /**
     * Swap cell1 (in the tree) with new cell2
     * @param {import("./cell")} cell1 
     * @param {import("./cell")} cell2 
     */
    swap(cell1, cell2) {
        cell2.__root = cell1.__root;
        cell2.__root.items.delete(cell1.id);
        cell2.__root.items.add(cell2.id);
        cell1.__root = null;
    }

    /** @param {DataView} view */
    serialize(view) {
        this.__view = view;
        this.__offset = 0;
        this.root.__serialize();

        const end = this.__offset;
        this.__offset = 0;
        return end;
    }

    countItems() {
        return this.root.countItems();
    }

    print() {
        this.root.print();
    }
}

QuadTree.Node = QuadNode;

module.exports = QuadTree;

<|start_filename|>static.js<|end_filename|>
const fs = require('fs');
const path = require('path');
const mime = require('mime-types');
const uWS = require('uWebSockets.js');

/** @param {string} dir */
const walkDir = dir => {
    /** @type {string[]} */
    let results = [];
    const list = fs.readdirSync(dir);
    list.forEach(file => {
        file = path.join(dir, file);
        const stat = fs.statSync(file);
        if (stat && stat.isDirectory()) {
            /* Recurse into a subdirectory */
            results = results.concat(walkDir(file));
        } else {
            /* Is a file */
            results.push(file);
        }
    });
    return results;
};

// Load files into memory
const buffers = new Map();

const publicRoot = path.resolve(__dirname, 'public');

const dir = walkDir(publicRoot).map(f => f.replace(publicRoot, '').replace(/\\/g, '/'));
for (const f of dir) {
    buffers.set(f.replace(/\.html$/, '').replace(/\/index$/, '') || '/', {
        mime: mime.lookup(f),
        buffer: fs.readFileSync(path.resolve(publicRoot, ...f.split('/'))),
    });
}

uWS.App()
    .get('/*', (res, req) => {
        const url = req.getUrl();
        if (buffers.has(url)) {
            const { mime, buffer } = buffers.get(url);
            if (mime) res.writeHeader('content-type', mime);

            res.writeHeader('Cross-Origin-Opener-Policy', 'same-origin');
            res.writeHeader('Cross-Origin-Embedder-Policy', 'require-corp');

            if (mime === 'text/html') {
                res.writeHeader('cache-control', 's-maxage=0,max-age=60');
            } else {
                res.writeHeader('cache-control', 's-maxage=86400,max-age=86400');
            }
            res.end(buffer);
        } else {
            res.writeStatus('302');
            res.writeHeader('location', '/');
            res.end();
        }
    })
    .listen(8080, sock =>
        console.log(sock ? 'Serving on port 8080' : 'Failed to listen on port 8080'),
    );


<|start_filename|>webgl/game/viewport.js<|end_filename|>
module.exports = class Viewport {
    constructor () {
        this.setBuffer();
    }

    setBuffer(buf) {
        if (!buf) {
            if (self.SharedArrayBuffer) buf = new SharedArrayBuffer(8);
            else buf = new ArrayBuffer(8);
        }

        this.sharedBuffer = buf;
        this.buffer = new Int32Array(this.sharedBuffer);
    }

    get width() { return Atomics.load(this.buffer, 0); }
    set width(v) { Atomics.store(this.buffer, 0, v) }

    get height() { return Atomics.load(this.buffer, 1); }
    set height(v) { Atomics.store(this.buffer, 1, v); }
}

<|start_filename|>webgl/game/protocol.js<|end_filename|>
const { EventEmitter } = require("events");
const Reader = require("../../src/network/reader");
const Writer = require("../../src/network/writer");
const ReplayDB = require("./replay-db");
const { deserialize } = require("./custom-bson");
const FakeSocket = require("./fake-socket");

// const uuidv4 = () => ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
//     (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16));

const PREVIEW_WIDTH = 1920 >> 2;
const PREVIEW_HEIGHT = 1080 >> 2;
const REPLAY_PREVIEW_FPS = 5;
const REPLAY_LENGTH = 20;

class ReplaySnapshot {
    constructor(length = 0) {
        this.score = 0;
        this.state = new ArrayBuffer(length);
        this.view = new Uint8Array(this.state);

        /** @type {number[]} */
        this.packetTimestamps = [];
        /** @type {ArrayBuffer[]} */
        this.packets = [];
        
        this.preview = new OffscreenCanvas(PREVIEW_WIDTH, PREVIEW_HEIGHT);
        this.ctx = this.preview.getContext("2d");
        this.ctx.imageSmoothingEnabled = true;
        this.ctx.imageSmoothingQuality = "high";
    }

    clearPreview() {
        this.ctx.clearRect(0, 0, this.preview.width, this.preview.height);
    }
}

class ReplaySystem {

    /**
     * @param {import("./renderer")} renderer
     * @param {import("./protocol")} protocol
     * @param {number} length
     */
    constructor(renderer, protocol, length) {
        this.t = 0;
        this.i = 0;

        this.protocol = protocol;
        this.renderer = renderer;

        this.source = new Uint8Array(renderer.core.buffer, 0, renderer.INDICES_OFFSET);
        this.snapshots = Array.from({ length: length * REPLAY_PREVIEW_FPS }, _ => new ReplaySnapshot(this.source.byteLength));
        
        const pool = this.sharedArrayBuffer = new SharedArrayBuffer(this.snapshots.length * PREVIEW_WIDTH * PREVIEW_HEIGHT * 4);
        console.log(`${(pool.byteLength / 1024 / 1024).toFixed(1)}MB preview buffer allocated for GIF generation`);
        this.previewPool = new Uint8ClampedArray(this.sharedArrayBuffer);
        this.renderer.loader.postMessage({ pool });
    }

    async init() {
        this.db = await ReplayDB();
    }

    encodePlayerData() {
        const writer = new Writer();
        writer.skip(1);
        const w = this.renderer.playerData.filter((p, i) => {
            if (!i || i > 250 || !p) return;
            writer.writeUInt8(i);
            writer.writeUTF16String(p.name || "");
            writer.writeUTF16String(p.skin || "");
            return true;
        });
        const end = writer.offset;
        writer.offset = 0;
        writer.writeUInt8(w.length);
        writer.offset = end;
        return writer.finalize();
    }

    /** @param {ArrayBuffer} state */
    encodeState(state) {
        const oldState = this.source.slice();
        this.source.set(new Uint8Array(state));
        const buffer = this.renderer.serializeState();
        this.source.set(oldState);
        return buffer;
    }

    async save() {
        if (this.saving) return self.postMessage(
            { event: "warning", message: "Saving Current Clip, Please Try Again Later." });
            
        this.saving = true;
        const snapshots = this.snapshots.filter(s => s.packets.length).map(s => s).reverse();

        /** @type {ArrayBuffer[]} */
        const buffers = snapshots.reduce((prev, curr) => 
            prev.concat(curr.packets), []).map(b => b.slice());

        /** @type {number[]} */
        let tArray = snapshots.reduce((prev, curr) => prev.concat(curr.packetTimestamps), []);
        const minTimestamp = Math.min(...tArray);
        tArray = tArray.map(t => t - minTimestamp);

        const handshake = this.protocol.HANDSHAKE_PACKET;
        const players = this.encodePlayerData();
        const timestamps = new Float32Array(tArray).buffer;

        const initial = this.encodeState(snapshots[0].state);

        let index = 0;

        self.postMessage({ event: "replay", state: "starting" });

        // Job takes too long, spread it out to per frame
        while (index < snapshots.length) {
            await new Promise(resolve => {
                const imageData = snapshots[index].ctx.getImageData(0, 0, PREVIEW_WIDTH, PREVIEW_HEIGHT);
                // Write the preview backward
                this.previewPool.set(imageData.data, imageData.data.byteLength * ~~index);
                index++;
                requestAnimationFrame(resolve);
            });
        }

        const replay = { handshake, initial, buffers, timestamps, players, 
            PREVIEW_WIDTH, PREVIEW_HEIGHT,
            REPLAY_PREVIEW_FPS, PREVIEW_LENGTH: snapshots.length };
        this.renderer.loader.postMessage({ replay }, buffers);
    }

    set score(v) {
        this.snapshots[0].score = Math.max(v, this.snapshots[0].score);
    }

    get maxScore() { return Math.max(...this.snapshots.map(s => s.score)); }

    // Record current state
    recordState() {
        if (this.curr) return;

        const tail = this.snapshots.pop();
        this.free(tail);
        this.snapshots.unshift(tail);
        tail.view.set(this.source);
        this.score = this.renderer.stats.score;
        this.requestPreview = true;
    }

    savePreview() {
        if (!this.requestPreview) return;
        this.requestPreview = false;
        const s = this.snapshots[0];
        s.clearPreview();
        s.ctx.drawImage(this.renderer.canvas, 0, 0, s.preview.width, s.preview.height);
    }

    /** @param {ArrayBuffer} packet */
    recordPacket(packet) {
        this.snapshots[0].packets.push(packet);
        this.snapshots[0].packetTimestamps.push(performance.now());
    }

    /** @param {ReplaySnapshot} snapshot */
    free(snapshot) {
        this.renderer.loader.postMessage(snapshot.packets, snapshot.packets);
        snapshot.score = 0;
        snapshot.view.fill(0);
        snapshot.packets = [];
        snapshot.packetTimestamps = [];
    }

    resetSnapshots() {
        this.snapshots.forEach(s => this.free(s));
    }

    async load(id = 0) {
        /** @type {{ buffers: ArrayBuffer[], players: ArrayBuffer }} */
        const data = await new Promise((resolve, reject) => {
            const tx = this.db.transaction(["replay-data"], "readonly");
            const dataStore = tx.objectStore("replay-data");
            const req = dataStore.get(id);
            tx.oncomplete = () => resolve(deserialize(req.result));
            tx.onerror = reject;
        });

        const initial = new Uint8Array(data.initial);
        const timestamps = new Float32Array(data.timestamps);
        this.curr = { initial, timestamps, packets: data.buffers };

        console.assert(this.curr.timestamps.length === this.curr.packets.length,
            "Packet and timestamp length MUST match");

        if (data.handshake) this.protocol.onMessage({ data: data.handshake });
        this.protocol.parsePlayers(data.players);

        console.log("Replay loaded", this.curr);
    }

    update(dt = 0) {
        if (!this.curr) return;
        // Overwrite state
        if (!this.t) {
            this.source.fill(0);
            const core = this.renderer.core;
            core.HEAPU8.set(this.curr.initial, this.renderer.INDICES_OFFSET);                 
            core.instance.exports.deserialize(0, this.renderer.INDICES_OFFSET);
        }
        // "Receive" packet
        while (this.curr.timestamps[this.i] < this.t)
            this.protocol.onMessage({ data: this.curr.packets[this.i++] });

        // Loop
        if (this.i >= this.curr.timestamps.length) this.resetTrack();
        else this.t += dt;
    }

    resetTrack() {
        this.i = 0;
        this.t = 0;
        this.renderer.camera.tp = true;
    }
}

const RecordOPs = [2, 4];

module.exports = class Protocol extends EventEmitter {
    
    /** @param {import("./renderer")} renderer */
    constructor(renderer) {
        super();
        this.pid = 0;
        this.bandwidth = 0;
        this.renderer = renderer;
        this.replay = new ReplaySystem(renderer, this, REPLAY_LENGTH);
        this.map = { hw: 10000, hh: 10000 };
        this.setupIntervals();
        this.onMessage = this.onMessage.bind(this);
    }

    get connecting() { return this.ws && this.ws.readyState == WebSocket.CONNECTING; }
    get connected() { return this.ws && this.ws.readyState == WebSocket.OPEN; }

    disconnect() {
        this.connected && this.ws.close();
        
        delete this.replay.curr;
        this.replay.resetTrack();
    }

    connect(urlOrPort, uid="", name = "", skin1 = "", skin2 = "") {

        this.disconnect();
        const currWs = this.ws = (typeof urlOrPort == "string") ? new WebSocket(`${urlOrPort}?${uid}`) : new FakeSocket(urlOrPort);
        this.ws.binaryType = "arraybuffer";

        this.ws.onopen = () => {
            this.renderer.clearCells();

            const writer = new Writer();
            writer.writeUInt8(69);
            writer.writeInt16(420);
            writer.writeUTF16String(name);
            writer.writeUTF16String(skin1);
            writer.writeUTF16String(skin2);
            this.ws.send(writer.finalize());
            this.emit("open");

            delete this.ping;
        }

        this.ws.onmessage = this.onMessage;

        this.ws.onerror = _ => self.postMessage({
            event: "error",
            message: "Connection failed"
        });

        this.ws.onclose = e => {
            this.bandwidth = 0;
            delete this.ping;
            delete this.dualIDs;

            this.emit("close");

            if (this.ws == currWs) {
                self.postMessage({ 
                    event: "disconnect", 
                    code: e.code, 
                    reason: e.reason 
                });
            }
        }
    }

    setupIntervals() {

        this.replayInterval = self.setInterval(() => this.replay.recordState(), 1000 / REPLAY_PREVIEW_FPS);

        this.pingInterval = self.setInterval(() => {
            this.renderer.stats.bandwidth = this.bandwidth;
            this.bandwidth = 0;
            
            if (!this.connected || !this.pid || this.ping) return;
            
            const PING = new ArrayBuffer(1);
            new Uint8Array(PING)[0] = 69;
            this.send(PING);
            this.ping = Date.now();
        }, 1000);

        const state = this.renderer.state;
        this.mouseInterval = self.setInterval(() => {
            if (!this.connected || !this.pid || !this.renderer.state.visible) return;

            const writer = new Writer();
            writer.writeUInt8(3);
            writer.writeFloat32(this.renderer.cursor.position[0]);
            writer.writeFloat32(this.renderer.cursor.position[1]);

            const currState = state.exchange();

            let spectate = 0;
            if (state.spectate) {
                state.spectate = 0;
                spectate = 255;
            } else if (currState.clicked) {
                spectate = this.renderer.getClickedPlayerID();
            }

            writer.writeUInt8(spectate);
            writer.writeUInt8(currState.splits);
            writer.writeUInt8(currState.ejects);
            writer.writeUInt8(currState.macro);
            writer.writeUInt8(currState.lineLock);
            writer.writeUInt8(currState.switch_tab);

            this.send(writer.finalize());

            if (currState.respawn) this.spawn(this.lastName, this.lastSkin1, this.lastSkin2);
            if (currState.clip && !this.replaying) this.replay.save();
        }, 1000 / 33); // TODO?
    }

    send(data) {
        this.connected && this.ws.send(data);
    }

    /** @param {{ data: ArrayBuffer }} e */
    onMessage(e) {
        const reader = new Reader(new DataView(e.data));
        const OP = reader.readUInt8();
        this.bandwidth += e.data.byteLength;

        switch (OP) {
            case 1:
                this.HANDSHAKE_PACKET = e.data;
                const pid1 = reader.readUInt8();
                const pid2 = reader.readUInt8();
                if (pid2) this.dualIDs = [pid1, pid2];
                this.map.hw = reader.readUInt16();
                this.map.hh = reader.readUInt16();
                console.log(`Map Dimension: ${this.map.hw << 1}x${this.map.hh << 1}`);
                const server = reader.readUTF16String();
                const uid = reader.readUTF8String();
                this.emit("protocol");
                if (!this.replaying) self.postMessage({ event: "connect", server, uid });
                break;
            case 2:
                this.renderer.clearCells();
                break;
            case 3:
                const id = reader.readUInt16();
                const name = reader.readUTF16String();
                const skin = reader.readUTF16String();
                this.renderer.loadPlayerData({ id, name, skin });
                break;
            case 4:
                this.parseCellData(e.data);
                break;
            // Leaderboard
            case 5:
                this.parseLeaderboard(reader);
                break;
            // Minimap
            case 6:
                this.parseMinimap(reader);
                break;
            // Stats
            case 7:
                this.parseStats(reader);
                if (this.renderer.state.auto_respawn) {
                    const RESPAWN = new ArrayBuffer(1);
                    new Uint8Array(RESPAWN)[0] = 7;
                    this.send(RESPAWN);
                }
                break;
            // Chat
            case 10:
                self.postMessage({ event: "chat", message: reader.readUTF16String() });
                break;
            // Log
            case 11:
                self.postMessage({ event: "server-log", message: reader.readUTF16String() });
                break;
            // PONG
            case 69:
                if (!this.ping) return;
                this.renderer.stats.ping = Date.now() - this.ping;
                delete this.ping;
                break;
        }
        
        if (!this.replaying && RecordOPs.includes(OP) && 
            (this.renderer.target.position[0] || this.renderer.target.position[1])) // monke check
            this.replay.recordPacket(e.data);
    }

    get player() { 
        if (Array.isArray(this.dualIDs)) return this.renderer.playerData[this.dualIDs[0]];
        return this.renderer.playerData[this.pid]; 
    }

    /** @param {ArrayBuffer} buffer */
    parsePlayers(buffer) {
        const reader = new Reader(new DataView(buffer));
        const length = reader.readUInt8();
        for (let i = 0; i < length; i++) {
            const data = { 
                id: reader.readUInt8(),
                name: reader.readUTF16String(), 
                skin: reader.readUTF16String()
            };
            this.renderer.loadPlayerData(data);
        }
    }

    /** @param {ArrayBuffer} buffer */
    parseCellData(buffer) {
        this.lastPacket = this.renderer.lastTimestamp;

        const r = this.renderer;
        const core = this.renderer.core;
        const header = new DataView(buffer, 1, 24);
        this.pid = header.getUint8(0);
        const prev = this.renderer.stats.mycells;
        const curr = header.getUint16(1, true);
        if (this.replaying && !prev && curr) r.camera.tp = true;
        r.stats.mycells = curr;
        r.stats.linelocked = header.getUint8(3);
        r.stats.score = header.getFloat32(4, true);
        r.syncMouse.x = header.getFloat32(8, true);
        r.syncMouse.y = header.getFloat32(12, true);
        r.target.position[0] = header.getFloat32(16, true);
        r.target.position[1] = header.getFloat32(20, true);
        
        core.HEAPU8.set(new Uint8Array(buffer, 25), r.INDICES_OFFSET);                 
        core.instance.exports.deserialize(0, r.INDICES_OFFSET);
    }

    /** @param {Reader} reader */
    parseLeaderboard(reader) {
        const rank = reader.readInt16();
        const count = reader.readUInt8();
        const lb = { rank, me: this.player, players: [] }
        for (let i = 0; i < count; i++) lb.players.push(
            this.renderer.playerData[reader.readUInt8()]);
        self.postMessage({ event: "leaderboard", lb });
    }

    /** @param {Reader} reader */
    parseMinimap(reader) {
        const count = reader.readUInt8();
        const minimap = [];
        for (let i = 0; i < count; i++) {
            const pid = reader.readUInt8();
            const player = this.renderer.playerData[pid] || {};
            player.id = pid;
            if (Array.isArray(this.dualIDs) ? this.dualIDs.includes(pid) : 
                this.pid == pid) player.me = true;
            const x = reader.readFloat32(), y = reader.readFloat32();
            player.x = x / (this.map.hw << 1) + 0.5;
            player.y = y / (this.map.hh << 1) + 0.5;
            player.score = reader.readFloat32();
            minimap.push(player);
        }
        self.postMessage({ event: "minimap", minimap });
    }

    /** @param {Reader} reader */
    parseStats(reader) {
        const kills = reader.readUInt32();
        const score = reader.readFloat32();
        const surviveTime = reader.readFloat32();
        self.postMessage({ event: "stats", kills, score, surviveTime });
    }

    /**
     * @param {string} name 
     * @param {string} skin1
     * @param {string} skin2
     */
    spawn(name, skin1, skin2) {
        const writer = new Writer();
        writer.writeUInt8(1);
        writer.writeUTF16String(name || "");
        writer.writeUTF16String(skin1 || "");
        writer.writeUTF16String(skin2 || "");
        this.send(writer.finalize());

        this.lastName = name;
        this.lastSkin1 = skin1;
        this.lastSkin2 = skin2;
    }

    sendChat(message) {
        const writer = new Writer();
        writer.writeUInt8(10);
        writer.writeUTF16String(message);
        this.send(writer.finalize());
    }

    get replaying() { return !!this.replay.curr; }

    async startReplay(id = 0) {
        this.disconnect();  
        this.renderer.cleanup();      
        await this.replay.load(id);
    }
}

<|start_filename|>webgl/game/input.js<|end_filename|>
const events = ["Feed", "Split", "Double Split", "Triple Split", "Quad Split", "Line Lock", "Respawn", "Clip", "Switch Tab", "32 Split", "64 Split", "128 Split", "256 Split"];
const defaultKeys = ["<KEY> "tab", "", "", "", ""];
const KeyNameMap = { " ": "SPACE", "": "NONE" };

module.exports = class Keyboard {
    /** @param {import("./hud")} hud */
    constructor(hud) {
        this.hud = hud;

        try {
            /** @type {string[]} */
            let keys = JSON.parse(localStorage.getItem("ogarx-keys"));
            if (!Array.isArray(keys) || 
                !keys.every(k => typeof k == "string"))
                throw new Error("Error parsing keybinds, resetting");
            if (keys.length > events.length) keys = keys.slice(0, events.length);
            else if (keys.length < events.length) keys = keys.concat(defaultKeys.slice(keys.length));
            this.keys = keys.map(k => k.toLowerCase());
        } catch (e) {
            console.error(e.message);
            this.keys = defaultKeys.concat([]);
        }

        this.menuElem = document.getElementById("key-menu");

        /** @type {Set<string>} */
        this.pressing = new Set();

        this.updateKeys();
        this.save();
    }

    save() {
        localStorage.setItem("ogarx-keys", JSON.stringify(this.keys));
    }

    updateKeys() {
        const keys = document.getElementById("keys");
        keys.innerHTML = "";

        this.labels = [];

        for (const i in events) {
            const label = document.createElement("span");
            const k = this.keys[i].toUpperCase();
            label.textContent = `${events[i]}`;
            const b = document.createElement("b");
            b.innerText = (KeyNameMap[k] || k).toUpperCase();
            b.classList.add("selectable");
            keys.appendChild(label);
            keys.appendChild(b);
            this.labels.push(b);

            b.addEventListener("mouseenter", _ => this.hovered = b);
            b.addEventListener("mouseleave", _ => this.hovered = null);
            b.addEventListener("click", e => {
                this.setKey(b, `MOUSE ${e.button}`);
                e.preventDefault();
                e.stopPropagation();
            });
        }
    }

    setKey(element = this.hovered, key = "") {
        if (!key || !key.length) return;
        const index = this.labels.indexOf(element);
        if (key.toLowerCase() === "delete" || !key) {
            element.innerText = "NONE";
            this.keys[index] = "";
        } else {
            element.innerText = (KeyNameMap[key] || key).toUpperCase();
            this.keys[index] = key.toLowerCase();
        }
        this.save();
    }

    /** @param {KeyboardEvent} e */
    keyDown(e) {
        if (e.ctrlKey) return;
        if (e.key == "Tab") e.preventDefault();
        if (e.key == "Escape") this.hud.toggle();
        if (e.key == "Enter") this.hud.toggle(this.hud.chatInput);
        if (this.pressing.has(e.key)) return;
        this.pressing.add(e.key);

        if (!e.key) return;
        const key = e.key.toLowerCase();

        if (this.hovered) return this.setKey(this.hovered, key);

        const action = events[this.keys.indexOf(key)];
        if (!action) return;
        const state = this.hud.state;

        switch (action) {
            case "Feed":         state.macro    = 1; break;
            case "Split":        state.splits   = 1; break;
            case "Double Split": state.splits   = 2; break;
            case "Triple Split": state.splits   = 3; break;
            case "Quad Split":   state.splits   = 4; break;
            case "Line Lock":    state.lineLock = 1; break;
            case "Respawn":      state.respawn  = 1; break;
            case "Clip":         state.clip     = 1; break;
            case "Switch Tab":   state.s_tab    = 1; break;
            case "32 Split":     state.splits   = 5; break;
            case "64 Split":     state.splits   = 6; break;
            case "128 Split":    state.splits   = 7; break;
            case "256 Split":    state.splits   = 8; break;
        }
    }

    /** @param {KeyboardEvent} e */
    keyUp(e) {
        if (e.ctrlKey || !e.key) return;
        this.pressing.delete(e.key);

        const key = e.key.toLowerCase();

        const action = events[this.keys.indexOf(key)];
        if (!action) return;
        const state = this.hud.state;

        switch (action) {
            case "Feed":         state.macro   = 0; break;
        }
    }

    blur() {
    }

    focus() {
        for (const k of this.pressing) this.keyUp(k);
        this.pressing.clear();
    }
}

<|start_filename|>webgl/game/stats.js<|end_filename|>
module.exports = class Mouse {
    constructor () {
        this.setBuffer();
    }

    setBuffer(buf) {
        if (!buf) {
            if (self.SharedArrayBuffer) buf = new SharedArrayBuffer(32);
            else buf = new ArrayBuffer(32);
        }

        this.sharedBuffer = buf;
        this.buffer = new Int32Array(this.sharedBuffer);
    }

    get fps() { return Atomics.load(this.buffer, 0); }
    set fps(v) { Atomics.store(this.buffer, 0, v) }

    get ping() { return Atomics.load(this.buffer, 1); }
    set ping(v) { Atomics.store(this.buffer, 1, v); }

    get cells() { return Atomics.load(this.buffer, 2); }
    set cells(v) { Atomics.store(this.buffer, 2, v); }

    get text() { return Atomics.load(this.buffer, 3); }
    set text(v) { Atomics.store(this.buffer, 3, v); }

    get bandwidth() { return Atomics.load(this.buffer, 4); }
    set bandwidth(v) { Atomics.store(this.buffer, 4, v); }

    get mycells() { return Atomics.load(this.buffer, 5); }
    set mycells(v) { Atomics.store(this.buffer, 5, v); }

    get linelocked() { return Atomics.load(this.buffer, 6); }
    set linelocked(v) { Atomics.store(this.buffer, 6, v); }

    get score() { return Atomics.load(this.buffer, 7); }
    set score(v) { Atomics.store(this.buffer, 7, v); }

    reset() { this.buffer.fill(0); }
}

<|start_filename|>src/network/protocols/ogarx.js<|end_filename|>
const { randomBytes } = require("crypto");
const Protocol = require("../protocol");
const Reader = require("../reader");
const Writer = require("../writer");
const DualHandle = require("../../game/dual");

class WebAssemblyPool {

    /** @param {number} size */
    static init(size) {
        this.blocks = Array.from({ length: size }, (_, index) => {
            const mem = new WebAssembly.Memory({ initial: 16, maximum: 32 });
            mem.used = false;
            return mem;
        }); // 1mb, 2mb
    }

    static get() {
        let mem = this.blocks.find(b => !b.used);
        
        if (mem) {
            mem.used = true;
            return mem;
        }
        
        mem = new WebAssembly.Memory({ initial: 16, maximum: 32 });
        mem.used = true;
        this.blocks.push(mem);
        return mem;
    }

    /** @param {WebAssembly.Memory} mem */
    static free(mem) {
        mem.used = false;
    }
}

module.exports = class OgarXProtocol extends Protocol {

    /** @param {DataView} view */
    static handshake(view) {
        const reader = new Reader(view);
        return reader.length >= 7 && // 3 bytes of funni and at least 2 * 2 bytes of zero
            reader.readUInt8() == 69 &&
            reader.readInt16() == 420;
    }

    /** @param {BufferSource} buffer */
    static async init(buffer, pool_size = 10) {
        this.Module = await WebAssembly.compile(buffer);
        WebAssemblyPool.init(pool_size);
    }

    /**
     * @param {import("../../game")} game
     * @param {import("uWebSockets.js").WebSocket} ws 
     * @param {ArrayBuffer} initMessage
     */
    constructor(game, ws, initMessage) {
        super(game);
        this.ws = ws;
        this.uid = randomBytes(20).toString("base64");
        this.init(initMessage);

        this.last_vlist_ptr = 131072; // 2 * 64k or 2 ^ 17
        this.last_vlist_len = 0;
        this.curr_vlist_ptr = 131072; // 2 * 64k or 2 ^ 17
        this.curr_vlist_len = 0;
    }

    /** @param {ArrayBuffer} initMessage */
    async init(initMessage, reconnect = false) {

        if (!this.memory) {
            const { get_cell_updated, get_cell_x, get_cell_y, get_cell_r, 
                get_cell_type, get_cell_eatenby } = this.game.engine.wasm;
    
            this.memory = WebAssemblyPool.get();
            this.wasm = await WebAssembly.instantiate(OgarXProtocol.Module, {
                env: { 
                    memory: this.memory,
                    get_cell_updated, get_cell_x, get_cell_y, get_cell_r, 
                    get_cell_type, get_cell_eatenby 
                }
            });
            this.view = new DataView(this.memory.buffer);
            this.join();
        }
        
        const reader = new Reader(new DataView(initMessage));
        reader.skip(3); // Skip handshake bytes

        this.controller.name = reader.readUTF16String(this.game.options.FORCE_UTF8);
        this.controller.skin = reader.readUTF16String(this.game.options.FORCE_UTF8);

        if (this.game.options.DUAL_ENABLED) {
            if (!this.dual) {
                /** @type {DualHandle} */
                this.dual = new DualHandle(this);
                this.dualActive = false;
            } else {
                // ??
            }
        }

        if (this.dual) {
            this.dual.controller.name = this.controller.name;
            this.dual.controller.skin = reader.readUTF16String(this.game.options.FORCE_UTF8);
            this.pids.add(this.dual.controller.id);
        }

        this.sendInitPacket();
        
        for (const { message, isServer } of this.game.history)
            isServer ? this.onLog(message) : this.onChat(message);

        this.game.emit("join", this.controller);
        this.dual && this.game.emit("join", this.dual.controller);

        this.game.emit("log", this.controller.name + (reconnect ? " reconnected" : " joined the game"))

        this.showonMinimap = true;
        this.showonLeaderboard = true;

        if (!this.game.options.CHAT_ENABLED) {
            this.onLog("Chat is disabled due to 🤡 activities", true);
        }
    }

    off() {
        delete this.ws;
        super.off();
        if (this.dual) this.dual.off();
        WebAssemblyPool.free(this.memory);
    }

    sendInitPacket() {
        const writer = new Writer();
        writer.writeUInt8(1);
        writer.writeUInt8(this.controller.id);
        writer.writeUInt8(this.dual ? this.dual.controller.id : 0);
        writer.writeUInt16(this.game.options.MAP_HW);
        writer.writeUInt16(this.game.options.MAP_HH);
        writer.writeUTF16String(this.game.name);
        writer.writeUTF8String(this.uid);
        this.send(writer.finalize());
    }

    clear() {
        const CLEAR_SCREEN = new ArrayBuffer(1);
        new Uint8Array(CLEAR_SCREEN)[0] = 2;
        this.send(CLEAR_SCREEN);
        if (!this.last_vlist_len && !this.curr_vlist_len) return;
        this.wasm.exports.clean(0, this.memory.buffer.byteLength);
    }

    onDrain() {}

    /** @param {DataView} view */
    onMessage(view) {
        const reader = new Reader(view);
        const OP = reader.readUInt8();
        const a = this.active;

        switch (OP) {
            case 1:
                this.controller.name = reader.readUTF16String(this.game.options.FORCE_UTF8);
                this.controller.skin = reader.readUTF16String(this.game.options.FORCE_UTF8);
                this.game.emit("info", this.controller);

                if (this.dual) {
                    this.dual.controller.name = this.controller.name;
                    this.dual.controller.skin = reader.readUTF16String(this.game.options.FORCE_UTF8);
                    this.game.emit("info", this.dual.controller);
                }

                this.active.requestSpawn();
                this.active.lastSpawnTick = this.game.engine.__now;
                break;
            case 3:
                a.mouseX = ~~reader.readFloat32();
                a.mouseY = ~~reader.readFloat32();
                const spectate = reader.readUInt8();
                if (this.alive) {
                    const splits = reader.readUInt8();
                    const ejects = reader.readUInt8();
                    const macro  = reader.readUInt8();
                    const lock   = reader.readUInt8();
                    const s_tab  = reader.readUInt8();
                    a.splitAttempts += splits;
                    a.ejectAttempts += ejects;
                    a.ejectMarco = Boolean(macro);
                    if (lock) a.toggleLock();
                    if (s_tab) {
                        if (!this.dual) return;
                        if (!this.controller.alive && !this.dual.controller.alive) {
                            this.controller.requestSpawn();
                        } else {
                            if (!this.controller.alive) {
                                this.controller.requestSpawn();
                            } else if (!this.dual.controller.alive) {
                                this.dual.controller.requestSpawn();
                            } else {
                                this.dualActive = !this.dualActive; // toggle            
                            }
                        }
                    }
                } else { // We are DEAD
                    if (spectate && spectate <= 250) {
                        const c = this.game.controls[spectate];
                        if (!c.handle) break; // ??
                        if (!c.handle.alive) break; // Can't spectate dead handle??
                        this.spectate = c.handle.owner || c.handle;
                    } else if (spectate === 255) {
                        let score = 0;
                        for (const c of this.game.controls) {
                            if (!(c.handle instanceof OgarXProtocol)) continue;
                            if (c.handle.score > score) {
                                score = c.score;
                                this.spectate = c.handle;
                            }
                        }
                    }
                }
                break;
            case 7:
                this.controller.autoRespawn = true;
                break;
            case 10:
                if (!this.game.engine.options.CHAT_ENABLED) return;
                const message = reader.readUTF16String(this.game.options.FORCE_UTF8);
                this.game.emit("chat", `${this.controller.name}: ${message}`);
                break;
            case 69:
                const PONG = new ArrayBuffer(1);
                new Uint8Array(PONG)[0] = 69;                
                this.send(PONG); // PING-PONG
                break;
            default:
                console.warn(`Unknown OP: ${OP}`);
        }
    }

    onError(message) {
        if (this.ws) this.ws.end(1000, message);
    }

    get active() {
        if (this.dual && this.dualActive) return this.dual.controller;
        else return this.controller;
    }

    /** @param {import("../../game/controller")} c */
    switchTo(c) {
        if (this.active == c) return;
        this.dualActive = !this.dualActive;
    }

    /** @param {import("../../game/controller")} c */
    onSpawn(c) {
        if (this.pids.has(c.id)) this.switchTo(c);
    }

    /** @param {import("../../game/controller")} c */
    onInfo(c) {
        this.sendPlayerInfo(c);
    }

    get alive() {
        if (this.dual) return this.controller.alive || this.dual.controller.alive;
        else return this.controller.alive;
    }

    get score() {
        if (this.dual) return this.controller.score + this.dual.controller.score;
        else return this.controller.score;
    }
    
    get maxScore() {
        if (this.dual) return Math.max(this.controller.maxScore, this.dual.controller.maxScore);
        else return this.controller.maxScore;
    }

    get kills() {
        if (this.dual) return this.controller.kills + this.dual.controller.kills;
        else this.controller.kills;
    }

    /** @param {import("../../game/handle")[]} handles */
    onLeaderboard(handles) {
        const LB_COUNT = 10;
        const count = Math.min(LB_COUNT, handles.length);
        const writer = new Writer();
        writer.writeUInt8(5);
        writer.writeInt16(handles.indexOf(this));
        writer.writeUInt8(count);
        for (let i = 0; i < count; i++)
            writer.writeUInt8(handles[i].controller.id);
        this.send(writer.finalize());
    }

    /** @param {import("../../game/handle")[]} handles */
    onMinimap(handles) {
        const writer = new Writer();
        writer.writeUInt8(6);
        writer.writeUInt8(handles.length);
        for (const h of handles) {
            writer.writeUInt8(h.controller.id);
            writer.writeFloat32(h.controller.viewportX);
            writer.writeFloat32(h.controller.viewportY);
            writer.writeFloat32(h.score);
        }
        this.send(writer.finalize());
    }

    onTick() {
        if (!this.controller) return; // ??????
        if (!this.wasAlive && this.alive) this.actualSpawnTick = this.game.engine.__now;

        const engine = this.game.engine;
        const c1 = this.controller;

        if (this.dual) {
            const c2 = this.dual.controller;

            if (!c1.alive && c2.alive) this.switchTo(c2);
            if (c1.alive && !c2.alive) this.switchTo(c1);
            if (this.wasAlive && !this.alive) {
                c1.lastSpawnTick = c2.lastSpawnTick = engine.__now;
                this.sendStats();
            }
        } else {
            if (this.wasAlive && !this.alive) {
                c1.lastSpawnTick = engine.__now;
                this.sendStats();
            }
        }
        this.wasAlive = this.alive;

        let target = this.active;
        if (this.alive) this.spectate = null;
        if (this.spectate) {
            // Some other handler will take care of this
            if (this.spectate instanceof OgarXProtocol) return;
            else target = this.spectate.controller;
        }

        const vlist = engine.query(target);
        // Query visible cells from the controller
        this.processVisibleList(vlist, target);
        
        for (const c of this.game.controls) {
            if (!c.handle) continue;
            if (c.handle.alive) {
                c.handle.spectate = null;
                continue;
            }
            if (c.handle.spectate === this) 
                c.handle.processVisibleList(vlist, target);
        }
    }

    /** @param {Uint16Array} vlist */
    processVisibleList(vlist, controller = this.controller) {
        if (!vlist.length) return;
        // Backpressure higher than watermark
        if (!this.ws || this.ws.getBufferedAmount() > this.game.options.SOCKET_WATERMARK) return;

        // Step 1
        this.wasm.exports.move_hashtable();
        // Step 2
        this.wasm.exports.copy(this.last_vlist_ptr, this.curr_vlist_ptr, this.curr_vlist_len * 2);
        // Update ptr and len
        this.last_vlist_len = this.curr_vlist_len;
        this.curr_vlist_ptr = this.last_vlist_ptr + this.last_vlist_len * 2; // 2 bytes per index
        this.curr_vlist_len = vlist.length;
        new Uint16Array(this.memory.buffer, this.curr_vlist_ptr, this.curr_vlist_len).set(vlist);
        
        const AUED_table_ptr = this.curr_vlist_ptr + this.curr_vlist_len * 2;
        
        // Step 3
        const AUED_end_ptr = this.wasm.exports.write_AUED(
            0, 65536,
            this.last_vlist_ptr, this.last_vlist_len,
            this.curr_vlist_ptr, this.curr_vlist_len,
            AUED_table_ptr, AUED_table_ptr + 16 // 4 * 4 bytes after the table
        );

        const A_count = this.view.getUint32(AUED_table_ptr + 0,  true);
        const U_count = this.view.getUint32(AUED_table_ptr + 4,  true);
        const E_count = this.view.getUint32(AUED_table_ptr + 8,  true);
        const D_count = this.view.getUint32(AUED_table_ptr + 12, true);

        // 1 byte OP + 1 byte pid + 2 bytes cell count + 1 byte linelocked + 
        // 4 bytes score + 8 bytes mouse + 8 bytes viewport + 4 * 2 bytes 0 padding = 33 bytes
        // We don't have to calculate this because serialize returns the write end
        // But this is a good way to verify it wrote as expect
        const buffer_length = 33 + 10 * A_count + 8 * U_count + 4 * E_count + 2 * D_count;
        
        const mem_check = AUED_end_ptr + buffer_length - this.memory.buffer.byteLength;
        if (mem_check > 0) {
            const extra_page = Math.ceil(mem_check / 65536);
            this.memory.grow(extra_page);
            this.view = new DataView(this.memory.buffer);
            console.log(`Growing ${extra_page} page of memory in ogar69 protocol ` +
                `memory for controller(${this.controller.name})`);
        }

        const o = this.game.options;

        // Step 4 serialize
        const buffer_end = this.wasm.exports.serialize(
            controller.id,
            this.game.engine.counters[controller.id].size,
            controller.lockDir,
            controller.handle.score,
            controller.mouseX, controller.mouseY,
            controller.viewportX, controller.viewportY,
            AUED_table_ptr, AUED_table_ptr + 16, AUED_end_ptr,
            -o.MAP_HW, o.MAP_HW, o.MAP_HH, -o.MAP_HH);
        
        const diff = buffer_end - AUED_end_ptr;
        console.assert(diff == buffer_length, "Buffer length must match");

        this.send(this.memory.buffer.slice(AUED_end_ptr, buffer_end));
    }

    sendStats() {
        if (!this.maxScore || this.controller.spawn) return;
        const writer = new Writer();
        writer.writeUInt8(7);
        writer.writeUInt32(this.kills);
        writer.writeFloat32(this.maxScore);
        writer.writeFloat32(this.game.engine.__now - this.actualSpawnTick);
        this.send(writer.finalize(), true);
    }

    /** @param {import("../../game/controller")} controller */
    onJoin(controller) {
        if (this.controller == controller) {
            // Send every controller's info to client
            for (const c of this.game.controls) c.handle && this.sendPlayerInfo(c);
            this.clear();
        } else {
            // Send controller info to client since it just joined
            this.sendPlayerInfo(controller);
        }
    }

    /** @param {import("../../game/controller")} controller */
    onLeave(controller) {
        if (controller == this.controller) this.game.emit("log", `${controller.name} rage quit`);
    }

    /** @param {import("../../game/controller")} controller */
    sendPlayerInfo(controller) {
        if (!controller.name && !controller.skin) return;
        const writer = new Writer();
        writer.writeUInt8(3);
        writer.writeUInt16(controller.id);
        writer.writeUTF16String(controller.name);
        writer.writeUTF16String(controller.skin);
        this.send(writer.finalize());
    }

    /** @param {string} message */
    onLog(message, bypass = false) {
        if (!bypass && this.game.engine.options.IGNORE_LOG) return;
        const writer = new Writer();
        writer.writeUInt8(11);
        writer.writeUTF16String(message);
        this.send(writer.finalize());
    }

    /** @param {string} message */
    onChat(message) {
        const writer = new Writer();
        writer.writeUInt8(10);
        writer.writeUTF16String(message);
        this.send(writer.finalize());
    }

    send(buffer) {
        if (this.ws) this.ws.send(buffer, true, true);
    }
}

<|start_filename|>webgl/game/custom-bson.js<|end_filename|>
const pako = require("pako");
const Reader = require("../../src/network/reader");

const isArrayBuffer = b => typeof b == "object" && b.byteLength !== undefined;

/** @param {Object<string, ArrayBuffer|ArrayBuffer[]>} indexedBuffers */
const serialize = indexedBuffers => {

    // Delete the fields that's not arraybuffer or array of arraybuffer
    for (const k of Object.keys(indexedBuffers)) {
        const v = indexedBuffers[k];
        if ((Array.isArray(v) && v.every(isArrayBuffer)) || 
            isArrayBuffer(v)) {
            // console.log(`Keeping key "${k}"`);
        } else {
            // console.log(`Deleting key "${k}"`);
            delete indexedBuffers[k];
        }
    }

    let length = 0;
    for (const key in indexedBuffers) {
        length += key.length + 1;
        const bufferOrBuffers = indexedBuffers[key];
        length += 4; // header bytes
        if (Array.isArray(bufferOrBuffers)) {
            bufferOrBuffers.forEach(b => length += b.byteLength + 4); 
        } else {
            length += bufferOrBuffers.byteLength;
        }
    }
    const out = new Uint8Array(length);
    const view = new DataView(out.buffer);
    let offset = 0;
    for (const key in indexedBuffers) {
        for (let i = 0; i < key.length; i++)
            out[offset++] = key.charCodeAt(i);
        out[offset++] = 0;

        const bufferOrBuffers = indexedBuffers[key];

        /** @param {ArrayBuffer} b */
        const writeBuffer = b => {
            view.setInt32(offset, b.byteLength, true);
            offset += 4;
            out.set(new Uint8Array(b), offset);
            offset += b.byteLength;
        }

        if (Array.isArray(bufferOrBuffers)) {
            view.setInt32(offset, -bufferOrBuffers.length, true); // Negative to mark this as an array
            offset += 4;
            for (const buffer of bufferOrBuffers) writeBuffer(buffer);
        } else {
            writeBuffer(bufferOrBuffers);
        }
    }

    console.assert(out.byteLength === offset, "Output bytes should match final offset");
    return pako.deflate(out, { level: 9, raw: true }).buffer;
}

/** @param {ArrayBuffer} buffer */
const deserialize = buffer => {
    const out = {};
    const reader = new Reader(new DataView(pako.inflate(buffer, { raw: true }).buffer));
    const readBuffer = l => reader.readBytes(l || reader.readInt32());
    while (!reader.EOF) {
        const key = reader.readUTF8String();
        const l = reader.readInt32();
        if (l > 0) out[key] = readBuffer(l);
        else out[key] = Array.from({ length: -l }, _ => readBuffer());
    }
    return out;
}

module.exports = { serialize, deserialize };

<|start_filename|>ipc-test/net/client.js<|end_filename|>
const net = require("net");
const path = require("path");

const pipename = str => process.platform == "win32" ? `\\\\.\\pipe\\${str.replace(/^\//, "").replace(/\//g, "-")}` : str;
const SOCKET_FILE = path.resolve(__dirname, "unix.sock");

const client = net.connect(pipename(SOCKET_FILE));

let count = 0;

const buffer = new ArrayBuffer(10);

client
    .on("connect", err => {
        if (!err) console.log("Connected on client");
        client.write(new Uint8Array(buffer));
    })
    .on("data", b => {
        client.write(new Uint8Array(buffer));
        count++;
    })
    .on("close", () => {
        console.log("Disconnected on client");
    });

setInterval(() => {
    console.log(`Message per second: ${count}`);
    count = 0;
}, 1000);

<|start_filename|>webgl/game/renderer.js<|end_filename|>
importScripts("https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.8.1/gl-matrix-min.js");    

const Stats = require("./stats");
const Mouse = require("./mouse");
const State = require("./state");
const Viewport = require("./viewport");
const Protocol = require("./protocol");
const WasmCore = require("./wasm-core");
const TextureStore = require("./texture-store");

const { makeProgram, COLORS } = require("./util");
const {
   SPRITE_VERT_SHADER_SOURCE, SPRITE_FRAG_SHADER_SOURCE,
   PARTICLE_VERT_SHADER_SOURCE, PARTICLE_FRAG_SHADER_SOURCE,
   MASS_VERT_SHADER_SOURCE, MASS_FRAG_PEELING_SHADER_SOURCE,
   BORDER_VERT_SHADER_SOURCE, BORDER_FRAG_SHADER_SOURCE
} = require("./shaders");

const NAME_MASS_MIN = 0.03;
const NAME_SCALE = 0.25;
const NAME_Y_OFFSET = 0;

const MASS_GAP = 0;
const MASS_SCALE = 0.25;
const MASS_Y_OFFSET = -0.33;

// Constants
const CELL_LIMIT = 1 << 16; // 65536

const MASS_CHARS       = "0123456789.k".split("");
const MASS_CHARS_COUNT = MASS_CHARS.length;

class Renderer {
    /** @param {OffscreenCanvas} canvas */
    constructor(canvas) {
        this.canvas = canvas;

        this.core = new WasmCore(this);

        /** @type {Map<string, WebGLFramebuffer|WebGLFramebuffer[]>} */
        this.fbo = new Map();
        /** @type {Map<string, WebGLFramebuffer>} */
        this.buffers = new Map();
        /** @type {Map<WebGLProgram, Map<string, WebGLUniformLocation>} */
        this.uniforms = new Map();

        /** @type {WebGLTexture[]} */
        this.circleTextures = [];
        /** @type {{ skin: string, name: string }[]} */
        this.playerData = Array.from({ length: 256 });
        this.playerData[0] = { name: "Server" };

        /** @type {{ id: number, skin?: ImageBitmap, name?: ImageBitmap }[]} */
        this.updates = [];
        this.stats = new Stats();
        this.mouse = new Mouse();
        this.state = new State();
        this.viewport = new Viewport();
        this.viewbox = { t: 0, b: 0, l: 0, r: 0 };
        this.syncMouse = { x: 0, y: 0 };

        this.initLoader();

        this.fps = 0;
        this.fpsInterval = setInterval(() => {
            this.stats.fps = this.fps;
            this.fps = 0;
        }, 1000);
    }

    start() {
        if (this.r) return false;
        const loop = now => {
            this.r = requestAnimationFrame(loop);
            try {
                this.render(now);
            } catch (e) { 
                this.stop();
                this.protocol.disconnect();
                throw e;
            }
        };
        this.r = requestAnimationFrame(loop);
        return true;
    }

    stop() {
        if (!this.r) return false;
        this.r = cancelAnimationFrame(this.r);
        return true;
    }

    /** @param {{ id: number, skin: string, name: string, persist: boolean }} arg0 */
    loadPlayerData({ id, skin, name, persist }) {
        if (this.IGNORE_SKIN && id <= 250) skin = "";

        const old = this.playerData[id];
        if (old) {
            if (this.textures.replace(old.name, name)) this.loader.postMessage({ id, name });
            if (this.textures.replace(old.skin, skin)) this.loader.postMessage({ id, skin, quality: this.state.skin_dim });
        } else {
            if (this.textures.add(name, persist)) this.loader.postMessage({ id, name });
            if (this.textures.add(skin, persist)) this.loader.postMessage({ id, skin, quality: this.state.skin_dim });
        }
        this.playerData[id] = { skin, name };
    }

    initLoader() {
        this.loader = new Worker("loader.min.js");
        /** @param {{ data: { id: number, skin?: ImageBitmap, name?: ImageBitmap }}} e */
        this.loader.onmessage = e => {
            if (!e.data) return;
            if (e.data.event === "replay") {
                self.postMessage(e.data);
                this.protocol.replay.saving = false;
            } else if (e.data.id) this.updates.unshift(e.data);
        }
    }

    /**
     * @param {WebGLProgram} prog 
     * @param {string} name
     */
    loadUniform(prog, name) {
        if (!this.uniforms.has(prog)) this.uniforms.set(prog, new Map());
        this.uniforms.get(prog).set(name, this.gl.getUniformLocation(prog, name));
    }

    /**
     * @param {WebGLProgram} prog 
     * @param {string} name 
     */
    getUniform(prog, name) {
        return this.uniforms.get(prog).get(name);
    }

    async initEngine() {

        const gl = this.gl = this.canvas.getContext("webgl2", { 
            depth: false,
            premultipliedAlpha: false, 
            powerPreference: "high-performance",
            preserveDrawingBuffer: true
        });

        if (!gl) return self.postMessage({ event: "webgl2" });

        gl.enable(gl.BLEND);

        // console.log("Loading WASM...");
        await this.core.load();
        this.wasm = this.core.instance.exports;

        // console.log("Loading font");
        let font = new FontFace("Bree Serif", "url(/static/font/BreeSerif-Regular.ttf)");
        self.fonts && fonts.add(font);
        await font.load();
        font = new FontFace("Lato", "url(/static/font/Lato-Bold.ttf)");
        self.fonts && fonts.add(font);
        await font.load();
        
        this.cursor = { position: vec3.create() };
        this.target = { position: vec3.create(), scale: 10 };
        this.camera = { position: vec3.create(), scale: 10, tp: false };
        this.proj = mat4.create();
        
        this.SKIN_DIM = this.state.skin_dim;
        this.IGNORE_SKIN = this.state.ignore_skin;
        this.CIRCLE_RADIUS = this.state.circle_radius;

        this.BYTES_PER_CELL_DATA = this.wasm.bytes_per_cell_data();
        this.INDICES_OFFSET = CELL_LIMIT * this.BYTES_PER_CELL_DATA;
        this.PELLETS_OFFSET = this.INDICES_OFFSET + CELL_LIMIT * 3;
       
        // name text vertex cpu buffers
        this.nameWidths = new Float32Array(256);
        this.nameFlags  = new Uint8Array(CELL_LIMIT);
        this.nameBuffer = new Float32Array(new ArrayBuffer(6 * CELL_LIMIT));

        // mass text vertex cpu buffers
        this.massWidths = new Float32Array(256);
        this.massCounts = new Uint8Array(CELL_LIMIT);
        this.massBuffer = new Float32Array(new ArrayBuffer(128 * CELL_LIMIT));

        const alloc = this.core.buffer.byteLength + this.nameWidths.byteLength + 
            this.nameBuffer.byteLength + this.massWidths.byteLength +
            this.massCounts.byteLength + this.massBuffer.byteLength;

        console.log(`${(alloc / 1024 / 1024).toFixed(1)} MB allocated for renderer`);
        
        this.textures = new TextureStore(this);

        const main_prog = this.main_prog = makeProgram(gl, SPRITE_VERT_SHADER_SOURCE, SPRITE_FRAG_SHADER_SOURCE);
        const mass_prog = this.mass_prog = makeProgram(gl, MASS_VERT_SHADER_SOURCE,   MASS_FRAG_PEELING_SHADER_SOURCE);
        const brdr_prog = this.brdr_prog = makeProgram(gl, BORDER_VERT_SHADER_SOURCE, BORDER_FRAG_SHADER_SOURCE);
        const prtl_prog = this.prtl_prog = makeProgram(gl, PARTICLE_VERT_SHADER_SOURCE.replace(/\$colors\$/g, COLORS.length), PARTICLE_FRAG_SHADER_SOURCE);

        this.loadUniform(main_prog, "u_proj");
        this.loadUniform(main_prog, "u_uvs");
        this.loadUniform(main_prog, "u_texture");

        this.loadUniform(mass_prog, "u_proj");
        this.loadUniform(mass_prog, "u_uvs");
        this.loadUniform(mass_prog, "u_mass_char");

        this.loadUniform(brdr_prog, "u_map");
        this.loadUniform(brdr_prog, "u_proj");
        this.loadUniform(brdr_prog, "u_color");

        this.loadUniform(prtl_prog, "u_proj");
        this.loadUniform(prtl_prog, "u_uvs");
        this.loadUniform(prtl_prog, "u_colors");
        this.loadUniform(prtl_prog, "u_texture");

        this.generatePelletVAO();
        this.generateCellVAO();
        this.generateNameVAO();
        this.generateMassVAO();
        this.generateBorderVAO();

        this.generateDeadcellTexture();
        this.generateCircleTextures();
        this.generateMassTextures();

        const c = 12 / 255;
        gl.useProgram(brdr_prog);
        gl.uniform4f(this.getUniform(brdr_prog, "u_color"), c, c, c, 1);

        gl.useProgram(mass_prog);
        gl.uniform1i(this.getUniform(mass_prog, "u_mass_char"), 1);

        this.loadPlayerData({ id: 253, skin: `/static/img/virus-${this.SKIN_DIM}.png`, persist: true });
        this.start();

        this.protocol = new Protocol(this);
        this.protocol.on("close", this.cleanup.bind(this));

        await this.protocol.replay.init();
    }

    cleanup() {
        if (this.protocol.replaying) return;
        this.clearCells();
        this.clearScreen();
        this.clearPlayerData();
        this.target.position.fill(0);
        this.textures.clear();
        this.stats.reset();
    }

    teleportCamera() {
        this.camera.scale = this.target.scale;
        this.camera.position.set(this.target.position);
        this.camera.tp = false;
    }

    clearCells() {
        this.core.HEAPU32.fill(0);
        this.massBuffer.fill(0);
    }

    clearPlayerData() {
        for (let i = 1; i <= 250; i++) this.playerData[i] = undefined;
    }

    /** @param {string} name */
    allocBuffer(name) {
        if (this.buffers.has(name)) throw new Error(`Already allocated buffer "${name}"`);
        const buf = this.gl.createBuffer();
        this.buffers.set(name, buf);
        return buf;
    }

    /** @param {string} name */
    allocFrameBuffer(name, number = 1) {
        if (this.fbo.has(name)) throw new Error(`Already allocated framebuffer "${name}"`);
        this.fbo.set(name, number == 1 ? this.gl.createFramebuffer() : 
            Array.from({ length: number }, _ => this.gl.createFramebuffer()));
    }

    freeFrameBuffer(name) {
        if (!this.fbo.has(name)) throw new Error(`Trying to free none existing framebuffer "${name}`);
        const fbs = this.fbo.get(name);
        if (Array.isArray(fbs)) {
            for (const f of fbs) this.gl.deleteFramebuffer(f);
        } else this.gl.deleteFramebuffer(fbs);
        this.fbo.delete(name);
    }

    generatePelletVAO() {
        const gl = this.gl;

        gl.bindVertexArray(this.pelletVAO = gl.createVertexArray());

        // 4 bytes per float * 2 triangles * 3 vertices per triangle * 3 floats per vertex = 48
        gl.bindBuffer(gl.ARRAY_BUFFER, this.allocBuffer("pellet_buffer"));
        gl.bufferData(gl.ARRAY_BUFFER, this.core.HEAPU8.subarray(0, 72 * CELL_LIMIT), gl.DYNAMIC_DRAW);
        
        const size = 3;
        const type = gl.FLOAT;
        const normalize = false;
        const stride = 0;
        const offset = 0;
        gl.vertexAttribPointer(0, size, type, normalize, stride, offset);
        gl.enableVertexAttribArray(0);
    }

    generateCellVAO() {
        const gl = this.gl;

        gl.bindVertexArray(this.cellVAO = gl.createVertexArray());

        // 4 bytes per float * 2 triangles * 3 vertices per triangle * 2 floats per vertex = 48
        gl.bindBuffer(gl.ARRAY_BUFFER, this.allocBuffer("cell_buffer"));
        gl.bufferData(gl.ARRAY_BUFFER, this.core.HEAPU8.subarray(0, 48 * CELL_LIMIT), gl.DYNAMIC_DRAW);
        
        const size = 2;
        const type = gl.FLOAT;
        const normalize = false;
        const stride = 0;
        const offset = 0;
        gl.vertexAttribPointer(0, size, type, normalize, stride, offset);
        gl.enableVertexAttribArray(0);
    }

    generateNameVAO() {
        const gl = this.gl;

        gl.bindVertexArray(this.nameVAO = gl.createVertexArray());

        // 4 bytes per float * 2 triangles * 3 vertices per triangle * 2 floats per vertex = 48
        gl.bindBuffer(gl.ARRAY_BUFFER, this.allocBuffer("name_buffer"));
        gl.bufferData(gl.ARRAY_BUFFER, this.nameBuffer, gl.DYNAMIC_DRAW);
        
        const size = 2;
        const type = gl.FLOAT;
        const normalize = false;
        const stride = 0;
        const offset = 0;
        gl.vertexAttribPointer(0, size, type, normalize, stride, offset);
        gl.enableVertexAttribArray(0);
    }

    generateMassVAO() {
        const gl = this.gl;
        gl.bindVertexArray(this.massVAO = gl.createVertexArray());
        gl.bindBuffer(gl.ARRAY_BUFFER, this.allocBuffer("mass_buffer"));
        gl.bufferData(gl.ARRAY_BUFFER, this.massBuffer, gl.DYNAMIC_DRAW);

        // |x|y|uv_index|
        const size = 3;
        const type = gl.FLOAT;
        const normalize = false;
        const stride = 0;
        const offset = 0;
        gl.vertexAttribPointer(0, size, type, normalize, stride, offset);
        gl.enableVertexAttribArray(0);
    }

    generateBorderVAO() {
        const gl = this.gl;
        gl.bindVertexArray(this.borderVAO = gl.createVertexArray());
        gl.bindBuffer(gl.ARRAY_BUFFER, this.allocBuffer("border_buffer"));
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
            -1, +1,
            -1, -1,
            +1, -1,
            -1, +1,
            +1, -1,
            +1, +1,
        ]), gl.STATIC_DRAW);

        const size = 2;
        const type = gl.FLOAT;
        const normalize = false;
        const stride = 0;
        const offset = 0;
        gl.vertexAttribPointer(0, size, type, normalize, stride, offset);
        gl.enableVertexAttribArray(0);
    }

    generateDeadcellTexture() {
        const gl = this.gl;

        gl.bindTexture(gl.TEXTURE_2D, this.deadCellTexture = gl.createTexture());

        const R = this.CIRCLE_RADIUS;
        const temp = new OffscreenCanvas(R << 1, R << 1);
        const temp_ctx = temp.getContext("2d");
        temp_ctx.globalAlpha = 0.75;
        temp_ctx.fillStyle = `rgb(75, 75, 75)`;
        temp_ctx.arc(R, R, R, 0, 2 * Math.PI, false);
        temp_ctx.fill();
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, temp);
        gl.generateMipmap(gl.TEXTURE_2D);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
    }

    generateDualTextures(color1 = "", color2 = "") {
        const gl = this.gl;
        if (!this.dualTextures) {
            this.dualTextures = [gl.createTexture(), gl.createTexture()];
            this.dualTextureColors = ["", ""];
        }

        const R = this.CIRCLE_RADIUS;
        const r = R * 0.95;
        const temp = new OffscreenCanvas(R << 1, R << 1);
        const temp_ctx = temp.getContext("2d");

        const path = new Path2D();
        path.arc(R, R, R, 0, 2 * Math.PI, false);
        path.arc(R, R, r, 0, 2 * Math.PI, false);
        
        if (this.dualTextureColors[0] != color1) {
            this.dualTextureColors[1] = color1;
            
            temp_ctx.fillStyle = color1;
            temp_ctx.fill(path, "evenodd");

            gl.bindTexture(gl.TEXTURE_2D, this.dualTextures[0]);
            gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, temp);
            gl.generateMipmap(gl.TEXTURE_2D);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
        }

        if (this.dualTextureColors[1] != color2) {
            this.dualTextureColors[1] = color2;

            temp_ctx.clearRect(0, 0, temp.width, temp.height);
            temp_ctx.fillStyle = color2;
            temp_ctx.fill(path, "evenodd");
    
            gl.bindTexture(gl.TEXTURE_2D, this.dualTextures[1]);
            gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, temp);
            gl.generateMipmap(gl.TEXTURE_2D);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
        }
    }

    generateCircleTextures() {
        const gl = this.gl;

        if (this.circleTextures.length) {
            this.circleTextures.forEach(t => gl.deleteTexture(t));
            this.circleTextures = [];
        }

        const R = this.CIRCLE_RADIUS;
        const temp = new OffscreenCanvas(R << 1, R << 1);
        const temp_ctx = temp.getContext("2d");

        // Generate circles
        for (const [r, g, b] of COLORS) {
            const tex = gl.createTexture();
            gl.bindTexture(gl.TEXTURE_2D, tex);
            this.circleTextures.push(tex);

            temp_ctx.fillStyle = `rgb(${r}, ${g}, ${b})`;
            temp_ctx.clearRect(0, 0, temp.width, temp.height);
            temp_ctx.arc(R, R, R, 0, 2 * Math.PI, false);
            temp_ctx.fill();
            gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, temp);
            gl.generateMipmap(gl.TEXTURE_2D);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
        }

        const UVS = new Float32Array([
            0, 1,
            1, 1,
            0, 0,
            1, 1,
            0, 0,
            1, 0,
        ]);

        gl.useProgram(this.main_prog);
        gl.uniform2fv(this.getUniform(this.main_prog, "u_uvs"), UVS);
        
        gl.useProgram(this.prtl_prog);
        gl.uniform2fv(this.getUniform(this.prtl_prog, "u_uvs"), UVS);

        const U_COLORS = new Float32Array(COLORS.length * 3);
        for (const i in COLORS) U_COLORS.set(COLORS[i].map(v => v / 255), i * 3);
        gl.uniform3fv(this.getUniform(this.prtl_prog, "u_colors"), U_COLORS);
    }

    generateMassTextures() {
        const gl = this.gl;

        // Generate mass char on texture unit 1
        gl.activeTexture(gl.TEXTURE1);
        gl.bindTexture(gl.TEXTURE_2D_ARRAY, gl.createTexture());

        const MASS_FONT_WIDTH  = 320;
        const MASS_FONT_HEIGHT = 320;

        gl.texImage3D(
            gl.TEXTURE_2D_ARRAY,
            0,
            gl.RGBA,
            MASS_FONT_WIDTH,
            MASS_FONT_HEIGHT,
            MASS_CHARS_COUNT,
            0,
            gl.RGBA,
            gl.UNSIGNED_BYTE,
            null
        );

        const temp = new OffscreenCanvas(MASS_FONT_WIDTH, MASS_FONT_HEIGHT);
        
        const temp_ctx = temp.getContext("2d");

        temp_ctx.font = "bold 280px Lato";
        temp_ctx.fillStyle = "white";
        temp_ctx.strokeStyle = "black";
        temp_ctx.textAlign = "center";
        temp_ctx.lineWidth = 30;
        temp_ctx.textBaseline = "middle";

        // uv array (4 uv per char, 2 float per uv)
        const UVS = new Float32Array(MASS_CHARS_COUNT * 4 * 2); 
        
        for (let index = 0; index < MASS_CHARS_COUNT; index++) {
            const char = MASS_CHARS[index];
            temp_ctx.clearRect(0, 0, temp.width, temp.height);
            temp_ctx.strokeText(char, temp.width >> 1, temp.height >> 1);
            temp_ctx.fillText(char, temp.width >> 1, temp.height >> 1);
            const w = (temp_ctx.measureText(char).width + 20) / temp.width;
            this.massWidths[char.charCodeAt(0)] = w;
            gl.texSubImage3D(
                gl.TEXTURE_2D_ARRAY,
                0,
                0,
                0,
                index,
                MASS_FONT_WIDTH,
                MASS_FONT_HEIGHT,
                1,
                gl.RGBA,
                gl.UNSIGNED_BYTE,
                temp);
            
            const x0 = 0.5 - w / 2;
            const x1 = 0.5 + w / 2;
            const y0 = 0;
            const y1 = 1;

            UVS[8 * index + 0] = x0;
            UVS[8 * index + 1] = y0;

            UVS[8 * index + 2] = x1;
            UVS[8 * index + 3] = y0;

            UVS[8 * index + 4] = x0;
            UVS[8 * index + 5] = y1;

            UVS[8 * index + 6] = x1;
            UVS[8 * index + 7] = y1;
        }

        gl.generateMipmap(gl.TEXTURE_2D_ARRAY);
        gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
        gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
    
        gl.useProgram(this.mass_prog);
        gl.uniform2fv(this.getUniform(this.mass_prog, "u_uvs"), UVS);
    }

    screenToWorld(out = vec3.create(), x = 0, y = 0) {
        const temp = mat4.create();
        mat4.invert(temp, this.proj);
        vec3.transformMat4(out, [x, -y, 0], temp);
    }

    updateTarget() {
        // Screen space to world space
        this.screenToWorld(this.cursor.position, this.mouse.x / 4096, this.mouse.y / 4096);
        const scroll = this.mouse.resetScroll();
        this.target.scale *= (1 + scroll / 1000);
        this.target.scale = Math.min(Math.max(this.target.scale, 0.01), 10000);
    }

    // Smooth update camera
    lerpCamera(d = 1 / 60) {
        const l = Math.min(Math.max(d * this.state.zoom, 0), 1);
        vec3.lerp(this.camera.position, this.camera.position, this.target.position, d);
        this.camera.scale += (this.target.scale - this.camera.scale) * l;

        const x = this.camera.position[0];
        const y = this.camera.position[1];
        const hw = this.viewport.width  * this.camera.scale / 2;
        const hh = this.viewport.height * this.camera.scale / 2;

        const v = this.viewbox;
        mat4.ortho(this.proj, v.l = x - hw, v.r = x + hw, 
            v.b = y - hh, v.t = y + hh, 0, 1);
    }

    checkResolution() {
        const gl = this.gl;
        const r = this.state.resolution;
        if (gl.canvas.width != r[0] || gl.canvas.height != r[1]) {
            gl.canvas.width  = r[0];
            gl.canvas.height = r[1];
        }
    }

    /** 
     * @param {Uint16Array} indices_buffer
     * @param {Uint8Array} types_buffer
     */
    buildNameVertexBuffer(indices_buffer, types_buffer) {
        let write_offset = 0;
        
        const indices = indices_buffer;
        const types = types_buffer;
        
        const f32 = this.core.HEAPF32;

        const flags  = this.nameFlags;
        const widths = this.nameWidths;
        const name_buffer = this.nameBuffer;

        for (const i in this.playerData) {
            const p = this.playerData[i];
            if (!i || i > 250 || !p || !p.name) continue;
            const w = this.textures.get(p.name);
            if (!w || !w.tex) continue;
            widths[i] = w.dim[0] / w.dim[1];
        }

        for (let i = 0; i < indices.length; i++) {

            const type = types[i];
            const offset = (indices[i] * this.BYTES_PER_CELL_DATA) >> 2;

            if (!widths[type]) {
                flags[i] = 0;
                continue;
            };
            
            flags[i] = 1;

            const x = f32[offset + 4];
            const y = f32[offset + 5];
            const s = f32[offset + 6];

            const x1 = NAME_SCALE * widths[type] * s;
            const x0 = -x1;

            const y0 = (NAME_Y_OFFSET - NAME_SCALE) * s;
            const y1 = (NAME_Y_OFFSET + NAME_SCALE) * s;

            name_buffer[write_offset++] = x + x0;
            name_buffer[write_offset++] = y + y0;

            name_buffer[write_offset++] = x + x1;
            name_buffer[write_offset++] = y + y0;

            name_buffer[write_offset++] = x + x0;
            name_buffer[write_offset++] = y + y1;
            
            name_buffer[write_offset++] = x + x1;
            name_buffer[write_offset++] = y + y0;

            name_buffer[write_offset++] = x + x0;
            name_buffer[write_offset++] = y + y1;

            name_buffer[write_offset++] = x + x1;
            name_buffer[write_offset++] = y + y1;
        }

        return write_offset;
    }

    /** 
     * @param {Uint16Array} indices_buffer
     * @param {Uint8Array} types_buffer
     */
    buildMassVertexBuffer(indices_buffer, types_buffer) {
        let write_offset = 0;

        const indices = indices_buffer;
        const types = types_buffer;
        
        const f32 = this.core.HEAPF32;

        const counts = this.massCounts;
        const widths = this.massWidths;
        const mass_buffer = this.massBuffer;

        // mass = 1 is short, 2 is long
        const long_mass = this.state.mass === 2;

        for (let i = 0; i < indices.length; i++) {

            const offset = (indices[i] * this.BYTES_PER_CELL_DATA) >> 2;

            const type = types[i];

            if (!type || type > 250) {
                counts[i] = 0;
                continue;
            };

            const x = f32[offset + 4];
            const y = f32[offset + 5];
            const s = f32[offset + 6];
            const m = s * s * 0.01;

            const mass = long_mass ? Math.round(m).toString() : 
                m > 1000 ? (m / 1000).toFixed(1) + "k" : Math.round(m).toString();
            
            // Save the char length to count array
            counts[i] = mass.length;

            let width = (mass.length - 1) * MASS_GAP * MASS_SCALE;

            for (let j = 0; j < mass.length; j++) 
                width += MASS_SCALE * widths[mass.charCodeAt(j)];

            let w = -width / 2;
            for (let j = 0; j < mass.length; j++) {
                const char_code = mass.charCodeAt(j);
                // 46 is . 107 is k
                const char_uv_offset = char_code === 46 ? 10 : (char_code === 107 ? 11 : char_code - 48);
                const char_width = widths[char_code];

                const x0 = w * s;
                const x1 = x0 + MASS_SCALE * char_width * s;
                const y0 = (+0.5 * MASS_SCALE + MASS_Y_OFFSET) * s;
                const y1 = (-0.5 * MASS_SCALE + MASS_Y_OFFSET) * s;

                w += MASS_SCALE * char_width + MASS_GAP;

                mass_buffer[write_offset++] = x + x0;
                mass_buffer[write_offset++] = y + y0;
                mass_buffer[write_offset++] = (char_uv_offset << 2) + 0;

                mass_buffer[write_offset++] = x + x1;
                mass_buffer[write_offset++] = y + y0;
                mass_buffer[write_offset++] = (char_uv_offset << 2) + 1;

                mass_buffer[write_offset++] = x + x0;
                mass_buffer[write_offset++] = y + y1;
                mass_buffer[write_offset++] = (char_uv_offset << 2) + 2;
                
                mass_buffer[write_offset++] = x + x1;
                mass_buffer[write_offset++] = y + y0;
                mass_buffer[write_offset++] = (char_uv_offset << 2) + 1;

                mass_buffer[write_offset++] = x + x0;
                mass_buffer[write_offset++] = y + y1;
                mass_buffer[write_offset++] = (char_uv_offset << 2) + 2;

                mass_buffer[write_offset++] = x + x1;
                mass_buffer[write_offset++] = y + y1;
                mass_buffer[write_offset++] = (char_uv_offset << 2) + 3;
            }
        }

        return write_offset;
    }

    /** @param {number} now */
    render(now) {
        
        if (!this.lastTimestamp) {
            this.lastTimestamp = now;
            return;
        }

        this.fps++;
        const delta = now - this.lastTimestamp;
        this.lastTimestamp = now;

        this.protocol.replay.update(delta);

        const lerp = this.protocol.lastPacket ? (now - this.protocol.lastPacket) / this.state.draw : 0;

        const { t, b, l, r } = this.viewbox;

        const skip = !this.state.visible && !this.protocol.replay.requestPreview;

        let cell_count = 0;
        let pellet_count = 0;
        
        try {
            const number = this.wasm.update_cells(0, this.INDICES_OFFSET, this.PELLETS_OFFSET,
                lerp, t, b, l, r, skip);
            cell_count   = number >> 16;
            pellet_count = number & 0xFFFF;
        } catch (e) {
            console.error(e);
        }

        this.stats.cells = cell_count + pellet_count;
        
        this.updateTarget();
        this.camera.tp ? this.teleportCamera() : this.lerpCamera(delta / this.state.draw);
        this.checkResolution();

        if (skip) return this.updateTextures();

        const gl = this.gl;
        gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
        
        // Background
        this.clearScreen();
        // Map
        this.drawMap();
        // Draw pellets
        this.drawPellets(pellet_count);        
        // Draw the rest
        this.drawCells(cell_count);

        this.protocol.replay.savePreview();
        
        this.updateTextures();
        this.lastDraw = now;
    }

    drawMap() {
        const gl = this.gl;
        if (this.protocol.map) {
            gl.useProgram(this.brdr_prog);
            gl.bindVertexArray(this.borderVAO);
            gl.uniform2f(this.getUniform(this.brdr_prog, "u_map"), this.protocol.map.hw, this.protocol.map.hh);
            gl.uniformMatrix4fv(this.getUniform(this.brdr_prog, "u_proj"), false, this.proj);
            gl.drawArrays(gl.TRIANGLES, 0, 6);
        }
    }
    
    /** @param {number} cell_count */
    drawPellets(pellet_count) {
        if (!pellet_count) return;

        const gl = this.gl;
        
        let vert_ptr = this.PELLETS_OFFSET + (pellet_count << 1);
        while (vert_ptr & 3) vert_ptr++;

        const end = this.wasm.draw_pellets(0, this.PELLETS_OFFSET, pellet_count, vert_ptr);
        const expect = vert_ptr + pellet_count * 72;
        const data = new Float32Array(this.core.buffer, vert_ptr, (end - vert_ptr) >> 2);

        console.assert(end === expect, `Expecting pellet end pointer to be ${expect}, but got ${end}`);
        
        gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
        gl.useProgram(this.prtl_prog);
        gl.uniformMatrix4fv(this.getUniform(this.prtl_prog, "u_proj"), false, this.proj);

        gl.bindVertexArray(this.pelletVAO);
        gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers.get("pellet_buffer"));
        gl.bufferSubData(gl.ARRAY_BUFFER, 0, data);
        gl.activeTexture(gl.TEXTURE0);
        gl.bindTexture(gl.TEXTURE_2D, this.circleTextures[0]);
        gl.drawArrays(gl.TRIANGLES, 0, pellet_count * 6);
    }

    /** @param {number} cell_count */
    drawCells(cell_count) {
        if (!cell_count) return;

        const gl = this.gl;
        
        const indices = new Uint16Array(this.core.buffer, this.INDICES_OFFSET, cell_count);
        const types_ptr = this.INDICES_OFFSET + (cell_count << 1);
        const types = this.core.HEAPU8.subarray(types_ptr, types_ptr + cell_count);

        let vert_ptr = types_ptr + cell_count;
        while (vert_ptr & 3) vert_ptr++;

        const end = this.wasm.draw_cells(0, this.INDICES_OFFSET, cell_count, vert_ptr);
        const expect = vert_ptr + cell_count * 48;
        console.assert(end === expect, `Expecting cell end pointer to be ${expect}, but got ${end}`);

        let text_index = cell_count;

        // Configurable if we want to draw skin/name/mass
        const render_skin = this.state.skin;
        const render_name = this.state.name;
        const render_mass = this.state.mass;

        const circles = this.circleTextures;
        const dead = this.deadCellTexture;
        const name_flags = this.nameFlags;
        const mass_count = this.massCounts;

        const pid = this.protocol.pid;
        const border_tex = this.protocol.dualIDs ? this.dualTextures[this.protocol.dualIDs.indexOf(this.protocol.pid)] : null;

        name_flags.fill(0);
        mass_count.fill(0);
        
        if (render_name || render_mass) {
            
            const { l, r, t, b } = this.viewbox;
            const w = r - l;
            const h = t - b;
            const cutoff = (w < h ? w : h) * NAME_MASS_MIN;

            text_index = this.wasm.find_text_index(0, this.INDICES_OFFSET, cell_count, cutoff);

            const text_indices = indices.subarray(text_index, indices.length);
            const text_types = types.subarray(text_index, types.length);

            if (render_name) {
                const end = this.buildNameVertexBuffer(text_indices, text_types);
                
                gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers.get("name_buffer"));
                gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.nameBuffer.subarray(0, end));
            }
            
            if (render_mass) {
                const end = this.buildMassVertexBuffer(text_indices, text_types);
                
                gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers.get("mass_buffer"));
                gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.massBuffer.subarray(0, end));
            }
        }

        const L = circles.length;
        // 12 floats per cell
        const VB = new Float32Array(this.core.buffer, vert_ptr, 12 * cell_count);

        const skins = this.playerData.map(d => {
            if (!d) return null;
            const w = this.textures.get(d.skin);
            return w ? w.tex : null;
        });

        const names = render_name ? this.playerData.map(d => {
            if (!d) return null;
            const w = this.textures.get(d.name);
            return w ? w.tex : null;
        }) : [];

        gl.useProgram(this.mass_prog);
        gl.uniformMatrix4fv(this.getUniform(this.mass_prog, "u_proj"), false, this.proj);

        gl.useProgram(this.main_prog);
        gl.uniformMatrix4fv(this.getUniform(this.main_prog, "u_proj"), false, this.proj);

        gl.bindVertexArray(this.cellVAO);
        gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers.get("cell_buffer"));
        gl.bufferSubData(gl.ARRAY_BUFFER, 0, VB);

        gl.activeTexture(gl.TEXTURE0);

        let name_draw_offset = 0;
        let mass_draw_offset = 0;

        gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);

        for (let i = 0; i < cell_count; i++) {
            const t = types[i];

            if (t !== 253) {
                if (t === 251) {
                    gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
                    gl.bindTexture(gl.TEXTURE_2D, dead);
                    gl.drawArrays(gl.TRIANGLES, 6 * i, 6);
                    gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
                } else {
                    gl.bindTexture(gl.TEXTURE_2D, circles[t % L]);
                    gl.drawArrays(gl.TRIANGLES, 6 * i, 6);
                }
            }

            if (render_skin && skins[t] || t === 253) {
                gl.bindTexture(gl.TEXTURE_2D, skins[t]);
                gl.drawArrays(gl.TRIANGLES, 6 * i, 6);
            }
            
            if (t === pid && border_tex) {
                gl.bindTexture(gl.TEXTURE_2D, border_tex);
                gl.drawArrays(gl.TRIANGLES, 6 * i, 6);
            }

            const ti = i - text_index;

            if (ti >= 0) {
                if (name_flags[ti]) {
                    gl.bindVertexArray(this.nameVAO);
                    gl.bindTexture(gl.TEXTURE_2D, names[t]);
                    gl.drawArrays(gl.TRIANGLES, name_draw_offset, 6);
                    name_draw_offset += 6;
                }

                const mass_chars = mass_count[ti];
                if (mass_chars) {
                    gl.useProgram(this.mass_prog);
                    gl.bindVertexArray(this.massVAO);
                    const draw_count = 6 * mass_chars;
                    gl.drawArrays(gl.TRIANGLES, mass_draw_offset, draw_count);
                    mass_draw_offset += draw_count;
                    gl.useProgram(this.main_prog);
                }
                
                gl.bindVertexArray(this.cellVAO);
            }
        }
    }

    serializeState() {
        return this.core.buffer.slice(this.INDICES_OFFSET, 
            this.wasm.serialize_state(0, this.INDICES_OFFSET));
    }

    getClickedPlayerID() {
        return this.wasm.get_clicked_type(0, this.cursor.position[0], this.cursor.position[1]);
    }

    updateTextures() {
        const gl = this.gl;
        if (this.updates.length) {
            gl.activeTexture(gl.TEXTURE11);
            let limit = 2;
            while (this.updates.length && limit--) {
                const { id, skin: skin_bitmap, name: name_bitmap } = this.updates.pop();
                const p = this.playerData[id];
                if (p) {
                    this.textures.setData(p.skin, skin_bitmap);
                    this.textures.setData(p.name, name_bitmap);
                }
                if (++limit > 2) break;
            }
        }
    }

    clearScreen() {
        const gl = this.gl;
        gl.bindFramebuffer(gl.FRAMEBUFFER, null);
        gl.clearColor(0, 0, 0, 1);
        gl.clear(gl.COLOR_BUFFER_BIT);
    }
}

self.addEventListener("message", async e => {
    const { data } = e;
    const renderer = self.r = new Renderer(data.offscreen);
    renderer.stats.setBuffer(data.stats);
    renderer.mouse.setBuffer(data.mouse);
    renderer.state.setBuffer(data.state);
    renderer.viewport.setBuffer(data.viewport);
    await renderer.initEngine();
    renderer.generateDualTextures(...data.dual);

    self.addEventListener("message", e => {
        const p = renderer.protocol;
        if (e.data.connect && !p.connecting) {
            p.connect(e.data.connect, e.data.uid, e.data.name, e.data.skin1, e.data.skin2);
        }
        if (e.data.spawn) {
            if (p.connecting) {
                p.once("protocol", () => p.spawn(e.data.name, e.data.skin1, e.data.skin2));
            } else p.spawn(e.data.name, e.data.skin1, e.data.skin2);
        }
        if (e.data.chat) p.sendChat(e.data.chat);
        if (e.data.replay) p.startReplay(e.data.replay);
        if (e.data.dual) renderer.generateDualTextures(...e.data.dual);
    });

    self.postMessage({ event: "ready" });
}, { once: true });

module.exports = Renderer;

<|start_filename|>src/c/core.c<|end_filename|>
#include "memory.h"
#include <math.h>

typedef struct {
    float x;
    float y;
    float r;
    unsigned char type;
    unsigned char flags;
    unsigned short eatenBy;
    float age;
    float boostX;
    float boostY;
    float boost;
} Cell;

typedef struct {
    float x;
    float y;
    float hw;
    float hh;
    void* tl;
    void* tr;
    void* bl;
    void* br;
    unsigned short count;
    unsigned short indices; // placeholder
} QuadNode;

#define IS_PLAYER(type) type <= 250
#define NOT_PLAYER(type) type > 250
#define IS_DEAD(type) type == 251
#define IS_ALIVE(type) type != 251
// #define IS_MOTHER_CELL(type) type == 252
#define IS_VIRUS(type) type == 253
#define IS_PELLET(type) type == 254
#define NOT_PELLET(type) type != 254
#define IS_EJECTED(type) type == 255

#define EXIST_BIT 0x1
#define UPDATE_BIT 0x2
#define INSIDE_BIT 0x4
#define LOCK_BIT 0x8
#define AUTOSPLIT_BIT 0x10
#define REMOVE_BIT 0x20
#define MERGE_BIT 0x40
#define POP_BIT 0x80

#define CLEAR_BITS 0x11

extern float get_score(unsigned char id);
extern void unlock_line(unsigned char id);

extern void remove_cell(unsigned short id, unsigned char type, 
    unsigned short eatenBy, unsigned char eatenByType);
extern void split_virus(float x, float y, float boostX, float boostY);
extern void pop_player(unsigned short id, unsigned char type, float mass);
extern void tree_update(unsigned short id);

size_t bytes_per_cell() { return sizeof(Cell); }

#define UPDATE_BITS 0x12
unsigned char get_cell_updated(Cell ptr[], unsigned short id) { 
    return IS_PLAYER(ptr[id].type) || (ptr[id].flags & UPDATE_BITS); 
};

float get_cell_x(Cell ptr[], unsigned short id) { return ptr[id].x; };
float get_cell_y(Cell ptr[], unsigned short id) { return ptr[id].y; };
unsigned short get_cell_r(Cell ptr[], unsigned short id) { return ptr[id].r; };
unsigned char  get_cell_type(Cell ptr[], unsigned short id) { return ptr[id].type; };
unsigned short get_cell_eatenby(Cell ptr[], unsigned short id) { return ptr[id].eatenBy; };

unsigned short new_cell(Cell cells[], unsigned short next_id, float x, float y, float size, unsigned char type, 
    float boost_x, float boost_y, float boost) {
    
    while (!next_id || (cells[next_id].flags & EXIST_BIT)) next_id++;

    Cell* cell = &cells[next_id];

    cell->x = x;
    cell->y = y;
    cell->r = size;
    cell->type = type;
    cell->boostX = boost_x;
    cell->boostY = boost_y;
    cell->boost = boost;
    cell->flags = EXIST_BIT;

    return next_id;
}

unsigned short kill_cell(Cell cells[], unsigned short id, unsigned short next_id) {
    
    while (!next_id || (cells[next_id].flags & EXIST_BIT)) next_id++;

    Cell* old_cell = &cells[id];
    Cell* new_cell = &cells[next_id];

    memcpy(new_cell, old_cell, sizeof(Cell));
    memset(old_cell, 0, sizeof(Cell));

    new_cell->type = 251;
    new_cell->flags = EXIST_BIT;
    new_cell->age = 0.0f;
    
    return next_id;
}

void update(Cell cells[], unsigned short* ptr, float dt,
    unsigned int eject_max_age,
    float auto_size, float decay_min, float static_decay, float dynamic_decay,
    float l, float r, float b, float t) {

    static_decay *= 0.01f;
    Cell* cell = &cells[*ptr];

    // Clear cell data
    while (cell->flags & REMOVE_BIT) {
        memset(cell, 0, sizeof(Cell));
        cell = &cells[*++ptr]; // increment to next index
    }

    if (!*ptr) return;

    unsigned char curr_type = 0;
    float curr_multi = 1.0f;

    // Player cells
    while (*ptr) {
        // Increment age, clear bits
        cell->age += dt;
        cell->flags &= CLEAR_BITS;

        if (IS_EJECTED(cell->type) && eject_max_age && cell->age > eject_max_age)
            cell->flags |= REMOVE_BIT;

        // Boost cell
        if (cell->boost > 1.0f) {
            float db = cell->boost * 0.0025f * dt;
            cell->x += cell->boostX * db;
            cell->y += cell->boostY * db;
            if (NOT_PLAYER(cell->type))
                cell->flags |= UPDATE_BIT;
            cell->boost -= db;
        } else {
            cell->boost = 1.0f;
        }

        if (IS_PLAYER(cell->type)) {

            if (curr_type != cell->type) {
                curr_type = cell->type;
                float score = get_score(curr_type);
                curr_multi = (score - 0.01f * decay_min * decay_min) * 0.00005f * dynamic_decay;
                if (curr_multi < 1.f) curr_multi = 1.f;
            }

            // Decay and set the autosplit bit for player cells
            if (cell->r > decay_min) {
                cell->r -= curr_multi * cell->r * static_decay * dt * 0.0001f;
            }

            if (auto_size && cell->r > auto_size && !(cell->flags & AUTOSPLIT_BIT)) {
                cell->flags |= AUTOSPLIT_BIT;
                cell->age = 0.0f;
            }
        }

        // Bounce and clamp the cells in the box
        unsigned char bounce = cell->boost > 1;
        float cr = cell->r;
        if (cell->x < l + cr) {
            cell->x = l + cr;
            cell->flags |= UPDATE_BIT;
            if (bounce) cell->boostX = -cell->boostX;
        } else if (cell->x > r - cr) {
            cell->x = r - cr;
            cell->flags |= UPDATE_BIT;
            if (bounce) cell->boostX = -cell->boostX;
        }
        if (cell->y > t - cr) {
            cell->y = t - cr;
            cell->flags |= UPDATE_BIT;
            if (bounce) cell->boostY = -cell->boostY;
        } else if (cell->y < b + cr) {
            cell->y = b + cr;
            cell->flags |= UPDATE_BIT;
            if (bounce) cell->boostY = -cell->boostY;
        }
        
        cell = &cells[*++ptr]; // increment to next index
    }
}

void update_player_cells(Cell cells[], unsigned short* indices, unsigned int n,
    float mouse_x, float mouse_y, 
    unsigned char lock_dir, float a, float b, float c, 
    float dt,
    float merge_initial, float merge_increase, float player_speed, float normalizer,
    float merge_time,    float no_merge_delay, unsigned char merge_version_new) {
    
    if (!n) return;

    if (merge_time > 0.0f) {
        if (merge_version_new) {
            for (unsigned int i = 0; i < n; i++) {
                Cell* cell = &cells[indices[i]];
                float increase = 25.f * cell->r * merge_increase;
                float time = increase > merge_initial ? increase : merge_initial;
                if (cell->age > normalizer * time) cell->flags |= MERGE_BIT;
            }
        } else {
            for (unsigned int i = 0; i < n; i++) {
                Cell* cell = &cells[indices[i]];
                float increase = 25.f * cell->r * merge_increase;
                float time = merge_initial + merge_increase;
                if (cell->age > normalizer * time) cell->flags |= MERGE_BIT;
            }
        }
    } else {
        for (unsigned int i = 0; i < n; i++) {
            Cell* cell = &cells[indices[i]];
            if (cell->age > no_merge_delay) cell->flags |= MERGE_BIT;
        }
    }

    // Move player cells
    if (lock_dir) {
        unsigned char all_flags = 0;
        
        float line_a_b_sqr_sum_inv = 1.f / (a * a + b * b);

        for (unsigned int i = 0; i < n; i++) {
            Cell* cell = &cells[indices[i]];
            all_flags |= cell->flags;
            cell->flags |= LOCK_BIT;

            float dx = mouse_x - cell->x;
            float dy = mouse_y - cell->y;
            float d = sqrtf(dx * dx + dy * dy);
            if (d < 1) continue; dx /= d; dy /= d;
            float speed = 1.76f * powf(cell->r, -0.4396754) * player_speed;
            float m = (speed < d ? speed : d) * dt;
            cell->x += dx * m;
            cell->y += dy * m;
            
            // Project point back to line
            // cell->x = (b * ( b * x - a * y) - a * c) * line_a_b_sqr_sum_inv;
            // cell->y = (a * (-b * x + a * y) - b * c) * line_a_b_sqr_sum_inv;
        }

        // This bit is used for checking wall
        if (all_flags & UPDATE_BIT) {
            // IF ANY CELL TOUCHES THE WALL
            unlock_line(cells[indices[0]].type);
        }
    } else {
        for (unsigned int i = 0; i < n; i++) {
            Cell* cell = &cells[indices[i]];

            float dx = mouse_x - cell->x;
            float dy = mouse_y - cell->y;
            float d = sqrtf(dx * dx + dy * dy);
            if (d < 1) continue; dx /= d; dy /= d;
            float speed = 1.76f * powf(cell->r, -0.4396754) * player_speed;
            float m = (speed < d ? speed : d) * dt;
            cell->x += dx * m;
            cell->y += dy * m;
        }
    }
}

int is_safe(Cell* cells, float x, float y, float r, QuadNode* root, QuadNode** sp, unsigned char ignoreType) {
    
    QuadNode** node_stack_pointer = sp;
    *node_stack_pointer++ = root;

    QuadNode* curr;

    int counter = 0;
    float dx;
    float dy;

    while (node_stack_pointer > sp) {
        // Pop from the stack
        curr = (QuadNode*) *--node_stack_pointer;

        if (curr->tl) {
            if (y - r < curr->y) {
                if (x + r > curr->x)
                    *node_stack_pointer++ = curr->br;
                if (x - r < curr->x)
                    *node_stack_pointer++ = curr->bl;
            }
            if (y + r > curr->y) {
                if (x + r > curr->x)
                    *node_stack_pointer++ = curr->tr;
                if (x - r < curr->x)
                    *node_stack_pointer++ = curr->tl;
            }
        }
        
        for (unsigned int i = 0; i < curr->count; i++) {
            Cell* cell = &cells[*(&curr->indices + i)];
            if (cell->type > ignoreType) continue;
            dx = cell->x - x;
            dy = cell->y - y;
            counter++;
            if (dx * dx + dy * dy < (r + cell->r) * (r + cell->r)) return -counter;
        }
    }
    return counter;
}

void sort_indices(Cell cells[], unsigned short indices[], int n) {
    if (!n) return;
    
    int t = 0;

    // Build Max Heap
    for (int i = 1; i < n; i++) { 
        // if child is bigger than parent 
        if (cells[indices[i]].boost < cells[indices[(i - 1) / 2]].boost ||
            cells[indices[i]].r  < cells[indices[(i - 1) / 2]].r) {
            int j = i;
            // swap child and parent until 
            // parent is smaller 
            while (cells[indices[j]].boost < cells[indices[(j - 1) / 2]].boost || 
                   cells[indices[j]].r < cells[indices[(j - 1) / 2]].r) { 
                t = indices[j];
                indices[j] = indices[(j - 1) / 2];
                indices[(j - 1) / 2] = t;
                j = (j - 1) / 2; 
            }
        }
    }

    for (int i = n - 1; i > 0; i--) {
        // swap value of first indexed  
        // with last indexed  
        t = indices[0];
        indices[0] = indices[i];
        indices[i] = t;
        // maintaining heap property 
        // after each swapping 
        int j = 0, index; 
        do { 
            index = (2 * j + 1); 
              
            // if left child is smaller than  
            // right child point index variable  
            // to right child 
            if ((cells[indices[index]].boost > cells[indices[index + 1]].boost || 
                 cells[indices[index]].r > cells[indices[index + 1]].r) && index < (i - 1)) index++; 
          
            // if parent is smaller than child  
            // then swapping parent with child  
            // having higher value 
            if ((cells[indices[j]].boost > cells[indices[index]].boost ||
                 cells[indices[j]].r > cells[indices[index]].r) && index < i) {
                    
                t = indices[j];
                indices[j] = indices[index];
                indices[index] = t;
            }
            j = index; 
        } while (index < i); 
    }
}

#define PHYSICS_NON 0
#define PHYSICS_EAT 1
#define PHYSICS_COL 2

#define SKIP_RESOLVE_BITS 0xa4

extern float get_line_a(unsigned char id);
extern float get_line_b(unsigned char id);
extern float get_line_c(unsigned char id);

extern void console_log(unsigned short id);

unsigned int resolve(Cell cells[],
    unsigned short* ptr, unsigned short pellet_count,
    QuadNode* root, QuadNode** sp,
    unsigned int no_merge_delay, unsigned int no_colli_delay, 
    float eat_overlap, float eat_multi, 
    float virus_boost, float virus_max_boost,
    float virus_size, float virus_max_size, unsigned int remove_tick) {

    unsigned int collisions = 0;
    unsigned short* ptr_copy = ptr;

    while (*ptr) {

        Cell* cell = &cells[*ptr++];

        unsigned char flags = cell->flags;
        unsigned char type = cell->type;

        // Cell is to be removed, popped, or inside another cell
        if (flags & SKIP_RESOLVE_BITS) continue;

        if (IS_PELLET(type)) {
            ptr += pellet_count;
            continue;
        }

        if (IS_EJECTED(type) && !(flags & UPDATE_BIT)) continue; 

        if (IS_DEAD(type)) {
            if (cell->age > remove_tick) {
                cell->flags |= REMOVE_BIT;
                cell->eatenBy = 0;
                continue;
            }
        }

        QuadNode** node_stack_pointer = sp;
        *node_stack_pointer++ = root;

        QuadNode* curr;

        unsigned char colli = cell->age > no_colli_delay;
        float x = cell->x;
        float y = cell->y;
        float r1 = cell->r;
        float a = r1 * r1;

        float cell_l = cell->x - r1;
        float cell_r = cell->x + r1;
        float cell_t = cell->y + r1;
        float cell_b = cell->y - r1;

        while (node_stack_pointer > sp) {
            // Pop from the stack
            curr = (QuadNode*) *--node_stack_pointer;

            // Has leaves, push leaves, if they intersect, to stack
            if (curr->tl) {
                if (cell_b < curr->y) {
                    if (cell_r > curr->x)
                        *node_stack_pointer++ = curr->br;
                    if (cell_l < curr->x)
                        *node_stack_pointer++ = curr->bl;
                }
                if (cell_t > curr->y) {
                    if (cell_r > curr->x)
                        *node_stack_pointer++ = curr->tr;
                    if (cell_l < curr->x)
                        *node_stack_pointer++ = curr->tl;
                }
            }

            unsigned short* iter = &curr->indices;
            unsigned short* end = iter + curr->count;

            while(iter < end) {
                unsigned short other_index = *iter++;
                Cell* other = &cells[other_index];
                
                if (cell == other) continue; // Same cell
                if (r1 < other->r) continue; // Skip double check
                
                unsigned char other_flags = other->flags;

                // Other cell can be skipped
                if (other_flags & SKIP_RESOLVE_BITS) continue;

                unsigned char action = PHYSICS_NON;

                // Check player x player
                if (IS_PLAYER(type)) {
                    if (type == other->type) { // same player
                        if (flags & other_flags & MERGE_BIT) // Both merge bits are set
                            action = PHYSICS_EAT; // player merge
                        else if (colli && other->age > no_colli_delay) action = PHYSICS_COL; // player collide
                    } else action = PHYSICS_EAT; // player eats everything else
                } else if (IS_VIRUS(type) && IS_EJECTED(other->type)) {
                    // Virus can only eat ejected cell
                    action = PHYSICS_EAT;
                } else if (IS_EJECTED(type) && IS_EJECTED(other->type)) {
                    // Ejected only collide with ejected cell
                    action = PHYSICS_COL;
                } else if (IS_DEAD(type)) {
                    // Dead cell can only collide with others
                    if (IS_DEAD(other->type)) action = PHYSICS_COL;
                }
                // else if (IS_MOTHER_CELL(type)) {
                //     // Mother cell eats everything?
                //     action = PHYSICS_EAT;
                // }

                if (action == PHYSICS_NON) continue;

                float dx = other->x - x;
                float dy = other->y - y;
                float r2 = other->r;

                float r_sum = r1 + r2;
                float d_sqr = dx * dx + dy * dy;
                
                // Does not overlap
                if (d_sqr >= r_sum * r_sum) continue;
                
                float d = sqrtf(d_sqr);

                collisions++;

                if (action == PHYSICS_COL) {
                    float m = r_sum - d;

                    if (d <= 0.f) {
                        continue;
                    } else {
                        dx /= d; 
                        dy /= d;
                    }
                    
                    // Other cell is inside this cell, mark it
                    other->flags |= (d + r2 < r1) << 2; 
                    // 1 << 2 = 0x4 which is INSIDE_BIT, but we are doing branchless here

                    float b = r2 * r2;
                    float sum = a + b;

                    float aM = b / sum;
                    float bM = a / sum;

                    float m1 = (m < r1 ? m : r1) * aM;
                    x -= dx * m1; // * 0.8f;
                    y -= dy * m1; // * 0.8f;

                    float m2 = (m < r2 ? m : r2) * bM;
                    other->x += dx * m2; // * 0.8f;
                    other->y += dy * m2; // * 0.8f;

                    // Mark the cell as updated
                    cell->flags |= UPDATE_BIT;
                    other->flags |= UPDATE_BIT;

                } else if (action == PHYSICS_EAT) {
                    if ((type == other->type || 
                        r1 > other->r * eat_multi) && 
                        d < r1 - other->r / eat_overlap) {

                        a = r1 * r1 + r2 * r2;
                        r1 = sqrtf(a);

                        if (IS_VIRUS(other->type)) { // || IS_MOTHER_CELL(other->type)) {
                            other->eatenBy = 0;
                        } else {
                            other->eatenBy = cell - cells;
                        }

                        // Mark the cells as updated/removed
                        cell->flags |= UPDATE_BIT;
                        other->flags |= REMOVE_BIT;
                        if (NOT_PLAYER(type)) cell->flags |= AUTOSPLIT_BIT;

                        if (IS_PLAYER(type) && IS_EJECTED(other->type)) {
                            float ratio = other->r / (r1 + 100.f);
                            cell->boost += ratio * 0.025f * other->boost;
                            float bx = cell->boostX + ratio * 0.02f * other->boostX;
                            float by = cell->boostY + ratio * 0.02f * other->boostY;
                            float norm = sqrt(bx * bx + by * by);
                            cell->boostX = bx / norm;
                            cell->boostY = by / norm;
                        }
                        if (IS_VIRUS(other->type)) // || IS_MOTHER_CELL(other->type))
                            cell->flags |= POP_BIT; // Mark this cell as popped
                        if (IS_VIRUS(type) && IS_EJECTED(other->type)) {
                            if (virus_max_size && r1 >= virus_max_size) {
                                cell->flags |= POP_BIT; // Mark this as virus to be split
                                cell->boostX = other->boostX;
                                cell->boostY = other->boostY;
                            }
                            if (virus_boost) {
                                float newBoost = cell->boost + virus_boost;
                                newBoost = newBoost > virus_max_boost ? virus_max_boost : newBoost;
                                cell->boostX = cell->boostX * cell->boost + other->boostX * virus_boost;
                                cell->boostY = cell->boostY * cell->boost + other->boostY * virus_boost;
                                float norm = sqrtf(cell->boostX * cell->boostX + cell->boostY * cell->boostY);
                                cell->boostX /= norm;
                                cell->boostY /= norm;
                                cell->boost = newBoost;
                            }
                        }
                    }
                }
            }
        }

        cell->r = r1;
        cell->x = x;
        cell->y = y;
    }
    
    unsigned char lock_type = 0;
    float line_a;
    float line_b;
    float line_c;
    float line_a_b_sqr_sum_inv;
    
    // Post resolve
    while (1) {
        unsigned short id = *ptr_copy++;
        if (!id) break;

        Cell* cell = &cells[id];
        unsigned char type = cell->type;
        unsigned char flags = cell->flags;

        if (flags & REMOVE_BIT) {
            remove_cell(id, type, cell->eatenBy, cells[cell->eatenBy].type);
            continue;
        } else if (flags & POP_BIT) {
            if (IS_VIRUS(type)) {
                cell->r = virus_size;
                split_virus(cell->x, cell->y, cell->boostX, cell->boostY);
            } else {
                pop_player(id, type, cell->r * cell->r * 0.01f);
            }
            continue;
        } else tree_update(id);

        if (flags & LOCK_BIT) {
            if (lock_type != type) {
                line_a = get_line_a(type);
                line_b = get_line_b(type);
                line_c = get_line_c(type);
                line_a_b_sqr_sum_inv = 1.f / (line_a * line_a + line_b * line_b);
                lock_type = type;
            }
            float x0 = cell->x;
            float y0 = cell->y;
            cell->x = (line_b * (line_b * x0 - line_a * y0) - line_a * line_c) * line_a_b_sqr_sum_inv;
            cell->y = (line_a * (-line_b * x0 + line_a * y0) - line_b * line_c) * line_a_b_sqr_sum_inv;
        }
    }

    return collisions;
}

unsigned int select(Cell cells[], QuadNode* root, 
    QuadNode** sp, unsigned short* list_pointer, 
    float l, float r, float b, float t) {
    
    unsigned short* write_pointer = list_pointer;

    QuadNode** node_stack_pointer = sp;
    // Push root to stack
    *node_stack_pointer++ = root;
    // Current node
    QuadNode* curr;

    while (node_stack_pointer > sp) {
        // Pop from the stack
        curr = *--node_stack_pointer;

        if (l < curr->x - curr->hw &&
            r > curr->x + curr->hw &&
            b < curr->y - curr->hh &&
            t > curr->y + curr->hh) {

            // Temp stack pointer to save where we started inclusive check
            QuadNode** temp_stack_pointer = node_stack_pointer;
            *node_stack_pointer++ = curr;

            QuadNode* curr_inclusive;
            
            while (node_stack_pointer > temp_stack_pointer) {
                // Pop from the stack
                curr_inclusive = *--node_stack_pointer;
                // Has leaves, push leaves without checking if they intersect
                if (curr_inclusive->tl) {
                    *node_stack_pointer++ = curr_inclusive->br;
                    *node_stack_pointer++ = curr_inclusive->bl;
                    *node_stack_pointer++ = curr_inclusive->tr;
                    *node_stack_pointer++ = curr_inclusive->tl;
                }

                // Copy count * 2 bytes of indices data directly to write pointer
                memcpy(write_pointer, &curr_inclusive->indices, curr_inclusive->count << 1);
                write_pointer += curr_inclusive->count;
            }
        } else {
            // Has leaves, push leaves, if they intersect, to stack
            if (curr->tl) {
                if (b < curr->y) {
                    if (r > curr->x)
                        *node_stack_pointer++ = curr->br;
                    if (l < curr->x)
                        *node_stack_pointer++ = curr->bl;
                }
                if (t > curr->y) {
                    if (r > curr->x)
                        *node_stack_pointer++ = curr->tr;
                    if (l < curr->x)
                        *node_stack_pointer++ = curr->tl;
                }
            }

            for (unsigned int i = 0; i < curr->count; i++) {
                unsigned short id = *(&curr->indices + i);
                Cell* cell = &cells[id];
                if (cell->x - cell->r <= r &&
                    cell->x + cell->r >= l &&
                    cell->y - cell->r <= t &&
                    cell->y + cell->r >= b &&
                    (NOT_PELLET(cell->type) || cell->age > 1)) {
                    *write_pointer++ = id;
                }
            }
        }
    }

    return write_pointer - list_pointer;
}

<|start_filename|>webgl/wasm/heap.c<|end_filename|>
typedef struct {
    float x;
    float y;
    float size;
    float skin;
    float r;
    float g;
    float b;
} Cell;

/*
#define SWAP(idx1, idx2, attr) t = arr[idx1].attr; arr[idx1].attr = arr[idx2].attr; arr[idx2].attr = t
#define SWAP_ALL(idx1, idx2) SWAP(idx1, idx2, x); SWAP(idx1, idx2, y); SWAP(idx1, idx2, size); SWAP(idx1, idx2, skin); SWAP(idx1, idx2, r); SWAP(idx1, idx2, g); SWAP(idx1, idx2, b);

void sort(Cell arr[], int n) {
    
    float t = 0;
    // Build Max Heap
    for (int i = 1; i < n; i++) { 
        // if child is bigger than parent 
        if (arr[i].size > arr[(i - 1) / 2].size) { 
            int j = i; 
      
            // swap child and parent until 
            // parent is smaller 
            while (arr[j].size > arr[(j - 1) / 2].size) { 
                SWAP_ALL(j, (j - 1) / 2); 
                j = (j - 1) / 2; 
            } 
        } 
    }

    for (int i = n - 1; i > 0; i--) { 
        // swap value of first indexed  
        // with last indexed  
        SWAP_ALL(0, i); 
        // maintaining heap property 
        // after each swapping 
        int j = 0, index; 
        do { 
            index = (2 * j + 1); 
              
            // if left child is smaller than  
            // right child point index variable  
            // to right child 
            if (arr[index].size < arr[index + 1].size && 
                                index < (i - 1)) index++; 
          
            // if parent is smaller than child  
            // then swapping parent with child  
            // having higher value 
            if (arr[j].size < arr[index].size && index < i) {
                SWAP_ALL(j, index);
            }
            j = index; 
        } while (index < i);
    }
} */

void sort(Cell cells[], Cell dist[], int indices[], int n) {

    int t = 0;

    for (int i = 0; i < n; i++)
        indices[i] = i;

    // Build Max Heap
    for (int i = 1; i < n; i++) { 
        // if child is bigger than parent 
        if (cells[indices[i]].size > cells[indices[(i - 1) / 2]].size) {
            int j = i;
            // swap child and parent until 
            // parent is smaller 
            while (cells[indices[j]].size > cells[indices[(j - 1) / 2]].size) { 
                t = indices[j];
                indices[j] = indices[(j - 1) / 2];
                indices[(j - 1) / 2] = t;
                j = (j - 1) / 2; 
            }
        }
    }

    for (int i = n - 1; i > 0; i--) {
        // swap value of first indexed  
        // with last indexed  
        t = indices[0];
        indices[0] = indices[i];
        indices[i] = t;
        // maintaining heap property 
        // after each swapping 
        int j = 0, index; 
        do { 
            index = (2 * j + 1); 
              
            // if left child is smaller than  
            // right child point index variable  
            // to right child 
            if (cells[indices[index]].size < cells[indices[index + 1]].size && 
                index < (i - 1)) index++; 
          
            // if parent is smaller than child  
            // then swapping parent with child  
            // having higher value 
            if (cells[indices[j]].size < cells[indices[index]].size && index < i) {
                t = indices[j];
                indices[j] = indices[index];
                indices[index] = t;
            }
            j = index; 
        } while (index < i); 
    }

    for (int i = 0; i < n; i++)
        dist[i] = cells[indices[i]];
}

<|start_filename|>src/modes/default/crazy.js<|end_filename|>
/** @type {typeof import("../../physics/engine").DefaultSettings} */
module.exports = {
    VIRUS_COUNT: 100,
    PLAYER_MAX_CELLS: 200,
    PLAYER_MERGE_NEW_VER: true,
    // PLAYER_AUTOSPLIT_SIZE: 0,
    PLAYER_MERGE_TIME: 5,
    VIRUS_MONOTONE_POP: true,
    EJECT_SIZE: 38,
    EJECT_LOSS: 41,
    EJECT_DELAY: 80,
    PELLET_COUNT: 1000,
    PLAYER_SPAWN_SIZE: 1500,
    MAP_HW: 32767 >> 2, // MAX signed short
    MAP_HH: 32767 >> 2, // MAX signed short,
};

<|start_filename|>src/c/ogarx.c<|end_filename|>
#include "limits.h"
#include "memory.h"

// Memory layout
// |64kb last visible hash table|64kb visible hash table
// |list of last visible cell indices (unsigned short)|list of visible cell indices (unsigned short)
// | dynamic buffer (A/U/E/D buffer + serialized buffer)

// Protocol onUpdate:
// 1. Copy current visible hash table into last table, fill current one with 0 (move_hashtable)
// 2. Copy current visible list into last list
// 3. Write indices of A/U/E/D cells (Add/Update/Eat/Delete)
// 4. Build final buffer (serialize)

#define PELLET_TYPE 254
#define TABLE_SIZE 65536

extern unsigned char get_cell_updated(void* ptr, unsigned short id);
extern float get_cell_x(void* ptr, unsigned short id);
extern float get_cell_y(void* ptr, unsigned short id);
extern unsigned short get_cell_r(void* ptr, unsigned short id);
extern unsigned short get_cell_eatenby(void* ptr, unsigned short id);
extern unsigned char  get_cell_type(void* ptr, unsigned short id);

// Step 1
void move_hashtable() {
    memcpy((void *) 0, (void *) TABLE_SIZE, TABLE_SIZE);
    memset((void *) TABLE_SIZE, 0, TABLE_SIZE);
}

// Step 2 (from high to low, it actually copies per byte, so overlapping is fine)
void copy(void* dist, void* src, size_t bytes) {
    memcpy(dist, src, bytes);
}

// Step 3 write AUED indices
void* write_AUED(
    unsigned char last_visible_table[], unsigned char curr_visible_table[],
    unsigned short last_visible_list[], unsigned int last_visible_list_length,
    unsigned short curr_visible_list[], unsigned int curr_visible_list_length,
    unsigned int count_table[], unsigned short dist[]) {

    // Write current visible cells to the hash table
    for (unsigned int i = 0; i < curr_visible_list_length; i++)
        curr_visible_table[curr_visible_list[i]] = 1;

    unsigned short* A_ptr = dist + 0;
    unsigned short* U_ptr = dist + 1;
    unsigned short* E_ptr = dist + 2;
    unsigned short* D_ptr = dist + 3;

    // The algorithm is the same as original ogar protocol
    for (unsigned int i = 0; i < curr_visible_list_length; i++) {
        unsigned short cell_id = curr_visible_list[i];
        if (last_visible_table[cell_id]) {
            if (get_cell_updated(0, cell_id)) {
                *U_ptr = cell_id;
                U_ptr += 4;
            }
        } else {
            *A_ptr = cell_id;
            A_ptr += 4;
        }
    }

    for (unsigned int i = 0; i < last_visible_list_length; i++) {
        unsigned short cell_id = last_visible_list[i];
        if (curr_visible_table[cell_id]) continue;
        unsigned short eatenby = get_cell_eatenby(0, cell_id);
        if (eatenby) {
            *E_ptr = cell_id;
            E_ptr += 4;
        } else {
            *D_ptr = cell_id;
            D_ptr += 4;
        }
    }

    // Save the write count to the table
    count_table[0] = (A_ptr - (dist + 0)) >> 2;
    count_table[1] = (U_ptr - (dist + 1)) >> 2;
    count_table[2] = (E_ptr - (dist + 2)) >> 2;
    count_table[3] = (D_ptr - (dist + 3)) >> 2;

    unsigned short* end = A_ptr;
    end = U_ptr > end ? U_ptr : end;
    end = E_ptr > end ? E_ptr : end;
    end = D_ptr > end ? D_ptr : end;

    return end;
}

// Marco god is flexing on you
#define writeUint8(v) *((unsigned char*) dist) = v; dist++
#define writeUint16(v) *((unsigned short*) dist) = v; dist += 2
#define writeInt16(v) *((short*) dist) = v; dist += 2
#define writeFloat32(v) *((float *) dist) = v; dist += 4

#define CLAMP(v, min, max) v < min ? min : v > max ? max : v

// Step 4
unsigned char* serialize(
    unsigned char pid,
    unsigned short cell_count,
    unsigned char line_lock,
    float score,
    float mx, float my,
    float vx, float vy,
    unsigned int table[],
    unsigned short* lists, unsigned char* dist,
    float l, float r, float t, float b) {

    // Write OP code
    writeUint8(4);
    // Write PID
    writeUint8(pid);
    // Write cell count and line lock
    writeUint16(cell_count);
    writeUint8(line_lock);
    writeFloat32(score);
    // Write mouse coordinates
    writeFloat32(mx);
    writeFloat32(my);
    // Write viewport floats
    writeFloat32(vx);
    writeFloat32(vy);

    // Initialize read pointers for AUED
    unsigned int A_count = table[0];
    unsigned int U_count = table[1];
    unsigned int E_count = table[2];
    unsigned int D_count = table[3];

    unsigned short* A_ptr = lists + 0;
    unsigned short* U_ptr = lists + 1;
    unsigned short* E_ptr = lists + 2;
    unsigned short* D_ptr = lists + 3;
    
    // Exact same serialization
    while (A_count--) {
        unsigned short cell_id = *A_ptr;

        writeUint16(cell_id);
        writeUint16(get_cell_type(0, cell_id));
        float radius = get_cell_r(0, cell_id);

        float x_min = l + radius;
        float x_max = r - radius;
        float y_min = b + radius;
        float y_max = t - radius;

        float x = get_cell_x(0, cell_id);
        writeInt16(CLAMP(x, x_min, x_max));
        float y = get_cell_y(0, cell_id);
        writeInt16(CLAMP(y, y_min, y_max));
        writeUint16(radius);

        A_ptr += 4;
    }

    writeUint16(0);

    while (U_count--) {
        unsigned short cell_id = *U_ptr;

        writeUint16(cell_id);
        float radius = get_cell_r(0, cell_id);

        float x_min = l + radius;
        float x_max = r - radius;
        float y_min = b + radius;
        float y_max = t - radius;

        float x = get_cell_x(0, cell_id);
        writeInt16(CLAMP(x, x_min, x_max));
        float y = get_cell_y(0, cell_id);
        writeInt16(CLAMP(y, y_min, y_max));
        writeUint16(radius);

        U_ptr += 4;
    }

    writeUint16(0);
    
    while (E_count--) {
        unsigned short cell_id = *E_ptr;

        writeUint16(cell_id);
        writeUint16(get_cell_eatenby(0, cell_id));

        E_ptr += 4;
    }

    writeUint16(0);

    while (D_count--) {
        unsigned short cell_id = *D_ptr;

        writeUint16(cell_id);

        D_ptr += 4;
    }

    writeUint16(0);

    return dist; // Return final pointer so js knows how to slice the buffer
}

void clean(void* ptr, size_t bytes) {
    memset(ptr, 0, bytes);
}


<|start_filename|>src/modes/default/instant.js<|end_filename|>
/** @type {typeof import("../../physics/engine").DefaultSettings} */
module.exports = {
    VIRUS_COUNT: 20,
    PLAYER_MAX_CELLS: 64,
    PLAYER_NO_MERGE_DELAY: 22,
    PLAYER_NO_COLLI_DELAY: 12,
    PLAYER_MERGE_NEW_VER: false,
    // PLAYER_AUTOSPLIT_SIZE: 0,
    PLAYER_SPLIT_CAP: 4,
    PLAYER_MERGE_TIME: 0,
    // VIRUS_MONOTONE_POP: true,
    EJECT_SIZE: 38,
    EJECT_LOSS: 41,
    EJECT_DELAY: 80,
    PELLET_COUNT: 1000,
    PLAYER_SPAWN_SIZE: 1500,
    MAP_HW: 32767 >> 2, // MAX signed short
    MAP_HH: 32767 >> 2, // MAX signed short,
};

<|start_filename|>webgl/game/options.js<|end_filename|>
const DEFAULT = {
    zoom: 3,
    skin: 1,
    name: 1,
    mass: 1,
    draw: 120,
    quality: 0,
    auto_respawn: 0,
    skin_quality: 1,
    text_quality: 1,
    circle_quality: 1,
    ignore_skin: 0,
    mouse_sync: 0
};
const OPTION_KEYS = Object.keys(DEFAULT);

const MultiChoice = {
    skin: ["Disabled", "Enabled"],
    name: ["Disabled", "Enabled"],
    mass: ["Disabled", "Short", "Long"],
    quality: ["1080p", "720p", "480p"],
    auto_respawn: ["Disabled", "Enabled"],
    skin_quality: ["High", "Medium", "Low", "Laptop"],
    text_quality: ["High", "Medium", "Low", "Laptop"],
    circle_quality: ["High", "Medium", "Low", "Laptop"],
    ignore_skin: ["Disabled", "Enabled"],
    // mouse_sync: ["Disabled", "Enabled"]
}

module.exports = class Options {

    /** @param {import("./hud")} hud */
    constructor(hud) {
        this.hud = hud;

        try {
            /** @type {Object<string,number>} */
            const options = JSON.parse(localStorage.getItem("ogarx-options"));
            for (const k of OPTION_KEYS) 
                if (typeof options[k] !== "number") 
                    options[k] = DEFAULT[k];
            Object.assign(this.hud.state, options);
        } catch (e) {
            Object.assign(this.hud.state, DEFAULT);
        }

        for (const key in MultiChoice) {
            const elem = document.getElementById(`render-${key}`);
            elem.addEventListener("click", () => {
                const newIndex = this.hud.state[key] = (this.hud.state[key] + 1) % MultiChoice[key].length;
                elem.innerText = MultiChoice[key][newIndex];
                this.save();
                if (key == "quality") this.hud.resize();
            });
            elem.innerText = MultiChoice[key][this.hud.state[key]];
        }

        const slider = document.getElementById("render-delay-input");
        const display = document.getElementById("render-delay-display");

        const updateDrawDelay = () => {
            this.hud.state.draw = ~~slider.value;
            display.innerText = slider.value;
            this.save();
        }
        
        slider.addEventListener("change",    updateDrawDelay);
        slider.addEventListener("mousemove", updateDrawDelay);
        slider.value = this.hud.state.draw;
        updateDrawDelay();

        this.borders = [document.getElementById("border-color-1"), document.getElementById("border-color-2")];
        this.borders.forEach(b => b.addEventListener("change", () => this.updateBorderColors()));
        this.borders[0].value = localStorage.getItem("ogarx-border-1") || "#5779dd";
        this.borders[1].value = localStorage.getItem("ogarx-border-2") || "#e64ca6";
        this.updateBorderColors();
    }

    updateBorderColors() {
        this.hud.worker && this.hud.worker.postMessage({ dual: this.borders.map(b => b.value) });
        document.documentElement.style.setProperty('--border-color-1', this.borders[0].value);
        document.documentElement.style.setProperty('--border-color-2', this.borders[1].value);
        localStorage.setItem("ogarx-border-1", this.borders[0].value);
        localStorage.setItem("ogarx-border-2", this.borders[1].value);
    }

    get borderColors() { return this.borders.map(b => b.value); }

    save() {
        const obj = {};
        for (const key of OPTION_KEYS) obj[key] = this.hud.state[key];
        localStorage.setItem("ogarx-options", JSON.stringify(obj));
    }
}

<|start_filename|>src/modes/default/extreme-omega.js<|end_filename|>
/** @type {typeof import("../../physics/engine").DefaultSettings} */
module.exports = {
    TIME_SCALE: 1.2, // magic that make everything work like a certain ball game
    PLAYER_MAX_CELLS: 256,
    PLAYER_MERGE_NEW_VER: true,
    PLAYER_AUTOSPLIT_SIZE: 0,
    PLAYER_MERGE_TIME: 3,
    VIRUS_COUNT: 0,
    VIRUS_SIZE: 200,
    VIRUS_MONOTONE_POP: true,
    EJECT_SIZE: 38,
    EJECT_LOSS: 38,
    EJECT_DELAY: 100,
    BOTS: 50,
    BOT_SPAWN_SIZE: 1000,
    PELLET_COUNT: 5000,
    PLAYER_VIEW_SCALE: 1.5,
    PLAYER_SPAWN_SIZE: 2000,
    PLAYER_SPAWN_DELAY: 1500,
    PLAYER_SAFE_SPAWN_RADIUS: 0.5,
    PLAYER_MIN_SPLIT_SIZE: 150,
    PLAYER_MIN_EJECT_SIZE: 60,
    WORLD_RESTART_MULT: 3,
    MAP_HW: 30000,
    MAP_HH: 30000,
    DYNAMIC_DECAY: 0,
    STATIC_DECAY: 0,
    NORMALIZE_THRESH_MASS: 30000,
    DUAL_ENABLED: true
};

<|start_filename|>src/network/protocols/web-console.js<|end_filename|>
const Protocol = require("../protocol");
const Reader = require("../reader");
const Writer = require("../writer");

module.exports = class WebConsoleProtocol extends Protocol {

    /** @param {DataView} view */
    static handshake(view) { 
        const reader = new Reader(view);
        return reader.length == 3 && 
            reader.readInt16() == 420 &&
            reader.readUInt8() == 69;
    }

    /** 
     * @param {import("../../game")} game
     * @param {import("../fake-socket")} ws 
     */
    constructor(game, ws) {
        super(game);
        this.ws = ws;
    }

    /** @param {import("../../game/controller")} controller */
    onJoin(controller) {
        const writer = new Writer();
        writer.writeUInt8(2);
        writer.writeUInt16(controller.id);
        this.ws.send(writer.finalize());
    }

    /** @param {import("../../game/controller")} controller */
    onLeave(controller) {
        const writer = new Writer();
        writer.writeUInt8(3);
        writer.writeUInt16(controller.id);
        this.ws.send(writer.finalize());
    }

    /** @param {import("../../game/controller")} controller */
    onSpawn(controller) {
        const writer = new Writer();
        writer.writeUInt8(4);
        writer.writeUInt16(controller.id);
        writer.writeUTF16String(controller.name);
        writer.writeUTF16String(controller.skin);
        this.ws.send(writer.finalize());
    }

    onTick() {
        const writer = new Writer();
        writer.writeUInt8(1);
        writer.writeFloat32(this.game.engine.usage);
        writer.writeUInt16(this.game.engine.cellCount);
        writer.writeUInt8(this.game.handles);
        this.ws.send(writer.finalize());
    }
}

<|start_filename|>webgl/game/minimap.js<|end_filename|>
const SVG = "http://www.w3.org/2000/svg";
const PADDING = 25;

module.exports = class Minimap {
    /** @param {import("./hud")} hud */
    constructor(hud, dim = 512) {
        this.hud = hud;
        this.dim = dim;
        this.circles = document.getElementById("minimap").firstElementChild;
        this.texts = document.getElementById("minimap").lastElementChild;

        this.circles.setAttribute("viewBox", `${-PADDING} ${-PADDING} ${dim + PADDING} ${dim + PADDING}`);
        this.texts.setAttribute("viewBox", `${-PADDING} ${-PADDING} ${dim + PADDING} ${dim + PADDING}`);

        /** @type {Map<number, [SVGCircleElement, SVGTextElement]>} */
        this.nodes = new Map();
    }

    /** @param {Player[]} minimap */
    onData(minimap) {
        
        for (const [id, [circle, text]] of [...this.nodes.entries()]) {
            if (!minimap.some(p => p.id == id)) {
                circle.remove();
                text.remove();
                this.nodes.delete(id);
            }
        }

        for (const p of minimap) {

            let node = this.nodes.get(p.id);
            let circle = node && node[0];
            let text = node && node[1];

            if (!node) {
                circle = document.createElementNS(SVG, "circle");
                circle.classList.add("minimap-node");
                text = document.createElementNS(SVG, "text");
                text.classList.add("minimap-text");
                text.setAttribute("fill", "#eee");

                this.circles.appendChild(circle);
                this.texts.appendChild(text);

                this.nodes.set(p.id, [circle, text]);
            };

            text.textContent = p.name;
            text.setAttribute("x", p.x * this.dim);
            text.setAttribute("y", (1 - p.y) * this.dim - PADDING);

            circle.setAttribute("fill", p.me ? "#93a7e1" : "#eee");
            circle.setAttribute("r",  Math.max(Math.log2(p.score) / 2, 3) * 2);
            circle.setAttribute("cx", p.x * this.dim);
            circle.setAttribute("cy", (1 - p.y) * this.dim);
        }
    }

    clear() { this.onData([]); }
}

/** @typedef {{ x: number, y: number, score: number, name: string, skin: string, id: number }} Player */


<|start_filename|>src/modes/default/virus.js<|end_filename|>
/** @type {typeof import("../../physics/engine").DefaultSettings} */
module.exports = {
    TIME_SCALE: 1.2, // magic that make everything work like a certain ball game
    PLAYER_MAX_CELLS: 128,
    PLAYER_MERGE_NEW_VER: true,
    PLAYER_AUTOSPLIT_SIZE: 0,
    PLAYER_MERGE_TIME: 4,
    VIRUS_COUNT: 4000,
    VIRUS_PUSH: true,
    VIRUS_MONOTONE_POP: true,
    EJECT_SIZE: 38,
    EJECT_LOSS: 38,
    EJECT_DELAY: 50,
    BOTS: 0,
    BOT_SPAWN_SIZE: 100,
    PELLET_COUNT: 5000,
    PLAYER_SPAWN_SIZE: 1000,
    PLAYER_NO_EJECT_POP_DEALY: 0,
    MAP_HW: 10000,
    MAP_HH: 10000,
    SAFE_SPAWN_TRIES: 8,
    WORLD_RESTART_MULT: 0.5,
    WORLD_KILL_OVERSIZE: false,
    WORLD_OVERSIZE_MESSAGE: "${c.name} died from covid overdose",
    PLAYER_MERGE_INCREASE: 0.0001,
    PLAYER_MERGE_TIME: 1,
    DECAY_MIN: 1000,
    DYNAMIC_DECAY: 3,
    STATIC_DECAY: 3,
    IGNORE_TYPE: 250
};


<|start_filename|>webgl/game/util.js<|end_filename|>
/** @template T @param {T[]} array */
module.exports.pick = array => array[~~(Math.random() * array.length)];

/**
* @param {WebGL2RenderingContext} gl
* @param {string} vs_src
* @param {string} fs_src
*/
module.exports.makeProgram = (gl, vs_src, fs_src) => {
   const vs = gl.createShader(gl.VERTEX_SHADER);
   const fs = gl.createShader(gl.FRAGMENT_SHADER);

   gl.shaderSource(vs, vs_src);
   gl.shaderSource(fs, fs_src);

   gl.compileShader(vs);
   gl.compileShader(fs);
   
   const prog = gl.createProgram();
   gl.attachShader(prog, vs);
   gl.attachShader(prog, fs);
   gl.linkProgram(prog);

   if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) {
        console.error(`vs info-log: ${gl.getShaderInfoLog(vs)}\n` +
                      `info-log: ${gl.getShaderInfoLog(fs)}`);
        throw new Error(`prog link failed: ${gl.getProgramInfoLog(prog)}`);
   }

   return prog;
}

const shuffleArray = array => {
   for (var i = array.length - 1; i > 0; i--) {
       var j = Math.floor(Math.random() * (i + 1));
       var temp = array[i];
       array[i] = array[j];
       array[j] = temp;
   }
   return array;
}

module.exports.COLORS = shuffleArray([
   [0, 255, 148],
   [0, 246, 39],
   [255, 68, 67],
   [0, 240, 234],
   [255, 0, 98],
   [0, 169, 255],
   [255, 226, 0],
   [148, 0, 255],
   [196, 4, 78],
   [192, 255, 58],
   [255, 106, 228],
   [255, 142, 0]
]);

<|start_filename|>src/modes/default/solotrick-ffa.js<|end_filename|>
/** @type {typeof import("../../physics/engine").DefaultSettings} */
module.exports = {
    VIRUS_COUNT: 0,
    PLAYER_MIN_SPLIT_SIZE: 150,
    PLAYER_MAX_CELLS: 16,
    PLAYER_MERGE_NEW_VER: true,
    // PLAYER_AUTOSPLIT_SIZE: 0,
    PLAYER_AUTOSPLIT_DELAY: 5,
    DYNAMIC_DECAY: 0.25,
    PLAYER_MERGE_TIME: 4,
    VIRUS_MONOTONE_POP: true,
    EJECT_SIZE: 40,
    EJECT_LOSS: 40.4,
    EJECT_DELAY: 10,
    BOTS: 10,
    PELLET_COUNT: 5000,
    PLAYER_SPAWN_SIZE: 1000,
    MAP_HW: 10000,
    MAP_HH: 10000
};

<|start_filename|>src/network/protocol.js<|end_filename|>
const Handle = require("../game/handle");

module.exports = class Protocol extends Handle {

    constructor(game) {
        super(game);
        this.disconnectTime = 0;
    }

    /** @param {DataView} view */
    static handshake(view) { return false; }
    
    /** @param {DataView} view */
    onMessage(view) {};
}

<|start_filename|>src/modes/index.js<|end_filename|>
const fs = require("fs");
const path = require("path");

const DefaultFolder = path.resolve(__dirname, "default");
const CustomFolder = path.resolve(__dirname, "custom");

if (!fs.existsSync(CustomFolder)) fs.mkdirSync(CustomFolder);

/** @type {Map<string, typeof import("../physics/engine").DefaultSettings>} */
const GameModes = new Map();

for (const file of fs.readdirSync(DefaultFolder)) {
    try {
        GameModes.set(`default/${file.replace(/\.js$/, "")}`, require(`./default/${file}`));
    } catch (e) {
        console.error(`Failed to load default gamemode from "${file}"`);
    }
}

for (const file of fs.readdirSync(CustomFolder)) {
    try {
        GameModes.set(`custom/${file.replace(/\.js$/, "")}`, require(`./custom/${file}`));
    } catch (e) {
        console.error(`Failed to load custom gamemode from "${file}"`);
    }
}

GameModes.set("default", {});

module.exports = GameModes;

<|start_filename|>webgl/game/skins.js<|end_filename|>
module.exports = class Skins {
    /** @param {import("./hud")} hud */
    constructor(hud) {
        this.hud = hud;

        let urls;
        try {
            /** @type {string[]} */
            urls = JSON.parse(localStorage.getItem("ogarx-skins"));
            if (!Array.isArray(urls) || !urls.every(s => typeof s === "string"))
                throw new Error("Failed to load skin");
        } catch (e) {
            console.error(e);
            urls = [];
        }

        const oldSkin = localStorage.getItem("ogarx_skin");
        if (oldSkin) {
            localStorage.removeItem("ogarx_skin");
            urls.push(oldSkin);
        } else if (!urls.length) urls.push("");

        const index1 = Math.min(~~localStorage.getItem("ogarx-skin-index-1") || 0, urls.length - 1);
        const index2 = Math.min(~~localStorage.getItem("ogarx-skin-index-2") || 0, urls.length - 1);

        this.listElem = document.getElementById("skins");

        document.getElementById("prev-skin").addEventListener("click", () => this.prev());
        document.getElementById("next-skin").addEventListener("click", () => this.next());

        /** @type {HTMLImageElement} */
        this.selectedImg1 = null;
        /** @type {HTMLImageElement[]} */
        this.elems = [];

        for (const i in urls) {
            const div = document.createElement("div");
            div.classList.add("skin-item");
            const img = document.createElement("img");
            this.initImg(img);
            div.appendChild(img);
            img.onerror = () => img.src = "/static/img/skin.png";
            img.src = urls[i];
            this.listElem.appendChild(div);
            this.elems.push(img);
        }

        this.selectedImg1 = this.elems[index1];
        this.selectedImg1.classList.add("selected-1");
        this.selectedImg1.addEventListener("load", () => this.sync(), { once: true });

        this.selectedImg2 = this.elems[index2];
        this.selectedImg2.classList.add("selected-2");
        this.selectedImg2.addEventListener("load", () => this.sync(), { once: true });

        document.getElementById("add-skin").addEventListener("click", () => this.addSlot());
        document.getElementById("delete-skin").addEventListener("click", () => this.delete());

        this.save();
    }

    /** @param {HTMLImageElement} img */
    initImg(img) {
        img.onmouseup = e => this.select(img, [0, null, 1][e.button]);
    }

    /** @param {HTMLImageElement} img */
    select(img, index = 0) {
        if (!(index === 0) && !(index === 1)) return;
        const elemName = `selectedImg${index + 1}`;
        const className = `selected-${index + 1}`;
        if (img.classList.contains(className)) return;
        this[elemName].classList.remove(className);
        img.classList.add(className);
        this[elemName] = img;
        this.sync();
        this.saveIndex();
    }

    addSlot() {
        const div = document.createElement("div");
        div.classList.add("skin-item");
        const img = document.createElement("img");
        this.initImg(img);
        div.appendChild(img);
        this.listElem.appendChild(div);
        img.src = "/static/img/skin.png";
        this.elems.push(img);
        this.save();
    }

    swap() {
        const img1 = this.selectedImg1;
        const img2 = this.selectedImg2;
        this.select(img1, 1);
        this.select(img2, 0);
        this.sync();
        this.saveIndex();
    }

    delete(index = 0) {
        if (this.elems.length === 1) {
            this.selectedImg1.src = "/static/img/skin.png";
            this.sync();
            this.save();
            return;
        }

        const index1 = this.index1;
        const index2 = this.index2;

        const currentIndex = this[`index${index + 1}`];
        const elemName = `selectedImg${index + 1}`;
        this[elemName].parentNode.remove();
        this.elems.splice(currentIndex, 1);

        if (this.elems[currentIndex]) {
            this.select(this.elems[currentIndex], index);
            this.save();
        }

        if (!this.elems[index1]) {
            this.select(this.elems[index1 - 1], 0);
            this.save();
        }

        if (!this.elems[index2]) {
            this.select(this.elems[index2 - 1], 1);
            this.save();
        }
    }

    get index1() { return this.elems.indexOf(this.selectedImg1); }
    get index2() { return this.elems.indexOf(this.selectedImg2); }

    set current1(v) {
        if (this.current[0] != v) {
            this.selectedImg1.src = v;
            this.save();
        }
    }

    set current2(v) {
        if (this.current[1] != v) {
            this.selectedImg2.src = v;
            this.save();
        }
    }

    get current() {
        return [
            this.getUrl(this.selectedImg1.src),
            this.getUrl(this.selectedImg2.src)
        ]; 
    }

    getUrl(s) { return s.endsWith("/static/img/skin.png") ? "" : s; }

    prev(index = 0) {
        if (this.elems.length <= 1) return;
        this.select(this.elems[(this[`index${index + 1}`] - 1 + this.elems.length) % this.elems.length], index);
        this.sync();
        this.saveIndex();
    }

    next(index = 0) {
        if (this.elems.length <= 1) return;
        this.select(this.elems[(this[`index${index + 1}`] + 1) % this.elems.length], index);
        this.sync();
        this.saveIndex();
    }

    sync() {
        const [skin1, skin2] = this.current;
        this.hud.updateSkin(skin1, 0);
        this.hud.updateSkin(skin2, 1);
    }

    saveIndex() {
        localStorage.setItem("ogarx-skin-index-1", this.index1);
        localStorage.setItem("ogarx-skin-index-2", this.index2);
    }

    save() {
        this.saveIndex();
        localStorage.setItem("ogarx-skins", JSON.stringify(this.elems.map(e => this.getUrl(e.src))));
    }
}

<|start_filename|>src/network/writer.js<|end_filename|>
const BufferPool = new DataView(new ArrayBuffer(1048576)); // 10 mb

module.exports = class Writer {
    
    constructor(le = true) {
        this.offset = 0;
        this.le = le;
    }

    /** @param {number} a */
    writeUInt8(a) {
        BufferPool.setUint8(this.offset++, a);
    }
    
    /** @param {number} a */
    writeInt8(a) {
        BufferPool.setInt8(this.offset++, a);
    }

    /** @param {number} a */
    writeUInt16(a) {
        BufferPool.setUint16(this.offset, a, this.le);
        this.offset += 2;
    }

    /** @param {number} a */
    writeInt16(a) {
        BufferPool.setInt16(this.offset, a, this.le);
        this.offset += 2;
    }

    /** @param {number} a */
    writeUInt32(a) {
        BufferPool.setUint32(this.offset, a, this.le);
        this.offset += 4;
    }

    /**
     * @param {number} a
     */
    writeInt32(a) {
        BufferPool.setInt32(this.offset, a, this.le);
        this.offset += 4;
    }

    /** @param {number} a */
    writeFloat32(a) {
        BufferPool.setFloat32(this.offset, a, this.le);
        this.offset += 4;
    }

    /** @param {number} a */
    writeFloat64(a) {
        BufferPool.setFloat64(this.offset, a, this.le);
        this.offset += 8;
    }

    /** @param {string} a */
    writeUTF8String(a) {
        for (let i = 0; i < a.length; i++)
            this.writeUInt8(a.charCodeAt(i));
        this.writeUInt8(0);
    }

    /** @param {string} a */
    writeUTF16String(a) {
        for (let i = 0; i < a.length; i++)
            this.writeUInt16(a.charCodeAt(i));
        this.writeUInt16(0);
    }

    skip(v = 0) {
        this.offset += v;
    }
    
    finalize() {
        return BufferPool.buffer.slice(0, this.offset);
    }
}


<|start_filename|>webgl/game/hud.js<|end_filename|>
const { COLORS } = require("./util");
const Stats = require("./stats");
const Mouse = require("./mouse");
const State = require("./state");
const Input = require("./input");
const Skins = require("./skins");
const Options = require("./options");
const Minimap = require("./minimap");
const Viewport = require("./viewport");
const ReplayMenu = require("./replay");

const msToText = ms => {
    if (ms < 1000) return `${Math.round(ms)}ms`;
    const s = ms / 1000;
    if (s < 60) return `${Math.round(s)}s`;
    const m = s / 60;
    if (m < 60) return `${Math.round(m)}min`;
    const h = m / 60;
    return `${h.toFixed(1)}h`;
}

const scoreToText = s => s > 1000000 ? `${(s / 1000000).toFixed(2)}M` : s > 1000 ? `${(s / 1000).toFixed(1)}K` : Math.round(s);
const SVG = "http://www.w3.org/2000/svg";

module.exports = class HUD {

    constructor() {
        /** @type {HTMLCanvasElement} */
        this.canvas = document.getElementById("canvas");
        this.canvas.width = window.innerWidth;
        this.canvas.height = window.innerHeight;
        
        this.showing = true;
        this.stats = new Stats();
        this.mouse = new Mouse();
        this.state = new State();
        this.viewport = new Viewport();
        this.server = "";

        this.registerEvents();
        this.initUIComponents();
        
        if (navigator.userAgent.includes("Chrome")) {

            this.worker = new Worker("js/renderer.min.js");
            const offscreen = this.canvas.transferControlToOffscreen();
            const initObject = { 
                offscreen, 
                stats: this.stats.sharedBuffer,
                mouse: this.mouse.sharedBuffer, 
                state: this.state.sharedBuffer,
                viewport: this.viewport.sharedBuffer,
                dual: this.options.borderColors
            };
    
            this.worker.postMessage(initObject, [offscreen]);
            this.worker.onmessage = e => {
                const { data } = e;
                if (data.event === "ready") this.ready = true;
                if (data.event === "chat") this.onChat(data.message);
                if (data.event === "server-log") this.onChat(data.message, true);
                if (data.event === "leaderboard") this.onLeaderboard(data.lb);
                if (data.event === "connect") this.onConnect(data.server, data.uid);
                if (data.event === "disconnect") this.onDisconnect();
                if (data.event === "minimap") this.minimap.onData(data.minimap);
                if (data.event === "stats") this.onStats(data.kills, data.score, data.surviveTime);
                if (data.event === "replay") this.onReplay(data.state);
                if (data.event === "error") this.onError(data.message || "Unknown Error");
                if (data.event === "warning") this.onWarning(data.message || "Unknown Warning");
                if (data.event === "webgl2") window.location.assign("/webgl2notsupported");
            }
        }
    }

    show(elem = this.hudElem) {
        if (elem == this.hudElem) this.hide(this.gameoverElem);
        elem.classList.add("fade-in");
        elem.classList.remove("fade-out");
        elem.hidden = false;
        elem.focus();
    }

    hide(elem = this.hudElem) {
        elem.classList.remove("fade-in");
        elem.classList.add("fade-out");
        setTimeout(() => elem.hidden = true, 250);
        elem.blur();
    }

    toggle(elem = this.hudElem) {
        elem.hidden ? this.show(elem) : this.hide(elem);
    }

    resize() {
        const DPR = window.devicePixelRatio;
        this.viewport.width  = this.canvas.clientWidth * DPR;
        const r = this.state.resolution;
        const ratio = (window.innerWidth / window.innerHeight) / (r[0] / r[1]);
        if (ratio < 1) {
            this.viewport.height = ratio * this.canvas.clientHeight * DPR;
        } else {
            this.viewport.height = this.canvas.clientHeight * DPR;
        }
    }

    registerEvents() {
        window.onresize = this.resize.bind(this);

        document.addEventListener("contextmenu", e => e.preventDefault());

        this.input = new Input(this);
        this.options = new Options(this);
        
        this.state.visible = !document.hidden;

        document.addEventListener("visibilitychange", () => this.state.visible = !document.hidden);
        
        window.addEventListener("keydown", e => this.input.keyDown(e));
        window.addEventListener("keyup", e => this.input.keyUp(e));
        window.addEventListener("blur", _ => this.input.blur());
        window.addEventListener("focus", _ => this.input.focus());
        window.addEventListener("mousedown", e => this.input.keyDown({ key: `MOUSE ${e.button}`}));
        window.addEventListener("mouseup",   e => this.input.keyUp({ key: `MOUSE ${e.button}`}));

        window.addEventListener("mousemove", e => {
            let dx = 2 * e.clientX / window.innerWidth  - 1;
            let dy = 2 * e.clientY / window.innerHeight - 1;
            const r = this.state.resolution;
            const ratio = (window.innerWidth / window.innerHeight) / (r[0] / r[1]);
            if (ratio > 1) {
                dy /= ratio;
            } else {
                dx *= ratio;
            }
            this.mouse.x = ~~(4096 * dx);
            this.mouse.y = ~~(4096 * dy);
        });

        this.canvas.addEventListener("wheel", e => {
            if (e.ctrlKey) return;
            this.mouse.updateScroll(e.deltaY);
        }, { passive: true });

        this.canvas.addEventListener("click", _ => this.state.clicked = 1);
    }

    updateSkin(src = "", index = 0, ignoreError = false) {

        if (this[`skinElem${index + 1}`].src == src) return;
        if (!src) {
            this[`skinElem${index + 1}`].src = "/static/img/skin.png";
            return;
        } // else console.log(`Setting skin[${index}] to ${src}`);

        const img = new Image();
        img.onload = () => {
            if (!index) this.skinText = src;
            this.skins[`current${index + 1}`] = this[`skinElem${index + 1}`].src = src;
        }
        ignoreError || (img.onerror = () => {
            this.onError(`Failed to load skin "${src}"`);
            this[`skinElem${index + 1}`].src = "/static/img/skin.png";
        });
        img.src = src;
    }

    initUIComponents() {
        this.hudElem = document.getElementById("hud");
        this.skinElem1 = document.getElementById("skin-1");
        this.skinElem2 = document.getElementById("skin-2");
        this.skinInput = document.getElementById("skin-input");
        this.swapElem = document.getElementById("swap-skin");
        this.serverInput = document.getElementById("server-input");
        this.nameInput = document.getElementById("name-input");

        this.replays = new ReplayMenu(this);
        this.replays.init();

        this.minimap = new Minimap(this);
        this.skins = new Skins(this);

        this.skinText = this.skins.current[0];
        this.skinInput.addEventListener("blur", () => this.updateSkin(this.skinText, 0));

        this.swapElem.addEventListener("click", () => this.skins.swap());

        this.playButton = document.getElementById("play");
        this.playButton.addEventListener("click", () => {
            this.hide();
            this.spawn();
            this.playButton.blur();
        });
        // TODO
        this.spectateButton = document.getElementById("spectate");
        this.spectateButton.addEventListener("click", () => {
            this.state.spectate = 1;
            this.hide();
        });
        this.respawnButton = document.getElementById("respawn-button");
        this.respawnButton.addEventListener("click", () => this.state.respawn = 1);

        this.chatElem = document.getElementById("chat");
        this.chatInput = document.getElementById("chat-input");
        this.chatInput.addEventListener("keydown", e => {
            e.stopPropagation();
            if (e.key == "Enter") {
                const message = this.chatInput.value.trim();
                if (message) {
                    this.sendChat(message);
                    this.chatInput.value = "";
                }
                this.hide(this.chatInput);
                this.canvas.focus();
            }
        });
        this.chatElem.addEventListener("focus", () => {
            this.chatElem.blur();
            this.canvas.focus();
        });
        this.chatElem.addEventListener("wheel", e => e.stopPropagation(), { passive: true });

        this.serverInput.value = "Select Server";
        
        this.nameInput.value = localStorage.getItem("ogarx-name") || "";

        this.nameInput.autocomplete = Math.random();
        this.skinInput.autocomplete = Math.random();
        this.serverTab = UIkit.offcanvas("#server-menu-offcanvas");

        const [skin1, skin2] = this.skins.current;
        this.updateSkin(skin1, 0, true);
        this.updateSkin(skin2, 1, true);

        this.chatInput.addEventListener("blur", () => this.hide(this.chatInput));

        this.lbElem = document.getElementById("leaderboard-data");

        document.querySelectorAll('[server="local"]').forEach(s => 
            s.addEventListener("click", () => this.connectToLocal(s.getAttribute("mode"))));

        this.gameoverElem = document.getElementById("game-over");
        this.respawnSpinner = document.getElementById("respawn-spinner");
        this.pingElem = document.getElementById("ping");
        this.fpsElem = document.getElementById("fps");
        this.bwElem = document.getElementById("bandwidth");
        this.renderCellElem = document.getElementById("cells");
        this.mycellsElem = document.getElementById("mycells");
        this.linelockElem = document.getElementById("linelock");
        this.scoreElem = document.getElementById("score");

        const gateways = []; 
        // [...document.querySelectorAll('[gateway]').values()]
        // .map(node => ({ list: node, gateway: node.getAttribute("gateway") }));

        if (/^https?\:\/\/localhost$/.test(window.origin)) {
            // gateways.push({ list: document.querySelector("[region='Dev']"), gateway: "localhost:6969" });
            this.mycellsElem.parentElement.hidden = false;
        }
        window.connect = this.connectToURL.bind(this);

        /** @type {Map<string, { signal: HTMLElement, pie: SVGElement }>} */
        const elemTable = new Map();
        const po = new PerformanceObserver((list) => {
            for (const entry of list.getEntries()) {
                if (entry.initiatorType != "fetch") return;
                const elem = elemTable.get(entry.name);
                if (!elem) return;
                const ping = ~~entry.duration;
                const { signal } = elem;
                signal.setAttribute("uk-tooltip", `${ping} ms`);
                if (ping < 100) signal.className = "signal-good";
                else if (ping < 200) signal.className = "signal-ok";
                else if (ping < 300) signal.className = "signal-bad";
                else signal.className = "signal-worse";
            }
        });
        po.observe({ type: 'resource', buffered: true });

        for (const { list, gateway } of gateways) {
            /** @type {Map<string, HTMLElement>} */
            const servers = new Map();
            
            /** @type {Map<string, SVGCircleElement>} */
            const pies = new Map();

            const region = list.getAttribute("region");
            const source = new EventSource(`${window.location.protocol}//${gateway}/gateway`);
        
            let timeout;
            const ping = async (endpoint = "", t = 5000) => {
                try {
                    await fetch(`${window.location.protocol}//${endpoint}/ping`);
                } catch (e) {}
                timeout = setTimeout(ping, t, endpoint);
            }
            
            const pieSVG = document.getElementById(`${region.toLowerCase()}-server-stats`);
            const text = pieSVG.previousElementSibling;

            source.onopen = () => {
                if (!timeout) {
                    elemTable.set(`${window.location.protocol}//${gateway}/ping`, {
                        signal: document.getElementById(`signal-${region.toLowerCase()}`)
                    });
                    ping(gateway, 1000);
                }
            }
            
            source.addEventListener("servers", event => {
                /** @type {{ servers: { uid: number, name: string, endpoint: string, bot: number, players: number, total: number, load: number }[]}} */
                const data = { servers: JSON.parse(event.data) };

                let totalUsage = 0;
                const init = ~~(Math.random() * 100);
                for (const id in data.servers) {
                    const server = data.servers[id];
                    if (!server.uid) continue;
                    let elem = servers.get(server.uid);
                    
                    const [r, g, b] = COLORS[(5 * id + init) % COLORS.length];
                    const color = `rgb(${r}, ${g}, ${b})`;

                    if (!elem) {
                        elem = document.createElement("p");
                        elem.classList.add("servers");
                        elem.innerHTML = `<span>${server.name}</span><span style="float: right" uk-tooltip="${server.bot} bots">${server.players}/${server.total}</span>`;
                        elem.style.borderRight = `2px solid ${color}`;
                        list.appendChild(elem);
                        servers.set(server.uid, elem);

                        elem.addEventListener("click", () => {
                            this.server = `${region} ${server.name}`;
                            this.connect(`${gateway.replace(/\:\d+$/, "")}:${server.endpoint}`);
                        });
                    } else {
                        elem.children[0].textContent = server.name;
                        elem.children[1].setAttribute("uk-tooltip", `${server.bot} bots`);
                        elem.children[1].textContent = `${server.players}/${server.total}`;
                    }

                    const percent = server.load * 100;
                    let pie = pies.get(server.uid);
                    if (!pie) {
                        pie = document.createElementNS(SVG, "circle");
                        pie.setAttribute("cx", "21");
                        pie.setAttribute("cy", "21");
                        pie.setAttribute("r", "15.91549430918954");
                        pie.setAttribute("fill", "transparent");
                        pie.setAttribute("stroke-width", (8 - ~~id / 100).toString())
                        pie.setAttribute("stroke", color);
                        pie.setAttribute("stroke-dashoffset", "25");
                        pieSVG.prepend(pie);
                        pies.set(server.uid, pie);
                    }
                    totalUsage = Math.min(100, totalUsage + percent);
                    pie.setAttribute("stroke-dasharray", `${Math.ceil(totalUsage)} ${100 - Math.ceil(totalUsage)}`);
                }

                text.textContent = ~~totalUsage + "%";

                for (const [k, v] of [...servers.entries()]) {
                    if (!data.servers.some(s => s.uid == k)) {
                        servers.delete(k);
                        pies.get(k).remove();
                        pies.delete(k);
                        v.remove();
                    }
                }
            });
        }

        this.updateInterval = setInterval(() => {
            this.pingElem.innerText = this.stats.ping;
            this.fpsElem.innerText = this.stats.fps;
            const kbs = this.stats.bandwidth / 1024;
            this.bwElem.innerText = kbs < 1024 ? `${~~kbs}KBs` : `${(kbs / 1024).toFixed(1)}MBs`;
            this.renderCellElem.innerText = this.stats.cells;
            const c = this.mycellsElem.innerText = this.stats.mycells;
            this.linelockElem.innerText = this.stats.linelocked ? "LOCKED" : "UNLOCKED";
            this.stats.linelocked ? this.linelockElem.classList.add("text-danger") : this.linelockElem.classList.remove("text-danger");
            this.scoreElem.innerText = scoreToText(this.stats.score);

            if (c && !this.gameoverElem.hidden) this.hide(this.gameoverElem);
        }, 50);
    }

    replay(replay = 0) {
        this.hide();
        if (this.worker) this.worker.postMessage({ replay });
    }

    sendChat(chat) {
        if (this.worker) this.worker.postMessage({ chat });
    }

    /** @param {string} message */
    onChat(message, isServer = false) {
        const elem = document.createElement("p");
        elem.textContent = message;
        this.chatElem.appendChild(elem);
        this.chatElem.scrollTo(0, this.chatElem.scrollHeight);
        if (isServer) elem.classList.add("server-log");
        while (this.chatElem.children.length > 100)
            this.chatElem.firstChild.remove();
    }

    /** @param {"success"|"failed"|"starting"} state */
    onReplay(state) {
        switch (state) {
            case "starting":
                this.onWarning("Saving Clip...");
                break;
            case "failed":
                this.onError("Failed to Save Clip");
                break;
            case "success":
                this.onSuccess("Clip Saved!");
                break;
        }
    }

    /**
     * @param {Object} lb 
     * @param {number} lb.rank
     * @param {{ name: string, skin: string }} lb.me
     * @param {{ name: string, skin: string }[]} lb.players
     */
    onLeaderboard(lb) {
        const { players, rank, me } = lb;
        this.lbElem.innerHTML = "";

        for (const i in players) {
            const e = document.createElement("p");
            e.textContent = `${~~i + 1}. ${players[i] ? (players[i].name || "") : ""}`;
            if (i == rank) e.classList.add("me");
            this.lbElem.appendChild(e);
        }

        if (!players[rank] && rank != 65535) {
            const e = document.createElement("p");
            e.textContent = `${rank + 1}. ${me ? (me.name || "") : ""}`;
            e.classList.add("me");
            this.lbElem.appendChild(e);
        }
    }

    get skinText() { return this.skinInput.value; }
    set skinText(v) { this.skinInput.value = v; }
    get nameText() { return this.nameInput.value; }

    connect(server) {
        server = server.trim();
        server == "local" ? this.connectToLocal() : this.connectToURL(
            `${window.location.protocol.replace("http", "ws")}//${server}`);
    }

    onSuccess(message) {
        UIkit.notification({ message, status: "success", timeout: 3000 });
    }

    onWarning(message) {
        UIkit.notification({ message, status: "warning", timeout: 3000 });
    }

    onError(message) {
        UIkit.notification({ message, status: "danger", timeout: 3000 });
    }

    /**
     * @param {number} kills 
     * @param {number} score 
     * @param {number} surviveTime 
     */
    onStats(kills, score, surviveTime) {
        document.getElementById("kills").innerText = kills;
        document.getElementById("max-score").innerText = scoreToText(score);
        document.getElementById("survive-time").innerText = msToText(surviveTime);
        this.show(this.gameoverElem);

        const a = this.state.auto_respawn;
        if (a) {
            this.respawnSpinner.hidden = false;
            this.respawnButton.setAttribute("disabled", "");
            this.respawnButton.innerText = "Respawning";
        } else {
            this.respawnSpinner.hidden = true;
            this.respawnButton.removeAttribute("disabled");
            this.respawnButton.innerText = "Respawn";
        }
    }

    showStats() {
        this.show(document.getElementById("stats1"));
        this.show(document.getElementById("stats2"));
        this.show(document.getElementById("stats3"));
    }

    hideStats() {
        this.hide(document.getElementById("stats1"));
        this.hide(document.getElementById("stats2"));
        this.hide(document.getElementById("stats3"));
    }

    get uid() { return localStorage.getItem("ogarx-uid"); }
    set uid(v) { localStorage.setItem("ogarx-uid", v); }

    onConnect(serverName = "Server", uid = "") {
        if (this.uid == uid) this.hide();
        this.uid = uid;
        this.resize(); // ???
        this.serverInput.value = this.server;
        this.show(this.playButton);
        this.show(this.spectateButton);
        console.log("CLEARING CHAT");
        this.chatElem.innerHTML = "";
        this.showStats();
        this.show(document.getElementById("leaderboard"));
        document.getElementById("server-name").innerText = serverName;
        this.serverTab.hide();
    }

    onDisconnect() {
        this.lbElem.innerHTML = "";
        this.chatElem.innerHTML = "";
        this.hide(this.playButton);
        this.hide(this.spectateButton);

        this.show();
        this.onError("Disconnected");
        
        this.hide(this.gameoverElem);
        this.hideStats();
        this.hide(document.getElementById("leaderboard"));
        this.minimap.clear();
        document.getElementById("server-name").innerText = "";
        this.serverTab.show();
    }

    spawn() {
        const name = this.nameText;
        const [skin1, skin2] = this.skins.current;
        
        localStorage.setItem("ogarx-name", name);
        this.worker.postMessage({ spawn: true, name, skin1, skin2 });
    }

    connectToLocal(mode = "") {
        this.sw = new SharedWorker(`js/sw.min.js?mode=${mode}`, "ogar-x-server");
        const [skin1, skin2] = this.skins.current;
        this.worker.postMessage({ connect: this.sw.port, name: this.nameText, skin1, skin2 }, [this.sw.port]);
    }

    connectToURL(url) {
        const [skin1, skin2] = this.skins.current;
        this.worker.postMessage({ connect: url, name: this.nameText, skin1, skin2, uid: this.uid });
    }
}

<|start_filename|>webgl/control/control.js<|end_filename|>
const Reader = require("../../src/network/reader");
const Writer = require("../../src/network/writer");

window.onload = () => {

    /** @type {Map<number, { name: string, skin: string }>} */
    const players = new Map();

    const usageElem = document.getElementById("usage");
    const cellsElem = document.getElementById("cells");
    const playersElem = document.getElementById("players");
    const playerList = document.getElementById("player-list");

    const sharedServer = new SharedWorker("js/sw.min.js", "ogar-x-server");
    sharedServer.onerror = console.error;
    const port = sharedServer.port;

    port.start();

    /** @param {ArrayBuffer} buffer */
    const send = buffer => port.postMessage({ event: "message", message: buffer }, [buffer]);

    const writer = new Writer();
    writer.writeInt16(420);
    writer.writeUInt8(69);
    send(writer.finalize());

    let pingInterval = null;

    let usage = 0;
    let cells = 0;
    let playerCount = 0;

    setInterval(() => {
        usageElem.textContent = `${(usage * 100).toFixed(2)}%`;
        cellsElem.textContent = cells;
        playersElem.textContent = playerCount;

        playerList.innerHTML = "";

        for (const [pid, player] of players) {
            const elem = document.createElement("p");
            elem.textContent = `[${pid}] ${player.name}`;
            playerList.appendChild(elem);
        }
    }, 500);
    
    port.addEventListener("message", e => {

        if (e.data.event === "open") {
            
            pingInterval = self.setInterval(() => {
                const PING = new ArrayBuffer(1);
                new Uint8Array(PING)[0] = 69;
                send(PING);
            }, 1000);

            return console.log("Connected to server");
        }

        if (e.data.event === "close") {
            clearInterval(pingInterval);
            return console.log("Disconnected");
        }
        
        if (e.data.event === "message") {
            const reader = new Reader(new DataView(e.data.message));
            const OP = reader.readUInt8();
            let id = 0;
            switch (OP) {
                case 1:
                    usage = reader.readFloat32();
                    cells = reader.readUInt16();
                    playerCount = reader.readUInt8();
                    break;
                case 2:
                    id = reader.readUInt16();
                    players.set(id, { name: "", skin: "" });
                    break;
                case 3:
                    id = reader.readUInt16();
                    players.delete(id);
                    break;
                case 4:
                    id = reader.readUInt16();
                    players.set(id, { 
                        name: reader.readUTF16String(), 
                        skin: reader.readUTF16String() 
                    });
                    break;
            }
        }
    });
};

<|start_filename|>webgl/wasm/client.c<|end_filename|>
#include "memory.h"

#define EATEN_TYPE 251

typedef struct {
    unsigned int type;
    float oldX;
    float oldY;
    float oldSize;
    float currX;
    float currY;
    float currSize;
    float netX;
    float netY;
    float netSize;
} CellData;

typedef struct {
    unsigned short id;
    unsigned short type;
    short x;
    short y;
    unsigned short size;
} AddPacket;

typedef struct {
    unsigned short id;
    short x;
    short y;
    unsigned short size;
} UpdatePacket;

typedef struct {
    unsigned short id;
    unsigned short by;
} EatPacket;

typedef struct {
    unsigned short id;
} DeletePacket;

unsigned int bytes_per_cell_data() { return sizeof(CellData); }

void deserialize(CellData data[], unsigned short* packet) {

    AddPacket* add_data = (AddPacket*) packet;

    while (add_data->id) {
        unsigned short id = add_data->id;
        CellData* cell = &data[id];

        cell->type = add_data->type;
        cell->oldX = cell->currX = cell->netX = add_data->x;
        cell->oldY = cell->currY = cell->netY = add_data->y;
        cell->oldSize = cell->currSize = cell->netSize = add_data->size;
        
        add_data++;
    }

    packet = (unsigned short*) add_data;
    packet++;

    UpdatePacket* update_data = (UpdatePacket*) packet;

    while (update_data->id) {
        unsigned short id = update_data->id;

        if (data[id].type) {
            data[id].oldX = data[id].currX;
            data[id].oldY = data[id].currY;
            data[id].oldSize = data[id].currSize;
            data[id].netX = update_data->x;
            data[id].netY = update_data->y;
            data[id].netSize = update_data->size;
        }

        update_data++;
    }

    packet = (unsigned short*) update_data;
    packet++;

    EatPacket* eat_data = (EatPacket*) packet;

    while (eat_data->id) {
        
        if (data[eat_data->by].type) {
            data[eat_data->id].netX = data[eat_data->by].netX;
            data[eat_data->id].netY = data[eat_data->by].netY;

            data[eat_data->id].oldX = 0.0f;
            data[eat_data->id].oldY = 0.0f;
            data[eat_data->id].netSize = 0.0f;
        } else {
            memset(&data[eat_data->id], 0, sizeof(CellData));
        }

        eat_data++;
    }

    packet = (unsigned short*) eat_data;
    packet++;

    DeletePacket* delete_data = (DeletePacket*) packet;

    while (delete_data->id) {
        memset(&data[delete_data->id], 0, sizeof(CellData));
        delete_data++;
    }
}

void sort_indices(CellData cells[], unsigned short indices[], unsigned int n) {
    if (!n) return;
    
    int t = 0;

    // Build Max Heap
    for (int i = 1; i < n; i++) { 
        // if child is bigger than parent 
        if (cells[indices[i]].currSize > cells[indices[(i - 1) / 2]].currSize) {
            int j = i;
            // swap child and parent until parent is bigger 
            while (cells[indices[j]].currSize > cells[indices[(j - 1) / 2]].currSize) { 
                t = indices[j];
                indices[j] = indices[(j - 1) / 2];
                indices[(j - 1) / 2] = t;
                j = (j - 1) / 2; 
            }
        }
    }

    for (int i = n - 1; i > 0; i--) {
        // swap value of first indexed  
        // with last indexed  
        t = indices[0];
        indices[0] = indices[i];
        indices[i] = t;
        // maintaining heap property 
        // after each swapping 
        int j = 0, index;
        do { 
            index = (2 * j + 1); 
              
            // if left child is smaller than  
            // right child point index variable  
            // to right child 
            if (cells[indices[index]].currSize < cells[indices[index + 1]].currSize && 
                index < (i - 1)) index++; 
          
            // if parent is smaller than child  
            // then swapping parent with child  
            // having higher value 
            if (cells[indices[j]].currSize < cells[indices[index]].currSize && index < i) {
                t = indices[j];
                indices[j] = indices[index];
                indices[index] = t;
            }
            j = index; 
        } while (index < i); 
    }
}

unsigned int update_cells(
    CellData data[],
    unsigned short indices[],
    unsigned short pellet_indices[],
    float lerp, float t, float b, float l, float r, unsigned char skip) {

    lerp = lerp > 1 ? 1 : lerp < 0 ? 0 : lerp;

    unsigned short count = 0;
    unsigned short pellet_count = 0;

    CellData* end = &data[65536];
    CellData* node = data;

    while (node < end) {
        if (node->type) {
            if (!node->netSize) {
                node->currX = lerp * (node->netX - node->currX) + node->currX;
                node->currY = lerp * (node->netY - node->currY) + node->currY;
                node->currSize = lerp * (node->netSize - node->currSize) + node->currSize;
                node->oldX += lerp * 0.5f;
                if (node->oldX >= 2.0f) memset(node, 0, sizeof(CellData));
            } else {
                node->currX = lerp * (node->netX - node->oldX) + node->oldX;
                node->currY = lerp * (node->netY - node->oldY) + node->oldY;
                node->currSize = lerp * (node->netSize - node->oldSize) + node->oldSize;
            }

            if (node->type &&
                node->currX - node->currSize < r &&
                node->currX + node->currSize > l &&
                node->currY - node->currSize < t &&
                node->currY + node->currSize > b) {

                if (node->type == 254) {
                    pellet_indices[pellet_count++] = node - data;
                } else {
                    indices[count++] = node - data;
                }
            }
        }
        node++;
    }

    if (!skip) sort_indices(data, indices, count);

    unsigned char* types = (unsigned char*) (indices + count);

    for (unsigned int i = 0; i < count; i++)
        *types++ = data[indices[i]].type;

    return pellet_count | ((unsigned int) count << 16);
}

float* draw_cells(CellData data[], unsigned short indices[], unsigned int n, float* out) {
    for (unsigned int i = 0; i < n; i++) {
        CellData* cell = &data[indices[i]];
        float x = cell->currX;
        float y = cell->currY;
        float r = cell->currSize;

        float x0 = x - r;
        float x1 = x + r;
        float y0 = y - r;
        float y1 = y + r;

        // Triangle 1
        *out++ = x0;
        *out++ = y0;
        *out++ = x1;
        *out++ = y0;
        *out++ = x0;
        *out++ = y1;

        // Triangle 2
        *out++ = x1;
        *out++ = y0;
        *out++ = x0;
        *out++ = y1;
        *out++ = x1;
        *out++ = y1;
    }
    return out;
}

float* draw_pellets(CellData data[], unsigned short indices[], unsigned int n, float* out) {
    for (unsigned int i = 0; i < n; i++) {
        unsigned short id = indices[i];
        CellData* cell = &data[id];

        float x = cell->currX;
        float y = cell->currY;
        float r = cell->currSize;

        float x0 = x - r;
        float x1 = x + r;
        float y0 = y - r;
        float y1 = y + r;

        // Triangle 1
        *out++ = x0;
        *out++ = y0;
        *out++ = id;
        *out++ = x1;
        *out++ = y0;
        *out++ = id;
        *out++ = x0;
        *out++ = y1;
        *out++ = id;

        // Triangle 2
        *out++ = x1;
        *out++ = y0;
        *out++ = id;
        *out++ = x0;
        *out++ = y1;
        *out++ = id;
        *out++ = x1;
        *out++ = y1;
        *out++ = id;
    }
    return out;
}

unsigned char get_clicked_type(CellData data[], float x, float y) {
    
    CellData* end = &data[65536];
    CellData* node = data;

    unsigned char click_type = 0;
    float max_size = 0;

    while (node < end) {
        if (node->type && node->type <= 250 && 
            node->currSize > max_size &&
            (node->currX - x) * (node->currX - x) + 
                (node->currY - y) * (node->currY - y) < node->currSize * node->currSize) {
            max_size = node->currSize;
            click_type = node->type;
        }
        node++;
    }

    return click_type;
}

unsigned int find_text_index(CellData data[], unsigned short indices[], unsigned int n, float cutoff) {
    for (unsigned int i = 0; i < n; i ++)
        if (data[indices[i]].currSize > cutoff) return i;
    return n;
}

unsigned short* serialize_state(CellData data[], AddPacket* packet) {

    CellData* end = &data[65536];
    CellData* node = data;

    while (node < end) {
        if (node->type && node->netSize) {
            packet->id = node - data;
            packet->type = node->type;
            packet->x = node->currX;
            packet->y = node->currY;
            packet->size = node->currSize;
            packet++;
        }
        node++;
    }

    // Add padding 0 bytes for a valid packet
    unsigned short* ptr = (unsigned short*) packet;
    *ptr++ = 0;
    *ptr++ = 0;
    *ptr++ = 0;
    *ptr++ = 0;
    
    return ptr;
}


<|start_filename|>webgl/game/wasm-core.js<|end_filename|>
module.exports = class WasmCore {
    /** @param {import("./renderer")} renderer */
    constructor(renderer) {
        this.renderer = renderer;
    }
    async load(page = 1024) {
        if (this.loading || this.instance) return false;
        this.loading = true;
        const res = await fetch("/static/wasm/client.wasm");
        const m = new WebAssembly.Memory({ initial: page, maximum: page });
        const e = { env: { memory: m } };
        this.instance = await WebAssembly.instantiate(await WebAssembly.compile(await res.arrayBuffer()), e);
        this.buffer = m.buffer;
        this.HEAPU8  = new Uint8Array(m.buffer);
        this.HEAPU16 = new Uint16Array(m.buffer);
        this.HEAPF32 = new Float32Array(m.buffer);
        this.HEAPU32 = new Uint32Array(m.buffer);
        this.loading = false;
        return true;
    }
}

<|start_filename|>ipc-test/net/server.js<|end_filename|>
const { existsSync, unlinkSync } = require("fs");
const net = require("net");
const path = require("path");

const pipename = str => process.platform == "win32" ? `\\\\.\\pipe\\${str.replace(/^\//, "").replace(/\//g, "-")}` : str;
const SOCKET_FILE = path.resolve(__dirname, "unix.sock");

if (existsSync(SOCKET_FILE)) unlinkSync(SOCKET_FILE);

const memory = new WebAssembly.Memory({ initial: 200 });
const view = new Uint8Array(memory.buffer);

const server = net.createServer();

server.on("connection", sock => {
    console.log("Connection established");
    sock
        .on("data", buffer => {
            sock.write(view);
        })
        .on("error", () => console.log("Connection errored"))
        .on("end", err => console.log(`Connection ended`));
});

server.listen(pipename(SOCKET_FILE));

<|start_filename|>src/network/reader.js<|end_filename|>
module.exports = class Reader {
    /** 
     * @param {DataView} view
     * @param {boolean} le
     */
    constructor(view, le = true) {
        this.view = view;
        this.offset = 0;
        this.le = le;
    }

    get length() { return this.view.byteLength; }
    get EOF() { return this.offset === this.view.byteLength; }

    readUInt8() {
        return this.view.getUint8(this.offset++);
    }

    readInt8() {
        return this.view.getInt8(this.offset++);
    }

    readUInt16() {
        const a = this.view.getUint16(this.offset, this.le);
        this.offset += 2;
        return a;
    }

    readInt16() {
        const a = this.view.getUint16(this.offset, this.le);
        this.offset += 2;
        return a;
    }

    readUInt32() {
        const a = this.view.getUint32(this.offset, this.le);
        this.offset += 4;
        return a;
    }

    readInt32() {
        const a = this.view.getInt32(this.offset, this.le);
        this.offset += 4;
        return a;
    }

    readFloat32() {
        const a = this.view.getFloat32(this.offset, this.le);
        this.offset += 4;
        return a;
    }

    readFloat64() {
        const a = this.view.getFloat64(this.offset, this.le);
        this.offset += 8;
        return a;
    }

    /** @param {number} bytes */
    skip(bytes) {
        this.offset += bytes;
    }

    readUTF8String() {
        const chars = [];
        while (this.offset < this.view.byteLength) {
            const ch = this.readUInt8();
            if (!ch) break;
            chars.push(String.fromCharCode(ch));
        }
        return chars.join("");
    }

    readUTF16String(force_utf_8 = false) {
        const chars = [];
        if (force_utf_8) {
            while (this.offset < this.view.byteLength) {
                const ch = this.readUInt16();
                if (!ch) break;
                chars.push(String.fromCharCode(ch & 255));
            }
        } else {
            while (this.offset < this.view.byteLength) {
                const ch = this.readUInt16();
                if (!ch) break;
                chars.push(String.fromCharCode(ch));
            }
        }
        return chars.join("");
    }

    readBytes(length = 0) {
        const buffer = this.view.buffer.slice(this.offset, this.offset + length);
        this.offset += length;
        return buffer;
    }
}


<|start_filename|>webgl/game/fake-socket.js<|end_filename|>
module.exports = class FakeSocket {
    /** @param {MessagePort} port */
    constructor(port) {
        this.port = port;
        this.readyState = WebSocket.CONNECTING;

        port.onmessage = e => {
            const { data } = e;
            if (data.event === "message") {
                this.onmessage({ data: data.message });
            } else if (data.event === "error") {
                this.onerror({ message: data.message });
            } else if (data.event === "close") {
                this.onclose({ code: data.code, reason: data.reason });
            } else if (data.event === "open") {
                this.readyState = WebSocket.OPEN;
                this.onopen();
            }
        }
        port.start();
        this.onopen = this.onmessage = this.onerror = this.onclose = () => {};
    }

    /** @param {BufferSource} buffer */
    send(buffer) {
        this.port.postMessage({ event: "message", message: buffer }, [buffer]);
    }

    close() {
        this.port.postMessage({ event: "close", code: 1001, message: "Client closed connection" });
        this.port.close();

        this.readyState = WebSocket.CLOSED;
    }
}

<|start_filename|>webgl/game/texture-store.js<|end_filename|>
class TextureWrapper {
    /** @param {import("./texture-store")} store */
    constructor(store, key = "", persist = false) {
        this.store = store;
        this.persist = persist;
        this.key = key;
        /** @type {WebGLTexture} */
        this.tex = null;
        this.dim = [0, 0];
        this.ref = 1;
    }

    incre() { 
        if (this.persist) return;
        this.ref++;
        // console.log(`Incremented texture[${this.key}] to ${this.ref}`);
    }

    decre() { 
        if (this.persist) return;
        this.ref--;
        // console.log(`Decremented texture[${this.key}] to ${this.ref}`);
        if (this.ref <= 0) return this.destroy();
        return false;
    }

    destroy() {
        if (this.persist) return false;
        if (this.tex) {
            this.store.gl.deleteTexture(this.tex);
            this.tex = null;
            // console.log(`Destroyed texture[${this.key}]`);
            return true;
        } else return false;
    }
}

module.exports = class TextureStore {

    /** @param {import("./renderer")} renderer */
    constructor(renderer) {
        this.renderer = renderer;
        /** @type {Map<string, TextureWrapper>} */
        this.textures = new Map();
    }

    get gl() { return this.renderer.gl; }

    add(key = "", persist = false) {
        if (!key) return;
        if (this.textures.has(key)) {
            this.textures.get(key).incre();
            return false;
        } else {
            // console.log(`Added "${key}" to texture store`);
            this.textures.set(key, new TextureWrapper(this, key, persist));
            return true;
        }
    }

    get(key = "") { return this.textures.get(key); }

    /** @param {ImageBitmap} bitmap */
    setData(key = "", bitmap) {
        if (!bitmap) return;
        const T = this.get(key);

        if (T) {
            const gl = this.gl;
            gl.bindTexture(gl.TEXTURE_2D, T.tex = gl.createTexture());
            gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
            gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, bitmap);
            gl.generateMipmap(gl.TEXTURE_2D);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
            gl.flush();
            T.dim = [bitmap.width, bitmap.height];
        } else console.warn(`Trying to call store.setData on none-existing key: "${key}"`);

        bitmap.close();
    }

    replace(oldKey = "", newKey = "") {
        if (oldKey == newKey) return;
        if (this.textures.has(oldKey) && this.textures.get(oldKey).decre()) this.textures.delete(oldKey);
        return this.add(newKey);
    }

    clear() {
        // console.log("Clearing texture store");
        for (const [key, T] of [...this.textures.entries()]) 
            T.persist || (T.destroy(), this.textures.delete(key));
    }
}

<|start_filename|>src/game/controller.js<|end_filename|>

module.exports = class Controller {
    /** @param {import("../physics/engine")} engine */
    constructor(engine, id = 0) {
        this.engine = engine;
        this.id = id;
        this.__name = "";
        this.__skin = "";
        this.spawn = false;
        this.updated = false;
        this.__mouseX = 0;
        this.__mouseY = 0;
        this.lockDir = false;
        this.linearEquation = new Float32Array(3);
        this.ejectMarco = false;
        this.splitAttempts = 0;
        this.ejectAttempts = 0;
        this.lastPoppedTick = 0;
        this.lastEjectTick = 0;
        this.lastSpawnTick = 0;
        this.viewportX = 0;
        this.viewportY = 0;
        this.viewportHW = 0;
        this.viewportHH = 0;
        this.maxScore = 0;
        this.autoRespawn = false;
        this.kills = 0;
        this.score = 0;

        this.box = new Float32Array(4);

        /** @type {import("./handle")} */
        this.handle = null;
    }

    get alive() { return !!this.engine.counters[this.id].size; }
    get name() { return this.__name || "Unnamed"; }
    get skin() { return this.__skin; }

    get mouseX() { return this.__mouseX; }
    get mouseY() { return this.__mouseY; }
    set mouseX(v) { if (!this.lockDir) this.__mouseX = v; }
    set mouseY(v) { if (!this.lockDir) this.__mouseY = v; }

    set name(v) {
        if (v != this.__name) {
            this.__name = v.slice(0, this.engine.options.PLAYER_NAME_MAX_LENGTH);
            this.updated = true;
        }
    }

    set skin(v) {
        if (v != this.__skin) {
            this.__skin = v;
            this.updated = true;
        }
    }

    toggleLock() {
        this.lockDir ? this.unlock() : this.lock();
    }

    lock() {
        if (this.engine.counters[this.id].size != 1) return false;
        const cell = this.engine.cells[[...this.engine.counters[this.id]][0]];
        const x1 = this.mouseX, y1 = this.mouseY, x2 = cell.x, y2 = cell.y;
        this.linearEquation[0] = y1 - y2;
        this.linearEquation[1] = x2 - x1;
        this.linearEquation[2] = x1 * y2 - x2 * y1;
        // INVALID LINEAR EQUATION
        if (!this.linearEquation[0] && !this.linearEquation[1]) return false;
        this.lockDir = true;
        return true;
    }

    unlock() {
        this.lockDir = false;
    }

    reset() {
        this.__name = "";
        this.__skin = "";
        this.updated = false;

        this.handle = null;
        this.lockDir = false;
        this.splitAttempts = 0;
        this.ejectAttempts = 0;
        this.lastPoppedTick = 0;
        this.lastEjectTick = 0;
        this.lastSpawnTick = 0;
        this.viewportX = 0;
        this.viewportY = 0;
        this.viewportHW = 0;
        this.viewportHH = 0;
        this.maxScore = 0;
        this.kills = 0;
        this.score = 0;
        this.autoRespawn = false;
        this.box.fill(0);
    }

    afterSpawn() {
        this.ejectAttempts = 0;
        this.ejectMarco = false;
        this.lastPoppedTick = 0;
        this.lastEjectTick = 0;
        this.updated = false; // reset updated field after spawning
        this.lockDir = false; // reset line lock
        this.maxScore = 0;
        this.kills = 0;
        this.spawn = false;
        this.autoRespawn = false;
        if (this.handle) this.handle.spectate = null;
    }

    get canSpawn() {
        const e = this.engine;
        // Player requesting spawn OR player is dead and requested auto respawn
        return (this.spawn || (!this.alive && this.autoRespawn)) && 
            (e.__now <= e.options.PLAYER_SPAWN_DELAY || 
             e.__now >= this.lastSpawnTick + e.options.PLAYER_SPAWN_DELAY);
    }

    requestSpawn() {
        this.spawn = true;
        if (this.canSpawn) this.engine.delayKill(this.id, true);
    }
}

<|start_filename|>src/network/fake-socket.js<|end_filename|>
module.exports = class FakeSocket {
    /** @param {MessagePort} port */
    constructor(port) {
        port.ws = this;
        this.port = port;
        this.readyState = WebSocket.OPEN;

        port.onmessage = e => {
            const { data } = e;
            if (data.event === "message") {
                this.onmessage(data.message);
            } else if (data.event === "close") {
                this.onclose({ code: data.code, reason: data.message });
            }
        }

        port.start();

        this.subscribe = this.onmessage = this.onclose = () => {};
        /** @type {import("./protocol")} */
        this.p = null;
    }

    get ip() { return "127.0.0.1"; }

    getBufferedAmount() { return 0; }

    /** @param {BufferSource} buffer */
    send(buffer) {
        this.port.postMessage({ event: "message", message: buffer }, [buffer]);
    }

    end(code = 1006, reason = "") {
        this.port.postMessage({ event: "close", code, reason });
        this.port.close();
        this.onclose(code, reason);
    }
}

<|start_filename|>src/network/protocols/index.js<|end_filename|>
module.exports = [require("./ogarx")];

<|start_filename|>webgl/game/replay.js<|end_filename|>
const { saveAs } = require("file-saver");
const ReplayDB = require("./replay-db");
const CLIPS_PER_PAGE = 15;

module.exports = class ReplayMenu {

    /** @param {import("./hud")} hud */
    constructor(hud) {
        this.hud = hud;
        this.modal = UIkit.modal("#replay-modal");
        this.title = document.getElementById("replay-title");
        this.elem = document.getElementById("replay-list");
        UIkit.util.on("#replay-modal", "beforeshow", () => this.sync());

        this.page = 0;
        this.count = null;
        /** @type {string[]} */
        this.keys = [];
        /** @type {HTMLDivElement[]} */
        this.components = [];
    }

    async init() {
        this.db = await ReplayDB();
        this.registerEvents();
    }

    registerEvents() {
        this.uploadButton = document.getElementById("upload-replay");
        /** @type {HTMLInputElement} */
        const input = this.uploadButton.firstElementChild;
        input.addEventListener("change", async e => {
            /** @type {FileList} */
            const files = e.target.files;
            for (let i = 0; i < files.length; i++) {
                const file = files.item(i);
                try {
                    await this.upload(file);
                    this.hud.onSuccess(`Loaded replay from "${file.name}"`);
                } catch (e) {
                    this.hud.onError(`Failed to load replay from "${file.name}": ${e.message}`);
                }
            }
            await this.sync();
        });
        this.uploadButton.addEventListener("click", () => input.click());

        this.prevButton = document.querySelector("#prev-clip");
        this.prevButton.addEventListener("click", () => this.prevPage());
        this.nextButton = document.querySelector("#next-clip");
        this.nextButton.addEventListener("click", () => this.nextPage());
    }

    get canGoPrev() { return this.page > 0; }
    get canGoNext() { return this.page < Math.ceil(this.count / CLIPS_PER_PAGE) - 1; }
    get isOverPage() { return this.page >= Math.ceil(this.count / CLIPS_PER_PAGE) && (this.page > 0); }

    updateButtons() {
        if (this.canGoPrev) this.prevButton.classList.remove("disabled");
        else this.prevButton.classList.add("disabled");
        if (this.canGoNext) this.nextButton.classList.remove("disabled");
        else this.nextButton.classList.add("disabled");
    }

    prevPage() {
        if (!this.canGoPrev) return;
        this.page--;
        return this.sync(true);
    }

    nextPage() {
        if (!this.canGoNext) return;
        this.page++;
        return this.sync(true);
    }

    /** @param {File} file */
    async upload(file) {
        if (file.type != "image/gif") throw new Error("Invalid File Type");
        const buffer = await file.arrayBuffer();
        const view = new DataView(buffer);
        const packetByteLength = view.getUint32(buffer.byteLength - 4, true);
        const gifBytesLength = buffer.byteLength - packetByteLength - 4;
        if (gifBytesLength > buffer.byteLength) throw new Error("Unexpected Bytes");
        if (String.fromCharCode(view.getUint8(gifBytesLength - 1)) !== ";") throw new Error("Unexpected Bytes");

        await new Promise((resolve, reject) => {
            const id = Date.now();
            const size = buffer.byteLength;
            const thumbnail = new Blob([buffer.slice(0, gifBytesLength)], { type: "image/gif" });
            const replay = buffer.slice(gifBytesLength, buffer.byteLength - 4);
    
            const tx = this.db.transaction(["replay-meta", "replay-data"], "readwrite");
            const metaStore = tx.objectStore("replay-meta");
            const dataStore = tx.objectStore("replay-data");
    
            metaStore.add({ size, thumbnail }, id);
            dataStore.add(replay, id);

            tx.oncomplete = resolve;
            tx.onerror = reject;
        });
    }

    async sync(force = false) {
        const count = await this.countReplays();
        if (force || this.count !== count) {
            this.count = count;
            if (this.isOverPage) return await this.prevPage();
            await this.update();
        }
        this.updateButtons();
    }

    async update() {
        if (!this.count) {
            this.title.textContent = "Clips (empty)";
            this.components.forEach(c => c.remove());
            this.components = [];
        } else {
            this.title.textContent = "Clips";

            // Descending, from latest to oldest
            this.keys = await this.getAllKeys();
            const keysToRender = this.keys.slice(this.page * CLIPS_PER_PAGE, (this.page + 1) * CLIPS_PER_PAGE);

            this.components = this.components.filter(c => {
                if (!keysToRender.some(k => c.getAttribute("replay-id") == k)) {
                    c.remove();
                    return false;
                }
                return true;
            });

            for (const k of keysToRender) {
                if (!this.components.some(c => c.getAttribute("replay-id") == k)) {
                    const comp = this.createReplayComponent(k);
                    const c = this.components.find(c => ~~c.getAttribute("replay-id") < ~~k);
                    c ? this.elem.insertBefore(comp, c) : this.elem.appendChild(comp);
                    this.components.unshift(comp);
                }
            }
        }
    }

    play(id = 0) {
        this.modal.hide();
        this.hud.replay(id);
    }

    async fetch(id = 0, type = "image/gif") {
        const [meta, data] = await Promise.all([this.getReplayMeta(id), this.getReplayData(id)]);
        return new Blob([meta.thumbnail, data, new Uint32Array([data.byteLength]).buffer], { type });
    }

    async delete(id = 0) {
        await new Promise((resolve, reject) => {
            const tx = this.db.transaction(["replay-meta", "replay-data"], "readwrite");
            const metaStore = tx.objectStore("replay-meta");
            const dataStore = tx.objectStore("replay-data");
            metaStore.delete(id);
            dataStore.delete(id);
            tx.oncomplete = resolve;
            tx.onerror = reject;
        });

        this.sync();
    }

    async copy(id = 0) {
        const replayGIF = await this.download(id);
        const clipboardItemInput = new ClipboardItem({ 'image/gif' : replayGIF });
        await navigator.clipboard.write([clipboardItemInput]);
        this.hud.onSuccess("Clip copied to clipboard");
    }

    /** @returns {Promise<ReplayMeta>} */
    getReplayMeta(id = "") {
        return new Promise((resolve, reject) => {
            const objStore = this.db.transaction(["replay-meta"], "readonly").objectStore("replay-meta");
            const req = objStore.get(id);
            req.onsuccess = _ => resolve(req.result);
            req.onerror = reject;
        });
    }

    /** @returns {Promise<ArrayBuffer>} */
    getReplayData(id = "") {
        return new Promise((resolve, reject) => {
            const objStore = this.db.transaction(["replay-data"], "readonly").objectStore("replay-data");
            const req = objStore.get(id);
            req.onsuccess = _ => resolve(req.result);
            req.onerror = reject;
        });
    }

    /** @returns {Promise<string[]>} */
    getAllKeys() {
        return new Promise((resolve, reject) => {
            const objStore = this.db.transaction(["replay-meta"], "readonly").objectStore("replay-meta");
            const req = objStore.getAllKeys();
            req.onsuccess = _ => resolve(req.result.reverse());
            req.onerror = reject;
        });
    }

    /** @returns {Promise<number>} */
    countReplays() {
        return new Promise((resolve, reject) => {
            const objStore = this.db.transaction(["replay-meta"], "readonly").objectStore("replay-meta");
            const req = objStore.count();
            req.onsuccess = _ => resolve(req.result);
            req.onerror = reject;
        });
    }
    
    createReplayComponent(id = 0) {
        const div = document.createElement("div");
        div.classList.add("replay-item", "uk-inline", "uk-width-1-5@l", "uk-width-1-3@m", "uk-width-1-1@s");
        div.setAttribute("replay-id", id);

        this.getReplayMeta(id).then(meta => {
            const img = new Image;
            img.classList.add("replay-thumbnail");
            const url = URL.createObjectURL(meta.thumbnail);
            img.onload = () => URL.revokeObjectURL(url);
            img.src = url;
            div.appendChild(img);
        });

        const playContainer = document.createElement("div");
        playContainer.classList.add("uk-position-center");
        const playIcon = document.createElement("a");

        const trash = document.createElement("a");
        trash.classList.add("uk-position-small", "uk-position-bottom-right", "ogarx-icon");
        trash.setAttribute("uk-icon", "icon: trash; ratio: 1.5");
        trash.setAttribute("uk-tooltip", "Delete Clip");
        trash.addEventListener("click", e => {
            e.stopPropagation();
            this.delete(id)
                .then(() => this.hud.onSuccess("Clip Deleted"));
        });
        
        const download = document.createElement("a");
        download.classList.add("uk-position-small", "uk-position-bottom-left", "ogarx-icon");
        download.setAttribute("uk-icon", "icon: download; ratio: 1.5");
        download.setAttribute("uk-tooltip", "Download Clip");
        download.addEventListener("click", e => {
            e.stopPropagation();
            this.fetch(id)
                .then(blob => saveAs(blob, `ogarx-${id}.gif`))
                .then(() => this.hud.onSuccess("Downloading Clip"));
        });

        // Browser doesn't allow copying gif to clipboard bruh
        // const copy = document.createElement("a");
        // copy.classList.add("uk-position-small", "uk-position-top-right", "ogarx-icon");
        // copy.setAttribute("uk-icon", "icon: copy; ratio: 1.5");
        // copy.setAttribute("uk-tooltip", "Copy To Clipboard");
        // copy.addEventListener("click", e => {
        //     e.stopPropagation();
        //     this.fetch(id)
        //         .then(blob => {
        //             navigator.clipboard.write([new ClipboardItem({ [blob.type] : blob })]);
        //             this.hud.onSuccess("Clip Copied");
        //         });
        // });

        playIcon.classList.add("play-icon", "ogarx-icon");
        playIcon.setAttribute("uk-icon", "icon: play; ratio: 3;");
        playIcon.addEventListener("click", () => this.play(id));
        playContainer.appendChild(playIcon);

        const overlays = [download, trash, playContainer];

        overlays.forEach(e => e.style.opacity = 0);
        overlays.forEach(e => div.appendChild(e));

        div.addEventListener("mouseenter", () => overlays.forEach(e => e.style.opacity = 1));
        div.addEventListener("mouseleave", () => overlays.forEach(e => e.style.opacity = 0));

        return div;
    }
}

/** @typedef {{ date: number, thumbnail: Blob, size: number }} ReplayMeta */

<|start_filename|>src/physics/cell.js<|end_filename|>
const CELL_EXISTS = 0x1;
const CELL_UPDATE = 0x2;
const CELL_INSIDE = 0x4;
const CELL_DEAD   = 0x8;
const CELL_AUTO   = 0x10;
const CELL_REMOVE = 0x20;
const CELL_MERGE  = 0x40;
const CELL_POP    = 0x80;

const TYPES_TO_STRING = { 252: "Mother Cell", 253: "Virus", 254: "Pellet", 255: "Ejected" };

module.exports = class Cell {
    /**
     * @param {DataView} view 
     * @param {number} id
     */
    constructor(view, id) {
        /** @type {import("./quadtree").Node} */
        this.__root = null;
        this.view = view;
        this.id = id;
    }

    get x() {
        return this.view.getFloat32(0, true);
    }

    set x(value) {
        this.view.setFloat32(0, value, true);
    }

    get y() {
        return this.view.getFloat32(4, true);
    }

    set y(value) {
        this.view.setFloat32(4, value, true);
    }

    get r() {
        return this.view.getFloat32(8, true);
    }

    set r(value) {
        this.view.setFloat32(8, value, true);
    }

    get type() {
        return this.view.getUint8(12);
    }

    set type(value) {
        this.view.setUint8(12, value);
    }

    get flags() {
        return this.view.getUint8(13);
    }

    remove() {
        this.view.setUint8(13, CELL_EXISTS | CELL_REMOVE);
    }

    get exists() {
        return this.view.getUint8(13) & CELL_EXISTS;
    }

    get existsStrict() {
        const flags = this.flags;
        return (flags & CELL_EXISTS) && !(flags & CELL_REMOVE);
    }

    get isUpdated() {
        return this.view.getUint8(13) & CELL_UPDATE;
    }

    set updated(value) {
        value && this.view.setUint8(13, this.view.getUint8(13) | CELL_UPDATE);
    }

    get shouldAuto() {
        return this.view.getUint8(13) & CELL_AUTO;
    }

    get eatenBy() {
        return this.view.getUint16(14, true);
    }

    get age() {
        return this.view.getFloat32(16, true);
    }

    /** DEBUG STUFF */
    get positionString() { return `Cell#${this.id}[x: ${this.x}, y: ${this.y}]` }
    get flagsString() { 
        const s = TYPES_TO_STRING[this.type];
        return `Cell#${this.id}[type=${s ? `${s}(${this.type})` : `Player#${this.type}`},flags=${this.flags.toString(2).padStart(8, "0")}]`;
    }

    toString() {
        // if (!this.exists) return `Cell[None]`;
        const s = TYPES_TO_STRING[this.type];
        return `Cell#${this.id}[type=${s ? `${s}(${this.type})` : `Player#${this.type}`},x=${this.x.toFixed(2)},y=${this.y.toFixed(2)},r=${this.r.toFixed(2)},mass=${(this.r * this.r / 100000).toFixed(1)}k,flags=${this.flags.toString(2).padStart(8, "0")}]`;
    }
}