BeamNG.Earth
BeamNG.drive용 1:1 비율 실시간 지구, 실제 고도·위성 이미지·부동 원점·보조 서버에서 LOD 스트리밍
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기Quake simulated and rendered inside the Firebird SQL database, running in your browser on Firebird 6 compiled to WebAssembly
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
Quake, simulated and rendered inside the Firebird SQL database, running entirely in your browser on Firebird 6 compiled to WebAssembly. A port of the idea behind Firebird DOOM to a true 3D engine. The series went on: Firebird Quake 2 (play) and Firebird Quake III Arena (play), with Bézier patches, MD3 player models and deathmatch bots that think in SQL.README 전문 보기 ↗
Quake, simulated and rendered inside the Firebird SQL database, running entirely in your browser on Firebird 6 compiled to WebAssembly. A port of the idea behind Firebird DOOM to a true 3D engine.
Play it: https://mariuz.github.io/firebird-quake/
The series went on: Firebird Quake 2 (play) and Firebird Quake III Arena (play), with Bézier patches, MD3 player models and deathmatch bots that think in SQL.
More pictures, of every level of the first two episodes, the start of the third and the registered monsters, with the commands that made them, are in docs/screenshots.md. The long account of how it is built is docs/ARCHITECTURE.md; what is still missing is docs/ROADMAP.md.
Every game tic is a PSQL procedure call. Every frame is a SELECT. JavaScript handles the keyboard,
the mouse and the canvas; everything else — collision against the BSP hulls, the player's physics,
doors, platforms, triggers, items, weapons, damage, the monster AI, and the visibility and projection of
every polygon on screen — happens in SQL.
keyboard/mouse → SELECT * FROM quake_tic(...) game logic: 20 Hz, PSQL
→ SELECT * FROM frame_faces_fast the faces on screen this frame (SQL picks, JS projects)
→ SELECT * FROM frame_ents alias models and sprites in the PVS, with their pose
→ SELECT * FROM frame_lightstyles this frame's light animation
→ SELECT ... FROM sound_events what to play, and where
→ JS rasterises polygons and models through the colormap → canvas
npm install
npm run fetch-pak # downloads the Quake shareware episode (quake106.zip) and extracts id1/pak0.pak
npm run serve # http://localhost:8080/ — add --coi if your browser blocks service workers
fetch-pak needs a tool that can read the LHA archive inside the shareware zip: 7-Zip, lha or
lhasa (sudo apt-get install lhasa on Debian/Ubuntu). If you own Quake, copy your pak0.pak with
PAK=/path/to/pak0.pak npm run fetch-pak and put pak1.pak beside it in public/pak/, or pick the
two files together in the page's file picker. With pak1.pak loaded the page offers the other three
episodes and the registered monsters (enforcer, hell knight, vore, spawn, rotfish, Shub-Niggurath), and
the screenshot tool can draw them.
LibreQuake is free game data for the Quake engine
(BSD-3-licensed models, textures, sounds and maps that keep Quake's file layout). The page and the
tools take it as an alternative to the shareware episode, and its pak1.pak as a free stand-in for
the registered one:
npm run fetch-librequake # downloads LibreQuake lite (v0.09-beta, 56 MB) into public/pak/lq1/pak0.pak and pak1.pak
The page's Data selector then offers LibreQuake (its own levels, lq_e0m1 onwards, with its
monsters and weapons) and Quake shareware + LibreQuake monsters: the shareware episode with
LibreQuake's pak1.pak beside it, which gives the enforcer, hell knight, vore, spawn, rotfish and
Shub-Niggurath models and sounds without the registered data. The deployed site serves both.
LibreQuake's models carry no frame names, only numbers, while the game picks animations by name
(run, pain, death…). They keep Quake's frame order, since progs.dat addresses frames by
number, so the loader applies the id models' frame layouts (src/framelayouts.js, generated by
npm run gen-frame-layouts from a pak with named frames) to any model whose frames are unnamed.
LibreQuake's own progs.dat runs too, in QuakeC mode (the Logic setting): FTEQCC overlaps the
locals of its functions, which the VM saves and restores around their calls as Quake's does.
The QuakeC VM runs a mod's own progs.dat. The page's Mod control lays one over the game data as
Quake lays a game directory over id1: a mod's zip as released (with its loose files, its pak files, or
both) or its pak files, picked from your disk, or FrikBot X, which the site serves. A mod with its own
progs.dat runs in QuakeC mode, and the Impulse box beside the control sends any impulse, as a key
bound to it would. With FrikBot X, start a deathmatch or coop game and send impulse 100 to add one of
its bots beside ours (101 adds one on the other team, 102 removes one).
npm run fetch-mods # FrikBot X into public/pak/mods/ (the site serves it); two more mods into mods/ for the test
npm run test:mod # the three mods' progs.dat files in QuakeC mode, FrikBot X's bots in a deathmatch
The page runs Firebird WASM's build without threads, which needs no SharedArrayBuffer, so no
cross-origin isolation and no reload on the first visit. FB_THREADS=1 npm run build ships the threaded
build instead, which does: the dev server sends the COOP/COEP headers with --coi, and on a static host
like GitHub Pages coi-serviceworker.js re-issues responses with them after a one-time reload. The build runs the schema once and ships the database as an image (schema-<hash>.fdb.gz, 0.7 MB) that the page opens instead of compiling the PSQL, and the page keeps the paks in the browser's cache after the first visit, so a revisit is playing in about three seconds. Node 20 or later is required for the
scripts.
Click the view to capture the mouse.
| keys | |
|---|---|
W A S D or the arrows | move (always running; Shift walks) |
mouse, PgUp/PgDn | look |
Ctrl, F or click | fire |
Space or E | jump, or swim up |
1–8, / or the wheel | choose a weapon, cycle weapons |
9 | all weapons and ammo (impulse 9) |
P | pause |
F6, F9 | quick save, quick load |
` | Quake's console: map e1m3, god, noclip, give s 100, kill, color 4 13, bind k +jump, alias, exec default.cfg, sql SELECT …, help for the rest; what it prints shows over the view for three seconds |
| Game setting | single player, or deathmatch or coop (QuakeC mode, progs.dat's rules) against 1 to 7 bots played by PSQL, which learn the level from where you and they go (FrikBot's waypoints) and run its ways to the pickups; the menu's Multiplayer starts a deathmatch |
| Demo buttons | record from this moment (as Quake's record: a demo begun mid-level carries a save of where it starts), play it back, save or open a .dem.json file |
| Save buttons | download the last game saved or loaded as a .sav.json file, or load one (saves otherwise live in the browser) |
Esc | Quake's menu: new game, load and save (twelve slots), options (customize controls, mouse speed, always run, volumes, screen size…), help |
Every key is a binding, as in Quake: the menu's Customize controls or the console's bind changes them, and the page keeps them.
On a phone or a tablet the controls appear with the first touch: a stick where the left thumb lands (as far as it is pushed, as fast), the right side of the view to look by dragging (a quick tap fires), buttons to fire, jump, change weapon, bring up the menu and go full screen, and arrows, OK and back to drive the menu.
The page's settings: the map (every .bsp in the pak), the skill, the detail
(640×400, 320×200 or 160×100), the liquids (opaque as in Quake, or translucent where the map was vised
for it, as LibreQuake's are, at the alphas the map asks for, or the console's r_wateralpha, r_lavaalpha…), the renderer mode (below), the sound volume and the music. Quake's
music was CD audio and is not in the pak: put track02.ogg…track11.ogg (or .mp3) in
public/music/, or pick that folder in the page, and each map plays its worldspawn track; without
them a synthesised drone fills in, or turn it off.
The page has a console onto the live game database. Everything is a table, so everything can be read or changed while playing:
SELECT * FROM player;
SELECT id, mtype, st, health, enemy_id FROM ents WHERE mtype IS NOT NULL ORDER BY st;
SELECT classname, COUNT(*) FROM ents GROUP BY classname ORDER BY 2 DESC;
SELECT FIRST 40 * FROM frame_faces_fast; -- what the painter is about to draw
EXECUTE PROCEDURE player_impulse(9); -- all weapons
SELECT * FROM spawn_monster('shambler', 300); -- one, 300 units ahead
UPDATE ents SET health = 1 WHERE mtype IS NOT NULL AND health > 0;
UPDATE ents SET flags = BIN_OR(flags, 64) WHERE classname = 'player'; -- god mode
Ctrl+Enter runs; ↑ and ↓ walk the statements run before (kept across reloads); Tab completes a
table, column, procedure or function name, or a keyword, and cycles the matches. window.quake
exposes the database, the renderer, the audio and the settings to the browser's own console as well.
So that the list of ideas stays about this project:
.dem.json, .sav.json),
not Quake's .dem and .sav; there is no QuakeWorld physics or client prediction.npm run perf).sql/schema.sql, src/loader.js)A BSP file is a relational database in disguise. loader.js parses it (src/bsp.js) and copies it in,
denormalised so the hot loops never need a second lookup:
| table | from |
|---|---|
faces | FACES + PLANES (flipped for side) + TEXINFO, plus a bounding sphere |
face_verts | EDGES and SURFEDGES resolved to an ordered vertex list per face |
leaves | LEAVES with the PVS decompressed to a hex string (leaf j visible ⇔ bit j−1) |
marksurfaces, hulls | MARKSURFACES; NODES (hull 0) and CLIPNODES (hulls 1 and 2), planes copied in |
models, anims | the world, its submodels (*N), the b_*.bsp boxes, every .mdl with its frame runs |
map_ents | the entity lump, which SPAWN_MAP_ENTS turns into rows of ents |
ents is the edict table: one wide row per entity with its origin, velocity, angles, bounding box,
model, frame, health, flags, think time, movement state and targets — the fields of entvars_t.
The WASM build binds parameters as text, so each table has a generated LOAD_<table> procedure that
parses 30 KB chunks of fixed-width fields in PSQL (no separators to search for). E1M1 (70 k rows)
loads in about 1.2 seconds.
sql/physics.sql)RHC is SV_RecursiveHullCheck: it splits the segment at every plane it crosses and recurses into both
sides, threading the trace state (allsolid, startsolid, inopen, inwater, the fraction and the hit
plane) through its parameters, because PSQL procedures can call themselves. TRACE_MOVE picks the hull
by the moving box's size, runs it against the world and against every brush-model entity at its own
origin, and clips against monsters and the player with a Minkowski slab test. FLY_MOVE, WALK_MOVE
(with the 18-unit step), MOVE_STEP (monsters), TOSS_MOVE and PUSH_MOVE (doors crushing and
carrying) are sv_phys.c; the clip planes and the pushed entities live in global temporary tables
because PSQL has no arrays. A primary-key lookup costs about 4 µs in the WASM engine, so a trace is
well under a millisecond.
sql/game.sql and the files after it, sql/weapons.sql, sql/monsters.sql)QUAKE_TIC runs the player (client.qc: water, jumping, the water jump, friction, acceleration, the
trigger and item touches, powerups, drowning), the pushers (with SUB_CalcMove semantics), the thinks
that are due, and the physics of everything that flies, bounces or falls. Doors link by touching boxes
and need keys; platforms, buttons, trains with path corners and secret doors move as in doors.qc and
plats.qc. Items and weapons from the axe to the thunderbolt, armour, damage momentum, gibs and backpacks
are items.qc, weapons.qc and combat.qc. The monsters run ai.qc's state machine — FIND_TARGET,
MOVE_TO_GOAL, NEW_CHASE_DIR, CHECK_ATTACK — with each monster's attacks and sounds defined in
monster_types (its branches on a monster's name are the zombie's pain, the spawn's explosion and the two bosses): the grunt, dog, knight, ogre, scrag, fiend, zombie, shambler
and Chthon of the shareware episode, and the enforcer, hell knight, vore, spawn, rotfish and
Shub-Niggurath of the registered one. Per-map gravity (Ziggurat Vertigo), the level's worldspawn
message and type, the intermission and the finale live in game. Everything the simulation wants heard
is a row in sound_events; visual effects are rows in fx_events.
sql/qcvm.sql)The game above is a rewrite of progs.dat in PSQL, so mods do not run. The start of the faithful route
is a QuakeC virtual machine in PSQL: src/progs.js loads progs.dat (statements, functions, global
and field definitions, strings, the global image) into tables, and QC_CALL is PR_ExecuteProgram
with one branch per opcode, the locals saved on a stack table, parameters copied into the callee's
locals, recursion for calls, entity fields as rows addressed by OP_ADDRESS, and forty of the
builtins (makevectors, setorigin, spawn, find, traceline against the loaded world, ftos,
lightstyle, the prints into qc_log...). It runs the shareware progs.dat: qc_spawn_map spawns
E1M1 through its 336 spawn functions in a second, and qc_frame runs StartFrame and the due
thinks, so items place themselves, monsters stand and doors link; qc_client_connect and
qc_player_frame run the player's own code, so the shotgun fires through FireBullets and a box of
shells is picked up through ammo_touch.
QuakeC mode (qc_enter) puts the original game on the engine: every edict's engine fields (origin,
velocity, angles, box, solid, movetype, flags, frame, model…) live in its ents row, the VM's field
access is routed there, and qc_server_frame is Host_ServerFrame: the client's friction and
acceleration, StartFrame, then each edict's physics by movetype (pushers on their own clock, toss
and missiles, falling monsters, the player's walk with its step), the touches delivered to QuakeC
through the same traces and pushers the PSQL game uses. A frame of E1M1 takes about 40 ms.
node scripts/screenshot.mjs e1m1 out --qc --at=1150,1030,-250,330 --tics=4 --single --fast renders a
QuakeC-mode frame. In the page, the Logic setting switches between the PSQL game and
QuakeC VM (progs.dat): the level is spawned by progs.dat, every tic is qc_tic, which takes
quake_tic's input and returns its row from the QuakeC player's fields (centre prints, messages, the
pickup flash, damage, the intermission's stats screen, the episode's finale text and changelevel
included), and a level change carries the
player's parms (SetChangeParms, DecodeLevelParms) as Quake does. The monsters see the player
through checkclient (the PVS from eye to eye), walk and chase through walkmove and movetogoal
(the engine's SV_movestep family), and the gunshots, blood, explosions and lightning they and the
player make arrive as effects from the progs' temp-entity messages. The interpreter runs a statement in
about 13.5 µs (one joined query and one UPDATE); on top of it, QuakeC is compiled to PSQL
(src/qcjit.js): the functions a level calls most become stored procedures of their own, with the
temporaries, locals and parameters in PSQL variables, constants as literals and jumps as nested
labelled loops, a few between frames while the game runs. The compiled code runs about six times
faster than the interpreter, and a server frame of E1M1 takes 15 to 22 ms with the monsters asleep
and 21 to 26 ms with grunts awake (25 to 44 interpreted: npm run bench:qc, QCJIT=all to compile
everything first), against 5 to 10 ms for the PSQL game's tic; what is left is mostly the engine's
own work (the traces of the monsters' steps, the client's physics).
sql/render.sql)FRAME_FACES finds the leaf the eye is in and, once per leaf, marks every face of every leaf in its PVS
into vis_faces (Quake's visframe). Visible brush models are marked too, at their origin. One cursor
then joins the marked faces to their vertices, dropping back faces and faces whose bounding sphere is
outside the frustum in the WHERE clause and computing the view transform, the projection and the texel
coordinates in the select list — evaluated by the engine rather than as PSQL statements, which cost
several times more. Edges that cross the near plane are clipped by the painter in view space; the
(face, seq) primary key yields each polygon's vertices in order, so no sort is needed.
FRAME_ENTS lists the alias models and sprites whose leaves are in the PVS.
Two renderer modes are selectable in the page. SQL picks faces, JS projects (the default,
FRAME_FACES_FAST): SQL does the visibility work — PVS marking, back faces, frustum — and emits one row
per visible face; the painter transforms the vertices it already holds from the BSP. SQL projects every
vertex (FRAME_FACES): the view transform and projection happen in the select list too, one row per
polygon vertex. Both paint the same pixels (the headless screenshot tool checks with --compare); the
first costs about a quarter of the second, since a frame is ~400 face rows instead of ~2000 vertex rows.
src/renderer.js)An 8-bit framebuffer of palette indices and a z-buffer, like Quake's. Polygons are scan-converted
with perspective-correct spans (1/z, s/z, t/z) over a surface cache: the miptex tiled under the face's
lightmap, run through the colormap — r_surf.c. Alias models are drawn with the lightmap value under
the entity and Gouraud light from the vertex normals, clipped against the near plane triangle by
triangle; sprites are billboards; the sky is Quake's two scrolling layers mapped by the pixel's
direction; explosions and blood are particles. The status bar comes from gfx.wad.
src/audio.js)sound_events rows are played with the Web Audio API, attenuated and panned from where they
happened. Entity ambients (drips, hums, torches) loop at their entities; the water and wind ambients come
from the BSP leaf the player is in, which QUAKE_TIC reports each frame. The music is the CD track
named by the map's worldspawn, from the files described above.
Each test boots the real Firebird WASM engine under Node, loads a level from the pak, and plays a
scene through QUAKE_TIC, asserting on the tables afterwards. They are the regression suite for the
physics, the movers and the AI, and they run in CI before every deploy.
| command | what it plays |
|---|---|
npm test | SQL smoke test: every procedure compiles, E1M1 loads, tics and frames run, every sound the game queued exists in the pak; the PSQL player's movement as Quake's (wall friction, SV_CheckStuck, the swim's step, the jump after the input, SV_AirMove's input), T_Damage in god mode, a thrown thing's 2000 cap (PAK=… PAK1=… node scripts/sql-smoke.mjs start runs it over LibreQuake, as CI does) |
npm run test:boss | E1M7: the rune wakes Chthon, he rises and throws lava, the lightning does nothing until both terminals are up, three bolts kill him, the exit opens |
npm run test:skill | the skills: each of the start map's halls sets its skill, nightmare's monsters attack without waiting and flinch at most every five seconds (two hits make a knight flinch twice on normal, once on nightmare), and QuakeC's localcmd("skill 3") sets it in QuakeC mode |
npm run test:save | save games: E1M1 saved mid-play and loaded back exactly (every entity, the client, the totals, the light styles); exported as the page keeps it and as a downloaded file (another data set's file refused), loaded onto E1M1 loaded again with other brush model ids; the same in QuakeC mode with progs.dat's globals, fields and run-time strings; no saving dead or in the intermission |
npm run test:water | see-through liquids (the Liquids setting): over lq_e0m2's water the floor shows through at alpha 0.5; over E1M1's slime, which id's vis tools did not see through, nothing changes; lq_e0m4's lava stays opaque as its own lavaalpha 1 asks; the alphas per kind (the map's keys, then r_lavaalpha, r_slimealpha, r_telealpha, else the water's); from under E1M1's water the surface's underside is in the frame |
npm run test:bsp2 | the BSP2 and 2PSB formats of larger modern maps: E1M1 rewritten in both loads to the same rows, plays the same 150 tics and draws the same faces as the original |
npm run test:fire | the shambler's lightning on sham_magic6, 9 and 10 (and 11 on nightmare) with magic7 and 8 skipped, and the lava fireballs' throw, with misc.qc's speed bug for spawners without a speed key |
npm run test:hazard | slime and lava as Quake's WaterMove hurts: slime 4 × waterlevel a second and nothing in the biosuit, lava 10 × waterlevel every 0.2 s and once a second in the suit, the PSQL game and QuakeC mode alike |
Tools for the same purpose:
npm run check # compile every sql/*.sql against the engine, nothing else
npm run bench # where a tic and a frame spend their time
npm run perf # every benchmark against the deployed history (perf-history.json), as CI runs it
npm run screenshots # headless frames to docs/ (node scripts/screenshot.mjs e1m1 --at=x,y,z,yaw [--fast] [--compare])
Posed and staged shots of every level, with their commands, are in docs/screenshots.md.
sql/schema.sql tables: the BSP, the models, the edicts, the player, the game, the events
sql/physics.sql traces against the hulls, linking, water, the movement procedures
sql/game.sql progs.dat's utilities and entity helpers, and the stubs the next five call
sql/movers.sql doors, plats, buttons, trains, secret doors
sql/triggers.sql SUB_UseTargets and the triggers, changelevel, teleporters
sql/items.sql the pickups
sql/combat.sql damage, the projectiles, impacts
sql/spawn.sql the entity lump: every classname's spawn function
sql/weapons.sql the player's tic: movement, firing, impulses
sql/monsters.sql the AI, the pushers, the think and physics loops, QUAKE_TIC, INIT_MAP
sql/render.sql visibility and projection
sql/qcvm.sql the QuakeC VM: progs.dat as tables, globals, fields, strings, edicts
sql/qcbuiltins.sql the builtins, in five procedures by kind
sql/qcexec.sql the interpreter
sql/qcserver.sql spawning a map, the client
sql/qcphysics.sql the server frame and its physics
sql/qcpage.sql the page's QuakeC mode: qc_begin_map, qc_tic
src/qcjit.js QuakeC compiled to PSQL: a stored procedure per hot function, the dispatchers
src/loader.js BSP and MDL to tables, the generated bulk loaders
src/main.js the page: input, the game loop, settings, the console
src/renderer.js the painter
src/audio.js sound events, ambients, music
scripts/ the tests, the benchmark, the screenshot tool, the pak fetcher, the build
IN (subquery) can scan the whole table: JOIN the marked set
instead, and the join to the vertices becomes an index walk.leaf_faces carries each face's
plane and sphere, so the frame query culls without probing faces by key for every candidate, and
it halved.EXECUTE BLOCKs of INSERTs.RHC calls itself for each side of each plane.CASE and IIF over string literals pad the result to the longest branch, so a sound name
picked that way wants a TRIM before it reaches the pak.time + 0.1 are compared with a small
tolerance, or a monster thinks at 8 Hz instead of 10.VARCHAR longer than 8191 needs CHARACTER SET ASCII; INSERT ... VALUES
takes one row; a procedure that calls one declared later needs a stub with the same signature
first; at is a reserved word.MIT for the code here. Firebird and Electric Firebird are Apache-2.0. The Quake shareware episode is
freely redistributable; Quake is a trademark of id Software. LibreQuake's data is BSD-3-Clause (its
progs.dat and pop.lmp are GPL-2 and are not used here).
npm run test:image | the schema image the build ships (dist/schema-<hash>.fdb.gz, which the page opens instead of compiling the PSQL): a dumped database reopened from its bytes has every procedure and plays E1M1 tic for tic and frame for frame as one built in place |
npm run test:page | the built page (dist/, after npm run build) in headless Chromium, served without COOP/COEP as GitHub Pages serves it: it opens the database from the schema image, loads the start map and runs tics with no error on the page, without cross-origin isolation; then QuakeC mode, a deathmatch with three bots, runs too (CI runs it after the build, before the deploy) |
npm run test:touch | the touch controls' logic: the stick where the thumb lands, its dead zone and rim, the look drag in degrees, a tap a shot, a button touched for less than a tic still read once, each thumb by its own touch id |
npm run test:compare | the two renderers agree: a frame from FRAME_FACES (every vertex projected in SQL) and from FRAME_FACES_FAST are the same pixels, from E1M1's start and before its first doors with an explosion lighting them |
npm run test:console | the Quake console (commands split at ;, quoted arguments, history, Tab) and the key bindings (a button held by any of its keys, a command once a press); god, notarget, noclip, give and kill in the PSQL game and in QuakeC mode: god takes a rocket at the feet without a scratch, noclip walks through the wall ahead and still picks up the item it passes; alias (and one that runs itself stopped), exec of a file with comments, the notify area's last four lines for three seconds |
npm run test:mod | mods (needs npm run fetch-mods): a mod's zip laid over the shareware pak (its loose files, or a pak2.pak inside it, over id1's); id's progs 1.06 recompiled, Reinforcer 1.1 and FrikBot X each spawn and play E1M1 through their own progs.dat; FrikBot X's bots connect into spare client slots on impulse 100 and roam the level on its own QuakeC physics |
npm run test:levels | every level, in both logics: the player standing at full health (on E1M8 once landed from its high start), the monsters there, every exit leading to a map the paks have and ending the level; progs.dat spawning it with the same number of monsters to kill, a second without a QuakeC error. CI runs the shareware levels; PAK1=/path/to/pak1.pak npm run test:levels runs episodes 2 to 4, the end and the deathmatch levels where you have the registered data |
npm run test:lq | LibreQuake's levels: each loads with the player standing (or swimming) at full health, its monsters, and exits that lead to maps the pak has; lq_e0m7's boss trap (two buttons sink the vore's pillar into lava, where a trigger_hurt kills it through its armour) in both modes; the light_globe and QuakeC's static torches drawn |
npm run test:nav | the bots' map of the level: the player and a bot drop nodes where they go, linked as they went (a drop one way, a stride both, every link a clear line); a bot runs a way bot_plan lays through a dozen links and arrives; a link it cannot cross is struck off; a save keeps the map |
npm run test:dm | deathmatch in QuakeC mode with bots played by PSQL: three clients on their own info_player_deathmatch, no monsters, the bots roaming; a bot fights and frags the player (progs.dat's obituary), the player respawns and frags a bot, fraglimit ends the level; each client in its own colours (colormap, the team, the console's color, the frame query and the painter's skin translation); coop keeps the monsters and a bot shoots a grunt; a deathmatch demo replays the bots exactly |
npm run test:demo | demos: E1M1 recorded through 700 calls of quake_tic (and 250 of qc_tic) with the player fighting and dying, then played back in a fresh database and after another level: every tic and every entity match the recording bit for bit; a demo recorded 120 tics into the level starts from a save of that moment and plays back the same, in both logics |
npm run test:infight | monsters fighting monsters: a knight hurt by an ogre turns on it and remembers the player, a knight spares a knight but a grunt turns on a grunt, the knight and the ogre fight until one dies, and the survivor goes back to the player; sight and attacks eye to eye (the player 22, a monster 25, a fish 10); a grunt's shot flashes for its frame and lights the room; the blasts' reach (T_RadiusDamage), autoaim (PF_aim), FindTarget and the monsters waking one another, each monster's CheckAttack, water splashes, the backpacks (DropBackpack, BackpackTouch) |
npm run test:parity | the two games side by side: E1M1 loaded twice, the same input given to the PSQL game and to QuakeC mode tic by tic, and the player the same in both to a hundredth (its place, view, health, armor, ammo, weapon and the view weapon's frame): walking and a ramp, strafing, wall slides, the shotgun, the nailgun held, a rocket jump, a fall and a hard landing, swimming, drowning and slime; every kind of item on the level picked up (the items bitmask, armor type and message line compared), the megahealth's rot, doors, buttons, a lift and the teleporter |
npm run test:registered | the pak1 monsters' AI, spawned into E1M1 without their models: each is made to see the player and watched for its signature behaviour |
npm run test:e1m8 | Ziggurat Vertigo: gravity 100, so jumps and grenades go far and falls are gentle; the exit leads to E1M5, the view moving to an intermission camera that holds, with the stats and the intermission music |
npm run test:e1m5 | the crucified zombies on the start map, and Gloom Keep's flooded moat: swimming, breath, drowning, surfacing |
npm run test:e1m2 | Castle of the Damned: the drawbridge slab sinks into the moat, and the player wades across on it |
npm run test:e1m3 | the Necropolis: the gold key springs the zombie pits; zombies shrug off pellets, fall and rise, throw flesh, and die only when gibbed; the water jump onto a ledge (CheckWaterJump, SV_WaterJump) |
npm run test:e1m4 | the Grisly Grotto: two buttons open the underwater door; swim through, surface for the secret, water-jump onto the ledge, exit to E1M8 |
npm run test:e1m6 | The Door To Chthon: the gold runekey doors refuse, the key wakes its guard, the doors take the key and open, the silver doors stay shut, the exit to E1M7 |
npm run test:qcplay | QuakeC mode: E1M1 played by the original progs.dat on the engine's physics; the world settles, the player falls, walks with Quake's friction, opens a door through its trigger field, picks up shells by walking over them, fires a rocket that explodes against a wall, and a grunt appears in the renderer's frame query |
npm run test:qctic | the page's QuakeC mode: qc_tic returns quake_tic's row from progs.dat's player through walking, firing, a pickup and a secret; the exit's intermission and changelevel; the level change carrying shells and health into E1M2 |
npm run test:qclq | LibreQuake's progs.dat in QuakeC mode: lq_e0m1 spawns, the doors link (FTEQCC's overlapping locals saved around calls), the player walks for 40 tics without an error or a runaway, and the run compiled to PSQL ends at the same point |
npm run test:qcjit | QuakeC compiled to PSQL: every function of id's progs.dat and of LibreQuake's compiles to a procedure; the self-contained ones give the interpreter's results and globals on the same arguments; E1M1 plays while the hottest functions get compiled between tics, the grunts hunt the player and the shotgun fires |
npm run test:qcai | progs.dat's monsters on the engine: checkclient lets a grunt see the player, it shoots him (with the gunshot and blood effects), the player kills it with the autoaim, and a dog runs at him through movetogoal |
npm run test:qcvm | the QuakeC VM in PSQL runs the real progs.dat: anglemod, the builtins, SetNewParms/DecodeLevelParms through entity fields, InitBodyQue, worldspawn and its light styles, then E1M1 spawned by its QuakeC spawn functions, five frames of thinks, and the player connecting, firing, picking up shells and cheating |