To content | To menu | To search

Tag - programming

Entries feed - Comments feed

Sunday 19 August 2012

3ds Max File Format (Part 2: The first inner structures; DllDirectory, ClassDirectory3)

Now that we understand the outer structure of the file, it's time to look closer to what's inside. The DllDirectory stream looks like a good starting point. After cleaning up a whole bunch of code to make it easier to plug in specialized handling code, a nice and readable output of this structure shows up as follows:

(StorageContainer) [20] { 
0 0x21c0: (StorageRaw) { 
        Size: 4
        String: .... 
        Hex: d8 00 e0 2e } 
1 0x2038: (StorageContainer) [2] { 
        0 0x2039: (StorageRaw) { 
                Size: 78
                String: V.i.e.w.p.o.r.t. .M.a.n.a.g.e.r. .f.o.r. .D.i.r.e.c.t.X. .(.A.u.t.o.d.e.s.k.). } 
        1 0x2037: (StorageRaw) { 
                Size: 38
                String: V.i.e.w.p.o.r.t.M.a.n.a.g.e.r...g.u.p. } } 
2 0x2038: (StorageContainer) [2] { 
        0 0x2039: (StorageRaw) { 
                Size: 98
                String: m.e.n.t.a.l. .r.a.y.:. .M.a.t.e.r.i.a.l. .C.u.s.t.o.m. .A.t.t.r.i.b.u.t.e.s. .(.A.u.t.o.d.e.s.k.). } 
        1 0x2037: (StorageRaw) { 
                Size: 42
                String: m.r.M.a.t.e.r.i.a.l.A.t.t.r.i.b.s...g.u.p. } } 
19 0x2038: (StorageContainer) [2] { 
        0 0x2039: (StorageRaw) { 
                Size: 54
                String: B.i.p.e.d. .C.o.n.t.r.o.l.l.e.r. .(.A.u.t.o.d.e.s.k.). } 
        1 0x2037: (StorageRaw) { 
                Size: 18
                String: b.i.p.e.d...d.l.c. } } } 

Thanks to the chunks, most of this is self-explanatory, and fairly easy to handle. The 0x21c0 chunk seems to be a header chunk for the DllDirectory, so we can call it DllHeader or something similar, and contains 4 bytes. This chunk is found in a version 2010 file, but doesn't seem to exist in files from version 3, so it's probably not crucial to handle and we can ignore it's contents until it seems we need it for something. The rest of the chunks in this container are all of id 0x2038, and are the entries in this list, so they are called DllEntry. Inside each of these, there is a 0x2039 chunk containing a description, and a 0x2037 chunk containing a name, both in UTF-16.

The meaning of chunk identifiers depends on the parent container chunk, so we have to code it like that as well. Each container type is set up with a handler to create the class that handles a chunk of a specified identifier. Parsing this block with some smarter code results in a data reading that looks as follows:

(DllDirectory) [20] { 
0 0x21c0: (CStorageValue) { 786432216 } 
1 0x2038: (DllEntry) [2] { 
        0 0x2039: (CStorageValue) { Viewport Manager for DirectX (Autodesk) } 
        1 0x2037: (CStorageValue) { ViewportManager.gup } } 
2 0x2038: (DllEntry) [2] { 
        0 0x2039: (CStorageValue) { mental ray: Material Custom Attributes (Autodesk) } 
        1 0x2037: (CStorageValue) { mrMaterialAttribs.gup } } 
19 0x2038: (DllEntry) [2] { 
        0 0x2039: (CStorageValue) { Biped Controller (Autodesk) } 
        1 0x2037: (CStorageValue) { biped.dlc } } } 

Next, we do a similar thing for the ClassDirectory3 stream.

(StorageContainer) [57] { 
0 0x2040: (StorageContainer) [2] { 
        0 0x2060: (StorageRaw) { 
                Size: 16
                String: ................ 
                Hex: ff ff ff ff 82 00 00 00 00 00 00 00 82 00 00 00 } 
        1 0x2042: (StorageRaw) { 
                Size: 22
                String: P.a.r.a.m.B.l.o.c.k.2. } } 
1 0x2040: (StorageContainer) [2] { 
        0 0x2060: (StorageRaw) { 
                Size: 16
                String: ....<).Z..B0`... 
                Hex: 00 00 00 00 3c 29 06 5a 1e 0c 42 30 60 11 00 00 } 
        1 0x2042: (StorageRaw) { 
                Size: 30
                String: V.i.e.w.p.o.r.t.M.a.n.a.g.e.r. } } 
8 0x2040: (StorageContainer) [2] { 
        0 0x2060: (StorageRaw) { 
                Size: 16
                String: ................ 
                Hex: 03 00 00 00 02 00 00 00 00 00 00 00 00 0c 00 00 } 
        1 0x2042: (StorageRaw) { 
                Size: 16
                String: S.t.a.n.d.a.r.d. } } 
13 0x2040: (StorageContainer) [2] { 
        0 0x2060: (StorageRaw) { 
                Size: 16
                String: ....._.d..+".... 
                Hex: fe ff ff ff ec 5f c7 64 b9 9e 2b 22 00 0c 00 00 } 
        1 0x2042: (StorageRaw) { 
                Size: 24
                String: N.e.L. .M.a.t.e.r.i.a.l. } } 
56 0x2040: (StorageContainer) [2] { 
        0 0x2060: (StorageRaw) { 
                Size: 16
                String: ...."".......... 
                Hex: ff ff ff ff 22 22 00 00 00 00 00 00 00 01 00 00 } 
        1 0x2042: (StorageRaw) { 
                Size: 10
                String: S.c.e.n.e. } } } 

This container does not seem to have a header chunk, but again simply contains a whole bunch of entries of id 0x2040, containing a binary blob with id 0x2060 and a UTF-16 string with id 0x2042 that has a description. There's a block in here with some data that I can recognize and reference from our own code. The NeL Material, which is a MAXScript, has a class id of (0x64c75fec, 0x222b9eb9) which matches the middle 8 bytes of the 16 byte blob (read them backwards). The last four bytes in the blob match with the last four bytes in the Standard (material) class entry, and appear to be the SuperClassID. When we look closer at the first four bytes, this appears to be a signed integer, given that there's both ff ff ff as 00 00 00 numbers without too much inbetween. For the NeL Material, which is a script, this value is -2, cross-referencing with other max files with scripted classes reveals the same. Builtin types, such as Scene, have this number as -1. Classes that come from plugins, such as ViewPortManager, have a positive value. Even closer inspection reveals that this value matches with the index of the associated dll in the DllDirectory, ViewPortManager being part of ViewPortManager.gup, and Standard being part of mtl.dlt. It can be expected that the indices of the classes in this list will be needed later on as well. A smarter parsing output looks as follows:

(ClassDirectory3) [57] { 
0 0x2040: (ClassEntry) [2] { 
        0 0x2060: (ClassDirectoryHeader) { 
                DllIndex: -1
                ClassID: (0x00000000, 0x00000082)
                SuperClassID: 130 } 
        1 0x2042: (CStorageValue) { ParamBlock2 } } 
1 0x2040: (ClassEntry) [2] { 
        0 0x2060: (ClassDirectoryHeader) { 
                DllIndex: 0
                ClassID: (0x30420c1e, 0x5a06293c)
                SuperClassID: 4448 } 
        1 0x2042: (CStorageValue) { ViewportManager } } 
56 0x2040: (ClassEntry) [2] { 
        0 0x2060: (ClassDirectoryHeader) { 
                DllIndex: -1
                ClassID: (0x00000000, 0x00002222)
                SuperClassID: 256 } 
        1 0x2042: (CStorageValue) { Scene } } } 

The ClassData stream is very similar, and seems to contain a global data storage for classes, or something in that style. It doesn't seem to have anything in it that interests me or seems crucial at this point, so I won't bother with it too much for now. It's fairly self-explanatory.

(ClassData) [7] { 
0 0x2100: (ClassDataEntry) [2] { 
        0 0x2110: (ClassDataHeader) { 
                ClassID: (0xbe7c7e52, 0x87d987f4)
                SuperClassID: 16 } 
        1 0x2120: (StorageRaw) { 
                Size: 0
                Hex: } } 
4 0x2100: (ClassDataEntry) [2] { 
        0 0x2110: (ClassDataHeader) { 
                ClassID: (0x33b673a4, 0x44b50d1e)
                SuperClassID: 4128 } 
        1 0x2120: (StorageContainer) [14] { 
                0 0x0190: (StorageRaw) { 
                        Size: 48
                        String: [email protected]@...= 
                        Hex: 00 00 00 00 00 00 00 00 1f 1c c1 c3 01 00 00 00 cd cc cc 3d cd cc cc 3d 00 00 00 00 cf f7 7b 40 e1 7a 1d 42 01 00 00 00 00 00 a0 40 cd cc cc 3d } 
                1 0x019c: (StorageRaw) { 
                        Size: 72
                        String: [email protected]@[email protected][email protected]=..........HC 
                        Hex: 00 00 00 00 00 00 00 00 1f 1c c1 c3 01 00 00 00 00 00 80 3f 00 00 a0 40 00 00 00 00 cf f7 7b 40 e1 7a 1d 42 01 00 00 00 00 00 a0 40 cd cc cc 3d cd cc cc 3d 00 40 9c 45 cd cc cc 3d 01 00 00 00 01 00 00 00 00 00 48 43 } 

So far, this was easy. After this comes the real stuff.

Friday 17 August 2012

3ds Max File Format (Part 1: The outer file format; OLE2)

The 3ds Max file format, not too much documentation to be found about it. There are some hints here and there about how it's built up, but there exists no central documentation on it.

Right now we are in the following situation. A few thousand of max files, created by a very old version of max (3.x), containing path references to textures and other max files that have been renamed and relocated or which simply no longer exist. Yes, we have a maxscript that can go through them all, and that manages to fix a large number of paths. However, there are a lot of paths that are stored as part as fields in plugins and material scripts that don't get noticed, and the performance of opening and closing this number of files from 3ds Max directly is horrible. The obvious solution? Figure out how we can read and save the max file with modified contents, without having to understand all of the actual data it contains. Fortunately, this is actually possible without too much work.

Some research online brings up the following blog post, relating to a change in the max file format in version 2010, which would make it easier to update asset paths: http://www.the-area.com/blogs/chris/reading_and_modifying_asset_file_paths_in_the_3ds_max_file. That's nice and all, but it's only from version 2010 on, and it very likely won't contain any assets referenced by path by old plugins and such.

So, starting at the beginning. The blog post I referred to above nicely hints us to the OLE structured file format. Since there exist a wide range of implementations for that, we can pretty much skip that, and accept that it's basically a filesystem in a file, so it's a file containing multiple file streams. A reliable open source implementation of this container format can be found in libgsf. When scanning a fairly recent max file, using the command gsf list, we can find the following streams inside this file:

f         52 VideoPostQueue
f     147230 Scene
f        366 FileAssetMetaData2
f       2198 DllDirectory
f      29605 Config
f       3438 ClassDirectory3
f        691 ClassData
f      29576 SummaryInformation
f       2320 DocumentSummaryInformation

The FileAssetMetaData2 is new in 3ds Max 2010.

One step further, we can start examining the contents of these streams. And it's usually easiest to start off with one of the more simple ones. VideoPostQueue seems small enough to figure out the overall logic of the file format, hoping that the rest is serialized in a similar way. Using the command gsf dump we can get a hex output of one of the streams, and using a simple text editor we can find how it's structured. Binary formats often contain 32 bit length values, which are usually easy to spot in small files, since they'll contain a large number of 00 values. It's basically a matter of finding possible 32bit length integers, and matching them together with various fixed length fields and other typical binary file contents, until something programatically logical turns up. Here's a manually parsed VideoPostQueue storage stream:

        50 00 (id: 0x0050)
        0a 00 00 00 (size: 10 - 6 = 4)
                01 00 00 00 (value: 1)
        60 00 (id: 0x0060)
        2a 00 00 80 (size: 42 - 6 = 36) (note: negative bit = container)
                10 00 (id: 0x0010)
                1e 00 00 00 (size: 30 - 6 = 24)
                        07 00 00 00 (value: 7)
                        01 00 00 00 (value: 1)
                        00 00 00 00
                        00 00 00 00
                        20 12 00 00 (value: 4610)
                        00 00 00 00
                20 00 (id: 0x0020)
                06 00 00 00 (size: 6 - 6 = 0)

The storage streams in the max container file contain a fairly simple chunk based file format (and in fact similar in format to the fairly well known .3ds file format). Being based on chunks is what allows 3ds Max to open a file for which certain plugins are missing. It's basically a tree structured format where every entry has an identifier and a size, so when an identifier is unknown, or when it's contents are incompatible, it can simply be kept as is or discarded. The only exceptions in the file that don't use this structure are SummaryInformation and DocumentSummaryInformation, which are supposedly in a standard Windows format, and the new FileAssetMetaData2 section is formatted differently as well unfortunately.

In this format, the chunk header consists of a 2-byte unsigned integer which is the identifier, and a 4-byte unsigned integer, where the 31 least significant bits are the size and the msb is a flag that helpfully lets us know if the chunk itself contains more chunks, and thus is a container, or not. For very large files, where 31 bits is insufficient for the size, the entire size field is set to 0, and the header increases with an additional 64-bit unsigned integer field which is similarly structured as the 32-bit size field. The size field includes the size of the header.

       0 | 0f 20 (id)
                 00 00 00 00 (size missing)
                             17 fe 01 00 00 00 00 80 (size in 64 bits)

With this information it is possible to read a max file, modify the binary contents of chunks (most of them are fairly basic of format), and we should be able to re-save the max file with our modified data. The DllDirectory section, for example, parsed programatically starts like this:

CStorageContainer - items: 20
        [0x21C0] CStorageValue - bytes: 4
        [0x2038] CStorageContainer - items: 2
                [0x2039] CStorageUCString - length: 39
                Viewport Manager for DirectX (Autodesk)
                [0x2037] CStorageUCString - length: 19
        [0x2038] CStorageContainer - items: 2
                [0x2039] CStorageUCString - length: 49
                mental ray: Material Custom Attributes (Autodesk)
                [0x2037] CStorageUCString - length: 21
        [0x2038] CStorageContainer - items: 2
                [0x2039] CStorageUCString - length: 37
                Custom Attribute Container (Autodesk)
                [0x2037] CStorageUCString - length: 23

Of course, it would be interesting if we could go further, and directly manipulate the parameters of our own plugins and scripts from our own tools back into the max files so that everything is centrally stored without any duplicate source data in the way. And that's exactly what I'll be doing next.

Sunday 22 August 2010

Why avoid closed silver bullet game engines, why not? A full page of Unity bashing

This is another short excerpt of text that was originally written as part of my final internship report in June. It is here aimed towards Unity, as this was the silver bullet of the year, but a lot of this can generally be applied to pretty much any closed silver bullet engine.

I have now worked with Unity for three projects. A small game at the global game jam this year, an online multiplayer board game prototype, and a first person shooter.

If you want to make generic re-hashed physics games, where the player is a character walking around a world, and you don't want to do anything technologically innovative, then Unity is for you, and then you should use it. For me, it is a pure waste of my time.

Initially, I had given it a chance, because it seemed interesting, and there was quite a lot of attention paid to it recently. While using it for the first time, though, I got the impression that the overly designed architecture, which Unity forces you to use, leads to some very sloppy coding practices (public variables, singletons, and such), and others who I talked to found this as well.

Another issue that quickly turned up with Unity was the fact that it's not possible to debug. When Unity crashes with a fatal error, and it really does that in quite a few situations, you can lose a very valuable amount of time on figuring out just where it's going wrong. Sure, with Unity it's possible to try after every single line of code if it works, because the compilation takes literally no time. After a while you start spending more than half of your time on just launching the game just to see if that last line of code doesn't crash Unity.

I expect from an engine that it only provides me with highly optimized implementations of fundamentally important techniques that take a long time to write. And that I can choose to bypass or modify as I wish. Unity does not give me this. Instead, it gives me a truckload of half working garbage made from a bunch of libraries they licensed from elsewhere which they crudely stitched together to form one static unified blob. In comparison, the most friendly engine that I have used is XNA, simply because it does all the boring stuff for you, but gives you complete freedom in the area of graphical programming. Unity is making me think of ditching C# altogether, just so I can avoid it.

But one of the major issues, really, is vendor lock-in. Mostly any script written for Unity, can not be relevantly used in any game that does not run under Unity, thanks to the Unity specific interfaces that are necessary for scripts to interact with each other in Unity. On the other hand, code written in C++ for one project, with one engine, can easily be retransformed for use in another project without major changes, even when using a different engine, because a proper engine does not force me to use poor design patterns. And a game written in C/C++ using OpenGL and OpenAL runs potentially on everything, because I can make it to, while a game written in Unity will only run on the platforms that Unity decides to support. Unity will not run on Linux, because they said so.

It also does not allow me to experiment with new technologies or techniques. It is not practical to, for example, make use of OpenCL from within Unity, as we do not truly have direct access, which makes it impractical to share data between Unity rendering and OpenCL without needlessly copying data back and forth between the mainboard ram and the gpu ram.

Then there are countless bugs and design flaws, especially in the networking and sound interfaces of the Unity engine, that they have known about for a long time already, as can be proven by various posts on the Unity forums, which are easier to fix by writing an own engine, than by working around them.

In the end though, if I constantly have to hear, only from people who actually don't even make games with Unity themselves, how good Unity is, that "everything" will be fixed in "the next version", and that I should just use it, then something is clearly wrong. It's more viral marketing hype than useful technology.

It's potentially a nice level editor, though.

But the thing is, a lot of people do prefer to work with a commercial all-in-one solution, such as Flash, Unity, or whatever the next silver bullet is. They just want to get their ideas out there, without having to bother with technical details, and they'll just hop on to the next bandwagon whenever it passes by. The upside of this is that they will keep following the current technological advances. The downside is that they'll be lagging behind the current technologies for as far as their engine of choice goes.

I'm not in favor of writing your own engine from scratch either. The thing that you need to be capable of doing, is to rush forward, ahead of the commonly known techniques, and extend what already exists. There is no use in rewriting again and again what we already have, it is more valuable to build upon that. To make a car analogy here; don't reinvent the wheel, make something that's not a wheel that can do it's function of transportation much better than a wheel, and mount it on an existing car.

So, in my opinion as a programmer, the best choice is to start out from a game engine which gives you full access to the source code, whether that be a commercially licensed or open source licensed engine does not matter. What matters is that you should have the ability to fix what is wrong, without wasting more time than necessary. And extend that with your own unique ideas and experiments.

Friday 15 January 2010

KaeteMIX - Node based sound editor (preview)

Beta version will be available for download in the near future.


Thursday 7 January 2010

Node based Sound Editor, Work in progress

Working on something cool.


Sunday 25 October 2009

SSE2 memcpy

SSE2 provides functionality for performing faster on aligned memory. By copying the first and last bytes of an unaligned memory destination using the conventional unaligned functionality, and copying everything in between as aligned, it is possible to make use of this performance improvement on large unaligned memory blocks as well.

In this graph the green lines are the conventional memcpy available in Microsoft Visual Studio 2008, the red lines are the SSE memcpy function available in Nevrax NeL, and the blue lines are the custom SSE2 function. The bright colored lines are the performance on alinged memory blocks, while the dark colored lines are tested on differently unaligned blocks of memory. Horizontally the copy function is tested on different sizes of memory, on the vertical axis the copy speed is displayed in MB/s.

As you can see, NeL's SSE memcpy performs very well on aligned memory, but gives horrible performance on unaligned memory, as it does not take the aligning of the memory blocks into account. The builtin memcpy function is fastest of all at copying blocks below 128 bytes, but also reaches it's speed limit there. The SSE2 memcpy takes larger sizes to get to it's maximum performance, but peaks above NeL's aligned SSE memcpy even for unaligned memory blocks.

Code is available below, ask before using. SSE2 memcpy

Continue reading...

Saturday 25 April 2009

Balanced - Sneak Preview

Currently working on this here with a small team.

Sneak Preview

Friday 5 September 2008

NeLSound XAudio2 Driver (Update)

The project to run NeLSound on top of Microsoft's new XAudio2 API is already nearing it's final stages of development. The driver currently implements all basic functionality required by NeLSound, as well as the some of the environment effects (commonly known as EAX), and has support for the OGG Vorbis music format. I am currently working on adding support for the ADPCM sample buffer format used by NeL to the driver, and I will soon work on the rest of the environment effects implementation as well. Also, the new owners of NeL have made available a new official website for NeL over at http://dev.ryzom.com/, and you can browse the code of the new sound driver at http://dev.ryzom.com/repositories/browse/nel/nel/src/sound/driver/xaudio2.

Sunday 24 August 2008

NeLSound XAudio2 Driver

I have recently started working on an XAudio2 driver for NeLSound, which will allow OpenNeL to make use of Microsoft's new XAudio2 sound library (included in the latest DirectX SDK), in addition to the three sound libraries already currently supported by OpenNeL (FMod, DSound and OpenAL). The project currently does not implement all functionality yet, as the rest of the features will be implemented in the coming weeks, but it already runs the sound_sources sample included with OpenNeL pretty nicely without problems. The code can be downloaded from http://nel.svn.sourceforge.net/viewvc/nel/trunk/nel/src/sound/driver/xaudio2/, and is released, like all other available OpenNeL code, under version 2 of the GNU General Public License.

Sunday 20 January 2008

Snowballs - Part 1. Gameplay

Hi, I'm Kaetemi, currently sort of working on bringing Snowballs back alive. Snowballs is the prototype game of the open source OpenNeL MMORPG engine. I recommend brining a visit to http://www.opennel.org/ for some more detailed information. As the title of this blog entry suggests, this bit of text is about the gameplay of Snowballs, and there will be an article about a few other parts of Snowballs here every few days, weeks or months, depending on how much time I have for this.

Here's the description of the current gameplay (or at least how it was intended to be) from the readme: "You're now a penguin on an artic snowfield. You have snowballs which you can throw at other people. When you throw a snowball, you have a few seconds during which you gather more snow before you can throw the next one. If you hit someone, your score increases, and the snowed opponent becomes immune to further snowing (he flashes on the screen) for 5s. He cannot retaliate while invulnerable." If you have already playded the game, you would probably have noticed already that this description isn't really a very accurate description of the actual game as it is now. In the code that we have the penguin isn't implemented, you can throw snowballs as fast as you like and you neither become immune nor flash on the screen when being hit by a snowball. In any case, that description would make an extremely boring game anyways.

Obviously if we want to improve the gameplay, we do have to look at what people playing the game actually enjoy doing. This seems to be mostly either exploring the terrain, hitting the left mouse button as fast as they can (preferably on a non-moving target who's most likely afk), or also just talking with each other. So what we need here is a gameplay which allows at least these few things.

In order to allow exploration, we need a continueous gameplay. This means that instead of resetting all player stats or whatever on every round or something like that, a more dynamic system should be in place. Similarly to how the game is implemented right now, anyone should just be able to join and leave the game at any time without any problems. A new player should also not have any extreme disadvantages over people who have been playing for a while. Not allowing any players to have any advantages over others at all would make the game extremely boring, though.

An appropriate way of solving this, is by having some sort of short-term advantages only. These could for example be implemented using either a time, or usage limit, but doing it in that style for every bonus is really boring, especially since other players have no other way than waiting for the advantage to go away on that player, and waiting is a really boring game. Instead, it must also be possible for a player to make another player lose his advantage over other players. A possible method is simply by letting x amount of bonuses go down when a player gets hit by a snowball, but since this isn't very effective when a player has something like an extremely rare overpowered defense shield bonus, or high speed snowball dodge skill, or something, other useful methods could be special bomb items (somewhat less rare than the extreme defense bonus) which instantly reset all advantages of all players in x radius around you (including yourself, obviously) without doing any other damage. Bonus stats and items or whatever can be given to players in different ways. Players could get higher hit points when they hit someone else, they could pick up bonus items or skills at random locations, there should be bonuses that are less common than others, there could even be bonuses that allow players to gain bonuses in an alternative way.

Now to attack another player, hitting the left mouse button really fast, and throwing loads of snowballs, is definitely more fun than just click, hold, wait and release, click, hold, wait and release, etc. One of the types of bonuses a player can gain, would be different attacks, trough which you'd then be able to switch with your mouse scroller. This includes things like throwing at your target at high speed with small target radius, large snowball bombs falling from the sky, snowballs being launched around you in a spiral, snowballs being launched to random positions in front of you at high speed, higher target radius the longer you hold your mouse button, loads of snowballs launched around you in all directions, etc, very much like games of the shoot 'em up genre (those 2d games where loads and loads of bullets are being shot at you the whole time).

There will also be some sort of scoreboard which shows the best players online. Points can be earned, obviously, by hitting someone with a snowball, or by picking up bonuses from the ground (gives some exploration value). A team system could be added, where you'd have to defend or take over a few different villages in the area. And for those who just want to talk to others, there will also be a bar, which is a safe zone where no snowballs can land; and after leaving it you'll have ten seconds to run as far as you can before you're back to being attacked by snowballs.