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 code31,956 lines in 44 filesgit ls-files public/js/*.js | xargs wc -l
game.js6,894 lineswc -l public/js/game.js
main.js2,680 lineswc -l public/js/main.js
Weapons with GLB26git ls-files 'public/models/weapons/*.glb' | wc -l
Character GLBs45git ls-files 'public/models/characters/*.glb' | wc -l
Props in GLB108git ls-files 'public/models/props/*.glb' | wc -l
Versioned animation clips573git ls-files public/models/anims | wc -l
Playable characters44, in 5 factionsCHARACTERS array in characters.js
Maps in the registry12MAPS object in maps.js
Visual harnesses in HTML15git ls-files 'public/*.html' | wc -l
Harness scripts194git ls-files 'tools/eval/*.mjs' 'tools/eval/*.py' | wc -l
Pipeline scripts54git ls-files 'tools/*.mjs' | wc -l
Written entry tasks26git ls-files 'docs/issues/[0-9]*.md' | wc -l
Version2.0.0-alpha.172public/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
Factions · characters5 · 44 (B 9 · C 9 · E 8 · F 9 · U 9)CHARACTERS
Maps in the menu12 - 2 open in rounds, 10 in captureMAPS / ctfMode
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/rubenmarcus/csbrasil.git && cd csbrasil
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
praca_poderesPraça dos Três Poderesroundsmap_brasilia.js1,830
piscina_tretaPiscina da Tretaroundsmap_piscina.js810
loja_hLoja H (Estacionamento)capturemap_havan.js1,964
ferro_velhoFerro Velho do Zécapturemap_ferrovelho.js1,888
quebradaQuebrada (Rua do Baile)capturemap_quebrada.js1,599
posto_tretaPosto da Tretacapturemap_posto.js489
upa_24hUPA 24h da Tretacapturemap_upa.js288
obras_prefeituraObras da Prefeituracapturemap_obras.js240
atacadao_tretaAtacadão da Tretacapturemap_atacadao.js255
parque_tretaParque da Tretacapturemap_parque.js402
velho_oesteVelho Oeste da Tretacapturemap_velho_oeste.js433
penitenciariaPenitenciária da Tretacapturemap_penitenciaria.js247

12 registered maps - 2 open in rounds and 10 in capture. ctfMode sets the initial mode; it does not lock it. There are 14 map_*.js files on disk, so a file alone does not make a map playable.

Block generated by node tools/gen-docs.mjs. Source: objeto MAPS 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 syntax eval:release eval:telemetry eval:identity eval:error-console eval:error-origin eval:webgl eval:webglguard eval:maprotate eval:shaderlog eval:shaderbudget eval:botbrain eval:prune eval:vminspect eval:faccao eval:mapid eval:mapjson eval:mapcontrato eval:pickuparma eval:parquewheel eval:redesign eval:matchoptions eval:charvoice eval:screenquery docs:check arch:check audio:check feet:check eval:vmlabhud eval:ctfhud eval:pause eval:ctfround eval:ctfwin eval:spawn eval:regen eval:pegada eval:dmgdir eval:ctflabels anims:check anims:merge:check walls:check media:check menuwalls:check travessao:check eval:medianet eval:posters eval:grafitelayout eval:simclock eval:backendhints changelog:check eval:velhooeste eval:penitenciaria eval:comentario eval:fixture eval:preload eval:docsautoria eval:replaycam

package.json has 118 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 docs/ROADMAP.md, and the executable plan is plans/08.