Skip to main content
CORO SOLTO: Treta Suprema — the canarinho, the game's mascot, spinning

What it is, and how to run it

CORO SOLTO: Treta Suprema (formerly CS BRASIL) is a browser FPS written in vanilla JavaScript on top of Three.js r160, in the style of Counter-Strike 1.6: rounds, bots, AWP, Tab scoreboard, voice radio. It runs from a link, with nothing to install.

The numbers below are not hand-written: they are regenerated by node tools/gen-docs.mjs from the code, and npm run docs:check (inside check:fast) fails the gate when any of them diverges from the tree. Before that, this page was aging at the very first commit — see what is generated, and what is not.

WhatHow muchWhere to check
Game code82,325 lines in 209 filesgit ls-files public/js/*.js | xargs wc -l
game.js10,190 lineswc -l public/js/game.js
main.js5,654 lineswc -l public/js/main.js
Weapons with GLB27git ls-files 'public/models/weapons/*.glb' | wc -l
Character GLBs74git ls-files 'public/models/characters/*.glb' | wc -l
Props in GLB274git ls-files 'public/models/props/*.glb' | wc -l
Versioned animation clips897git ls-files public/models/anims | wc -l
Playable characters63, in 7 groupsCHARACTERS array in characters.js
Maps in the menu14MAPS minus MAPAS_PARADOS
Visual harnesses in HTML26git ls-files 'public/*.html' | wc -l
Harness scripts1029git ls-files 'tools/eval/*.mjs' 'tools/eval/*.py' | wc -l
Pipeline scripts91git ls-files 'tools/*.mjs' | wc -l
Written entry tasks27git ls-files 'docs/issues/[0-9]*.md' | wc -l
Version2.1.0-alpha.145public/js/version.js and package.json (match)

Block generated by node tools/gen-docs.mjs. Source: the command in the right column of each row

And the match rules that move around the most, all read from the constants in public/js/game.js:

RuleValueConstant
Groups · characters7 · 63 (B 9 · C 9 · E 8 · F 9 · M 9 · P 10 · U 9)CHARACTERS
Maps in the menu14 - 4 open in rounds, 10 in captureMAPS minus MAPAS_PARADOS
Respawn2.2 sRESPAWN_DELAY
Round99 s, 3 winsROUND_TIME / ROUNDS_TO_WIN
Capturetarget = all flags on the map, 2 rounds (480 s safety net)capsToWin = ctfPts.length / CTF_ROUNDS_TO_WIN
Health regenerationOFF - ?regen=1 turns it back onREGEN
Ranking / /u/ pagesOFF - controlled by one flagRANKING_ON in src/lib/site.ts

Block generated by node tools/gen-docs.mjs. Source: constantes de public/js/game.js · RANKING_ON de src/lib/site.ts

The menu accepts from 2×2 to 8×8 bots (the engine accepts 1 to 8 per side); the default is 4×4.

Two of these are a recent choice, not a defect

Health regeneration was turned off on 05/08 (REGEN = QS.get('regen') === '1'). It existed, CoD-style — 6 s without taking damage and 22 HP/s — and the owner reported it as a bug ("the 1st player's health goes back to 100, I don't know why") precisely because it was invisible: no icon, no sound, no line in the settings. A rule the player does not notice is indistinguishable from a defect. It remains fully intact behind ?regen=1, with player↔bot symmetry. Whoever turns it back on must ship the feedback along with it — and solve what it had been papering over: with no healing, medkit, or armor, every life after first contact was already lost.

The ranking was turned off and replaced with anonymous telemetry. /ranking and /u/* respond 200 with a notice + noindex (not 404 — the URLs are indexed and will come back), and /api/leaderboard responds {disabled:true}.

The gate is NOT green, and that is declared

How many invariants pass is not derivable from the code — it is the result of a run, and it even depends on which inputs exist on the machine. That is why that scoreboard is not repeated here: it lives in the header of KNOWN-BUGS.md, pasted from a real run, with the list of red ones, root cause, and arquivo:linha for each one. That is the file maintained day by day.

For today's state, run — do not repeat a number from memory:

npm run eval:vm && node tools/eval/invariants.mjs --json # 10-12 min

Order matters: a viewmodel invariant measured with yesterday's JSON invents a red (see How to contribute).

Run it in 3 commands​

git clone https://github.com/corosolto/client.git && cd client
npm install
npm run dev # opens http://localhost:4321 — this page IS the game

The audio pack (npm run fetch-audio) is optional: without it the game uses synthesized sounds. The public/audio/ folder is not versioned.

Linux, WebGL, and compatibility mode​

The game tries WebGL2 and WebGL1, starting with the browser default and reducing antialiasing, GPU preference, and stencil before giving up. WebGL1, llvmpipe/SwiftShader, or another degraded tier automatically uses low quality for that session: DPR 0.75, no bloom or shadows, and static portraits in character selection.

Use ?safe=1 to prioritize WebGL1 and the lowest-cost path. If it still cannot start, inspect chrome://gpu or the Graphics section in about:support, enable hardware acceleration, and update Mesa/the graphics driver through your distribution. A web page cannot force a driver after the browser refuses to create even a WebGL1 context.

Alternative without Astro (zero build dependency)​

The evaluation harness ships a 24-line static server that serves public/ and maps / to the source of the game page:

node tools/eval/serve.mjs 8123 # http://localhost:8123

It exists exactly because src/pages/index.astro is pure HTML — you can serve the raw file without going through Astro (tools/eval/serve.mjs:15).

The gotcha that costs everyone their first hour​

There is no public/index.html. Serving the public/ folder statically gives you a directory index with eval.html, mapview.html and company — none of them is the game. The game's HTML is src/pages/index.astro, served at the root route by Astro. There is no /game route.

The independent confirmation is in the harness itself: tools/eval/serve.mjs:15 needs a special case if (p === '/') that reads src/pages/index.astro from disk, precisely because there is no index.html in public/ to serve.

This section used to be a list of README errors

Until 04/08/2026 it existed because the root README.md told you to run cd public && python3 -m http.server and spoke of a "game at /game/". Both lines were fixed — today's README says the right thing. What remains is the fact itself, which is still the first stumbling block for anyone arriving.

The real repository structure​

Two code zones and a third zone that is the reason this doc exists (the harness):

No counts here: the tree says what each thing is, and the numbers live in the generated table up top. Mixing the two is how the hand-written ARCH.md was born wrong.

public/ THE GAME — vanilla ES modules, ZERO build
js/
game.js the Game class (loop, bots, shooting, HUD) — the largest file in the repo
main.js menu, DOM wiring, persistence
vmattach.js springs.js weapons.js fparms.js handik.js viewmodel/weapons
maps.js the map REGISTRY (what is not here is not playable)
map_brasilia.js map_piscina.js map_havan.js
map_ferrovelho.js map_quebrada.js the registered maps
map_piscinao_ramos.js "Piscinão" — exists on disk, OUTSIDE the registry
mapprops.js map_decals.js props and graffiti
bloom.js textures.js vao.js stylize.js gpuparticles.js graphics/FX
characters.js glbchars.js characters
audio.js version.js site-bg.js
models/ weapons, characters, props and animation clips in GLB
vendor/ vendored Three.js (no CDN, no npm at runtime)
style.css the entire HUD
*.html visual harnesses (eval, mapview, weapontest, vm-inspect…)

src/ THE SITE (Astro + Vercel adapter)
pages/index.astro ⚠ THIS IS THE GAME (HTML + import map + HUD)
pages/sobre.astro landing/FAQ with JSON-LD
pages/personagens.astro como-jogar.astro ranking.astro mapa.astro
pages/u/[...path].astro public profile
pages/api/*.ts SSR: leaderboard, submit-match, register, badge, avatar
layouts/Layout.astro the site shell (not the game's)
lib/ supabase, svg, geo, fmt

tools/
eval/ THE HARNESS — rulers, gate and probes. See "Quality gates"
invariants.mjs the gate
ref-measure.py measures the reference frames (the house doctrine)
harness.mjs boots the real Game in node with stubbed DOM
ARCH.md BAR.md conflict map (generated) and the visual ruler
gen-arch.mjs generates and VALIDATES ARCH.md
gen-docs.mjs generates and VALIDATES the numeric blocks of this documentation
gen-asset.mjs generates a 3D prop from text (Tripo/Meshy)
gen-image.mjs generates 2D art from text (OpenRouter)

(database: schema/migrations are PRIVATE — outside the repo)
.github/workflows/ci.yml the gate running in CI

The maps registered today, and which mode each one opens in:

IdMenu nameOpens inFile in public/js/Lines
amazoniaTreta na Amazôniacapture--
escadaoEscadão (Morro)capture--
praca_poderesPraça dos Três Poderesrounds--
piscina_tretaPiscina da Tretarounds--
loja_hLoja H (Estacionamento)capture--
ferro_velhoFerro Velho do Zécapture--
quebradaQuebrada (Rua do Baile)capture--
corregoCórrego (Favela de SP)capture--
lajesLajes (Comunidade)capture--
posto_tretaPosto da Tretacapture--
upa_24hUPA 24h da Tretacapture--
velho_oesteSertão da Tretacapture--
invasao_javaporcosCidade Invadidarounds--
operacao_flavioOperação Fláviorounds--

14 maps in the menu - 4 open in rounds and 10 in capture. ctfMode sets the initial mode; it does not lock it. There are 26 map_*.js files on disk; entries in MAPAS_PARADOS stay out of the menu.

Block generated by node tools/gen-docs.mjs. Source: objeto MAPS menos MAPAS_PARADOS de public/js/maps.js

The two zones​

In one line each: public/ is the game (vanilla, ES modules, no framework and no bundler) and src/ is the site (Astro with SSR, where frameworks are welcome). What each boundary rule pays for, and why it is hard, is in Stack and tooling — a single page, so that there are no two versions of the same boundary.

What you need to know before editing is the consequence: the game is loaded by the Astro page through an import map versioned by content hash (src/pages/index.astro).

Preserve the published manifest

scripts/module-cache.mjs hashes the published modules under public/js/, and the import map applies that revision to the entire graph. Do not bump it manually or include benches removed by scripts/prune-dist.mjs. npm run eval:shaderbudget (SB7) checks both properties.

Commands you will use​

npm run dev # site + game (Astro, :4321) — the / route IS the game
npm run build # dist/client + dist/server
npm run eval:vm # viewmodel framing — RUN BEFORE the invariants
npm run eval:invariants # the invariants — pure node, 10-12 min
npm run eval:bots # botsim 60 s per map, fixed seeds
npm run eval:mat # material/light/fog/texture on the maps
npm run docs # regenerates the numeric blocks of this documentation
node tools/eval/serve.mjs 8123 # static server without Astro

And the two gates, with the exact list of what each one runs — straight from package.json:

npm run check:fast # node tools/eval/runner.mjs --suite=fast

package.json has 806 scripts; the reason behind each one lives in SCRIPTS.md.

Block generated by node tools/gen-docs.mjs. Source: node -p "Object.keys(require('./package.json').scripts)"

npm run check is the same set the CI runs in .github/workflows/ci.yml.

Use check:fast in the loop, check before the PR

check takes 10-12 min because it boots the game five times. check:fast covers the rulers (quality gates) that were born from the most recent bugs (pause menu, capture round, regeneration, animation manifest) and runs in about a minute.

Where to go now​

The sidebar order is the reading order, and each page delivers one thing:

  1. Stack and tooling — what this is built with, with the declared version of each piece. It is where the public/ × src/ boundary is explained in full.
  2. AI instrumentation — how the work gets done here. If you have never collaborated with agents in a repository, start with this one.
  3. The gate — what an invariant is, how to write one, the two house laws, and the mutation test of the ruler itself. It is the most useful page on the site.
  4. Architecture — how N agents edit the same file without colliding, and the conflict table. Read it before touching game.js.
  5. How to contribute — what a PR needs to get in, and the first-contribution tasks already written in docs/issues/ (with a ready-made abrir-issues.sh — they have not been opened on GitHub yet).
  6. License — the repo-root LICENSE declares it (AGPL-3.0 today); the surfaces that repeat its name and must change together are listed in CONTRIBUTING.md.
  7. Current state — live sources for production health, data coverage, and debt since the last pasted measurement.

Where the project is going is not in this documentation: it is ROADMAP.md, and the executable plan is the release plan plans/08.