Download

← All updates

Update

quark 1.18.22

Everything that changed in this version, and why.

The owner asked for the physics editor to "set value equations, not just fixed values, like if i want the sqrt of speed to change my ground friction ... curves, ease in, out, whatever with bounds". They also asked for them to run "in the game too" (decision card, 2026-10-24), plus default values from other games, a Code view that shows how values drive the movement, a separate code graph and movement graph, a 3D movement graph, locked units and A/B compare. Protocol 43 → 44. The .qmode format only gains lines that older games ignore. No default movement value changed.

Movement equations (src/game/moveexpr.h/.cpp, customrules, pmove, protocol)

  • What: a custom mode's .qmode takes expr.<key>=<expression> for gravity, speed, jump and every movement key. The result is that setting's multiplier, clamped to its usual range. The plain <key>= line stays as the fallback, holding the value at rest, and is what an older game reads.
  • Language:
    • numbers and pi;
    • other settings by key (their multiplier) and base.<key> (the standard absolute value);
    • live inputs p.speed, p.hspeed, p.vspeed, p.air, p.ground, p.onground, p.charges, p.dashing, p.hooked and p.height (capped at 50 m, and computed only when read);
    • ?:, || &&, comparisons, + - * / ^, unary - + !;
    • sqrt, abs, min, max, pow, exp, log, floor, ceil, round, sign, clamp, lerp, step, ease(t,"mode") and curve(x,x0,x1,y0,y1,"mode"), which is flat outside its bounds;
    • 13 ease modes.
  • Static vs dynamic:
    • Static equations read only settings. They are resolved once when the rules are set, in dependency order, so they cost nothing per step.
    • Dynamic equations are compiled to stack bytecode, with the static parts folded and fused forms for constant operands, c?k1:k2 and constant-bound curves. They run at the start of every 1 ms pmove step.
  • Cost (exprbench, median of 5 × 2M steps): a whole pmove step is about 170 ns. Rules without dynamic equations cost one predicted branch per command (noise level). One dynamic equation adds 6–10 ns, and five add about 90 ns (18 ns each), against a budget of 50 ns per equation.
  • Determinism: server and client must agree to the bit, or prediction drifts.
    • All the math is IEEE double, and the build has no fast-math and -ffp-contract=off.
    • exp, log and pow are the game's own code built from exact operations (within 1.4e-15 of glibc), because C libraries differ in the last bit.
    • Numbers are parsed by the game's own code, because strtod depends on the locale.
    • Golden hashes of the math and of a compiled program are checked in tests, so Windows (MinGW) and Linux must match.
    • Network test: 5 dynamic equations, 0 mispredictions in 800 against a client predicting by the received rules, and 798 in 800 when predicting by the standard rules.
  • Errors: syntax errors, unknown names, cycles (including a setting reading itself) and over-long equations leave that setting at its fixed value. The error gives its column and reason.
  • Everything outside pmove follows the same per-step value, through one helper (stepTuning / moveValue, dashMaxCharges(state)):
    • the server's grapple onto players (range, cooldown);
    • the HUD dash pips and hook bar;
    • the syringe refill;
    • dashReady, and dashAimedDown (pmove's own super / hyper events already use the per-step values);
    • the bots' hook checks;
    • the physics editor's strafe bot.
  • Left on whole-command values, and documented: the server's player probes (first step of the command) and the bots' safeHook plan.
  • Charges policy: when an equation lowers dash_charges mid-air, held charges above the new max go. Recharging charges keep their timers and come back once the max allows. An old loop that dropped the oldest recharges was removed; it only ran when the max changed, so normal play is unaffected.
  • Protocol 44: older clients would mispredict a mode with equations. The own movement state carries the air / ground timer (mods bit 128 plus a u16 phaseMs), only while an equation reads it, so a normal game sends the same bytes as 43. Demos: the generic converter covers 43 (the header reads unchanged), and the wire round trip is tested at 43 and 44.
  • Unchanged without equations:
    • a trace hash over 5.4M ms of random movement on 5 maps is identical before and after (ca0bd28eac78f498);
    • all 15 physics presets × 3 maps × 2 sets match at 1 ms (328k samples);
    • the dashtech, ladders and stairs outputs are identical.
  • Tests: testMoveExpr (movement group), testMoveExprNetwork (modes), testMoveExprCallers, quark_tests moveexpr and quark_tests exprbench. Coverage:
    • parse errors with columns, the limits, precedence, every function and ease;
    • the clamps, static order, cycles, NaN fallback, the .qmode round trip and golden hashes;
    • a friction curve: stops in 0.808 m / 213 ms, against 1.206 m / 365 ms standard;
    • gravity driven by p.air: jump apex 1.039 m against 1.221 m;
    • charges clamping and recharging, super dash with a changing dash_vertical;
    • the network prediction test.

Physics editor (tools/physics_editor)

  • Other games (references/, 22 games, 189 values, check.py, build.py):
    • Quake 1 / QW, Quake 2, Quake 3 VQ3, CPMA, Warfork, Xonotic, Half-Life, CS 1.6, CS:GO, CS2, Source SDK defaults, HL2, TF2 Soldier and Scout, UT99, Halo CE, Overwatch 2, Valorant, Titanfall 2, Apex, Minecraft and Celeste.
    • Values are converted to a 1.8 m player by each game's own player height (metric games keep metres).
    • Each value has a source and a confidence: exact (engine source or cvar default), measured (community measurement) or estimated (derived, e.g. Quake/Source air accel against Quark's capped model).
    • Closest to Quark by mean |ln ×| over 9 core settings: Xonotic 0.149, CPMA 0.353, Titanfall 2 0.409, Quake 0.49.
    • Left out for lack of public numbers: Quake Live, Quake Champions, Reflex, Diabotical, newer Unreal, Doom, Splitgate, Halo Infinite, CoD, Fortnite and Portal 2.
  • Values:
    • Compare with games: a picker grouped or sorted by closeness, a column per game, confidence dots, ⚠ outside the range, "use" per value and "load all" per game (written as an # imported from line).
    • Each setting is fixed, ƒ equation (highlighting, autocomplete, live errors, a static/dynamic badge, the game's own value through the helper's new physexpr) or ⌒ curve (input, bounds, 13 easings, draggable ends, live plot).
  • Code: the .qmode, plus "Movement code": the game's real pmove.cpp / pmove.h / gamedefs.h, read-only and highlighted. Every constant is annotated base × multiplier = value for the current set, with a side list of every place each setting is read and plain notes on each function and step.
  • Code graph (new tab): settings → the 18 steps of a 1 ms pmove step, in order → the state they change. Changed settings are highlighted, and clicking a node opens its code.
  • Movement graph (was Graph): curves as before, plus a 3D view with a scrubber, lock units (fixed axis ranges and 3D camera, kept across runs and sets) and A/B: edited, standard, any saved .qmode or a game, overlaid or side by side, with B − A.
  • Drive starts even where mouse lock is refused (the Quark Project app now allows it too).
  • Tests: 277 page checks against the real helper and the in-page mock. The page's evaluator matches physexpr on 50 expressions, and the mock matches physsim on dynamic equations.
  • Helper events: the physics editor helper reads pmove's ME_* events, the same ones the game's sounds use, so it now sees 1.18.21's chained held dashes and buffered jumps. Before, it guessed from presses.

Tests (tests/tests.cpp, rules_tests.h)

  • Why: the tickrate timing tests failed on and off on cloud VMs for unchanged code too. A 1 ms sleep sometimes took 20-50 ms (VM steal), which pushes more than 1% of ticks over the 4 ms stall threshold in a 1 s window.
  • What: the harness measures its own waits. An attempt that failed while the host paused the test thread longer than 4 ms is printed, set aside and run again. A failure with no such pause counts at once, and the third attempt always counts. No assertion changed.

Docs

  • docs/RELEASE_CHECKLIST.md: "Custom mode with movement equations" (a dynamic friction curve plays the same online, protocol 44) and "Physics editor v2".
  • tools/physics_editor/INTERFACE.md (v2), README.md, native/README.md and references/README.md.