Jekyll2019-09-29T16:05:46+00:00https://heinrich5991.github.io/blog/feed.xmlmusings on teeworldsheinrich5991The Anatomy of a One Tick Unfreeze2019-09-28T00:00:00+00:002019-09-28T00:00:00+00:00https://heinrich5991.github.io/blog/blog/one-tick-unfreeze<p>I saw the deepfly bind the other day:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bind mouse1 "+fire; +toggle cl_dummy_hammer 1 0"
</code></pre></div></div>
<div style="text-align:center"><iframe width="560" height="315" src="https://www.youtube.com/embed/R5ZKcHXube0?start=21" frameborder="0" allow="autoplay; picture-in-picture" allowfullscreen=""></iframe></div>
<p><a href="https://youtu.be/R5ZKcHXube0?t=21">Deepfly</a> is a technique that allows you to
fly with a tee that’s deep frozen, i.e. frozen until it hits an undeep tile.
This means that any input that the deep frozen input gives is discarded, except
when it gets hammered or shot by the unfreeze laser, in which case it is given
a game tick where it can fire a weapon or use its hammer. In the case of
deepfly, you join the game with a dummy, a second tee that is also controlled
by your client. You then hook the dummy, hammer it, causing it to unfreeze and
hammer you back, causing you to generate upward movement.</p>
<p>I heard that you need a reliable connection to the server to manage to make the
deepfly work. This sounded a lot like frame-perfect input being necessary.
I found it a little weird, so I decided to investigate.</p>
<p>One tick unfreezes are part of DDRace and DDNet gameplay. They work reliably
for shotgun, grenade and laser, at the very least since
<a href="https://github.com/ddnet/ddnet/commit/39704a04a7c7e4db04d90b13c989ec94b4b42202">39704a04</a>
by <a href="https://github.com/Speedy-Consoles">Speedy Consoles</a>. But they’ve been used
by tees all the way back to the beginning of DDRace. So how do they work and
why?</p>
<p>Currently input and freeze interact as follows:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code> Tee 1 (weak) Tee 2 (strong)
+---------------+ +---------------+
| weapon input | | weapon input |
start -->| if not frozen | +->| if not frozen | +-> ...
| (non-/auto) | | | (non-/auto) | | ...
+---------------+ | +---------------+ | ...
| | | | ...
v | v | ...
+------------------+ | +------------------+ | ...
| unfreeze targets |-+ | unfreeze targets |-+ ... --+
+------------------+ +------------------+ |
|
|
|
+---------------+ +---------------+ |
| weapon input | | weapon input | |
| if not frozen |<---+ | if not frozen |<---+ ... <-+
| (only auto) | | | (only auto) | | ...
+---------------+ | +---------------+ | ...
| | | | ...
v | v | ...
+------------------+ | +------------------+ | ...
| unfreeze targets | | | unfreeze targets | | ...
+------------------+ | +------------------+ | ...
| | | | ...
v | v | ...
+------------------+ | +------------------+ | ...
end <--| check own freeze | +--| check own freeze | +-- ...
+------------------+ +------------------+
</code></pre></div></div>
<p>Firstly,
<a href="https://github.com/ddnet/ddnet/blob/ef32fc4beda0e02e9f83f5784d3a1e862b424944/src/engine/server/server.cpp#L2022">go through all the tees by client ID</a> and
<a href="https://github.com/ddnet/ddnet/blob/ef32fc4beda0e02e9f83f5784d3a1e862b424944/src/game/server/gamecontext.cpp#L933">let</a>
<a href="https://github.com/ddnet/ddnet/blob/ef32fc4beda0e02e9f83f5784d3a1e862b424944/src/game/server/player.cpp#L477">them</a>
<a href="https://github.com/ddnet/ddnet/blob/ef32fc4beda0e02e9f83f5784d3a1e862b424944/src/game/server/entities/character.cpp#L702">fire</a> their automatic
(laser, shotgun, grenade) and non-automatic (pistol, hammer) weapons, <a href="https://github.com/ddnet/ddnet/blob/ef32fc4beda0e02e9f83f5784d3a1e862b424944/src/game/server/entities/character.cpp#L363">if
they’re not frozen</a> by the time
it’s their turn. This immediately unfreezes targets of
<a href="https://github.com/ddnet/ddnet/blob/ef32fc4beda0e02e9f83f5784d3a1e862b424944/src/game/server/entities/character.cpp#L565">l</a><a href="https://github.com/ddnet/ddnet/blob/ef32fc4beda0e02e9f83f5784d3a1e862b424944/src/game/server/entities/laser.cpp#L28">a</a><a href="https://github.com/ddnet/ddnet/blob/ef32fc4beda0e02e9f83f5784d3a1e862b424944/src/game/server/entities/laser.cpp#L69">ser</a>
and
<a href="https://github.com/ddnet/ddnet/blob/ef32fc4beda0e02e9f83f5784d3a1e862b424944/src/game/server/entities/character.cpp#L428">hammer</a>,
allowing tees with higher client IDs to fire their weapons even if they were
frozen at the start of the tick.</p>
<p>Then, <a href="https://github.com/ddnet/ddnet/blob/ef32fc4beda0e02e9f83f5784d3a1e862b424944/src/game/server/gameworld.cpp#L253">go through all the tees by weak-strong hook
order</a><sup id="fnref:1"><a href="#fn:1" class="footnote">1</a></sup>, starting with strong hook, and
<a href="https://github.com/ddnet/ddnet/blob/ef32fc4beda0e02e9f83f5784d3a1e862b424944/src/game/server/entities/character.cpp#L750">let them fire</a> their automatic
weapons if they’re not frozen. Unfreeze every hit tee, so that tees later in
the order can also act. Then check whether the tee is on a freeze tile or is
deep frozen and freeze it in that case.</p>
<p>Now, after this explanation, try to figure out how deepfly (hammering your
dummy tee who is deep frozen and who hammers you back, this is usually done via
the deepfly bind shown above). Try to find something strange. ;)</p>
<hr />
<p>Well, it turns out that deepfly only works if your dummy has a lower client ID
than you do. If you’re reading this blog post right as it came out, try it on
the official servers – if not, maybe it is fixed in the future and will work.</p>
<div style="text-align:center">
<img src="/blog/assets/2019-09-28-one-tick-unfreeze-no-unfreeze.gif" alt="Tees unsuccessfully trying to fly out of freeze" />
</div>
<p>Additionally, know that moment when trying to fly up from freeze and the tee
below you is just too dumb to hammer you back? They probably have a higher
client ID than you do and simple <strong>can’t</strong> hammer you while they’re in freeze.</p>
<div style="text-align:center">
<img src="/blog/assets/2019-09-28-one-tick-unfreeze-unfreeze.gif" alt="Tees successfully flying out of freeze" />
</div>
<hr />
<p>So, deepfly needs frame-perfect input and only works on tees with lower client
IDs than yourself. I don’t consider frame-perfect input to be a good game
design, so I propose fixing that. This would allow players using a normal
client to perform this trick even if they have high ping. While we’re at it, we
could also fix the higher-lower client ID thing.</p>
<p>My proposed fix (<a href="https://github.com/ddnet/ddnet/pull/1922">#1922</a>,
<a href="https://github.com/ddnet/ddnet/pull/1922/commits/f19220f1bbbb647954d6544865e3c2a2e9953037">f19220f1</a>)
does this by remembering whether you were frozen last tick and if you were, it
lets you hammer like an automatic weapon, i.e. simply by having held that
button.</p>
<p>What are advantages and disadvantages of this patch?</p>
<p>First, this allows people to do more than they could have done before: Do
deepfly/hammer out of freeze even if you’re the one that is frozen and have a
higher client ID than the tee that hammers you. This could give you an
advantage on server ranks.</p>
<p>Second, this allows people to do the deepfly/hammer out of freeze more easily.
However, this is something that was possible before, using frame-perfect input,
an <a href="https://youtu.be/aANF2OOVX40">unlocked scroll wheel</a> bound to <code class="highlighter-rouge">+fire</code> or a
nonstandard client.</p>
<p>The second point is also an advantage; it allows people to more easily
experiment with tricks that have already been possible before, but were just
hard to execute without hardware or software support. This might remove some
incentive to use nonstandard clients.</p>
<p>Thanks to <a href="https://ddnet.tw/players/Patiga/">Patiga</a> and
<a href="https://ddnet.tw/players/Zwelf/">Zwelf</a> for investigating the intricate
mechanics of one tick unfreezes, deepfly, etc. and testing out my fix for it.
Thanks to <a href="https://ddnet.tw/players/Zeta-Hoernchen/">Zeta-Hoernchen</a> for
recording the GIFs with me.</p>
<p>PS: This post has rested for almost two months before being published. I guess
I’m not made for blogging. The initial inspiration to publish anything at all
came from my enjoyment of reading the <a href="https://teedune.wordpress.com/">Dunely
Daily</a>, back when it was updated.</p>
<div class="footnotes">
<ol>
<li id="fn:1">
<p>Internally, this is entity insertion order, i.e. character spawn order. <a href="#fnref:1" class="reversefootnote">↩</a></p>
</li>
</ol>
</div>heinrich5991I saw the deepfly bind the other day: