An exact port of the DDNet (DDRaceNetwork) physics core to Python.
Ported 1:1 from the DDNet-20.0 sources:
| C++ | Python |
|---|---|
src/game/collision.cpp/.h |
ddnet_physics/collision.py |
src/game/gamecore.cpp/.h |
ddnet_physics/gamecore.py |
src/game/tuning.h |
ddnet_physics/tuning.py |
src/game/teamscore.cpp/.h |
ddnet_physics/teams.py |
src/game/prng.cpp/.h |
ddnet_physics/prng.py |
src/game/mapitems.h |
ddnet_physics/tiles.py |
src/base/vmath.h + math.h |
ddnet_physics/vmath.py |
src/game/server/entities/character.cpp (DDRace layer) |
ddnet_physics/ddrace.py |
src/game/server/entities/projectile.cpp |
ddnet_physics/weapons.py |
- Collision — every layer and every tile. game / front / tele / speedup /
switch (with the derived door layer) / tune. All
TILE_*constants frommapitems.h: stoppers, through, hookblocker, CP movers, time checkpoints, teleporters, and so on. - Tee physics core (
CCharacterCore): gravity, movement (MoveBoxwith velramp), jumps (ground/air,m_Jumpssupport), the full hook state machine (tele-hook with deterministic out selection via PCG-XSH-RR, hook drag, hook time), tee-vs-tee collision and hooking, movement restrictions from stopper tiles. - DDRace layer (server physics): freeze / deep freeze / live freeze and
every tile effect from
DDRacePostCoreTick(teleporters, evil tele, speedups, CP movers, solo, endless hook, unlimited jumps, walljump, telegun, ...), plus weapon impulses (hammer, shotgun pull, explosion, jetpack) and projectile physics. - Tuning — every parameter from
tuning.hwith the same ×100 quantization asCTuneParamin the game. - Teams (
CTeamsCore), PRNG (CPrng), teleporter indexes. - Loading real
.mapfiles via the optional twmap library.
The core is pure Python 3.9+, zero dependencies. DDNet collision is grid-based (tilemap + 28×28 AABB), so no external collision library is needed — the mechanics match the C++ operation by operation.
Requires Python 3.9+; the physics core itself has zero dependencies.
git clone https://github.com/lothiann/DDNet-PyEngine.git
cd DDNet-PyEngine
pip install -r requirements.txt # only needed for loading .map filesfrom ddnet_physics import Sim, Collision, TILE_SOLID
col = Collision.blank(50, 30)
col.fill_game_rect(0, 25, 50, 5, TILE_SOLID) # floor
sim = Sim(col) # sandbox
core = sim.add_tee(0, (16 * 32, 10 * 32)) # id, position (x, y)
for _ in range(60): # 50 ticks = 1 second
sim.step()
sim.set_input(0, jump=1, direction=-1) # jump while moving left
sim.step()
print(core.pos, core.vel, core.hook_state)sim.set_input(cid, ...) accepts: direction, target_x, target_y,
jump, hook.
from ddnet_physics import Collision, Vec2, TILE_SOLID
col = Collision.blank(10, 10)
col.set_game(5, 5, TILE_SOLID)
col.check_point(170, 170) # inside a solid tile?
col.test_box(Vec2(160, 160), Vec2(28, 28)) # tee AABB inside a solid?
col.intersect_line(Vec2(0, 0), Vec2(320, 320)) # ray -> (tile, point, point before)
pos, vel, grounded = col.move_box(
Vec2(100, 100), Vec2(300, 0), Vec2(28, 28), Vec2(0, 0)
)
col.get_move_restrictions(Vec2(160, 208)) # CANTMOVE_* bitmaskfrom ddnet_physics import Sim, load_collision, find_entities, ENTITY_SPAWN
col = load_collision("maps/LearnToPlay.map")
spawns = find_entities(col, ENTITY_SPAWN)
sim = Sim(col)
core = sim.add_tee(0, spawns[0])Sim.step() replicates the DDNet server order (gameworld.cpp ->
character.cpp):
- every tee:
core.tick(use_input=True)— withsv_no_weak_hook 0the deferred tick (tee collision / hook influence) runs inside; - every tee:
core.move()(+ quantization, like the real server).
Sim(col, no_weak_hook=True) corresponds to sv_no_weak_hook 1:
tick(do_deferred_tick=False) for everyone first, then tick_deferred()
for everyone, then move(). Sim(col, quantize=False) disables the
position/velocity quantization.
Every method is also available manually — the classes mirror the C++ API:
core = CharacterCore(collision, world, teams)
core.reset()
core.input.direction = -1
core.tick(use_input=True) # gravity, input, hook
core.move() # position integration
core.tick_deferred() # tee-vs-tee forces (if not already run in tick)
core.triggered_events # COREEVENT_* bitmask for this tick
core.quantize() # rounding, like on the network- Formulas, constants, tuning quantization and the order of operations are
1:1 with DDNet-20.0 (including quirks like the condition in
TileExistsNext). - The only difference: C++ computes in 32-bit
float, Python in 64-bitdouble. The port is slightly more precise; trajectories match, but over very long simulations a microscopic divergence from the client is possible. Sim(quantize=True)(default) rounds the state after every tick like the real server (positions to integers, velocities to 1/256).
Verified: a standing tee keeps the bit-identical position for hundreds of ticks (with and without quantization), a ground jump returns it to the same pixel, and pushing into a wall stops the tee exactly at the tile boundary.
Race timing / score bookkeeping (start, finish, time checkpoints as time
— not as movement) is game logic, not physics. The DDRace movement layer
(freeze, tile effects, weapon impulses) lives in ddnet_physics/ddrace.py
and ddnet_physics/weapons.py.
ddnet_physics/
├── vmath.py # Vec2 + math (vmath.h/math.h)
├── tiles.py # all tile/layer/protocol constants
├── tuning.py # Tuning — physics parameters (tuning.h)
├── collision.py # Collision — the whole CCollision
├── gamecore.py # CharacterCore / World — CCharacterCore / CWorldCore
├── teams.py # TeamsCore — CTeamsCore
├── prng.py # Prng — PCG-XSH-RR (prng.cpp)
├── map_loader.py # .map loading (twmap)
├── ddrace.py # DDRace layer: freeze + tile effects (character.cpp)
├── weapons.py # weapons & projectile physics (character.cpp/projectile.cpp)
└── sim.py # Sim — wrapper with the server tick order
tests/test_physics.py
examples/basic.py
python -m unittest discover -s testsThis project is a Python port of the DDNet sources and is distributed under the DDNet license (a zlib-style license) — see LICENSE.
Upstream: ddnet/ddnet — Teeworlds Copyright (C) 2007-2014 Magnus Auvinen, DDRace Copyright (C) 2010-2011 Shereef Marzouk, DDNet Copyright (C) Dennis Felsing.