The owner asked for a model editor: a local tool to look at every model the way the game draws it and replace it. The editor itself is a tool outside the game. The game changes make its exports draw exactly like the built-in models, let every model the game draws be replaced, and keep a player's own model files out of ranked. Gameplay, the network protocol (43) and demos are unchanged: 1.18.12 plays with 1.18.7 and newer games and servers.
Ranked fairness: your own model files stay out of ranked (src/client/gltf.cpp, src/client/models.h, src/client/overrides.*, src/client/game.cpp)
- Why: a player's own
overrides/models/...files (the user overrides folder,%APPDATA%\quark\overrideson Windows) applied in ranked too. Ranked forces the STANDARD player model, so onemodels/player/STANDARD.glbwas enough to make every enemy huge or neon for that player alone; projectiles and the crown worked the same way. - What: both sets of model overrides are loaded once at startup (
ModelOverrides: the shipped set fromassets/overrides/and yours,loadModelOverrides).drawWorldpicks one set once a frame: a player in a ranked match or a time trial (MS_RANKED | MS_TIMETRIAL,rankedPlayer()) gets the shipped set (or the default boxes where nothing is shipped); everyone else, including spectators of a ranked match, gets yours over the shipped ones, as before. - Cost: the same as before: one pointer pick per model drawn, decided once a frame. No file is read during a match.
overrides::find/readRelativetake where to look:FROM_ANY(yours, then the shipped ones; the default),FROM_SHIPPEDorFROM_USER. Sounds and the font are unchanged (they don't change what you can see of other players).- Test (
testOverrides): a user STANDARD.gltf loads and is drawn, and never in ranked; every model's ranked pick is the shipped one.
Every model replaceable: the props (src/client/models.cpp, src/client/game.cpp)
- Why: the arms, the bomb and the payload objective were drawn box by box inline in
drawWorld/drawHeldWeapon, so they had no export and couldn't be overridden. Every other model already could. - What: a new model kind,
MODEL_PROP(models/prop/<NAME>), built bypropPartsand drawn through the same parts + override path as players, weapons, projectiles and the crown:GUN_ARM: the arm and gun every other player holds out.ARM: the arm a weapon model is held on in hide and seek and tag.BOMB_PACK: the bomb on a carrier's back (player space).BOMB: the dropped or planted bomb.CART: the payload cart (11 boxes).GATE: a checkpoint gate (both posts).
- Position, aim, crouch and body size stay transforms in game.cpp (
drawProp,drawArm). Colours that change at run time are slots: the bomb's blink isglow(reference: its dark grey); the cart'steam,metal,glowand a newaccentslot (the map's accent colour, the cart's rim); the gate'steam(reference: the unreached grey). - Reference palettes are per model (
referencePalette(kind, idx)), andoverrideTintstakes the model, because a prop's reference colours aren't a player's. - Defaults are pixel-identical: the same box calls with the same floats. Before / after screenshots of the cart, both gates, the bomb and its backpack, the gun arms and the held-weapon arms compare identical. Same number of boxes per frame.
- The asset export now writes 30 models (was 24: 5 players, 11 weapons, 7 projectiles, the crown and the 6 props). The provenance registry lists the 6 new ones as AI-made (assets/provenance/models.json). docs/ASSETS.md documents them and fixes stale counts (11 weapons).
Box model files (src/client/gltf.cpp, src/client/models.*, tests)
- Why: an override was drawn as a plain lit mesh. A model editor export of a player had no outlines, a crouch squashed the whole body, the face plates showed in first person, and the default players' constant-offset colours turned into a plain multiply (the HEAVY / ROBOT / KNIGHT grey is team x 0.6 + 0.06, the SCOUT visor team x 1.8 + 0.3, the ROBOT eye team x 2 + 0.4).
- What: an override whose first mesh carries its box list (
meshes[0].extras.quark, version 2) is drawn by the game's own box path, exactly like a built-in model: 1 px outlines, a crouch squashing only the boxes markedcrouch,visOnlyboxes (face plates) hidden on your own body, per-box shadows, the same colour formula.ModelPartcarriesmul/add/teamMul(drawn colour = slot colour xmul+ team colour xteamMul+add) andcrouch.exportModelPartsderives them from the builders (each slot set to black, then white, with the team white; crouch from a half-height build). The export writes them withshadow,visOnlyandalpha, at 9 significant digits so the floats come back exactly. An unchanged export put back as an override draws pixel-identically to the default (all 30 checked in screenshots).- The triangles in the file are then only for other viewers (Blender, the website, the editor).
- Checks (once at startup, when the file loads): 1 to 256 boxes, every number finite, centres within 64 m, half sizes over 0 and at most 32 m, colours /
mul/add/teamMulfrom 0 to 16, alpha over 0 and at most 1, known slot names, booleans. A bad box list fails the file and keeps the default (logged). No extras, or another version (the editor's early version 1 notes), is drawn as a mesh as before. - Cost: parsed once at startup; drawing goes through
overrideBoxPartsinto the reused box list, so a box override costs what the built-in boxes cost. Not animated in a box override: the rocket's flame flicker, the sticky's core blink and the bubble's wobble (it still spins and turns). - Override tints: slots a model's drawing doesn't colour (glow, metal, base, accent on a player or the crown, and so on) now draw as authored (tint 1) on a mesh override. Before, they were tinted by white / reference: a player's
glowcame out x (1, 1.82, 6.67). - Tests: every default model's file round-trips to identical boxes and draws (any palette, crouched) as the built-in model does; 17 bad box lists are refused; the editor's version 1 extras load as a mesh; undriven slots keep the reference.
- docs/ASSETS.md has the schema ("Box models").
Drawing: the box list reserved once (src/client/models.h, src/client/game.cpp)
- The list every model is drawn from (
parts_) is reserved once at startup for the larger ofkMaxDefaultParts = 32(the tests check no default model has more) and the biggest box override, so drawing never grows it. Why: the props moved into it (up to 11 boxes for the cart), and a growth mid-frame is an allocation on the frame path (a 1% low risk). Per-frame work is unchanged apart from filling that reused list.
Screenshot tooling (src/client/main.cpp, src/client/game.cpp)
--camnow also places the camera in a match (the late aim re-read is skipped then), and--modelsin bomb mode has the lineup carry the bomb (turned away, backpacks showing) with a dropped bomb in front. Why: exact before / after pixel comparisons of the objective props (the cart, the gates, the bomb and its backpack) for the change above.
Model editor (tools/model_editor)
- A local page that runs from disk (
file://) in Chrome or Edge, with no server, no network and no libraries.python3 tools/model_editor/build.py <export> <out>(orbuild.cmd, default output%USERPROFILE%\Desktop\Quark\model-editor) builds it fromquark_tests exportassets: the page plusdata/manifest.js(every model with its box list, the shipped model overrides, each map's palette and sun), about 400 KB, Python 3 standard library only. On Windows,tools\model_editor\install.cmd [output folder]does it all from a repository checkout: it builds onlyquark_testswith MinGW inbuild-model-editor\, exports the assets and runs build.cmd (non-interactive, opens no browser, safe to re-run). - Drawing: a WebGL2 port of the game's dynamic object shader (lighting, shadow map, outlines, translucent pass, sky) in orbit, first-person viewmodel, third-person (gun arm, held weapon, crown), projectile in flight, the
--modelslineup and a track view with the cart and gates. Checked against the game's own screenshots: model faces match to 0 to 1 of 255, viewmodels 97 to 99% of pixels within 3 / 255. - Editing: box by box with fields, picking, a move / resize gizmo, undo / redo; recolouring rewrites
mul(keepingadd/teamMulwhile they fit);mul/add/teamMuland the crouch flag are editable; boxes are checked against the game's limits before export..glb/.gltfimport, read as the game's loader reads it. - Export:
models/<kind>/<NAME>.glboverrides to a folder (File System Access) or a zip, with the box list in the extras, plus aprovenance.jsonsidecar (docs/PROVENANCE.md). Problem checks: the 20 000 triangle limit, bounds, slots the game doesn't drive.