<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>SpyParty - A Spy Game About Subtle Behavior &#187; metrics</title>
	<atom:link href="http://www.spyparty.com/category/metrics/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.spyparty.com</link>
	<description>Chris Hecker&#039;s new espionage game about subtle behavior, performance, perception, and deception.</description>
	<lastBuildDate>Sun, 16 Mar 2014 05:01:44 +0000</lastBuildDate>
	<language>en-US</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.8.2</generator>
	<item>
		<title>Loadtesting for Open Beta, Part 4: Done optimizing the lobbyserver!</title>
		<link>http://www.spyparty.com/2013/05/21/loadtesting-for-open-beta-part-4-done-optimizing-the-lobbyserver/</link>
		<comments>http://www.spyparty.com/2013/05/21/loadtesting-for-open-beta-part-4-done-optimizing-the-lobbyserver/#comments</comments>
		<pubDate>Tue, 21 May 2013 06:13:11 +0000</pubDate>
		<dc:creator><![CDATA[checker]]></dc:creator>
				<category><![CDATA[beta]]></category>
		<category><![CDATA[indie games]]></category>
		<category><![CDATA[metrics]]></category>
		<category><![CDATA[programming]]></category>

		<guid isPermaLink="false">http://www.spyparty.com/?p=3210</guid>
		<description><![CDATA[Check out Loadtesting for Open Beta, Part 1, Part 2, and Part 3 to read the previous installments of this epic tale! It&#8217;s been a while since the last update in this series, sorry about that!  At the end of Part 3, I mentioned the SimCity launch giving me pause about my goal of testing [&#8230;]]]></description>
				<content:encoded><![CDATA[<p><em>Check out <a title="Loadtesting for Open Beta, Part 1" href="http://www.spyparty.com/2013/02/27/loadtesting-for-open-beta-part-1/">Loadtesting for Open Beta, Part 1</a>, <a title="Loadtesting for Open Beta, Part 2" href="http://www.spyparty.com/2013/03/03/loadtesting-for-open-beta-part-2/">Part 2</a>, and <a title="Loadtesting for Open Beta, Part 3" href="http://www.spyparty.com/2013/03/18/loadtesting-for-open-beta-part-3/">Part 3</a> to read the previous installments of this epic tale!</em></p>
<p>It&#8217;s been a while since the last update in this series, sorry about that!  At the end of <a title="Loadtesting for Open Beta, Part 3" href="http://www.spyparty.com/2013/03/18/loadtesting-for-open-beta-part-3/">Part 3</a>, I mentioned the <a href="http://kotaku.com/tag/sim-city">SimCity launch</a> giving me pause about my goal of testing the <strong>SpyParty</strong> lobbyserver to 1000 simultaneous robots.  Well, I got scared enough after their launch that I increased my optimization target to 2000 simultaneous robots on my old and slow server, and then I also decided to bite the bullet and upgrade the server hardware after I hit 2000 to give myself some extra headroom.  I really don&#8217;t think I&#8217;m going to hit these numbers at Open Beta launch or even for a long time after that, but I&#8217;d rather err on the conservative side and have it purr along nicely.</p>
<p>Since I waited so long to post this Part 4, I can&#8217;t really give a play-by-play of all the optimizations I did as they happened, so I&#8217;m going to give the general arc I followed, and then talk about some of the interesting stops along the way.</p>
<a name="iprof%2C+atop%2C+oprofile%2C+et+al."></a><h3>iprof, atop, oprofile, et al.</h3>
<p>As I mentioned at the end of the last post, I&#8217;d fixed some of the huge and obvious things with the network bandwidth usage, so it was time to start profiling the CPU usage.  There are lots of different kinds of profilers, but the one I use the most is based on <a href="http://silverspaceship.com/src/iprof/">Sean Barrett&#8217;s iprof</a>.  I&#8217;ve modified it a fair bit over the years,<sup><a href="http://www.spyparty.com/2013/05/21/loadtesting-for-open-beta-part-4-done-optimizing-the-lobbyserver/#footnote_0_3210" id="identifier_0_3210" class="footnote-link footnote-identifier-link" title="I&rsquo;ll&nbsp; release my changes at some point.">1</a></sup> but the core of the system is still the same.  It&#8217;s a runtime profiler that requires instrumenting your code into blocks, it&#8217;s efficient enough that you can leave it on all the time as long as you don&#8217;t stick a &#8220;prof block&#8221; in an inner loop, and you can generally see where you&#8217;re spending your time hierarchically.  It can draw to the screen, but I also have it output to a string, and so on the lobbyserver I can have it output to the log after a spike, and also catch a signal I send and it&#8217;ll force a prof dump.  Here&#8217;s an example:</p>
<pre style="padding-left: 30px;"><span style="font-size: x-small;">2013/04/17-16:12:10: 85.156 ms/frame (fps: 11.74)  sort self - current frame</span><br /><span style="font-size: x-small;">2013/04/17-16:12:10: zone                                                     self     hier    count</span><br /><span style="font-size: x-small;">2013/04/17-16:12:10:  ProcessMessages                                      59.2910  59.2910     1.00</span><br /><span style="font-size: x-small;">2013/04/17-16:12:10: +Send                                                 17.8164  18.4493  1120.69</span><br /><span style="font-size: x-small;">2013/04/17-16:12:10: +ClientsUpkeepAndCloseLoop                             2.3034   2.6989   793.15</span><br /><span style="font-size: x-small;">2013/04/17-16:12:10:  Log                                                   1.3559   1.3559    25.97</span><br /><span style="font-size: x-small;">2013/04/17-16:12:10:  unpack_bytes                                          0.7311   0.7311    26.56</span><br /><span style="font-size: x-small;">2013/04/17-16:12:10:  unpack                                                0.6674   0.6674  3551.71</span><br /><span style="font-size: x-small;">2013/04/17-16:12:10: +ClientsPacketLoop                                     0.5492   3.7160   792.58</span><br /><span style="font-size: x-small;">2013/04/17-16:12:10: +ClientsUpdated                                        0.4023  10.9843     0.56</span><br /><span style="font-size: x-small;">2013/04/17-16:12:10: +SendQueuedClientRoomMessages                          0.2524   7.3086     1.00</span><br /><span style="font-size: x-small;">2013/04/17-16:12:10:  FindClientByID                                        0.2494   0.2494   267.05</span><br /><span style="font-size: x-small;">2013/04/17-16:12:10: +JournalQueuedSave                                     0.2374   0.3882     1.43</span><br /><span style="font-size: x-small;">2013/04/17-16:12:10: +Tick                                                  0.2112  25.6313     1.00</span><br /><span style="font-size: x-small;">2013/04/17-16:12:10:  iprof_update                                          0.2051   0.2051     1.00</span><br /><span style="font-size: x-small;">2013/04/17-16:12:10: +JournalSavePrep                                       0.1560   0.5442     1.43</span></pre>
<p>As you can see, it&#8217;s pretty easy to read, and you can drill down on individual blocks and see who calls them and who they call:</p>
<pre style="padding-left: 30px;"><span style="font-size: x-small;">2013/04/17-16:12:10: 85.156 ms/frame (fps: 11.74)  sort graf - current frame</span><br /><span style="font-size: x-small;">2013/04/17-16:12:10: zone                                                     self     hier    count</span><br /><span style="font-size: x-small;">2013/04/17-16:12:10:     LoginReply                                         0.0006   0.0006     0.01</span><br /><span style="font-size: x-small;">2013/04/17-16:12:10:     JOINING                                            0.0007   0.0008     0.03</span><br /><span style="font-size: x-small;">2013/04/17-16:12:10:    +TYPE_CLIENT_GAME_ID_REQUEST_PACKET                 0.0011   0.0014     0.44</span><br /><span style="font-size: x-small;">2013/04/17-16:12:10:    +NewWaitingForJoinClients                           0.0019   0.0019     0.01</span><br /><span style="font-size: x-small;">2013/04/17-16:12:10:    +TYPE_CLIENT_PLAY_PACKET                            0.0086   0.0087     0.12</span><br /><span style="font-size: x-small;">2013/04/17-16:12:10:    +TYPE_CLIENT_INVITE_PACKET                          0.0213   0.0225     2.37</span><br /><span style="font-size: x-small;">2013/04/17-16:12:10:    +TYPE_CLIENT_IN_MATCH_PACKET                        0.0280   0.0282     0.49</span><br /><span style="font-size: x-small;">2013/04/17-16:12:10:    +JournalQueuedSave                                  0.0563   0.0571     1.18</span><br /><span style="font-size: x-small;">2013/04/17-16:12:10:    +RoomsChanged                                       0.0852   0.0939    15.38</span><br /><span style="font-size: x-small;">2013/04/17-16:12:10:    +NewInLobbyClients                                  0.1155   0.1342    14.55</span><br /><span style="font-size: x-small;">2013/04/17-16:12:10:    +ClientsUpkeepAndCloseLoop                          0.1362   0.2093   150.55</span><br /><span style="font-size: x-small;">2013/04/17-16:12:10:    +TYPE_CLIENT_MESSAGE_PACKET                         0.3122   0.3170     8.87</span><br /><span style="font-size: x-small;">2013/04/17-16:12:10:    +SendQueuedClientRoomMessages                       6.7122   7.0013   501.99</span><br /><span style="font-size: x-small;">2013/04/17-16:12:10:    +ClientsUpdated                                    10.3361  10.5720   424.67</span><br /><span style="font-size: x-small;">2013/04/17-16:12:10: -Send                                                 17.8164  18.4493  1120.69</span><br /><span style="font-size: x-small;">2013/04/17-16:12:10:     unpack                                             0.6329   0.6329  3362.06</span></pre>
<p>This is super useful.  The biggest downside to it is that it&#8217;s not thread-aware, but I&#8217;ve made it thread-safe via the brute force method of having it ignore all threads that aren&#8217;t the &#8220;main&#8221;.  My code is mostly single-threaded, but the threadedness increased a fair bit during these optimizations, so I hope to eventually modify iprof to be thread-aware without losing too much simplicity and performance.  However, until I make those modifications, any background thread activity will show up attributed to one of these main-thread blocks.  You can still get useful data, you just have to be aware of this.  For example, ProcessMessages in the loop above is hiding a WaitForMultipleObjectsEx call on Windows, or a call to select/epoll on POSIX, so it&#8217;s not actually taking that much active time on the main thread.</p>
<p>I also used <a href="http://oprofile.sourceforge.net/news/">oprofile</a>, which is a nice sampling profiler on Linux that can profile per-thread using just the debug information in an application, and <a href="http://atoptool.nl/">atop</a> for keeping track of things happening on the machine as a whole.</p>
<p>Here&#8217;s a list of the stuff I ended up optimizing:</p>
<ul>
<li>I was originally sending out the chat messages to all clients as they came in, but I started queuing them up and sending them all out at once to reduce send calls.  Of course, once you do this, you have to make sure you don&#8217;t overflow the network packet if you have queued a lot of messages that tick, so that makes the code more complicated and harder to modify, which is a tradeoff one often has to make while optimizing, and it&#8217;s why you want to put off most optimization until you need it&#8230;although you should have a rough plan for how you&#8217;ll optimize a piece of code in the future even if you write it the dumb way first.</li>
<li>I made more threads, including putting network sending and receiving on separate threads, making a separate thread for logging, and a thread for saving files to the disk.  There were already threads for talking to the database and Kerberos, for receiving network packets, and for checking for new client builds.  These are all relatively simple threads to add, because they&#8217;re all just throwing data into a queue on one thread and taking it out on another, although multithreading a program always makes it harder to understand.  I discovered a fair number of deadlock bugs in <a href="https://developers.google.com/talk/libjingle/">libjingle</a>, the library I&#8217;m using for <a href="http://en.wikipedia.org/wiki/NAT_traversal">NAT traversal</a> and some cross platform threading stuff, and I&#8217;ve fixed some of them.  I&#8217;ve veered far enough from the original libjingle code that I&#8217;m probably just going to have to put my version up as a fork, sadly.</li>
<li>I timesliced the login phase for the clients.  Previously, when a client would log in, I&#8217;d process a bunch of stuff immediately, including some authentication stuff which can be somewhat time consuming.  In a load test where hundreds of clients log in to the server at the same time, this would bog down, so I now process a maximum of 20ms worth of clients each tick.  This makes some clients wait a bit longer before they&#8217;re logged in, but doesn&#8217;t result in a positive feedback loop where there&#8217;s a really long tick, so a lot of packets will have arrived while it was happening, so the next tick is really long too, etc.</li>
<li>Like the player list packets, I also made the room list packets incremental, and able to span multiple network packets.  This way all the lists of players and rooms that the lobby sends to the clients can be differential and arbitrarily long, so there&#8217;s no more hard limit on the number of clients that can join the lobby.  I think there&#8217;s actually a bug in this code, but I&#8217;ve only ever seen it once, even after tens of thousands of robot sessions, so I just hope it shows up more at some point.</li>
<li>I switched the POSIX networking inner loop in libjingle from <a href="http://linux.die.net/man/2/select">select</a> to <a href="http://linux.die.net/man/4/epoll">epoll</a>.  This was not so much an optimization as it was simply to allow more than 1024 sockets to work at all.  epoll is also a lot faster, but I&#8217;m currently kinda using it in a dumb way, so I&#8217;m not benefiting from that speed boost much yet.</li>
<li>There were also a bunch of smaller traditional code optimizations, like using maps to cache lookups, using free lists to avoid some allocations, and whatnot.  Oh, and don&#8217;t forget to <a href="https://twitter.com/checker/status/335503826939424768">change the ulimit -n settings in limits.conf</a> on Linux, so your process can actually accept a lot of connections!</li>
</ul>
<p>As I was doing these optimizations, I would run a loadtest with a bunch of robots and profile the lobby.  I was at 500 robots at the end of Part 3, and I slowly raised the ceiling as I improved the code over the weeks:  569 robots&#8230;741 robots&#8230;789 robots, 833, 923, 942, 990, 997, 1008, 1076, 1122, 1158, 1199, 1330, 1372, 1399, 1404, 1445, 1503, 1614, 1635, 1653, 1658, 1659&#8230;</p>
<p>When I hit 1659 it was late one night, and so I stopped for the day.  When I resumed work and did the next couple of optimizations, I figured I&#8217;d get it to 1800 or something.  I always launched 20% or so more robots than I was hoping to support in a given test to account for internet and <a href="http://aws.amazon.com/ec2/">EC2</a> variation, and for plain old bugs in the clients that would sometimes manifest themselves, so this time I must have launched 2500 robots, because when I looked up from the profiles running in ssh terminals and over to my <strong>SpyParty</strong> client logged into the test lobby, I saw this:</p>
<div id="attachment_3224" style="width: 610px" class="wp-caption aligncenter"><a href="http://cdn.spyparty.com/wp-content/uploads/2013/05/SpyParty-v0.1.2681.1-20130501-15-23-57-0.png"><img class="size-large wp-image-3224" title="SpyParty-v0.1.2681.1-20130501-15-23-57-0" alt="" src="http://cdn.spyparty.com/wp-content/uploads/2013/05/SpyParty-v0.1.2681.1-20130501-15-23-57-0-600x415.png" width="600" height="415" /></a><p class="wp-caption-text">This wasn&#8217;t supposed to happen yet.</p></div>
<p>Uh, I guess I was done optimizing?</p>
<p>I was actually kind of disappointed, to be honest.  I had all sorts of cool ideas for optimizations I was planning to do that I&#8217;d come up with while testing and profiling the code, and now, if I was going to follow my own plan and stop when I hit 2000 simultaneous robots, I would have to just take a bunch of notes for next time I optimized so I could pick up where I left off, and move on.  The good news is I&#8217;m pretty sure I can make the lobby almost twice as efficient if and when the time comes to do that!</p>
<a name="Room+at+the+Inn"></a><h3>Room at the Inn</h3>
<p>If you look closely at that screenshot, you&#8217;ll see the thumb on the scrollbar for the player list is pretty small.  That&#8217;s because all 2010 players are in a single room, which is not going to work very well for a lobby full of real people.  In fact, the only reason there were 2010 players in that room was because at the time I&#8217;d limited the room size to 2010 because I didn&#8217;t want to bother teaching the robots how to use rooms.  There were actually a few hundred more robots knocking on the door but they couldn&#8217;t get in.  But, now that I&#8217;d hit my 2k target, it was time to fix that.</p>
<p>I immediately realized I had a problem.  Currently, when you connect to the lobby, it sends you a list of rooms, and you have to pick one to log in.  But, what if the rooms are full?  Oops, you couldn&#8217;t log in.  So, as soon as I set the room size down to something more reasonable, like 100, then the first 100 robots got in and the rest just sat there failing to join.</p>
<p>It seemed like there were a number of solutions to this problem, including allowing players to create new rooms before logging in, but in the end I went with the simplest and most robust solution, which is to have the lobby create a new empty room if all the current rooms are full.  The initial room is always called <em>Headquarters</em>, so I named these new dynamic rooms <em>Headquarters 2</em> and onward.  Very creative, I know.  Somebody suggested using spy movie titles for these room names, but I figured that wouldn&#8217;t scale very well, ignoring the potential copyright issues.  If the lobby ever finds one of these dynamic rooms empty, it kills it, unless all the other rooms are full.  I also have the lobby automatically put you in a now-guaranteed-to-exist-non-full-room if you log in and try to join a full room, even if it wasn&#8217;t full when you clicked on it, so this eliminated a login race condition too, which is always a good sign.</p>
<p>This last bit also made it so I didn&#8217;t need to make the loadtesting robots know very much about rooms:  they always try to join Headquarters and if they don&#8217;t end up there, oh well.  As they join, they kind of spill over into the latest dynamic room until it fills up, and then they continue to the next, kind of like filling up an ice tray with water from one end.  I should probably make them test the actual room features by creating and changing rooms and whatnot, but the single giant 2010 player room was a way more intense loadtest than having 20 rooms with 100 players in each due to the chat broadcasting.</p>
<p>I don&#8217;t know if 100 is the right limit for room populations.  100 would still be way too many people to have in a single reasonable conversation, but I didn&#8217;t want to put too low of a limit on the size before I have tested things with humans instead of just robots.</p>
<a name="The+Client"></a><h3>The Client</h3>
<p>There&#8217;s this annoying thing that happens when you&#8217;re testing computer code, and it&#8217;s that you encounter problems and bugs not only in the code you&#8217;re trying to test, but also in your test code.  This was no different.  I was constantly fixing various bugs in the robots that would keep them from all connecting correctly, and I even made sure some of the optimizations helped the client side so I could run more robots on a given EC2 server.  Plus, just making sure the robots keep trying to connect and login was important, because if there was a timeout due to an initial burst, you want them to try again automatically after it dies down, rather than just sitting there not doing anything.</p>
<p>As I said in <a title="Loadtesting for Open Beta, Part 1" href="http://www.spyparty.com/2013/02/27/loadtesting-for-open-beta-part-1/">Part 1</a>, I started out running about 50 robots on each m1.small EC2 instance.  That didn&#8217;t scale, for some reason I&#8217;m still trying to figure out.  That worked okay with a low number of instances, but as I increased the number of instances, I had to lower the number of robots on each instance, eventually to around 20 per m1.small.  An AWS account starts with only being able to start 20 instances, so I did a total of two instance limit requests to Amazon, first to 100 and then to 300.  It&#8217;s scary to have 300 instances running&#8230;even though m1.small instances are only 6¢ an hour each, that&#8217;s still $18 an hour when there are 300 of them running, and Amazon rounds up to the hour, so if you miss shutting them down by a minute you just lost a large pizza!  It looks like Google&#8217;s new <a href="https://cloud.google.com/pricing/compute-engine">Compute Engine</a> thing is about twice as expensive for their somewhat similar low end machine (ignoring performance differences), but charges in 1 minute increments after the first 10, which might be cheaper for this very transient use-case.</p>
<p>I seem to remember reading somewhere that Amazon allocates instances for the same account to the same physical machine if possible, which might explain this scaling problem, since it means I was probably maxing out a given piece of server hardware with too many instances bursting at the same time.  It&#8217;s hard to tell if this is the case, and I need to do more testing before saying for sure.  A <a href="http://www.spyparty.com/2013/03/03/loadtesting-for-open-beta-part-2/comment-page-1/#comment-67637">commenter</a> said there might be a packets-per-second limitation in EC2, as well, but I haven&#8217;t verified that.  Once I&#8217;ve tried a few different things, I&#8217;ll do a long technical post on <a href="http://chrishecker.com">chrishecker.com</a> about EC2, <a href="https://www.linode.com/">linode</a>, and my dedicated host machine, comparing the different results I got.</p>
<p>Finally, I had to do some optimization on the <strong>SpyParty</strong> game client when the numbers started getting high.  I went a little nuts with the chat system early on and it has completion on all commands, room names, and player names, but the code that builds the completion tree was calling the memory allocator 35k times per update when the numbers of players got high, so I had to remove some of the stupid in that code as well.</p>
<a name="The+New+Server"></a><h3>The New Server</h3>
<p>With all that done, and 2010 robots running on the old server, I haggled with my hosting provider and started renting a newer and much faster server.  I use <a href="http://www.softlayer.com/">SoftLayer</a> for dedicated hosting, and have for years.<sup><a href="http://www.spyparty.com/2013/05/21/loadtesting-for-open-beta-part-4-done-optimizing-the-lobbyserver/#footnote_1_3210" id="identifier_1_3210" class="footnote-link footnote-identifier-link" title="Well, they were servermatrix when I started, and then The Planet, and now SoftLayer.">2</a></sup> My old server was a Pentium 4 with a single hyperthreaded core, 1GB ram, and a 100Mbps uplink, and the new server is a Xeon 3460 with four hyperthreaded cores, 4GB ram, and 1Gbps uplink, so it&#8217;s slightly more expensive but a lot faster.  That said, everybody seems to be using <a href="http://en.wikipedia.org/wiki/Virtual_private_server">VPS</a> hosts these days.  I talked to some other indie game developers, but I didn&#8217;t have time to do a full evaluation of the tradeoffs, so went with the devil I knew, so to speak.  It seems like VPS is going to be a bit slower but also a bit cheaper, but the big advantage of VPS to me is that you can move the virtual machine image to faster hardware and have it up and running again in minutes.  That&#8217;s a pretty great scaling sweetspot between having a single physical server and praying it doesn&#8217;t melt, and a scalable system that elastically uses cloud computing The Right Way™, but it&#8217;s also hundreds of times easier to get a VPS image working and then move it to a faster machine than it is to scale elastically.  So, I dunno, it&#8217;s definitely something worth looking into more during the year as I see how things are scaling.</p>
<p>The new server ate the robots for lunch:</p>
<div id="attachment_3230" style="width: 610px" class="wp-caption aligncenter"><a href="http://cdn.spyparty.com/wp-content/uploads/2013/05/SpyParty-v0.1.2703.1-20130518-20-57-40-0.png"><img class="size-large wp-image-3230" title="SpyParty-v0.1.2703.1-20130518-20-57-40-0" alt="" src="http://cdn.spyparty.com/wp-content/uploads/2013/05/SpyParty-v0.1.2703.1-20130518-20-57-40-0-600x421.png" width="600" height="421" /></a><p class="wp-caption-text">The new server works pretty well.</p></div>
<p>For reference, 4850 simultaneous players is pretty far up the <a href="http://store.steampowered.com/stats/">top 100 Steam games by player count</a>, so I don&#8217;t think I have to worry about those numbers for a while.  Here&#8217;s atop&#8217;s view of things:</p>
<div id="attachment_3237" style="width: 610px" class="wp-caption aligncenter"><a href="http://cdn.spyparty.com/wp-content/uploads/2013/05/2013-05-18-20_47_06-atop.png"><img class="size-large wp-image-3237" title="2013-05-18 20_47_06-atop" alt="" src="http://cdn.spyparty.com/wp-content/uploads/2013/05/2013-05-18-20_47_06-atop-600x344.png" width="600" height="344" /></a><p class="wp-caption-text">Well within parameters.</p></div>
<a name="What%26%238217%3Bs+Next%3F"></a><h3>What&#8217;s Next?</h3>
<p>So, that&#8217;s it for the lobbyserver loadtesting.  Now I need to move the website and registration system over to the new server, test them a bit, and start inviting everybody in in big batches.  Soon I&#8217;ll send out email to the beta testers to set up some scheduled human loadtests as well.  The robots will be jealous, left out in the cold, looking in at all the humans actually playing the game. </p>
<p>Open Beta is fast approaching.</p>
<hr/><ol class="footnotes"><li id="footnote_0_3210" class="footnote">I&#8217;ll  release my changes at some point.</li><li id="footnote_1_3210" class="footnote">Well, they were servermatrix when I started, and then The Planet, and now SoftLayer.</li></ol>]]></content:encoded>
			<wfw:commentRss>http://www.spyparty.com/2013/05/21/loadtesting-for-open-beta-part-4-done-optimizing-the-lobbyserver/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
		<item>
		<title>Loadtesting for Open Beta, Part 3</title>
		<link>http://www.spyparty.com/2013/03/18/loadtesting-for-open-beta-part-3/</link>
		<comments>http://www.spyparty.com/2013/03/18/loadtesting-for-open-beta-part-3/#comments</comments>
		<pubDate>Mon, 18 Mar 2013 06:37:06 +0000</pubDate>
		<dc:creator><![CDATA[checker]]></dc:creator>
				<category><![CDATA[beta]]></category>
		<category><![CDATA[indie games]]></category>
		<category><![CDATA[metrics]]></category>
		<category><![CDATA[programming]]></category>

		<guid isPermaLink="false">http://www.spyparty.com/?p=3139</guid>
		<description><![CDATA[Read Loadtesting for Open Beta, Part 1 and Part 2 to catch up on the spine-tingling story so far! When we last left our hero, our differential state update change was a resounding success and reduced the network bandwidth utilization from 98% to 3%, and it looked like we could move on to optimizing the [&#8230;]]]></description>
				<content:encoded><![CDATA[<p><em>Read <a title="Loadtesting for Open Beta, Part 1" href="http://www.spyparty.com/2013/02/27/loadtesting-for-open-beta-part-1/">Loadtesting for Open Beta, Part 1</a> and <a title="Loadtesting for Open Beta, Part 2" href="http://www.spyparty.com/2013/03/03/loadtesting-for-open-beta-part-2/">Part 2</a> to catch up on the spine-tingling story so far!</em></p>
<p><a title="Loadtesting for Open Beta, Part 2" href="http://www.spyparty.com/2013/03/03/loadtesting-for-open-beta-part-2/">When we last left our hero</a>, our differential state update change was a resounding success and reduced the network bandwidth utilization from 98% to 3%, and it looked like we could move on to optimizing the lobbyserver code itself to get to our goal of 1000 simultaneous loadtesting robots, until we noticed <a title="Loadtesting for Open Beta, Part 2" href="http://www.spyparty.com/2013/03/03/loadtesting-for-open-beta-part-2/#Up+Next,+The+Case+of+the+Missing+Robots">some of our robots were missing</a>!  This led me on a wild and wooly chase through the code, which I will recount for you now&#8230;</p>
<a name="Where%26%238217%3Bd+the+robots+go%3F"></a><h3>Where&#8217;d the robots go?</h3>
<p>The first order of business was to figure out why some robots were dying when they <em>weren&#8217;t</em> supposed to, and some weren&#8217;t dying when they <em>were</em> supposed to.  Robots: they never do what you tell them.</p>
<p>If you look at this graph of the number of running robots from last time, you can see that right off the bat, a bunch of them die on all the machines, and then they keep dying for about 30 seconds, and then it stabilizes.  Each of these machines should have 50 robots running solidly during the test period.</p>
<div id="attachment_3116" style="width: 610px" class="wp-caption aligncenter"><a href="http://cdn.spyparty.com/wp-content/uploads/2013/03/ec2-loadtest-client-counts.png"><img class="size-large wp-image-3116" title="ec2-loadtest-client-counts" src="http://cdn.spyparty.com/wp-content/uploads/2013/03/ec2-loadtest-client-counts-600x344.png" alt="" width="600" height="344" /></a><p class="wp-caption-text">The number of loadtest robots running on each EC2 instance.</p></div>
<p>Then, to make matters worse, some of them don&#8217;t die when they&#8217;re supposed to at the end of the test.  In the graph above, they only all finally die when I kill them manually from a separate script at 03:16:30.  This points towards two different problems I&#8217;m going to have to debug on the test machines&#8230;that only manifest themselves intermittently&#8230;with gdb&#8230;in the cloud. Good times!</p>
<p>Okay, first things first, let&#8217;s see if the robots will tell me where they&#8217;re going.  The lobbyclient robots can output verbose log files, but I had them turned off because I was worried about bogging down the client machines.  It turns out this isn&#8217;t much of a problem as I&#8217;ll discuss below, so I turned on logging and re-ran a test.  Then I ssh&#8217;d into one of the servers, and looked at the log files.  Well, before I looked the files themselves, I just did an <span style="font-family: courier new,courier;">ls</span> of the directory:</p>
<pre style="padding-left: 30px;">-rw-r--r-- 1 root root 258577 Mar  5 03:02 out59<br />-rw-r--r-- 1 root root 332320 Mar  5 03:02 out60<br />-rw-r--r-- 1 root root 177743 Mar  5 03:02 out61<br />-rw-r--r-- 1 root root 181639 Mar  5 03:02 out62<br />-rw-r--r-- 1 root root 264535 Mar  5 03:02 out63<br />-rw-r--r-- 1 root root 333515 Mar  5 03:02 out64<br />-rw-r--r-- 1 root root 282875 Mar  5 03:02 out65<br />-rw-r--r-- 1 root root 271040 Mar  5 03:02 out66<br />-rw-r--r-- 1 root root    264 Mar  5 03:01 out67<br />-rw-r--r-- 1 root root    264 Mar  5 03:01 out68<br />-rw-r--r-- 1 root root 284838 Mar  5 03:02 out69<br />-rw-r--r-- 1 root root 332967 Mar  5 03:02 out70<br />-rw-r--r-- 1 root root 303352 Mar  5 03:02 out71<br />-rw-r--r-- 1 root root 310596 Mar  5 03:02 out72<br />-rw-r--r-- 1 root root 194669 Mar  5 03:02 out73<br />-rw-r--r-- 1 root root 313193 Mar  5 03:02 out74<br />-rw-r--r-- 1 root root 238246 Mar  5 03:02 out75<br />-rw-r--r-- 1 root root 264190 Mar  5 03:02 out76<br />-rw-r--r-- 1 root root 198096 Mar  5 03:02 out77<br />-rw-r--r-- 1 root root 233980 Mar  5 03:02 out78<br />-rw-r--r-- 1 root root    264 Mar  5 03:01 out79<br />-rw-r--r-- 1 root root    264 Mar  5 03:01 out80<br />-rw-r--r-- 1 root root 301029 Mar  5 03:02 out81<br />-rw-r--r-- 1 root root 299694 Mar  5 03:02 out82<br />-rw-r--r-- 1 root root    264 Mar  5 03:01 out83<br />-rw-r--r-- 1 root root 351158 Mar  5 03:02 out84<br />-rw-r--r-- 1 root root 188071 Mar  5 03:02 out85<br />-rw-r--r-- 1 root root 242228 Mar  5 03:02 out86</pre>
<p>Well, there&#8217;s a clue, at least for the early-dyers.  The contents of those 264 byte log files look like this:</p>
<pre style="padding-left: 30px;">Lobby Standalone Client: 1000.0.0.5<br />init genrand w/0, first val is 1178568022<br />Running for 61 seconds.<br />LobbyClient started, v1000.0.0.5 / v12<br />LobbyClient UDP bound to port 32921<br />lobbyclient: sendto_kdc.c:617: cm_get_ssflags: Assertion `i &lt; selstate-&gt;nfds' failed.</pre>
<p>A-ha!  sendto_kdc.c is a file in the <a href="http://web.mit.edu/Kerberos/">Kerberos</a> libraries, which I use for login authentication.</p>
<p>I really love Kerberos, <a href="http://web.mit.edu/kerberos/dialogue.html">the architecture just feels right to me</a>, the API is simple, clean, and flexible, it&#8217;s cross-platform and open source, so I&#8217;ve been able to contribute features and bug fixes as I&#8217;ve used it and trace into the code when I was confused about something, and the folks at MIT that develop it are smart, knowledgeable, open-minded, and <a href="http://www.google.com/search?hl=en&amp;q=%2B&quot;Chris Hecker&quot; site%3Amail-archive.com kerberos">don&#8217;t mind some crazy indie game developer asking dumb questions</a> about the best way to do things that were pretty clearly not part of the original university and enterprise use-cases.  Most importantly, it&#8217;s battle-tested; it&#8217;s used by tons of different applications, and it&#8217;s the foundation of the modern Windows domain and Xbox authentication systems, so I know it works.  <strong>The last thing you ever want to do is roll your own authentication system.</strong></p>
<p>So, that assert&#8217;s the first place to look for the early-dying robots.</p>
<p>Next, I looked into the never-dying robots.  I logged into one of the machines that still had zombie robots<sup><a href="http://www.spyparty.com/2013/03/18/loadtesting-for-open-beta-part-3/#footnote_0_3139" id="identifier_0_3139" class="footnote-link footnote-identifier-link" title="ZOMBIE ROBOTS!!!">1</a></sup> running, ran <span style="font-family: Courier New,Courier,mono;">pidof lobbyclient</span> to figure out the process ID of one of them, and attached gdb to the robot.  A quick <span style="font-family: Courier New,Courier,mono;">thread apply all backtrace full</span> and I found the thread that was hanging while the main thread was trying to join them and exit cleanly.  It looked like the bad code was in a call to <a href="http://linux.die.net/man/2/poll">poll</a>, and it just so happened it was in sendto_kdc.c as well! I realized I was going to need some debug symbols, but this was easy since I build the Kerberos libraries myself,<sup><a href="http://www.spyparty.com/2013/03/18/loadtesting-for-open-beta-part-3/#footnote_1_3139" id="identifier_1_3139" class="footnote-link footnote-identifier-link" title="I have some local patches I haven&rsquo;t cleaned up enough to contribute yet">2</a></sup> so a quick scp of the debuginfo rpm and reattaching gdb and I could dig down a bit deeper.</p>
<p>The Kerberos libraries are built with optimizations on, which always makes debugging interesting, but I think it builds programming character to debug optimized code, so I don&#8217;t mind.<sup><a href="http://www.spyparty.com/2013/03/18/loadtesting-for-open-beta-part-3/#footnote_2_3139" id="identifier_2_3139" class="footnote-link footnote-identifier-link" title="gdb is not the best for assembly language debugging, but I did learn about &ldquo;layout asm&rdquo;, which helps a bit.">3</a></sup>  Here&#8217;s the code in question:</p>
<pre>    if (in-&gt;end_time.tv_sec == 0)<br />        timeout = -1;<br />    else {<br />        e = k5_getcurtime(&amp;now);<br />        if (e)<br />            return e;<br />        timeout = (in-&gt;end_time.tv_sec - now.tv_sec) * 1000 +<br />            (in-&gt;end_time.tv_usec - now.tv_usec) / 1000;<br />    }<br />    /* We don't need a separate copy of the selstate for poll, but use one<br />     * anyone for consistency with the select wrapper. */<br />    *out = *in;<br />    *sret = poll(out-&gt;fds, out-&gt;nfds, timeout);</pre>
<p>Well, these loadtesting machines are under some load themselves so they can be a bit sluggish, and there&#8217;s a problem with this code in that scenario if the call to k5_getcurtime() happens later than the in-&gt;end_time passed in by the caller.  As it says on the <a href="http://linux.die.net/man/2/poll">poll manpage</a>, <em>&#8220;Specifying a negative value in timeout means an infinite timeout.&#8221;</em>  Digging around on the stack verified the timeout was negative.</p>
<p>Okay, so now we have a pretty good clue for each of the problems.  The second problem with the poll timeout seemed easy to fix, but the first one was pretty mysterious and might take some real debugging.  I decided to<a href="http://mailman.mit.edu/pipermail/krbdev/2013-March/011451.html"> check with the krbdev mailing list</a> to see if they had any ideas while I looked into the problems more deeply.  While doing so, I looked at the main Kerberos source repository and <a href="http://mailman.mit.edu/pipermail/krbdev/2013-March/011452.html">found a commit for the timeout problem</a>, so it had already been fixed in a later version.  I was hoping maybe this was true of the assert as well.  True to form, the most excellent Greg Hudson <a href="http://mailman.mit.edu/pipermail/krbdev/2013-March/011453.html">replied with three more commits</a> he thought might help.  Meanwhile, I hacked the code to loop on a call to sleep() instead of asserting to convert the early-dyers into never-dying zombies so I could attach the debugger, since that&#8217;d worked so well on the second problem.</p>
<p>Sadly, although the negative-timeout-check fixed the original zombies, none of the fixes prevented the assert problem.  It wasn&#8217;t asserting anymore because the asserters were now looping, so now I had more zombies to deal with.</p>
<div id="attachment_3151" style="width: 610px" class="wp-caption aligncenter"><a href="http://cdn.spyparty.com/wp-content/uploads/2013/03/2013-03-17-16_45_07-50-not-working.png"><img class="size-large wp-image-3151" title="2013-03-17 16_45_07-50-not-working" src="http://cdn.spyparty.com/wp-content/uploads/2013/03/2013-03-17-16_45_07-50-not-working-600x302.png" alt="" width="600" height="302" /></a><p class="wp-caption-text">Lots of zombie robots!</p></div>
<p>Time to get down and dirty and debug it for real.</p>
<p>As an aside, it&#8217;s a weird feeling when you&#8217;re debugging something on an <a href="http://aws.amazon.com/ec2/">EC2 instance</a>, since you&#8217;re paying for it hourly.  I felt a definite pressure to hurry up and debug faster&#8230;oh no, there went another $0.06 * 5 instances!</p>
<a name="Too+deep+we+delved+there%2C+and+woke+the+nameless+fear%21"></a><h3>Too deep we delved there, and woke the nameless fear!</h3>
<p>Like I said, debugging optimized code builds character, and I built a lot of character with this bug.  The assert was in a function that was inlined by the optimizer, which was in a function that was inlined by the optimizer, which was in a loop, which looked like it had been unrolled.  It was slow going, with lots of restarts and stuffing values into memory and registers so the code would execute again.  At one point, I thought I&#8217;d <a href="http://mailman.mit.edu/pipermail/krbdev/2013-March/011466.html">narrowed it down to a compiler bug in gcc</a>, because it seemed like a variable wasn&#8217;t getting reloaded from the stack correctly sometimes, but it was really hard to tell with all the inlining.  Even thinking it was a compiler bug was pretty silly and that thought always violates <a title="One Bug’s Story, or, Assume it’s a bug!" href="http://www.spyparty.com/2013/02/09/one-bugs-story-or-assume-its-a-bug/">Assume it&#8217;s a Bug</a>, so I should have known better, but it happens. </p>
<p>Finally, a combination of stepping through the code, and looking at the code, and modifying the code revealed the problem. Here&#8217;s <a href="https://github.com/krb5/krb5/blob/krb5-1.9.2-final/src/lib/krb5/os/sendto_kdc.c#L1255">the source file at the version I was debugging</a>, linked to the area of the code where the bug lurked.  If you search for &#8220;host+1&#8243;, you will see that it occurs twice, once inside the loop, and once outside the loop.  This is what threw me when I was debugging&#8230;initially I didn&#8217;t notice there were two separate calls to service_fds(), so in the debugger I thought it was looping again but loading weird values.  I can only assume the second call almost never occurred in the wild for anybody but me after the inner loop on hosts completed, because in that case host+1 is n_conns+1, which is out-of-bounds for the connections.<sup><a href="http://www.spyparty.com/2013/03/18/loadtesting-for-open-beta-part-3/#footnote_3_3139" id="identifier_3_3139" class="footnote-link footnote-identifier-link" title="It never crashed because conns has a preallocated number of connections that was always bigger than n_conns+1">4</a></sup>  This bug was easy for me to fix locally, and it looks like it was (inadvertently?) fixed in <a href="https://github.com/krb5/krb5/commit/8b9d249e40601047e69c92d7acb578fd0bbafc00">this commit</a> in the main Kerberos code.</p>
<p>Thank goodness for open source code, where you can modify it and debug it when you run into troubles!</p>
<div id="attachment_3150" style="width: 610px" class="wp-caption aligncenter"><a href="http://cdn.spyparty.com/wp-content/uploads/2013/03/2013-03-17-16_36_15-50-working.png"><img class="size-large wp-image-3150" title="2013-03-17 16_36_15-50-working" src="http://cdn.spyparty.com/wp-content/uploads/2013/03/2013-03-17-16_36_15-50-working-600x302.png" alt="" width="600" height="302" /></a><p class="wp-caption-text">No more zombies!</p></div>
<a name="Moar+Robots%21"></a><h3>Moar Robots!</h3>
<p>Now that I (thought I) was done debugging the robots, and I still had 5 EC2 instances running, I decided to see how well the instances did with 100 robots on each.  My original tests indicated I could only run about 50 per <a href="http://aws.amazon.com/ec2/instance-types/">m1.small</a> instance, but the client also got a lot more efficient with the differential state update change described last time, and it turns out 100 per instance is no problem, as you can see here:</p>
<div id="attachment_3147" style="width: 610px" class="wp-caption aligncenter"><a href="http://cdn.spyparty.com/wp-content/uploads/2013/03/2013-03-16-02_53_11-100-robots.png"><img class="size-large wp-image-3147" title="2013-03-16 02_53_11-100-robots" src="http://cdn.spyparty.com/wp-content/uploads/2013/03/2013-03-16-02_53_11-100-robots-600x379.png" alt="" width="600" height="379" /></a><p class="wp-caption-text">Top on an m1.small instance running 100 robots at only 20% CPU.</p></div>
<p> The lobby was a little more grim with 501 clients:</p>
<div id="attachment_3153" style="width: 610px" class="wp-caption aligncenter"><a href="http://cdn.spyparty.com/wp-content/uploads/2013/03/SpyParty-v0.1.2602.1-20130316-02-53-39-0.png"><img class="size-large wp-image-3153" title="SpyParty-v0.1.2602.1-20130316-02-53-39-0" src="http://cdn.spyparty.com/wp-content/uploads/2013/03/SpyParty-v0.1.2602.1-20130316-02-53-39-0-600x415.png" alt="" width="600" height="415" /></a><p class="wp-caption-text">500 robots and me.</p></div>
<p> Here&#8217;s how the CPU looks with all these robots in the lobby, chatting at each other:</p>
<div id="attachment_3148" style="width: 610px" class="wp-caption aligncenter"><a href="http://cdn.spyparty.com/wp-content/uploads/2013/03/2013-03-16-02_53_25-100-in-lobby.png"><img class="size-large wp-image-3148" title="2013-03-16 02_53_25-100-in-lobby" src="http://cdn.spyparty.com/wp-content/uploads/2013/03/2013-03-16-02_53_25-100-in-lobby-600x322.png" alt="" width="600" height="322" /></a><p class="wp-caption-text">atop in CPU mode with 500 robots in the lobby jabbering.</p></div>
<p>There are two cores in this machine, which is why the lobbyserver is at 115% CPU.  It&#8217;s mostly single-threaded for simplicity, but it uses threads for servicing network connections.</p>
<p>However, once the robots start playing each other, the CPU usage drops a bunch:</p>
<div id="attachment_3149" style="width: 610px" class="wp-caption aligncenter"><a href="http://cdn.spyparty.com/wp-content/uploads/2013/03/2013-03-16-02_53_49-100-playing.png"><img class="size-large wp-image-3149" title="2013-03-16 02_53_49-100-playing" src="http://cdn.spyparty.com/wp-content/uploads/2013/03/2013-03-16-02_53_49-100-playing-600x322.png" alt="" width="600" height="322" /></a><p class="wp-caption-text">Stop talking, start playing!</p></div>
<p>This is pretty good news.  I think it means the chat system needs some work, because when everybody&#8217;s in the lobby all the chats go to all the players, but when people in are a match, chats only go between those two players, and they don&#8217;t get any of the lobby chats.  We&#8217;ll find out soon as I describe below.  Memory looks pretty good with 501 clients, staying at about 256kb per client:</p>
<pre style="padding-left: 30px;">2013/03/16-04:53:11: MEMORY_POSIX 501/993/492: resident 25540/25540, virtual 198000/198000<br />2013/03/16-04:53:11: MEMORY_NEW 501/993/492: bytes 132098963, news 69166, deletes 55478</pre>
<p>One last atop screenshot&#8230;this one is while the robots are starting up and connecting, but before they&#8217;re in the lobby:</p>
<div id="attachment_3146" style="width: 610px" class="wp-caption aligncenter"><a href="http://cdn.spyparty.com/wp-content/uploads/2013/03/2013-03-16-02_52_57-startup.png"><img class="size-large wp-image-3146" title="2013-03-16 02_52_57-startup" src="http://cdn.spyparty.com/wp-content/uploads/2013/03/2013-03-16-02_52_57-startup-600x322.png" alt="" width="600" height="322" /></a><p class="wp-caption-text">Loadtest startup performance.</p></div>
<p>This one shows Kerberos and <a href="http://www.openldap.org/">OpenLDAP</a> taking a fair amount of time at the start of a new loadtest.  I use LDAP as the database backend for Kerberos, among other things, and when all of these robots are trying to get login tickets at the same time, it bogs down a bit.  I&#8217;m not too worried about this profile, since this scenario of 500 people all needing tickets at the same time is going to be rare (the tickets last a while, so this doesn&#8217;t happen every time), and there are well-known ways of scaling Kerberos and OpenLDAP if I need them.</p>
<p>Finally, here&#8217;s a shot of the 100 robots per instance:</p>
<div id="attachment_3152" style="width: 610px" class="wp-caption aligncenter"><a href="http://cdn.spyparty.com/wp-content/uploads/2013/03/2013-03-17-16_47_28-100-working-plus-deadlock.png"><img class="size-large wp-image-3152" title="2013-03-17 16_47_28-100-working-plus-deadlock" src="http://cdn.spyparty.com/wp-content/uploads/2013/03/2013-03-17-16_47_28-100-working-plus-deadlock-600x302.png" alt="" width="600" height="302" /></a><p class="wp-caption-text">Wait a second&#8230;</p></div>
<p>Oh no!  Who the hell is that single zombie robot at the end on instance 4!?!  Sigh.  I find that machine, log in, attach the debugger, and check it out.  It looks like I have a pretty rare deadlock between two threads during shutdown.  I&#8217;m just going to ignore it for now and deal with it later.  All the bugs above were preventing robots from doing a good job at loadtesting, while this one is just preventing 1 out of 500 from shutting down completely&#8230;it can wait.  Here&#8217;s a shot of this guy, still in the lobby, mocking me:</p>
<div id="attachment_3154" style="width: 610px" class="wp-caption aligncenter"><a href="http://cdn.spyparty.com/wp-content/uploads/2013/03/SpyParty-v0.1.2602.1-20130316-02-55-54-0.png"><img class="size-large wp-image-3154" title="SpyParty-v0.1.2602.1-20130316-02-55-54-0" src="http://cdn.spyparty.com/wp-content/uploads/2013/03/SpyParty-v0.1.2602.1-20130316-02-55-54-0-600x415.png" alt="" width="600" height="415" /></a><p class="wp-caption-text">At least I have one more Sniper win on this debug server than this troll!</p></div>
<p>There&#8217;s actually another bug I found in the new differential state update code while I was testing this, where the server will send a duplicate client sometimes, but I had a comment in the code that I thought it might be possible, and now I know it is.  It turns out when you have 500 clients pounding on a server, you find bugs.</p>
<a name="Coming+Up+Next+Time"></a><h3>Coming Up Next Time</h3>
<p>Okay, so now we&#8217;ve got things where I can easily run a predictable number of loadtesting robots against the debug lobbyserver, and I&#8217;ve got some high level profiles telling me that I&#8217;m now CPU bound inside the server itself.  That points to a clear next step:  profile the code.  I use an old hacked up version of <a href="http://silverspaceship.com/src/iprof/">Sean Barrett&#8217;s iprof</a> for all my client runtime profiling, so my next task is to integrate that into the server code, and get it running on Linux.  That shouldn&#8217;t be too hard, and then I&#8217;ll be able to tell what&#8217;s actually taking the time<sup><a href="http://www.spyparty.com/2013/03/18/loadtesting-for-open-beta-part-3/#footnote_4_3139" id="identifier_4_3139" class="footnote-link footnote-identifier-link" title="This is only partially true, because iprof is single-threaded&hellip;I really wish there was a good cross-platform light-weight way to get per-thread timings.">5</a></sup> when a lot of clients are in the lobby.</p>
<p>My prediction, based on the above, is that the chat message handling is going to be the main culprit.  If so, it&#8217;ll be easy to queue up the chats and send them out in bunches, but I need to be careful here, because the robots chat a lot more than real humans would right now, so I don&#8217;t want to spend too much time optimizing this.  I think I&#8217;ll keep the robots as they are for the initial profiles, and then dial back their chattiness to more realistic levels after I&#8217;ve plucked the low-hanging chat fruit.  I also need to teach the robots how to use lobby rooms for a more realistic test.</p>
<p>Finally, I&#8217;m wondering if my usage of select() is going to be an issue as I get close to 1000 robots.  I may need to port to epoll().  We shall see!</p>
<p>&#8220;Assume Nothing!&#8221;</p>
<p>And finally, the SimCity launch has given me pause&#8230;I&#8217;m still forging ahead with my 1000 simultaneous goal, but I really hope it&#8217;s enough and things go smoothly.  I would much rather have a slow buildup of players over the next year as I roll out more cool stuff than a giant spike that melts everything and makes players grumpy.</p>
<p><a title="Loadtesting for Open Beta, Part 4: Done optimizing the lobbyserver!" href="http://www.spyparty.com/2013/05/21/loadtesting-for-open-beta-part-4-done-optimizing-the-lobbyserver/">On to Part 4&#8230;</a></p>
<hr/><ol class="footnotes"><li id="footnote_0_3139" class="footnote">ZOMBIE ROBOTS!!!</li><li id="footnote_1_3139" class="footnote">I have some local patches I haven&#8217;t cleaned up enough to contribute yet</li><li id="footnote_2_3139" class="footnote">gdb is not the best for assembly language debugging, but I did learn about &#8220;layout asm&#8221;, which helps a bit.</li><li id="footnote_3_3139" class="footnote">It never crashed because conns has a preallocated number of connections that was always bigger than n_conns+1</li><li id="footnote_4_3139" class="footnote">This is only partially true, because iprof is single-threaded&#8230;I really wish there was a good cross-platform light-weight way to get per-thread timings.</li></ol>]]></content:encoded>
			<wfw:commentRss>http://www.spyparty.com/2013/03/18/loadtesting-for-open-beta-part-3/feed/</wfw:commentRss>
		<slash:comments>9</slash:comments>
		</item>
		<item>
		<title>Loadtesting for Open Beta, Part 2</title>
		<link>http://www.spyparty.com/2013/03/03/loadtesting-for-open-beta-part-2/</link>
		<comments>http://www.spyparty.com/2013/03/03/loadtesting-for-open-beta-part-2/#comments</comments>
		<pubDate>Sun, 03 Mar 2013 23:28:11 +0000</pubDate>
		<dc:creator><![CDATA[checker]]></dc:creator>
				<category><![CDATA[beta]]></category>
		<category><![CDATA[indie games]]></category>
		<category><![CDATA[metrics]]></category>
		<category><![CDATA[programming]]></category>

		<guid isPermaLink="false">http://www.spyparty.com/?p=3109</guid>
		<description><![CDATA[In our last exciting episode of Loadtesting for Open Beta, we did some initial profiling to see how the lobbyserver held up under attack by a phalanx of loadtesting robots spawned in the cloud. It didn&#8217;t hold up, obviously, or the beta would already be open. Specifically, it failed by saturating the server&#8217;s 100Mbps network [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>In our <a title="Loadtesting for Open Beta, Part 1" href="http://www.spyparty.com/2013/02/27/loadtesting-for-open-beta-part-1/">last exciting episode of <em>Loadtesting for Open Beta</em></a>, we did some initial profiling to see how the lobbyserver held up under attack by a phalanx of loadtesting robots spawned in the cloud. It didn&#8217;t hold up, obviously, or the beta would already be open.</p>
<p>Specifically, it failed by saturating the server&#8217;s 100Mbps network link, which turned out to be a great way to fail because it meant there were some pretty simple things I could do to optimize the bandwidth utilization.  I had done the initial game<span style="font-size: medium;">↔</span>lobby protocol in the simplest way possible, so every time any player state changed, like a new connection, or switching from chatting in the lobby to playing, it sent out the entire list of player states to everybody.  This doesn&#8217;t scale at all, since as you add more players, most aren&#8217;t changing state, but you&#8217;re sending all of their states out to everybody even if only one changes.  This doesn&#8217;t mean it was the wrong way to program it initially; it&#8217;s really important when you&#8217;re writing complicated software<sup><a href="http://www.spyparty.com/2013/03/03/loadtesting-for-open-beta-part-2/#footnote_0_3109" id="identifier_0_3109" class="footnote-link footnote-identifier-link" title="especially by yourself!">1</a></sup> to do things the simplest way possible, as long as you have a vague plan for what you&#8217;ll do if it turns into a problem later.  In this case, I knew what I was doing was probably not going to work in the long run, but it got things up and running more quickly than overengineering some fancy solution I might not have needed, and I waited until it actually <em>was</em> a problem before fixing it.</p>
<a name="Tell+Me+Something+I+Don%26%238217%3Bt+Know"></a><h3>Tell Me Something I Don&#8217;t Know</h3>
<p>The solution to this problem is pretty obvious: differential state updates.  Or, in English, only send the stuff that&#8217;s changed to the people who care about it.  Doing differential updates is significantly more complicated than just spamming everybody with everything, however.  You still have to send the initial state of all the curent players when new players log in, you have to be able to add and remove players in the protocol, which you didn&#8217;t have to before because you were just sending the complete new state every time, etc.</p>
<p>This was going to be a fairly large change, so I took it by steps.  I knew that I&#8217;d have to send out the complete state of everybody to new logins, so it made sense to start by optimizing that initial packet using normal data size optimization techniques.  I pretty easily got it from about 88 bytes per player down to 42 bytes per player, which is nice, because my goal for these optimizations is 1000 simultaneous players, and at 88 bytes they wouldn&#8217;t all fit in my 64kb maximum packet size, where at 42 bytes they should fit, no problem, so I don&#8217;t have to add any kind of break-up-the-list-across-packets thing.  However, it turns out I actually got the ability to send the entire list across multiple packets while I was doing this, because I had to program the ability to add players as part of the differential updates, so now I could just use that packet type to send any clients in a really large player list that didn&#8217;t fit in a single packet.  But, like I said in the last episode, although I don&#8217;t think I&#8217;ll hit 1000 simultaneous outside of load testing for a while, it&#8217;s always nice to know you have that sort of thing in your back pocket for the future.</p>
<p>Once I&#8217;d tested the new optimized player list, I started making the updates differential.  New players get the initial list, and then they&#8217;re considered up-to-date and just get updates along with everybody else.  The list of new players is sent as additions to players already in the lobby.  For each player, I track some simple flags about what&#8217;s been updated in their state, so if they set or clear their /away message for example, that flag is set, and I only send that information.</p>
<p>In programming, usually when you&#8217;ve got the right design, you get some unintentional upside, and this case was no different.  Previously, I was not sending live updates to player stats (wins, game time, etc.) to the players in the lobby until the player was done playing the match, or some other state changed that caused everybody&#8217;s state to be re-sent.  Now, since the differential updates are efficient, I&#8217;m updating player stats in real time as well, so people in the lobby can see wins as they accumulate for players in matches, which is nice and how you&#8217;d expect it to work.</p>
<a name="Results"></a><h3>Results</h3>
<p>It basically worked exactly as planned.  After lots of debugging, of course.  Here you can see the profiles for one of the loadtests, which got to 340 simultaneous players in the lobby:</p>
<div id="attachment_3117" style="width: 610px" class="wp-caption aligncenter"><a href="http://cdn.spyparty.com/wp-content/uploads/2013/03/SpyParty-v0.1.2553.1-20130303-00-13-24-0.png"><img class="size-large wp-image-3117" title="SpyParty-v0.1.2553.1-20130303-00-13-24-0" src="http://cdn.spyparty.com/wp-content/uploads/2013/03/SpyParty-v0.1.2553.1-20130303-00-13-24-0-600x447.png" alt="" width="600" height="447" /></a><p class="wp-caption-text">I really need to have the robot Sniper win sometimes.</p></div>
<p>&nbsp;</p>
<div id="attachment_3115" style="width: 610px" class="wp-caption aligncenter"><a href="http://cdn.spyparty.com/wp-content/uploads/2013/03/2013-03-03-00_13_34-atop-mem.png"><img class="size-large wp-image-3115" title="2013-03-03 00_13_34-atop-mem" src="http://cdn.spyparty.com/wp-content/uploads/2013/03/2013-03-03-00_13_34-atop-mem-600x243.png" alt="" width="600" height="243" /></a><p class="wp-caption-text">atop in memory mode</p></div>
<p>&nbsp;</p>
<div id="attachment_3114" style="width: 610px" class="wp-caption aligncenter"><a href="http://cdn.spyparty.com/wp-content/uploads/2013/03/2013-03-03-00_13_31-atop-cpu.png"><img class=" wp-image-3114" title="2013-03-03 00_13_31-atop-cpu" src="http://cdn.spyparty.com/wp-content/uploads/2013/03/2013-03-03-00_13_31-atop-cpu-600x243.png" alt="" width="600" height="243" /></a><p class="wp-caption-text">atop in cpu mode</p></div>
<p>Look ma, 3% network utilization!  That&#8217;s whats so awesome about a really spiky profile&#8230;when you pound one of the spikes down, things just get better!</p>
<p>Here&#8217;s the new table of packet sizes for this run.  If you compare this with the <a title="Loadtesting for Open Beta, Part 1 - Packet Size Table" href="http://www.spyparty.com/2013/02/27/loadtesting-for-open-beta-part-1/#Update:+Assuming+More+Nothing&amp;#8230;Er,+Less+Nothing?">previous results</a>, you can see the PLAYER_LIST packets are way way way smaller, and this table was accumulated from two longer test runs, so it&#8217;s not even a fair comparison!  It&#8217;s interesting, because the TYPE_LOBBY_MESSAGE_PACKET is smaller as well, and I think that&#8217;s because now the robots can actually start games since the network isn&#8217;t saturated, and this means they don&#8217;t broadcast chats to the entire lobby while they&#8217;re playing, so that&#8217;s a nice side effect of optimizing the bandwidth.</p>
<table border="0" cellspacing="0" cellpadding="0" align="center">
<thead>
<tr>
<td><strong>Packet Type</strong></td>
<td align="right"><strong>Total Bytes</strong></td>
</tr>
</thead>
<tbody>
<tr>
<td>TYPE_LOBBY_MESSAGE_PACKET</td>
<td align="RIGHT">58060417</td>
</tr>
<tr>
<td>TYPE_LOBBY_PLAYER_LIST_UPDATE_PACKET</td>
<td align="RIGHT">29751413</td>
</tr>
<tr>
<td>TYPE_CLIENT_GAME_JOURNAL_PACKET</td>
<td align="RIGHT">18006186</td>
</tr>
<tr>
<td>TYPE_LOBBY_ROOM_LIST_PACKET</td>
<td align="RIGHT">16674479</td>
</tr>
<tr>
<td>TYPE_LOBBY_PLAYER_LIST_ADDITION_PACKET</td>
<td align="RIGHT">4280563</td>
</tr>
<tr>
<td>TYPE_LOBBY_PLAYER_LIST_PACKET</td>
<td align="RIGHT">3482691</td>
</tr>
<tr>
<td>TYPE_CLIENT_MESSAGE_PACKET</td>
<td align="RIGHT">1501822</td>
</tr>
<tr>
<td>TYPE_CLIENT_LOGIN_PACKET</td>
<td align="RIGHT">477356</td>
</tr>
<tr>
<td>TYPE_CLIENT_INVITE_PACKET</td>
<td align="RIGHT">435368</td>
</tr>
<tr>
<td>TYPE_LOBBY_INVITE_PACKET</td>
<td align="RIGHT">275781</td>
</tr>
<tr>
<td>TYPE_LOBBY_LOGIN_PACKET</td>
<td align="RIGHT">235878</td>
</tr>
<tr>
<td>TYPE_LOBBY_GAME_ID_PACKET</td>
<td align="RIGHT">96000</td>
</tr>
<tr>
<td>TYPE_LOBBY_GAME_OVER_PACKET</td>
<td align="RIGHT">68901</td>
</tr>
<tr>
<td>TYPE_CLIENT_GAME_ID_CONFIRM_PACKET</td>
<td align="RIGHT">40257</td>
</tr>
<tr>
<td>TYPE_LOBBY_PLAY_PACKET</td>
<td align="RIGHT">32498</td>
</tr>
<tr>
<td>TYPE_CLIENT_IN_MATCH_PACKET</td>
<td align="RIGHT">25714</td>
</tr>
<tr>
<td>TYPE_LOBBY_IN_MATCH_PACKET</td>
<td align="RIGHT">21204</td>
</tr>
<tr>
<td>TYPE_CLIENT_CANDIDATE_PACKET</td>
<td align="RIGHT">16089</td>
</tr>
<tr>
<td>TYPE_CLIENT_PLAY_PACKET</td>
<td align="RIGHT">12419</td>
</tr>
<tr>
<td>TYPE_CLIENT_GAME_ID_REQUEST_PACKET</td>
<td align="RIGHT">9610</td>
</tr>
<tr>
<td>TYPE_LOBBY_WELCOME_PACKET</td>
<td align="RIGHT">4494</td>
</tr>
<tr>
<td>TYPE_CLIENT_JOIN_PACKET</td>
<td align="RIGHT">4494</td>
</tr>
<tr>
<td>TYPE_KEEPALIVE_PACKET</td>
<td align="RIGHT">1011</td>
</tr>
<tr>
<td>TYPE_CLIENT_IDLE_PACKET</td>
<td align="RIGHT">24</td>
</tr>
</tbody>
</table>
<p>Hmm, I just noticed as I&#8217;m writing this that the resident memory utilization in the atop screenshot is way lower now than before&#8230;I wonder why&#8230; On the application side I take about 250kb per player right now, which at 340 players should be about 85MB.  Looking at the lobbyserver logs, right about when the screenshot was taken, the lobby self-reported this data:</p>
<pre style="padding-left: 30px;">2013/03/03-02:13:15: MEMORY_POSIX 348/757/409: resident 12808/12808, virtual 160276/160276<br />2013/03/03-02:13:15: MEMORY_NEW 348/757/409: bytes 91766974, news 45707, deletes 36155</pre>
<p>The MEMORY_NEW stats looks about right for this load and my quick math, but the MEMORY_POSIX stats—which are read from /proc/pid/status—match the atop results: expected virtual but low resident.   Maybe it was just paged out for a second, or maybe I&#8217;m not touching much of that 250kb and so it doesn&#8217;t stay resident.  A lot of it is network buffers, so it makes some sense with this lower bandwidth protocol that it wouldn&#8217;t be resident compared to last profile because less buffering is having to be done.  I&#8217;ll have to investigate this more.</p>
<a name="Up+Next%2C+The+Case+of+the+Missing+Robots"></a><h3>Up Next, The Case of the Missing Robots</h3>
<p>So, the bandwidth optimizations were a resounding success!  Plus, both the CPU and memory utilization of the lobbyserver are really reasonable and haven&#8217;t been optimized at all, so we&#8217;re sitting pretty for getting to 1000 simulataneous robots&#8230;</p>
<p>Except, where are the remaining 160 robots?  In the test above, I ran 10 EC2 instances, each with 50 robots, thinking the optimizations might let me get to 500 simultaneous and find the next performance issue&#8230;but it never got above 340 in the lobby.  I updated my perl loadtesting framework and had each instance output how many lobbyclients were running every two seconds with this shell command over ssh:</p>
<pre style="padding-left: 30px;">'while true; do echo `date +%T`,`pidof lobbyclient | wc -w`; sleep 2; done'</pre>
<p>And then I loaded that into gnuplot,<sup><a href="http://www.spyparty.com/2013/03/03/loadtesting-for-open-beta-part-2/#footnote_1_3109" id="identifier_1_3109" class="footnote-link footnote-identifier-link" title="&hellip;which I hate, but I forgot to install excel on my new laptop, and Google&rsquo;s spreadsheet sucks at pivottables, and the Office for Web excel doesn&rsquo;t even have them as far as I could tell!">2</a></sup> and graphed the number of robots on each instance:</p>
<div id="attachment_3116" style="width: 610px" class="wp-caption aligncenter"><a href="http://cdn.spyparty.com/wp-content/uploads/2013/03/ec2-loadtest-client-counts.png"><img class="size-large wp-image-3116" title="ec2-loadtest-client-counts" src="http://cdn.spyparty.com/wp-content/uploads/2013/03/ec2-loadtest-client-counts-600x344.png" alt="" width="600" height="344" /></a><p class="wp-caption-text">The number of loadtest robots running on each EC2 instance.</p></div>
<p>You can see that they all started up with 50, but then a bunch of them lost clients until they found a steady state.   Something is killing my robots, and I need to figure out what it is&#8230;</p>
<p><a title="Loadtesting for Open Beta, Part 3" href="http://www.spyparty.com/2013/03/18/loadtesting-for-open-beta-part-3/">Turn the page to Part 3&#8230;</a></p>
<hr/><ol class="footnotes"><li id="footnote_0_3109" class="footnote">especially by yourself!</li><li id="footnote_1_3109" class="footnote">&#8230;which I hate, but I forgot to install excel on my new laptop, and Google&#8217;s spreadsheet sucks at pivottables, and the Office for Web excel doesn&#8217;t even have them as far as I could tell!</li></ol>]]></content:encoded>
			<wfw:commentRss>http://www.spyparty.com/2013/03/03/loadtesting-for-open-beta-part-2/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>Loadtesting for Open Beta, Part 1</title>
		<link>http://www.spyparty.com/2013/02/27/loadtesting-for-open-beta-part-1/</link>
		<comments>http://www.spyparty.com/2013/02/27/loadtesting-for-open-beta-part-1/#comments</comments>
		<pubDate>Thu, 28 Feb 2013 03:21:24 +0000</pubDate>
		<dc:creator><![CDATA[checker]]></dc:creator>
				<category><![CDATA[beta]]></category>
		<category><![CDATA[indie games]]></category>
		<category><![CDATA[metrics]]></category>
		<category><![CDATA[programming]]></category>

		<guid isPermaLink="false">http://www.spyparty.com/?p=3072</guid>
		<description><![CDATA[Way back in 2011, right before I opened up Early-Access Beta signups, I loadtested and optimized the signup page to make sure it wouldn&#8217;t crash if lots of people were trying to submit their name and email and confirm their signup. I always intended to write up a technical post or two about that optimization [&#8230;]]]></description>
				<content:encoded><![CDATA[<p><a title="Here we go…it’s SpyParty Beta time!" href="http://www.spyparty.com/2011/05/10/here-we-go-its-spyparty-beta-time/">Way back in 2011</a>, right before I opened up <a title="Sign Up for the SpyParty Early-Access Beta!" href="http://www.spyparty.com/beta-sign-up/"><em>Early-Access Beta</em></a> signups, I loadtested and optimized the signup page to make sure it wouldn&#8217;t crash if lots of people were trying to submit their name and email and confirm their signup. I always intended to write up a technical post or two about that optimization process because it was an interesting engineering exercise, but I have yet to get around to it. However, I can summarize the learnings here pretty quickly: <a href="http://wordpress.org/">WordPress</a> is excruciatingly slow, <a href="https://www.varnish-cache.org/">Varnish</a> is incredibly fast, I ♥ <a href="http://www.perl.org/">Perl</a>,<sup><a href="http://www.spyparty.com/2013/02/27/loadtesting-for-open-beta-part-1/#footnote_0_3072" id="identifier_0_3072" class="footnote-link footnote-identifier-link" title="See this thread for how I wrote the dynamic loadtesting form submission in a way that would saturate the network link.">1</a></sup> <a href="http://httpd.apache.org/">Apache</a> with plain old mod_php (meaning <em>not</em> loading WordPress) was actually <em>way</em> faster than I expected, slightly faster even than <a href="http://nginx.org/">nginx</a> + php-fpm in my limited tests, <a href="http://aws.amazon.com/cloudfront/">CloudFront</a> is pretty easy to use,<sup><a href="http://www.spyparty.com/2013/02/27/loadtesting-for-open-beta-part-1/#footnote_1_3072" id="identifier_1_3072" class="footnote-link footnote-identifier-link" title="I use CF for images and other static stuff, with W3 Total Cache to keep them synced to S3, but I only use W3TC for this CDN sync, since Varnish blows it out of the water for actual caching.">2</a></sup> and even cheap and small dedicated servers can handle a lot of traffic if you&#8217;re smart about it.</p>
<p>Like with any kind of optimization, <em><a href="http://www.phatcode.net/res/224/files/html/ch03/03-01.html">Assume Nothing</a></em>, so you should always write the loadtester first, and run it to get a baseline performance profile, and continue running it as you optimize the hotspots. When I started, the signup submission could only handle 2 or 3 submits per second. When I was done, it could handle 400 submissions per second. I figured that was enough.<sup><a href="http://www.spyparty.com/2013/02/27/loadtesting-for-open-beta-part-1/#footnote_2_3072" id="identifier_2_3072" class="footnote-link footnote-identifier-link" title="Let me be clear, I think 400 submissions per second is really pretty slow for raw performance on a modern computer, but web apps these days have so many layers that you lose a ton of performance relative to what would happen if you wrote the whole thing in C. For an interesting example of this, there&rsquo;s a wacky high performance web server called G-WAN that gets rid of all the layers and lets you write the pages directly in compiled C.">3</a></sup> If more than 400 people were signing up for the <strong>SpyParty</strong> beta every second, well, let&#8217;s file that under &#8220;good problem to have&#8221;.</p>
<p>After all the loadtesting and optimizing, the signups <a title="Beta Data" href="http://www.spyparty.com/2011/05/12/beta-data/">went off without a hitch</a>.</p>
<p>Loadtesting and optimizing the beta signup process was important, because the entire reason I took signups instead of just letting people play immediately was &#8220;fear of the unknown&#8221;. I couldn&#8217;t know in advance how many people would be interested in the game, and getting a couple web forms scalable in case that number was &#8220;a lot&#8221; was much easier than getting the full game and its server scalable, and that&#8217;s ignoring the very real need to exert some control over the growth of the community, to make sure the game wasn&#8217;t incredibly buggy on different hardware configurations or that there wasn&#8217;t some glaring balance issue, etc. Overall, starting with signups and a closed beta was great for the game, even if it&#8217;s meant frustrating people who signed up and want to play.</p>
<p>But it&#8217;s been long enough, and I&#8217;m now finally actively loadtesting and optimizing for opening the beta!</p>
<a name="Lobby+Loadtesting+Framework"></a><h3>Lobby Loadtesting Framework</h3>
<p>Like with the signup form, I&#8217;m loadtesting first. This will tell me where I need to optimize, and allow me to test my progress against the baseline. However, loadtesting a game lobby server is a lot more complicated than loadtesting a web form, so it&#8217;s a bit slower-going. I&#8217;ve had to create a robot version of the game client that logs into the lobby, chats, invites other robots to play, and then reports on the results of the fake games played. I build this on top of the game&#8217;s client interface, so it looks just like a real game to the lobby.</p>
<p>As with all testing, you need to make sure you aren&#8217;t <a href="http://en.wikipedia.org/wiki/Werner_Heisenberg">Heisenberg</a>-ing<sup><a href="http://www.spyparty.com/2013/02/27/loadtesting-for-open-beta-part-1/#footnote_3_3072" id="identifier_3_3072" class="footnote-link footnote-identifier-link" title="I just read on wikipedia that the uncertainty principle is often confused with the observer effect, and so on the surface this verbing of Heisenberg&rsquo;s name isn&rsquo;t correct, except he apparently also confused the two, so I&rsquo;m going to keep on verbing.">4</a></sup> your results, so I wanted to get fairly close to the same load that would happen with multiple real game clients hitting the server. This means I had to have a good number of machines running these robots hitting the test lobby at the same time, and that means using cloud computing. I was inspired by the <a href="http://blog.apps.chicagotribune.com/2010/07/08/bees-with-machine-guns/"><em>bees with machine guns</em></a> article about using Amazon Web Services&#8217;s Elastic Compute Cloud (EC2) to launch a bunch of cheap http load testers. I use AWS for <strong>SpyParty</strong> already, distributing updates and uploading crashdumps using S3, so this seemed like a good fit. At first I tried modifying the bees code to do what I want, but I found the Python threading technique they used for controlling multiple instances didn&#8217;t scale well running on Windows, and since I wanted more control over the instances anyway and the core idea was not terribly difficult to implement, I wrote my own version in Perl, which I&#8217;m much more familiar with. The code uses <a href="http://search.cpan.org/~mallen/Net-Amazon-EC2-0.23/lib/Net/Amazon/EC2.pm">Net::Amazon::EC2</a> to talk to AWS to start, list, and stop EC2 instances, and <a href="http://search.cpan.org/~rkitover/Net-SSH2-0.48/lib/Net/SSH2.pm">Net::SSH2</a> to talk to the instances themselves, executing commands and waiting for exit codes, downloading logs, and whatnot. I just use an existing CentOS EC2 AMI<sup><a href="http://www.spyparty.com/2013/02/27/loadtesting-for-open-beta-part-1/#footnote_4_3072" id="identifier_4_3072" class="footnote-link footnote-identifier-link" title="ami-c9846da0">5</a></sup> and then have the scripts download and install my robots onto it from S3 every time I start one up; I didn&#8217;t want to bother with creating a custom AMI when my files are pretty small. I&#8217;m going to post all the loadtest framework code once I&#8217;ve got it completely working so others can use it.</p>
<a name="How+Much+is+Enough%3F"></a><h3>How Much is Enough?</h3>
<p>In loadtesting the loadtesters, I found that an <a href="http://aws.amazon.com/ec2/instance-types/"><em>m1.small</em> instance</a> could run about 50 loadtest bots simultaneously with my current client code. I can switch to larger and more expensive EC2 instance types if I need to run more robots per instance, and as I optimize the server I&#8217;m pretty sure the client code will get optimized as well, which will allow more concurrency. Amazon limits accounts to 20 simultaneous EC2 instances until you apply for an exception, so I&#8217;ve done that,<sup><a href="http://www.spyparty.com/2013/02/27/loadtesting-for-open-beta-part-1/#footnote_5_3072" id="identifier_5_3072" class="footnote-link footnote-identifier-link" title="although they haven&rsquo;t gotten back to me so I guess I&rsquo;ll apply again&hellip;sigh, customer service &ldquo;in the cloud&rdquo; &nbsp;Update: Woot! &nbsp;My limit has been increased, now I can DDOS myself to my heart&rsquo;s content!">6</a></sup> but even with that limitation, I can loadtest to about 1000 concurrent clients, which seems like more than enough for now.</p>
<p>I still don&#8217;t know exactly what to expect when I open up the beta, but I don&#8217;t think I&#8217;ll hit 1000 simultaneous <strong>SpyParty</strong> players outside of loadtesting anytime soon. If you look at <a href="http://store.steampowered.com/stats/">the Steam Stats page</a>, 1000 simultaneous players is right in the middle of the top 100 games on the entire service, including some pretty popular mainstream games with mature player communities. In the current closed beta, I think our maximum number of simultaneous players has been around 25, and it&#8217;s usually between 10 and 15 on any given night at peak times, assuming there&#8217;s no event happening and I haven&#8217;t just sent out a big batch of invites. I still have about 6000 people left to invite for the first time from the signup list, and 9000 who didn&#8217;t register on their first invite to re-invite, all of whom I&#8217;ll use for live player loadtesting after the 1000 robots are happily playing without complaints. I think the spike from those last closed invites will be bigger than the open beta release spike, unless there are a ton of people who didn&#8217;t want to sign up with their email address, but who will buy the game once the beta is open. I guess that&#8217;s possible, but who knows? Again, if we go over 1000 simultaneous, I guess I will scramble to move the lobby to a bigger server, and keep repeating the &#8220;good problem to have&#8221; mantra over and over again, but I&#8217;m betting it&#8217;s not going to happen and things will go smoothly.</p>
<p>After open beta there will be a long list of awesome stuff coming into the game, including new maps and missions, spectation and replays, the <a title="The New SpyParty Character Art Style" href="http://www.spyparty.com/2012/08/27/the-new-spyparty-character-art-style/">new art</a>, and lots more, but once things are open it&#8217;ll be easier to predict the size of those spikes and plan accordingly. Eventually I&#8217;ll probably (hopefully?) have to move the lobby off my current server, but I&#8217;m pretty sure based on my initial testing that the old girl can keep things going smoothly a bit longer.</p>
<a name="Initial+Loadtesting+Baseline"></a><h3>Initial Loadtesting Baseline</h3>
<p>Okay, so what happens when I unleash the robots? Well, I haven&#8217;t let 1000 of them loose yet, but I&#8217;ve tried 500, and things fall over, as you might expect. It looks like around 250 is the maximum that can even connect right now, which is actually more than I thought I&#8217;d start out with.</p>
<div id="attachment_3075" style="width: 610px" class="wp-caption aligncenter"><a href="http://cdn.spyparty.com/wp-content/uploads/2013/02/SpyParty-v0.1.2532.0-20130227-11-56-33-0.png"><img class="size-large wp-image-3075" title="SpyParty-v0.1.2532.0-20130227-11-56-33-0" src="http://cdn.spyparty.com/wp-content/uploads/2013/02/SpyParty-v0.1.2532.0-20130227-11-56-33-0-600x505.png" alt="" width="600" height="505" /></a><p class="wp-caption-text">The loadtesting robots are not very good conversationalists.</p></div>
<p>Things don&#8217;t work very well even with 250 clients, though, with connections failing, and match invites not going through.<sup><a href="http://www.spyparty.com/2013/02/27/loadtesting-for-open-beta-part-1/#footnote_6_3072" id="identifier_6_3072" class="footnote-link footnote-identifier-link" title="Let&rsquo;s ignore the lobby UI also drawing all over itself for now.">7</a></sup> However, when I looked at <a href="http://www.atoptool.nl">atop</a> while the robots were pounding on the lobby, a wonderful thing was apparent:</p>
<div id="attachment_3096" style="width: 610px" class="wp-caption aligncenter"><a href="http://cdn.spyparty.com/wp-content/uploads/2013/02/2013-02-27-12_32_00-atop-cpu.png"><img class="size-large wp-image-3096" title="2013-02-27 12_32_00-atop-cpu" src="http://cdn.spyparty.com/wp-content/uploads/2013/02/2013-02-27-12_32_00-atop-cpu-600x256.png" alt="" width="600" height="256" /></a><p class="wp-caption-text">atop in CPU mode</p></div>
<p>&nbsp;</p>
<div id="attachment_3095" style="width: 610px" class="wp-caption aligncenter"><a href="http://cdn.spyparty.com/wp-content/uploads/2013/02/2013-02-27-12_31_53-atop-mem.png"><img class="size-large wp-image-3095" title="2013-02-27 12_31_53-atop-mem" src="http://cdn.spyparty.com/wp-content/uploads/2013/02/2013-02-27-12_31_53-atop-mem-600x256.png" alt="" width="600" height="256" /></a><p class="wp-caption-text">atop in memory mode</p></div>
<p>Neither the CPU utilization nor the memory utilization was too terrible, but the lobbyserver was saturating the 100 Mbps ethernet link! That&#8217;s awesome, because that&#8217;s going to be easy to fix!</p>
<p>Before I explain, let me say that the best kind of profile is one with a single giant spike, one thing that&#8217;s obviously completely slow and working poorly. The worse kind of profile is a flat line, where everything is taking 3% of the time and there&#8217;s no single thing you can optimize. This is a great profile, because it points right towards the first thing I need to fix, which is the network bandwidth.</p>
<p>My protocol between the game clients and the lobby server is really pretty dumb in a lot of ways, but the biggest way it&#8217;s dumb is that on any state change of any client, it sends the entire list of clients and their current state to every client. This is the simplest thing to do and means there&#8217;s no need to track which clients have received which information, and this in turn means it&#8217;s the right thing to do first when you&#8217;re getting things going, but it&#8217;s also terribly wasteful performance-wise compared to just sending out the clients who changed each tick. So, I was delighted to see that bandwidth was my first problem, because it&#8217;s easy to see that I have to fix the protocol. I&#8217;m guessing switching to a differential player state update will cut the bandwidth by 50x, which will then reveal the next performance spike.</p>
<p style="text-align: left;">I can&#8217;t wait to find out what it will be!<sup><a href="http://www.spyparty.com/2013/02/27/loadtesting-for-open-beta-part-1/#footnote_7_3072" id="identifier_7_3072" class="footnote-link footnote-identifier-link" title="You can see the CPU usage is pretty high relative to the memory usage, and seeing slapd and krb5kdc in there is a bit worrying, since that&rsquo;s kerberos and ldap, which are used for the login and client authentication and are going to be a bit harder to optimize if they start poking their heads up too high, but both of them have very battle-tested enterprise-scale optimization solutions via replication, so worst-case is I&rsquo;ll have to get another machine for them, I think. If the lobbyserver itself is still CPU-bound after fixing the bandwidth issue, then I&rsquo;ll start normal code optimization for it, including profiling, of course. I&rsquo;ll basically recurse on the lobbyserver executable!">8</a></sup></p>
<p>Oh, and the total EC2 bill for my loadtesting over the past few days: $5.86</p>
<a name="So%26%238230%3BOpen+Beta%3F"></a><h3>So&#8230;Open Beta?</h3>
<p>Within weeks! Weeks, I tell you!</p>
<p>Oh, and as I&#8217;ve said before, everybody who is signed up will get invited in before open beta. I will then probably have a short &#8220;quiet period&#8221; where I let things settle down before really opening it up, so if you want in before open beta, <a title="Sign Up for the SpyParty Early-Access Beta!" href="http://www.spyparty.com/beta-sign-up/">sign up now</a>.</p>
<a name="Update%3A+Assuming+More+Nothing%26%238230%3BEr%2C+Less+Nothing%3F"></a><h3>Update: Assuming More Nothing&#8230;Er, Less Nothing?</h3>
<p>After posting this article, I was about to start optimizing the client list packets, when it occurred to me I wasn&#8217;t <a href="http://www.phatcode.net/res/224/files/html/ch03/03-01.html">assuming enough nothing</a>, because I was assuming it was the client list taking all the bandwidth. This made me a bit nervous, which is the right feeling to have when you&#8217;re not following your own advice,<sup><a href="http://www.spyparty.com/2013/02/27/loadtesting-for-open-beta-part-1/#footnote_8_3072" id="identifier_8_3072" class="footnote-link footnote-identifier-link" title="&hellip;let alone Mike Abrash&rsquo;s advice!">9</a></sup> so I implemented a really simple bit of code that accumulated the per-packet send and recieve sizes, and printed them on exit, and then threw another 250 robots at the server for 60 seconds. The results validated the client list assumption, it&#8217;s by far the biggest bandwidth consumer, sending 1.6GB in 60 seconds.<sup><a href="http://www.spyparty.com/2013/02/27/loadtesting-for-open-beta-part-1/#footnote_9_3072" id="identifier_9_3072" class="footnote-link footnote-identifier-link" title="Or actually trying to send, since 1.6GB in 60 seconds is 200Mbps, which is not happening on a 100Mbps link!">10</a></sup> However, it did show that the lobby sending chat and status messages to the clients is also maybe going to be a problem, so yet again: <em>measuring things is crucial</em>.</p>
<table border="0" cellspacing="0" cellpadding="0" align="center">
<thead>
<tr>
<td><strong>Packet Type</strong></td>
<td align="right"><strong>Total Bytes</strong></td>
</tr>
</thead>
<tbody>
<tr>
<td>TYPE_LOBBY_PLAYER_LIST_PACKET</td>
<td align="right">1632549877</td>
</tr>
<tr>
<td>TYPE_LOBBY_MESSAGE_PACKET</td>
<td align="right">66687600</td>
</tr>
<tr>
<td>TYPE_LOBBY_ROOM_LIST_PACKET</td>
<td align="right">9474937</td>
</tr>
<tr>
<td>TYPE_CLIENT_INVITE_PACKET</td>
<td align="right">303056</td>
</tr>
<tr>
<td>TYPE_CLIENT_MESSAGE_PACKET</td>
<td align="right">226779</td>
</tr>
<tr>
<td>TYPE_CLIENT_LOGIN_PACKET</td>
<td align="right">157795</td>
</tr>
<tr>
<td>TYPE_LOBBY_INVITE_PACKET</td>
<td align="right">131667</td>
</tr>
<tr>
<td>TYPE_LOBBY_LOGIN_PACKET</td>
<td align="right">77951</td>
</tr>
<tr>
<td>TYPE_KEEPALIVE_PACKET</td>
<td align="right">43032</td>
</tr>
<tr>
<td>TYPE_CLIENT_GAME_JOURNAL_PACKET</td>
<td align="right">5478</td>
</tr>
<tr>
<td>TYPE_LOBBY_PLAY_PACKET</td>
<td align="right">1888</td>
</tr>
<tr>
<td>TYPE_LOBBY_WELCOME_PACKET</td>
<td align="right">1491</td>
</tr>
<tr>
<td>TYPE_CLIENT_JOIN_PACKET</td>
<td align="right">1491</td>
</tr>
<tr>
<td>TYPE_CLIENT_PLAY_PACKET</td>
<td align="right">836</td>
</tr>
<tr>
<td>TYPE_CLIENT_IN_MATCH_PACKET</td>
<td align="right">713</td>
</tr>
<tr>
<td>TYPE_LOBBY_IN_MATCH_PACKET</td>
<td align="right">532</td>
</tr>
<tr>
<td>TYPE_CLIENT_CANDIDATE_PACKET</td>
<td align="right">490</td>
</tr>
<tr>
<td>TYPE_LOBBY_GAME_ID_PACKET</td>
<td align="right">300</td>
</tr>
<tr>
<td>TYPE_CLIENT_GAME_ID_REQUEST_PACKET</td>
<td align="right">30</td>
</tr>
</tbody>
</table>
<p>It&#8217;s interesting that the clients are only sending 300KB worth of chat messages to the lobby, but it&#8217;s sending 66MB back to them, but 66MB is around 250 * 300KB, so it makes back-of-the-envelope sense. I&#8217;m probably going to need to investigate that more once I&#8217;ve hammered the player list traffic down. Maybe I&#8217;ll have to accumulate them every tick, compress them all, and send them out.</p>
<p><a title="Loadtesting for Open Beta, Part 2" href="http://www.spyparty.com/2013/03/03/loadtesting-for-open-beta-part-2/">This way to Part 2&#8230;</a></p>
<hr/><ol class="footnotes"><li id="footnote_0_3072" class="footnote">See <a href="http://www.perlmonks.org/?node_id=901638">this thread</a> for how I wrote the dynamic loadtesting form submission in a way that would saturate the network link.</li><li id="footnote_1_3072" class="footnote">I use CF for images and other static stuff, with <a href="http://wordpress.org/extend/plugins/w3-total-cache/">W3 Total Cache</a> to keep them synced to S3, but I only use W3TC for this CDN sync, since Varnish blows it out of the water for actual caching.</li><li id="footnote_2_3072" class="footnote">Let me be clear, I think 400 submissions per second is really pretty slow for raw performance on a modern computer, but web apps these days have so many layers that you lose a ton of performance relative to what would happen if you wrote the whole thing in C. For an interesting example of this, there&#8217;s a wacky high performance web server called <a href="http://gwan.com/benchmark/babel.html">G-WAN</a> that gets rid of all the layers and lets you write the pages directly in compiled C.</li><li id="footnote_3_3072" class="footnote">I just read on wikipedia that the <a href="http://en.wikipedia.org/wiki/Uncertainty_principle">uncertainty principle</a> is often confused with the <a href="http://en.wikipedia.org/wiki/Observer_effect_%28physics%29">observer effect</a>, and so on the surface this verbing of Heisenberg&#8217;s name isn&#8217;t correct, except he apparently also confused the two, so I&#8217;m going to keep on verbing.</li><li id="footnote_4_3072" class="footnote">ami-c9846da0</li><li id="footnote_5_3072" class="footnote">although they haven&#8217;t gotten back to me so I guess I&#8217;ll apply again&#8230;sigh, customer service &#8220;in the cloud&#8221;  Update: Woot!  My limit has been increased, now I can DDOS myself to my heart&#8217;s content!</li><li id="footnote_6_3072" class="footnote">Let&#8217;s ignore the lobby UI also drawing all over itself for now.</li><li id="footnote_7_3072" class="footnote">You can see the CPU usage is pretty high relative to the memory usage, and seeing slapd and krb5kdc in there is a bit worrying, since that&#8217;s <a href="http://web.mit.edu/Kerberos/">kerberos</a> and <a href="http://www.openldap.org/">ldap</a>, which are used for the login and client authentication and are going to be a bit harder to optimize if they start poking their heads up too high, but both of them have very battle-tested enterprise-scale optimization solutions via replication, so worst-case is I&#8217;ll have to get another machine for them, I think. If the lobbyserver itself is still CPU-bound after fixing the bandwidth issue, then I&#8217;ll start normal code optimization for it, including profiling, of course. I&#8217;ll basically recurse on the lobbyserver executable!</li><li id="footnote_8_3072" class="footnote">&#8230;let alone Mike Abrash&#8217;s advice!</li><li id="footnote_9_3072" class="footnote">Or actually <em>trying to send</em>, since 1.6GB in 60 seconds is 200Mbps, which is not happening on a 100Mbps link!</li></ol>]]></content:encoded>
			<wfw:commentRss>http://www.spyparty.com/2013/02/27/loadtesting-for-open-beta-part-1/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
		<item>
		<title>A Room With a View&#8230;of the Courtyard</title>
		<link>http://www.spyparty.com/2012/10/10/a-room-with-a-view-of-the-courtyard/</link>
		<comments>http://www.spyparty.com/2012/10/10/a-room-with-a-view-of-the-courtyard/#comments</comments>
		<pubDate>Wed, 10 Oct 2012 06:28:23 +0000</pubDate>
		<dc:creator><![CDATA[checker]]></dc:creator>
				<category><![CDATA[art]]></category>
		<category><![CDATA[beta]]></category>
		<category><![CDATA[design]]></category>
		<category><![CDATA[metrics]]></category>

		<guid isPermaLink="false">http://www.spyparty.com/?p=2719</guid>
		<description><![CDATA[After a too-long PAX-and-New Character Art-based build hiatus, I&#8217;ve updated the Early-Access Beta build 4 times in the last week or so!  It feels good to be updating the game for the beta testers again! There are a lot of small fixes and features in these builds, but the two big ones are the addition [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>After a too-long <a title="Gigantic PAX West 2012 SpyParty and Storyteller Gallery!" href="http://www.spyparty.com/2012/09/06/gigantic-pax-west-2012-spyparty-and-storyteller-gallery/">PAX</a>-and-<a title="The New SpyParty Character Art Style" href="http://www.spyparty.com/2012/08/27/the-new-spyparty-character-art-style/">New Character Art</a>-based build hiatus, I&#8217;ve updated the <a title="Sign Up for the SpyParty Early-Access Beta!" href="http://www.spyparty.com/beta-sign-up/"><em>Early-Access Beta</em></a> build 4 times in the last week or so!  It feels good to be updating the game for the beta testers again! There are a lot of small fixes and features in these builds, but the two big ones are the addition of <em>chat rooms</em> and a brand new map, <em>Courtyard</em>.</p>
<a name="Courtyard"></a><h3>Courtyard</h3>
<p>In the old days, we walked to the only map, the <a href="http://www.spyparty.com/images/#lightbox=spyparty-2010-08-11-10-29-03-23.png">Ballroom</a>, uphill, both ways, and <a href="http://www.youtube.com/watch?v=Xe1a1wHxTyo">we liked it</a>.  Eventually, after a couple years of playtesting on that single map, I decided to test my working theory that making new and interesting maps for <strong>SpyParty</strong> wouldn&#8217;t be too terribly hard, at least relative to something like doing the AI and animations for a new mission.  In other words, I was working under the assumption that because most of the gameplay in <strong>SpyParty</strong> is based on the character behaviors and the perception and deception mechanics, doing new maps would be a relatively easy way to add new gameplay.  The maps would obviously be important and add a lot of variety to the game, but their layouts would not be as crucial to the feel of the game as they are in a shooter or a platformer.  I mean, Ballroom itself is just a box and everybody seemed to like it!</p>
<p>So, I tested this theory by adding <a title="A Deathtrap and a Walk in the Park" href="http://www.spyparty.com/2011/01/19/a-deathtrap-and-a-walk-in-the-park/">the Balcony and Veranda maps</a>, and I&#8217;d say the theory was proven to be true, since both of those maps <a title="Action Test, Seduce Target, and New Maps Playtest Reports" href="http://www.spyparty.com/2011/02/12/action-test-seduce-target-and-new-maps-playtest-reports/#The+New+Maps">worked well</a> and people like playing them.   The Ballroom is still the go-to map for serious play because it&#8217;s the most tuned and balanced, but the other maps add some spice and hint at how the game will feel with lots of different settings and map topologies.</p>
<p><a href="http://cdn.spyparty.com/wp-content/uploads/2012/10/SpyParty-20121007-15-31-49-0.png"><img class="alignright size-medium wp-image-2727" style="margin-left: 10px; margin-bottom: 10px;" title="SpyParty-20121007-15-31-49-0" src="http://cdn.spyparty.com/wp-content/uploads/2012/10/SpyParty-20121007-15-31-49-0-300x235.png" alt="" width="300" height="235" /></a>On the runup to PAX, I had <a title="That Yoda Quote About Opening Betas and the PAX West 2012 Guest Indie" href="http://www.spyparty.com/2012/08/23/that-yoda-quote-about-opening-betas-and-pax-west-2012-guest-indie/">an idea for a new map</a> where the statues would be centrally located, but the Sniper couldn&#8217;t see them all at the same time.  I was wondering how that would change the <em>Inspect Statue</em> and <em>Swap Statue</em> missions, which have turned out to be two of the riskiest missions for Spies to attempt.  My original thought for this level was a single statue right in the center, but I decided that would be too hard, so I settled on three statues embedded in a central pillar, where the Sniper can only see one at a time.</p>
<p>I hacked this map together one night at PAX, and we playtested it the next day.  The initial tuning was way too hard for the Sniper, not to mention how hard it was to <a title="What topiary should I put on top of the new Courtyard map?" href="http://www.spyparty.com/2012/09/05/what-topiary-should-i-put-on-top-of-the-new-courtyard-map/">get your bearings</a>.  After some tuning, and <a href="http://www.spyparty.com/2012/09/05/what-topiary-should-i-put-on-top-of-the-new-courtyard-map/comment-page-1/#comment-66583">Wessel&#8217;s suggestion</a> of using a statue of <a href="http://2.bp.blogspot.com/_x5HBgG-7hVw/TOi2zgcZYKI/AAAAAAAAAUM/-4DEf4g0tss/s1600/IMAG0117.jpg">Alan Turing</a> to give the Sniper an orientation reference point, the Courtyard seems to be working out well.   I think I overcorrected a bit and the statue missions are now a bit hard for the Spy again, and it needs more playtesting and tuning, but it&#8217;s definitely already interesting and fun to play for both roles.</p>
<p>Current stats for Courtyard look like this:</p>
<table border="0" align="center">
<thead>
<tr>
<td><strong>Game Type</strong></td>
<td><strong>Total Games</strong></td>
<td><strong>Spy Wins</strong></td>
</tr>
</thead>
<tbody>
<tr>
<td align="center"> all</td>
<td align="center"> 420</td>
<td align="center">172 (41%)</td>
</tr>
<tr>
<td align="center">Known 3</td>
<td align="center">14</td>
<td align="center">10 (71%)</td>
</tr>
<tr>
<td align="center">Known 4</td>
<td align="center">32</td>
<td align="center">14 (44%)</td>
</tr>
<tr>
<td align="center">Known 5</td>
<td align="center">18</td>
<td align="center">6 (33%)</td>
</tr>
<tr>
<td align="center">Pick 3 of 4</td>
<td align="center">63</td>
<td align="center">27 (43%)</td>
</tr>
<tr>
<td align="center">Pick 3 of 5</td>
<td align="center">14</td>
<td align="center">8 (57%)</td>
</tr>
<tr>
<td align="center">Pick 4 of 5</td>
<td align="center">90</td>
<td align="center">28 (31%)</td>
</tr>
<tr>
<td align="center">Any ? of 4</td>
<td align="center">15</td>
<td align="center">4 (27%)</td>
</tr>
<tr>
<td align="center">Any ? of 5</td>
<td align="center">174</td>
<td align="center">75 (43%)</td>
</tr>
</tbody>
</table>
<p>You&#8217;ll recall there there are currently three game types in <strong>SpyParty</strong>, &#8220;Known n&#8221; where the Sniper knows the n missions the Spy will try to accomplish, &#8220;Pick n of m&#8221;, where the Sniper knows the m missions available, but not which n ones the Spy picked, and &#8220;Any n of m&#8221;, where the Spy can choose which n to accomplish opportunistically during play, rather than having to pick them ahead of time.  We&#8217;ve started abbreviating these k4, p4/5, a3/4, and so on in the beta chat and forums.</p>
<p>As you can see from this table, the different modes have different levels of handicapping between the players.  I&#8217;ve just gathered these numbers from all the games on the Courtyard map, so this is totally ignoring skill differences, early games where players were just testing out the map, and the like.  But, it&#8217;s still interesting to see the balance.</p>
<p>Unfortunately, as you can see, the Any game types are missing their n parameter.  As I started gathering this data, I realized I&#8217;d screwed up and left that number out of the database for Any game types, so I can&#8217;t tell if the a?/5 games are a3/5 or a4/5, which is a bummer, because it makes a big difference in difficulty for the Spy and Sniper.  I&#8217;ll probably have to ignore old Any games when I do real ranking with this data.  I&#8217;ve since fixed it,<sup><a href="http://www.spyparty.com/2012/10/10/a-room-with-a-view-of-the-courtyard/#footnote_0_2719" id="identifier_0_2719" class="footnote-link footnote-identifier-link" title="I&rsquo;ve tested the results with beta member &ldquo;tytalus&rdquo;, who was very patient!">1</a></sup> and although there aren&#8217;t enough games with the new correct data yet to be statistically significant, here they are:</p>
<table border="0" align="center">
<thead>
<tr>
<td><strong>Game Type</strong></td>
<td><strong>Total Games</strong></td>
<td><strong>Spy Wins</strong></td>
</tr>
</thead>
<tbody>
<tr>
<td align="center"> all</td>
<td align="center"> 46</td>
<td align="center">16 (34%)</td>
</tr>
<tr>
<td align="center">Known 4</td>
<td align="center">9</td>
<td align="center">0 (0%)</td>
</tr>
<tr>
<td align="center">Pick 3 of 4</td>
<td align="center">15</td>
<td align="center">9 (60%)</td>
</tr>
<tr>
<td align="center">Pick 4 of 5</td>
<td align="center">1</td>
<td align="center">1 (100%)</td>
</tr>
<tr>
<td align="center">Any 3 of 4</td>
<td align="center">1</td>
<td align="center">0 (0%)</td>
</tr>
<tr>
<td align="center">Any 3 of 5</td>
<td align="center">1</td>
<td align="center">0 (0%)</td>
</tr>
<tr>
<td align="center">Any 4 of 5</td>
<td align="center">19</td>
<td align="center">6 (31%)</td>
</tr>
</tbody>
</table>
<a name="Chat+Rooms"></a><h3>Chat Rooms</h3>
<p>Soon I will be opening up the beta, and this (hopefully) means a lot of people will be playing <strong>SpyParty</strong> simultaneously, at least towards the end of the closed beta when I send out the rest of the invitations and into the beginning of the open beta when anybody can sign up and play immediately.  The old lobby had a single room, and when 10 or 12 people were in there chatting, it got a bit hard to keep track of who was talking to who and what they were saying.</p>
<p>So, I did the <a title="Chat Room “Design”" href="http://www.spyparty.com/2012/08/14/chat-room-design/">simplest chat room design possible</a>, with the goal of load-balancing the lobby if there were 30 or 100 simultaneous users chatting.  I basically decided to put all the room creation and multiple-join/leave commands on the chat console, and have the GUI only allow you to select a single existing chat room at a time.  This way advanced users can hang out in multiple chat rooms and make new rooms, but newbies can just join one and chat and play a match without thinking too hard about it.</p>
<p>The jury is still out on whether the current chat rooms work very well, and we&#8217;ll have to wait until I invite more people in to see if they solve the load balancing problem, but they&#8217;re certainly slightly confusing at the current density.  I tried to make sure joining &#8220;Headquarters&#8221; (the default room that&#8217;s always there) was easy enough that you didn&#8217;t have to think about it and it acts just like a single room lobby if there aren&#8217;t many people online, but I didn&#8217;t want to dump people automatically into a chat room for fear of causing problems when the game was scaled.  We&#8217;ll see how it works out.  I foresee a lot of changes in this code and UI as the number of simultaneous users goes up.</p>
<p>One change I made shortly after releasing the feature was adding a &#8220;Created by &lt;username&gt;&#8221; line in the chat room description.  Since users can create any rooms they like, this will at least keep people honest, or at least culpable!  Before I added this players were creating rooms that sounded official, which was funny but probably fraught with peril.  I also don&#8217;t support hidden rooms or password protected rooms or anything yet.  I&#8217;d like to keep the community from segregating as much as possible for the time being.</p>
<p>On the subject of chat, I really need to do something soon about my text rendering code, it is incredibly slow right now:</p>
<pre style="padding-left: 30px;">checker: /fps<br />SpyParty version v0.1.2202.0<br />19.869 ms/frame (fps: 50.33)  sort self - current frame<br />zone                                                     self     hier    count<br /> checkerlib::simple_font::DrawStringOrthoPixels       18.6377  18.6377    54.78<br />+chat_display                                          0.3964  16.6585     1.00<br /> SwapBuffers                                           0.2445   0.2445     1.00<br />+IdleFinal                                             0.2400   0.4846     1.00<br />+_global                                               0.0582  19.8692     0.00</pre>
<a name="Logging+Performance%2C+etc."></a><h3>Logging Performance, etc.</h3>
<p>There were a bunch of other smaller fixes, one of which other programmers might find interesting:  I use the spypartyhelper.exe to write the log files now, rather than having the main spyparty.exe write them out.  The helper has always done the crash dump management (using a modified version of <a href="http://code.google.com/p/google-breakpad/">Google&#8217;s Breakpad</a>) and the auto-updating privilege escalation, but now I also have it handle logging, using a named pipe from spyparty.exe.  The reason I made this change was some machines (like my main development machine) would happily write out the logs in the main process without affecting performance at all, even though I had <a href="http://msdn.microsoft.com/en-us/library/yeby3zcb.aspx">commit mode</a> turned on for the log file.  I used commit mode so if the game crashed, the log file would contain the most up-to-date log entries, so when the helper grabbed it and put it in the crash dump report and uploaded it to my server I could use it for debugging.  However, on some machines, the commit mode logging would take hundreds of times longer, and I couldn&#8217;t figure out what the difference was between fast and slow machines.  What&#8217;s worse, it became clear that a lot of beta testers were assuming the game just loaded slowly, which subtly (or not-so-subtly) affects how you feel about the game, even though all the time was being spent writing log statements.  Once I realized people were suffering with slow load times, I knew I had to fix it.</p>
<p>The weirdest bug I fixed involved the Spy playing the cough animation as soon as control was taken from the AI.  The cough animation is supposed to be played when the Spy bails out of a conversation too soon after saying &#8220;banana bread&#8221; to contact the Double Agent.  I added the cough because some people were cheesing out and running away from the circle before the contact animation had finished, and it was too hard for the Sniper to spot this glitch.  So, the cheesers got a big surprise a few builds back!  But, it shouldn&#8217;t be happening in a new game if you haven&#8217;t even done anything yet, and beta testers couldn&#8217;t <a title="How to Report Bugs the SpyParty Way" href="http://www.spyparty.com/2012/04/12/how-to-report-bugs-the-spyparty-way/">get a good repro</a>.  Well, after a bunch of debugging, it turned out I wasn&#8217;t clearing a timer in the <a href="http://chrishecker.com/My_AIIDE_2010_Lecture_on_Game_AI">Contact Double Agent situation</a> on new game, so if somebody was shot within 2 seconds of a &#8220;banana bread&#8221;, then since they were moving at the start of the <em>next game</em> but the timer hadn&#8217;t expired, the situation decided they&#8217;d cheesed out of the conversation and made them cough.  Yay for cross-game bugs!  I guess it was the ghost of the previous victim making the Spy cough!</p>
<a name="Mail+Deliverability"></a><h3>Mail Deliverability</h3>
<p>Man, sending mail reliably on the internet today is fraught with peril.  I&#8217;ve been trying to figure out how often a beta invitation or confirmation mail simply doesn&#8217;t get delivered, or gets dumped in a spam folder.  So far, I&#8217;ve struggled with and been victorious getting <a href="http://en.wikipedia.org/wiki/SenderID">SenderID</a> and <a href="http://en.wikipedia.org/wiki/Sender_Policy_Framework">SPF</a> working, and I&#8217;m still wrestling with <a href="http://en.wikipedia.org/wiki/DomainKeys_Identified_Mail">DKIM</a>, but I am confident I&#8217;ll prevail&#8230;eventually.  Of course, <a href="http://mailchimp.com/about/authentication/">all the major mail clients use different standards</a>.  I guess I could just pay a mail sending service to handle this for me, but it just seems crazy if I&#8217;m forced to do that to get mail delivered.  Then again, I&#8217;ve wasted a ton of time dealing with this, so maybe it would have been worth it.  I tend to think the learning and understanding I get from working through these problems myself is worth the time investment, but that could easily be rationalization, who knows?!</p>
<p><em>Update:</em>  I think I have prevailed in the email war.  I&#8217;ve got all the major email providers<sup><a href="http://www.spyparty.com/2012/10/10/a-room-with-a-view-of-the-courtyard/#footnote_1_2719" id="identifier_1_2719" class="footnote-link footnote-identifier-link" title="Out of 20k beta signups: gmail 56%, hotmail+uk+live 14%, yahoo 7% aol 1%">2</a></sup> verifying my DKIM and SPF records.  I had to battle <a href="http://www.php.net/">php</a> messing up newlines which confused the hash, <a href="http://en.wikipedia.org/wiki/Security-Enhanced_Linux">SELinux</a> disallowing <a href="http://www.postfix.org/">postfix</a> from talking to <a href="http://www.opendkim.org/">opendkim</a>, and whole bunch of other randomness.  My only problem now is with AOL, and their postmaster just admitted the problem was on their side and they&#8217;re fixing it.  Hopefully that is the end of any spyparty.com email going into the spam folder, woohoo!  I sent out 500 invites tonight to celebrate, which brings us to  2011-05-11 06:35:21 US Central Time on the in-order invites.<sup><a href="http://www.spyparty.com/2012/10/10/a-room-with-a-view-of-the-courtyard/#footnote_2_2719" id="identifier_2_2719" class="footnote-link footnote-identifier-link" title="I do 60% in-order and 40% random in each batch.">3</a></sup></p>
<hr/><ol class="footnotes"><li id="footnote_0_2719" class="footnote">I&#8217;ve tested the results with beta member &#8220;tytalus&#8221;, who was very patient!</li><li id="footnote_1_2719" class="footnote">Out of 20k beta signups: gmail 56%, hotmail+uk+live 14%, yahoo 7% aol 1%</li><li id="footnote_2_2719" class="footnote">I do 60% in-order and 40% random in each batch.</li></ol>]]></content:encoded>
			<wfw:commentRss>http://www.spyparty.com/2012/10/10/a-room-with-a-view-of-the-courtyard/feed/</wfw:commentRss>
		<slash:comments>10</slash:comments>
		</item>
		<item>
		<title>Scalability and Asserts I&#8217;m Not Yet Fixing</title>
		<link>http://www.spyparty.com/2012/08/02/scalability-and-asserts-im-not-yet-fixing/</link>
		<comments>http://www.spyparty.com/2012/08/02/scalability-and-asserts-im-not-yet-fixing/#comments</comments>
		<pubDate>Thu, 02 Aug 2012 21:51:37 +0000</pubDate>
		<dc:creator><![CDATA[checker]]></dc:creator>
				<category><![CDATA[beta]]></category>
		<category><![CDATA[metrics]]></category>
		<category><![CDATA[programming]]></category>

		<guid isPermaLink="false">http://www.spyparty.com/?p=2501</guid>
		<description><![CDATA[I&#8217;ve been a bit quiet because I&#8217;ve been working on (hopefully) completely invisible stuff involving backend server scalability.  What does that mean, you ask?  In practical player-facing terms, it means I&#8217;m trying to get the lobby and registration system robust enough to first invite the rest of the waiting list in,1 and then to open [&#8230;]]]></description>
				<content:encoded><![CDATA[<p><img class="alignright  wp-image-2530" style="margin: 0px 0px 10px 10px;" title="flood-gates" src="http://cdn.spyparty.com/wp-content/uploads/2012/08/flood-gates-300x203.jpg" alt="" width="300" height="203" /></p>
<p>I&#8217;ve been a bit quiet because I&#8217;ve been working on (hopefully) completely invisible stuff involving backend server scalability.  What does that mean, you ask?  In practical player-facing terms, it means I&#8217;m trying to get the lobby and registration system robust enough to first invite the rest of the waiting list in,<sup><a href="http://www.spyparty.com/2012/08/02/scalability-and-asserts-im-not-yet-fixing/#footnote_0_2501" id="identifier_0_2501" class="footnote-link footnote-identifier-link" title="currently at 16681 people as of this post">1</a></sup> and then to open the beta up completely, allowing people to hear about the game, visit the site, <a title="SpyParty Early-Access Beta Pricing" href="http://www.spyparty.com/2011/08/11/spyparty-early-access-beta-pricing/">pay</a> their money, and get into the <a title="Sign Up for the SpyParty Early-Access Beta!" href="http://www.spyparty.com/beta-sign-up/">beta</a> instantly.  Between 10 and 500 people sign up a day, depending on how much press the game is getting at the time, and I&#8217;m very curious to find out how many of those will join the beta if it&#8217;s available immediately, and they aren&#8217;t told they have to wait.</p>
<p>I&#8217;m currently working on the text-mode robot client.  I&#8217;ve got it logging into the lobby and pretending it&#8217;s a full <strong>SpyParty</strong> client, and it can chat, and invite people to games.  I posted on <a href="http://facebook.com/SpyParty">facebook</a> and <a href="http://twitter.com/SpyParty">twitter</a> asking for <a href="http://www.facebook.com/SpyParty/posts/449407218427239">suggestions of what the robots should say to each other</a> as they&#8217;re hanging out in the lobby.</p>
<p>Next up, I&#8217;m going to get the robots playing fake matches and games and reporting the game results to the lobby, and logging in and out a bunch.  Then, I&#8217;m going to hack up the <a href="http://blog.apps.chicagotribune.com/2010/07/08/bees-with-machine-guns/">bees with machineguns</a> load testing app to launch a bunch of <a href="http://aws.amazon.com/ec2/">EC2</a> micro instances with multiple robots running on each, and aim them all at my test server.  I expect chaos.</p>
<p>Once I&#8217;ve got the obvious stuff fixed, and have figured out how many clients my system can host at a given machine size, I&#8217;ll do some tests with beefier machines to make sure it scales linearly.  There are four basic machine resources: CPU, memory, network, and disk&#8230;I assume I&#8217;ll run out of memory first, but I don&#8217;t know for sure.  I was originally going to try to get the entire backend infrastructure running in the cloud, but I think for the near term I&#8217;m going to just make sure I get a machine that can accept way more clients signing up and playing than I think I&#8217;m going to get when I open up the beta, and hope it holds up.  If it dies due to too much traffic, well, I guess that&#8217;s a good problem to have, at least!  I did this same kind of thing with when I started taking beta signups, and I stress tested and optimized it well enough that <a title="Beta Data" href="http://www.spyparty.com/2011/05/12/beta-data/">it didn&#8217;t even break a sweat</a>, so hopefully the beta launch itself will go as smoothly. If not, then if Blizzard <a href="http://www.gameinformer.com/b/news/archive/2012/05/17/blizzard-apologizes-for-diablo-iii-launch-problems.aspx">can do it</a>, so can I! Yikes.</p>
<p>Then, once I&#8217;ve got the backend scalable for the robots, I&#8217;ll start inviting much larger groups into the beta to loadtest with live players.  There are some features I&#8217;m going to need to add to make this work, like chat rooms and colored chat text, and it&#8217;s going to be pretty raw to start with, but it should work okay.  The beta testers have been very forgiving of my incredibly primitive lobby so far, so hopefully that attitude will continue!</p>
<p>After everybody&#8217;s in, I&#8217;ll shut off the signups and let it stew for a few days, and if it&#8217;s working, I&#8217;ll open it up!  I hesitate to give time estimates on this stuff, because I&#8217;ve never hit a date, but most of this will be happening over the next few weeks.</p>
<a name="Asserts%21"></a><h3>Asserts!</h3>
<p>Programming is complicated, and handling errors makes it even more complicated.  Oftentimes, it&#8217;s good programming practice to not handle some types of errors gracefully, but simply <em>assert</em> that you aren&#8217;t in a state you shouldn&#8217;t be in, and if the assertion fails, you exit with an error.</p>
<pre style="padding-left: 60px;">assert(1 + 1 == 2);  // integer arithmetic better work or we're hosed</pre>
<p>Of course, during development, the impossible is not only possible, but likely, and so your asserts fire.  And, often in complicated code, you&#8217;ll assert things that should be true, but aren&#8217;t catastrophic if they&#8217;re not true, and so you usually pop up a dialog that gives you the choice to <em>break</em> into the debugger, <em>abort</em> the program, or <em>ignore</em> the assert (just this once, or forever).  The problem is, once you let yourself have that ignore option, you can get lazy, and start using asserts as popup reminders of things to fix.  This tends to be really bad on a large team, because the game is asserting every 2 seconds, and you&#8217;re just hitting ignore all the time to other people&#8217;s asserts, and it becomes a habit.  However, on an indie sized team, which in my case means exactly one programmer, one can use asserts this way and they can still be useful.  I can get a feel for how often certain types of things are going wrong while I&#8217;m testing, and I can remember what I was doing when a specific assert fired most of the time&#8230;it goes from a purely quantitative tool to a qualitative tool.</p>
<p>I also can leave the asserts on in beta builds, but in a way that fires silently once and then auto-ignores-forever, and then have them shipped off over the network to the server, so I can see what kinds of things are going wrong on player machines, and how frequently they go wrong.</p>
<p>I tend to be pretty liberal with asserts in my code, and so they fire a lot, and in turn the server logs a lot of them.  About 30000 of them so far in the beta.  Here they are, sorted by frequency:</p>
<div align="center">
<table class="graph" style="width: 450px;" border="0">
<tbody>
<tr>
<td>4918</td>
<td><strong>internalMcostTest(sx, sy)</strong><br />object_system\subsystems\pathing\pathing.cpp</td>
</tr>
<tr>
<td>2237</td>
<td><strong>holding</strong><br />object_system\subsystems\animation\animation.cpp</td>
</tr>
<tr>
<td>2237</td>
<td><strong>ad</strong><br />object_system\subsystems\animation\animation.cpp</td>
</tr>
<tr>
<td>2145</td>
<td><strong>am &amp;&amp; (right || left)</strong><br />object_system\subsystems\animation\animation.cpp</td>
</tr>
<tr>
<td>1780</td>
<td><strong>am-&gt;playing.blend_out == am-&gt;queue.front().blend_in</strong><br />object_system\subsystems\animation\animation.cpp</td>
</tr>
<tr>
<td>1485</td>
<td><strong>am &amp;&amp; (boneId != -1) &amp;&amp; ad</strong><br />object_system\subsystems\animation\animation.cpp</td>
</tr>
<tr>
<td>1415</td>
<td><strong>cd-&gt;object_of_interest</strong><br />character.cpp</td>
</tr>
<tr>
<td>1350</td>
<td><strong>d-&gt;NearBookcaseID</strong><br />situations\bookcase\bookcase.cpp</td>
</tr>
<tr>
<td>1114</td>
<td><strong>(err = glGetError()) == GL_NO_ERROR, &#8220;code: 0&#215;00000501&#8243;</strong><br />spyparty.cpp</td>
</tr>
<tr>
<td>1105</td>
<td><strong>cd-&gt;object_of_interest &amp;&amp; (cd-&gt;object_of_interest == BriefcaseID)</strong><br />situations\briefcase\briefcase.cpp</td>
</tr>
<tr>
<td>994</td>
<td><strong>StatueAD &amp;&amp; (StatueAD-&gt;Object-&gt;Type == object_types::STATUE) &amp;&amp; (object_system::GetObject(StatueAD-&gt;ParentID)-&gt;Type == object_types::PEDESTAL)</strong><br />situations\pedestal\pedestal.cpp</td>
</tr>
<tr>
<td>952</td>
<td><strong>am &amp;&amp; (am-&gt;playing.type == &amp;core_talks)</strong><br />situations\conversation\conversation.cpp</td>
</tr>
<tr>
<td>813</td>
<td><strong>BriefcaseAD &amp;&amp; HoldingBriefcase &amp;&amp; d-&gt;PlayingCycle</strong><br />situations\briefcase\briefcase.cpp</td>
</tr>
<tr>
<td>801</td>
<td><strong>!d-&gt;PlayingCycle</strong><br />situations\drinks\drinks.cpp</td>
</tr>
<tr>
<td>628</td>
<td><strong>verify( pathing::pathGetCharacterValue(x, y) == BriefcasePathValue )</strong><br />situations\briefcase\briefcase.cpp</td>
</tr>
<tr>
<td>539</td>
<td><strong>verify( animation::HandleDetach(am, am-&gt;Events[i].event-&gt;boneId, cd-&gt;holding_right, cd-&gt;holding_left, &amp;OnRight) &amp;&amp; OnRight )</strong><br />situations\briefcase\briefcase.cpp</td>
</tr>
<tr>
<td>478</td>
<td><strong>cd-&gt;object_of_interest == BriefcaseID</strong><br />situations\briefcase\briefcase.cpp</td>
</tr>
<tr>
<td>469</td>
<td><strong>!PoppedYesNoQuestioner</strong><br />spyparty.cpp</td>
</tr>
<tr>
<td>413</td>
<td><strong>d-&gt;GoalPedestalID == d-&gt;NearPedestalID</strong><br />situations\pedestal\pedestal.cpp</td>
</tr>
<tr>
<td>278</td>
<td><strong>HoldingBriefcase &amp;&amp; BriefcaseAD</strong><br />situations\briefcase\briefcase.cpp</td>
</tr>
<tr>
<td>252</td>
<td><strong>verify( HandleDetach(am, am-&gt;Events[i].event-&gt;boneId, cd-&gt;holding_right, cd-&gt;holding_left, &amp;OnRight) &amp;&amp; OnRight )</strong><br />situations\pedestal\pedestal.cpp</td>
</tr>
<tr>
<td>236</td>
<td><strong>IK.Bone &amp;&amp; IK.Target &amp;&amp; IK.MeshHardpoint</strong><br />object_system\subsystems\animation\animation.cpp</td>
</tr>
<tr>
<td>229</td>
<td><strong>am-&gt;queue.empty()</strong><br />object_system\subsystems\animation\animation.cpp</td>
</tr>
<tr>
<td>216</td>
<td><strong>x == p-&gt;ex &amp;&amp; y == p-&gt;ey</strong><br />object_system\subsystems\pathing\pathing.cpp</td>
</tr>
<tr>
<td>193</td>
<td><strong>0, &#8220;unknown packet type: 9&#8243;</strong><br />spy_server.cpp</td>
</tr>
<tr>
<td>188</td>
<td><strong>!&#8221;stuck!&#8221;</strong><br />character.cpp</td>
</tr>
<tr>
<td>172</td>
<td><strong>cd-&gt;disposable_left</strong><br />situations\pedestal\pedestal.cpp</td>
</tr>
<tr>
<td>160</td>
<td><strong>verify( pathing::pathGetCharacterValue(x, y) == pathing::PATH_VALUE_INFINITE )</strong><br />situations\briefcase\briefcase.cpp</td>
</tr>
<tr>
<td>151</td>
<td><strong>!cd-&gt;holding_right || (cd-&gt;holding_right == d-&gt;BookID)</strong><br />situations\bookcase\bookcase.cpp</td>
</tr>
<tr>
<td>139</td>
<td><strong>(cd-&gt;holding_left == d-&gt;BookID) &amp;&amp; (!cd-&gt;holding_right || (cd-&gt;holding_right == d-&gt;BookID))</strong><br />situations\bookcase\bookcase.cpp</td>
</tr>
<tr>
<td>110</td>
<td><strong>e.MatchTimestamp &gt; RoundTimeline[LastMarkSuspectIdx].MatchTimestamp</strong><br />round_events.cpp</td>
</tr>
<tr>
<td>97</td>
<td><strong>verify( GetConnectionNames(Us, sizeof(Us), Them, sizeof(Them)) )</strong><br />spyparty.cpp</td>
</tr>
<tr>
<td>78</td>
<td><strong>0, &#8220;unknown packet type: 15&#8243;</strong><br />spy_server.cpp</td>
</tr>
<tr>
<td>77</td>
<td><strong>0, &#8220;unknown packet type: 9&#8243;</strong><br />sniper_client.cpp</td>
</tr>
<tr>
<td>74</td>
<td><strong>Distance2(cd-&gt;Object-&gt;Position, ps_sci-&gt;Object-&gt;Position) &lt;= MaxHandoffDistance2</strong><br />situations\briefcase\briefcase.cpp</td>
</tr>
<tr>
<td>70</td>
<td><strong>!gd-&gt;ForceGoToPedestalID</strong><br />situations\steal_statue\steal_statue.cpp</td>
</tr>
<tr>
<td>66</td>
<td><strong>am-&gt;playing.type == &amp;core_briefcase_pickups</strong><br />situations\briefcase\briefcase.cpp</td>
</tr>
<tr>
<td>59</td>
<td><strong>!&#8221;spy stuck!&#8221;</strong><br />spy_server.cpp</td>
</tr>
<tr>
<td>59</td>
<td><strong>0, &#8220;unknown packet type: 9&#8243;</strong><br />spyparty.cpp</td>
</tr>
<tr>
<td>55</td>
<td><strong>pathTestOpen(sx, sy)</strong><br />object_system\subsystems\pathing\pathing.cpp</td>
</tr>
<tr>
<td>50</td>
<td><strong>0, &#8220;unknown packet type: 7&#8243;</strong><br />spy_server.cpp</td>
</tr>
<tr>
<td>48</td>
<td><strong>d-&gt;BookBookcaseID &amp;&amp; cd-&gt;IsGoalOwner(this)</strong><br />situations\bookcase\bookcase.cpp</td>
</tr>
<tr>
<td>43</td>
<td><strong>!&#8221;what do to here?&#8221;</strong><br />examples\lobby\lobbyclient.cpp</td>
</tr>
<tr>
<td>40</td>
<td><strong>verify( animation::GetPlayingAnimationInfo(am, &amp;time, &amp;duration) )</strong><br />situations\conversation\conversation.cpp</td>
</tr>
<tr>
<td>40</td>
<td><strong>HoldingBriefcase</strong><br />situations\briefcase\briefcase.cpp</td>
</tr>
<tr>
<td>40</td>
<td><strong>am-&gt;playing.animid == -1</strong><br />situations\pedestal\pedestal.cpp</td>
</tr>
<tr>
<td>35</td>
<td><strong>n</strong><br />spyparty.cpp</td>
</tr>
<tr>
<td>32</td>
<td><strong>e.MatchTimestamp &gt; RoundTimeline[LastMarkBookIdx].MatchTimestamp</strong><br />round_events.cpp</td>
</tr>
<tr>
<td>26</td>
<td><strong>verify( animation::HandleDetach(am, am-&gt;Events[i].event-&gt;boneId, cd-&gt;holding_right, cd-&gt;holding_left, &amp;OnRight) &amp;&amp; !OnRight )</strong><br />situations\bookcase\bookcase.cpp</td>
</tr>
<tr>
<td>26</td>
<td><strong>object_system::GetObject(cd-&gt;holding_left) &amp;&amp; (object_system::GetObject(cd-&gt;holding_left)-&gt;Type == FNV1(&#8220;BOOK&#8221;))</strong><br />situations\bookcase\bookcase.cpp</td>
</tr>
<tr>
<td>26</td>
<td><strong>it != am-&gt;AnimHandleMap.end()</strong><br />network.cpp</td>
</tr>
<tr>
<td>26</td>
<td><strong>IsTypingString</strong><br />spyparty.cpp</td>
</tr>
<tr>
<td>26</td>
<td><strong>0, &#8220;unknown packet type: 8&#8243;</strong><br />spyparty.cpp</td>
</tr>
<tr>
<td>25</td>
<td><strong>DefaultCharacterStatePacket &amp;&amp; (ndata == CharacterStatePacketSizeBytes)</strong><br />sniper_client.cpp</td>
</tr>
<tr>
<td>25</td>
<td><strong>cd-&gt;object_of_interest == d-&gt;BookID</strong><br />situations\bookcase\bookcase.cpp</td>
</tr>
<tr>
<td>24</td>
<td><strong>!p2pauth_con &amp;&amp; (p2pauthn_state == WAITING_AUTHN)</strong><br />examples\lobby\lobbyclient.cpp</td>
</tr>
<tr>
<td>24</td>
<td><strong>!IsSpy &amp;&amp; d-&gt;TargetBookcaseID</strong><br />situations\bookcase\bookcase.cpp</td>
</tr>
<tr>
<td>24</td>
<td><strong>HoldingDrink</strong><br />situations\drinks\drinks.cpp</td>
</tr>
<tr>
<td>23</td>
<td><strong>obj-&gt;Rotation.IsIdentity()</strong><br />character.cpp</td>
</tr>
<tr>
<td>23</td>
<td><strong>HoldingBook</strong><br />situations\bookcase\bookcase.cpp</td>
</tr>
<tr>
<td>23</td>
<td><strong>!err, &#8220;krb5_rd_priv err: -1765328342&#8243;</strong><br />examples\lobby\lobbyclient.cpp</td>
</tr>
<tr>
<td>22</td>
<td><strong>(w &gt;= 0) &amp;&amp; (w &lt;= 1)</strong><br />object_system\subsystems\animation\animation_cal3dutils.cpp</td>
</tr>
<tr>
<td>22</td>
<td><strong>d_cust &amp;&amp; (d_cust-&gt;State == drinks_data::INVALID)</strong><br />situations\serving\serving.cpp</td>
</tr>
<tr>
<td>21</td>
<td><strong>!&#8221;somehow in a valid state but !HoldingDrink?!&#8221;</strong><br />situations\drinks\drinks.cpp</td>
</tr>
<tr>
<td>20</td>
<td><strong>propid &amp;&amp; ( (Spy-&gt;holding_right == propid) || (Spy-&gt;holding_left == propid))</strong><br />situations\steal_statue\steal_statue.cpp</td>
</tr>
<tr>
<td>20</td>
<td><strong>!HoldingDrink</strong><br />situations\drinks\drinks.cpp</td>
</tr>
<tr>
<td>18</td>
<td><strong>!&#8221;somehow didn&#8217;t get statue&#8221;</strong><br />situations\pedestal\pedestal.cpp</td>
</tr>
<tr>
<td>18</td>
<td><strong>ad &amp;&amp; (ad-&gt;Object-&gt;Type == object_types::STATUE)</strong><br />situations\pedestal\pedestal.cpp</td>
</tr>
<tr>
<td>16</td>
<td><strong>o &amp;&amp; !(o-&gt;Flags &amp; object::UNMANAGED)</strong><br />object_system\object_manager.cpp</td>
</tr>
<tr>
<td>16</td>
<td><strong>am-&gt;queue.empty()</strong><br />situations\drinks\drinks.cpp</td>
</tr>
<tr>
<td>13</td>
<td><strong>!&#8221;something went wrong picking up briefcase!&#8221;</strong><br />situations\briefcase\briefcase.cpp</td>
</tr>
<tr>
<td>12</td>
<td><strong>!&#8221;should have detached&#8221;</strong><br />situations\bookcase\bookcase.cpp</td>
</tr>
<tr>
<td>11</td>
<td><strong>!&#8221;should not get here&#8221;</strong><br />situations\conversation\conversation.cpp</td>
</tr>
<tr>
<td>11</td>
<td><strong>0, &#8220;unknown packet type: 11&#8243;</strong><br />spyparty.cpp</td>
</tr>
<tr>
<td>10</td>
<td><strong>(w &gt;= 0) &amp;&amp; (w &lt;= 1) &amp;&amp; (u &gt;= 0) &amp;&amp; (u &lt;= 1) &amp;&amp; (v &gt;= 0) &amp;&amp; (v &lt;= 1)</strong><br />checkerlib\misc\geomutils.cpp</td>
</tr>
<tr>
<td>10</td>
<td><strong>verify( network::SendPacket(&amp;gs, sizeof(gs), true) )</strong><br />spy_server.cpp</td>
</tr>
<tr>
<td>10</td>
<td><strong>(rem &gt;= 0.0f) &amp;&amp; (rem &lt; 1.0f)</strong><br />spyparty.cpp</td>
</tr>
<tr>
<td>10</td>
<td><strong>Pedestal</strong><br />situations\pedestal\pedestal.cpp</td>
</tr>
<tr>
<td>10</td>
<td><strong>(err = glGetError()) == GL_NO_ERROR, &#8220;code: 0&#215;00000505&#8243;</strong><br />spyparty.cpp</td>
</tr>
<tr>
<td>9</td>
<td><strong>Statue &amp;&amp; (Statue-&gt;Type == object_types::STATUE)</strong><br />object_utils.cpp</td>
</tr>
<tr>
<td>9</td>
<td><strong>Level</strong><br />network.cpp</td>
</tr>
<tr>
<td>9</td>
<td><strong>!err</strong><br />examples\lobby\async_krb5_wrapper.cpp</td>
</tr>
<tr>
<td>9</td>
<td><strong>0, &#8220;unknown packet type: 1&#8243;</strong><br />sniper_client.cpp</td>
</tr>
<tr>
<td>8</td>
<td><strong>SpyCheck == player_control::state::TESTING</strong><br />situations\check_watch\check_watch.cpp</td>
</tr>
<tr>
<td>8</td>
<td><strong>ObjectIDToByte.empty() || nettest_mode</strong><br />network.cpp</td>
</tr>
<tr>
<td>8</td>
<td><strong>!IsSpy &amp;&amp; cd-&gt;object_of_interest</strong><br />situations\pedestal\pedestal.cpp</td>
</tr>
<tr>
<td>8</td>
<td><strong>ByteToObjectID.empty() || nettest_mode</strong><br />network.cpp</td>
</tr>
<tr>
<td>8</td>
<td><strong>0, &#8220;unknown packet type: 20&#8243;</strong><br />sniper_client.cpp</td>
</tr>
<tr>
<td>8</td>
<td><strong>0, &#8220;unknown packet type: 13&#8243;</strong><br />spy_server.cpp</td>
</tr>
<tr>
<td>8</td>
<td><strong>0, &#8220;unknown packet type: 13&#8243;</strong><br />spyparty.cpp</td>
</tr>
<tr>
<td>7</td>
<td><strong>ObjectIDToByte.empty() &amp;&amp; ByteToObjectID.empty()</strong><br />network.cpp</td>
</tr>
<tr>
<td>7</td>
<td><strong>(CameraMode == SNIPER_CAMERA) &amp;&amp; Level</strong><br />round_events.cpp</td>
</tr>
<tr>
<td>7</td>
<td><strong>0, &#8220;unknown packet type: 24&#8243;</strong><br />spy_server.cpp</td>
</tr>
<tr>
<td>6</td>
<td><strong>SwapStatueID &amp;&amp; ( (Spy-&gt;holding_right == SwapStatueID) || (Spy-&gt;holding_left == SwapStatueID))</strong><br />situations\steal_statue\steal_statue.cpp</td>
</tr>
<tr>
<td>6</td>
<td><strong>RoundTimeline.size() &lt; round_events_packet::MAX_NUM_EVENTS</strong><br />round_events.cpp</td>
</tr>
<tr>
<td>6</td>
<td><strong>o</strong><br />network.cpp</td>
</tr>
<tr>
<td>6</td>
<td><strong>mc &amp;&amp; (mc-&gt;Object-&gt;Type == object_types::STATUE) &amp;&amp; (StatueMeshIndex &lt; mc-&gt;Meshes.size())</strong><br />object_utils.cpp</td>
</tr>
<tr>
<td>6</td>
<td><strong>IsChatAllowed()</strong><br />spyparty.cpp</td>
</tr>
<tr>
<td>6</td>
<td><strong>ad</strong><br />object_utils.cpp</td>
</tr>
<tr>
<td>6</td>
<td><strong>0, &#8220;unknown packet type: 21&#8243;</strong><br />sniper_client.cpp</td>
</tr>
<tr>
<td>5</td>
<td><strong>!&#8221;shouldn&#8217;t get here&#8221;</strong><br />situations\steal_statue\steal_statue.cpp</td>
</tr>
<tr>
<td>5</td>
<td><strong>mark_value &lt;= 1.0f</strong><br />spyparty.cpp</td>
</tr>
<tr>
<td>5</td>
<td><strong>client &amp;&amp; client-&gt;OtherClientID</strong><br />spyparty_lobby.cpp</td>
</tr>
<tr>
<td>5</td>
<td><strong>0, &#8220;unknown packet type: 25&#8243;</strong><br />spy_server.cpp</td>
</tr>
<tr>
<td>4</td>
<td><strong>!&#8221;shouldn&#8217;t get here, must have moved while holding book&#8221;</strong><br />situations\book_transfer\book_transfer.cpp</td>
</tr>
<tr>
<td>4</td>
<td><strong>object_system::GetObject(History.States[i].ID)</strong><br />network.cpp</td>
</tr>
<tr>
<td>4</td>
<td><strong>Level</strong><br />sniper_client.cpp</td>
</tr>
<tr>
<td>4</td>
<td><strong>Level-&gt;ActiveGameTypeIndex &lt; Level-&gt;GameTypes.size()</strong><br />sniper_client.cpp</td>
</tr>
<tr>
<td>4</td>
<td><strong>!err</strong><br />examples\lobby\lobbyclient.cpp</td>
</tr>
<tr>
<td>4</td>
<td><strong>am</strong><br />network.cpp</td>
</tr>
<tr>
<td>4</td>
<td><strong>0, &#8220;unknown packet type: 10&#8243;</strong><br />spyparty.cpp</td>
</tr>
<tr>
<td>3</td>
<td><strong>SpyCheck == player_control::state::TESTING</strong><br />situations\seduction\seduction.cpp</td>
</tr>
<tr>
<td>3</td>
<td><strong>!&#8221;shouldn&#8217;t get here&#8221;</strong><br />examples\lobby\lobbyclient.cpp</td>
</tr>
<tr>
<td>3</td>
<td><strong>i_ind == i_dep+1</strong><br />character.cpp</td>
</tr>
<tr>
<td>3</td>
<td><strong>!Focus</strong><br />player_control.cpp</td>
</tr>
<tr>
<td>3</td>
<td><strong>Control-&gt;GetSpyTriggeredResult(this) == player_control::state::TESTING</strong><br />situations\double_agent\double_agent.cpp</td>
</tr>
<tr>
<td>3</td>
<td><strong>!(ChooserCurrentCharacter-&gt;Object-&gt;Flags &amp; object_system::object::UNMANAGED)</strong><br />ui.cpp</td>
</tr>
<tr>
<td>3</td>
<td><strong>am-&gt;Events[i].animation-&gt;getCoreAnimation()</strong><br />situations\drinks\drinks.cpp</td>
</tr>
<tr>
<td>3</td>
<td><strong>0, &#8220;unknown packet type: 16&#8243;</strong><br />spyparty.cpp</td>
</tr>
<tr>
<td>3</td>
<td><strong>0, &#8220;unknown packet type: 15&#8243;</strong><br />spyparty.cpp</td>
</tr>
<tr>
<td>2</td>
<td><strong>verify( network::SendPacket(&amp;p, sizeof(p), true) )</strong><br />network.cpp</td>
</tr>
<tr>
<td>2</td>
<td><strong>verify( network::SendPacket(&amp;gs, sizeof(gs), true) )</strong><br />spyparty.cpp</td>
</tr>
<tr>
<td>2</td>
<td><strong>verify( ConfirmGameIDToLobby(CurrentPlayID, CurrentGameID) )</strong><br />spy_server.cpp</td>
</tr>
<tr>
<td>2</td>
<td><strong>verify( animation::GetPlayingAnimationInfo(am, &amp;time, &amp;duration) )</strong><br />situations\book_transfer\book_transfer.cpp</td>
</tr>
<tr>
<td>2</td>
<td><strong>t &gt;= 0</strong><br />round_events.cpp</td>
</tr>
<tr>
<td>2</td>
<td><strong>State.CurrentNode-&gt;Parent-&gt;StringSoFar == State.CurrentLeaf-&gt;StringSoFar</strong><br />spyparty.cpp</td>
</tr>
<tr>
<td>2</td>
<td><strong>s</strong><br />checkerlib\misc\glutils.cpp</td>
</tr>
<tr>
<td>2</td>
<td><strong>mc</strong><br />situations\steal_statue\steal_statue.cpp</td>
</tr>
<tr>
<td>2</td>
<td><strong>glMultiTexCoord2f_ &amp;&amp; glActiveTexture_</strong><br />spy_server.cpp</td>
</tr>
<tr>
<td>2</td>
<td><strong>GameIDs.find(PacketPlayID) == GameIDs.end()</strong><br />examples\lobby\lobbyclient.cpp</td>
</tr>
<tr>
<td>2</td>
<td><strong>!err &amp;&amp; p2pauth_con &amp;&amp; krbtgt.data &amp;&amp; (krbtgt.length &lt; KRBTGT_LIMIT)</strong><br />examples\lobby\lobbyclient.cpp</td>
</tr>
<tr>
<td>2</td>
<td><strong>(err = glGetError()) == GL_NO_ERROR, &#8220;code: 0&#215;00000506&#8243;</strong><br />spyparty.cpp</td>
</tr>
<tr>
<td>2</td>
<td><strong>!decoder.underflowed() &amp;&amp; decoder.on_last_byte()</strong><br />examples\lobby\lobbyclient.cpp</td>
</tr>
<tr>
<td>2</td>
<td><strong>CurrentNetworkObjectID</strong><br />network.cpp</td>
</tr>
<tr>
<td>2</td>
<td><strong>!ChooserScrollDrag</strong><br />ui.cpp</td>
</tr>
<tr>
<td>2</td>
<td><strong>(cd-&gt;object_of_interest == d-&gt;BookID) || (ps-&gt;status != pathing::PATH_success)</strong><br />situations\bookcase\bookcase.cpp</td>
</tr>
<tr>
<td>2</td>
<td><strong>cd-&gt;object_of_interest &amp;&amp; (cd-&gt;object_of_interest == d-&gt;BookID)</strong><br />situations\bookcase\bookcase.cpp</td>
</tr>
<tr>
<td>2</td>
<td><strong>ATPCachedDescription</strong><br />player_control.cpp</td>
</tr>
<tr>
<td>2</td>
<td><strong>Array &amp;&amp; Num</strong><br />checkerlib\misc\utils.h</td>
</tr>
<tr>
<td>2</td>
<td><strong>am-&gt;playing.type == core_book_hidefilm_okay</strong><br />situations\book_transfer\book_transfer.cpp</td>
</tr>
<tr>
<td>2</td>
<td><strong>0, &#8220;unknown packet type: 8&#8243;</strong><br />sniper_client.cpp</td>
</tr>
<tr>
<td>2</td>
<td><strong>0, &#8220;unknown packet type: 2&#8243;</strong><br />spy_server.cpp</td>
</tr>
<tr>
<td>2</td>
<td><strong>0, &#8220;unknown packet type: 22&#8243;</strong><br />sniper_client.cpp</td>
</tr>
<tr>
<td>1</td>
<td><strong>verify( network::SendPacket(DefaultCharacterStatePacket, CharacterStatePacketSizeBytes, true) )</strong><br />spy_server.cpp</td>
</tr>
<tr>
<td>1</td>
<td><strong>verify( network::SendPacket(CommandsPacketBuffer, SizeBytes, true) )</strong><br />network.cpp</td>
</tr>
<tr>
<td>1</td>
<td><strong>verify( Camera.Project(vector_3(0, 0,0), &amp;origin) )</strong><br />spyparty.cpp</td>
</tr>
<tr>
<td>1</td>
<td><strong>StatueAD &amp;&amp; (StatueAD-&gt;Object-&gt;Type == object_types::STATUE)</strong><br />situations\pedestal\pedestal.cpp</td>
</tr>
<tr>
<td>1</td>
<td><strong>!&#8221;somehow didn&#8217;t get book&#8221;</strong><br />situations\bookcase\bookcase.cpp</td>
</tr>
<tr>
<td>1</td>
<td><strong>ScreamSound</strong><br />spy_server.cpp</td>
</tr>
<tr>
<td>1</td>
<td><strong>network::IsConnected()</strong><br />spy_server.cpp</td>
</tr>
<tr>
<td>1</td>
<td><strong>it != NetTestByteMap.end()</strong><br />network.cpp</td>
</tr>
<tr>
<td>1</td>
<td><strong>fabs(1-Length2(Axis)) &lt; 1e-5</strong><br />checkerlib\misc\math4d.h</td>
</tr>
<tr>
<td>1</td>
<td><strong>(err = glGetError()) == GL_NO_ERROR, &#8220;code: 0&#215;00000502&#8243;</strong><br />spyparty.cpp</td>
</tr>
<tr>
<td>1</td>
<td><strong>DefaultCharacterStatePacket &amp;&amp; (ndata == sizeof(*DefaultCharacterStatePacket) + DefaultCharacterStatePacket-&gt;NumCharacters*sizeof(DefaultCharacterStatePacket-&gt;States[0]))</strong><br />sniper_client.cpp</td>
</tr>
<tr>
<td>1</td>
<td><strong>!decoder.underflowed()</strong><br />network.cpp</td>
</tr>
<tr>
<td>1</td>
<td><strong>!decoder.underflowed() &amp;&amp; decoder.on_last_byte()</strong><br />network.cpp</td>
</tr>
<tr>
<td>1</td>
<td><strong>(d &gt;= 0) &amp;&amp; (d &lt;= 1)</strong><br />c:\users\checker\dev\spyparty\project\spyparty\code\network.h</td>
</tr>
<tr>
<td>1</td>
<td><strong>CharacterStatePacket &amp;&amp; (CharacterStatePacket-&gt;NumCharacters &lt;= MaxNumCharacters)</strong><br />network.cpp</td>
</tr>
<tr>
<td>1</td>
<td><strong>ByteToObjectID.find(CurrentNetworkObjectID) == ByteToObjectID.end()</strong><br />network.cpp</td>
</tr>
<tr>
<td>1</td>
<td><strong>0, &#8220;unknown packet type: 8&#8243;</strong><br />spy_server.cpp</td>
</tr>
<tr>
<td>1</td>
<td><strong>0, &#8220;unknown packet type: 6&#8243;</strong><br />spy_server.cpp</td>
</tr>
<tr>
<td>1</td>
<td><strong>0, &#8220;unknown packet type: 6&#8243;</strong><br />sniper_client.cpp</td>
</tr>
<tr>
<td>1</td>
<td><strong>0, &#8220;unknown packet type: 4&#8243;</strong><br />sniper_client.cpp</td>
</tr>
<tr>
<td>1</td>
<td><strong>0, &#8220;unknown packet type: 2&#8243;</strong><br />sniper_client.cpp</td>
</tr>
<tr>
<td>1</td>
<td><strong>0, &#8220;unknown packet type: 25&#8243;</strong><br />sniper_client.cpp</td>
</tr>
<tr>
<td>1</td>
<td><strong>0, &#8220;unknown packet type: 19&#8243;</strong><br />spyparty.cpp</td>
</tr>
<tr>
<td>1</td>
<td><strong>0, &#8220;unknown packet type: 18&#8243;</strong><br />spyparty.cpp</td>
</tr>
<tr>
<td>1</td>
<td><strong>0, &#8220;got new play id with existing: 0xca98 &#8220;</strong><br />sniper_client.cpp</td>
</tr>
<tr>
<td>1</td>
<td><strong>0, &#8220;got new play id with existing: 0x57a5 &#8220;</strong><br />sniper_client.cpp</td>
</tr>
<tr>
<td>1</td>
<td><strong>0, &#8220;got new play id with existing: 0&#215;5267 &#8220;</strong><br />sniper_client.cpp</td>
</tr>
<tr>
<td>1</td>
<td><strong>0, &#8220;got new play id with existing: 0x3e15 &#8220;</strong><br />sniper_client.cpp</td>
</tr>
<tr>
<td>1</td>
<td><strong>0, &#8220;got new play id with existing: 0x33fc &#8220;</strong><br />sniper_client.cpp</td>
</tr>
<tr>
<td>1</td>
<td><strong>0, &#8220;got new play id (0xabeb) with existing (0xcdb6) &#8220;</strong><br />sniper_client.cpp</td>
</tr>
<tr>
<td>1</td>
<td><strong>0, &#8220;got new play id (0x6f9f) when already playing with existing (0xfb2e) &#8220;</strong><br />sniper_client.cpp</td>
</tr>
</tbody>
</table>
</div>
<hr/><ol class="footnotes"><li id="footnote_0_2501" class="footnote">currently at 16681 people as of this post</li></ol>]]></content:encoded>
			<wfw:commentRss>http://www.spyparty.com/2012/08/02/scalability-and-asserts-im-not-yet-fixing/feed/</wfw:commentRss>
		<slash:comments>11</slash:comments>
		</item>
		<item>
		<title>The First 999 Beta Tester Usernames</title>
		<link>http://www.spyparty.com/2012/06/12/the-first-999-beta-tester-usernames/</link>
		<comments>http://www.spyparty.com/2012/06/12/the-first-999-beta-tester-usernames/#comments</comments>
		<pubDate>Wed, 13 Jun 2012 03:49:28 +0000</pubDate>
		<dc:creator><![CDATA[checker]]></dc:creator>
				<category><![CDATA[beta]]></category>
		<category><![CDATA[metrics]]></category>

		<guid isPermaLink="false">http://www.spyparty.com/?p=2419</guid>
		<description><![CDATA[There are now 999 people registered in the Early-Access Beta.1 Here are their usernames: aaboyles aaerox abettega ablemonkey aca15 acbgardner aces acron actionpotential adam adman aerox2 aethyr agent agentmidnight agradula ahalay ahnimal aisugirl akuda alessandro ali alkapwn almostpeaceful74 amagineer amar amassingham amonjura ancillas andrew andy angellion01 angryamish angryjaffa angrywombat ankhwatcher annoyedguy anotheruselesspwn antoine antonio applemint apreche [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>There are now 999 people registered in the <em><a title="Sign Up for the SpyParty Early-Access Beta!" href="http://www.spyparty.com/beta-sign-up/">Early-Access Beta</a></em>.<sup><a href="http://www.spyparty.com/2012/06/12/the-first-999-beta-tester-usernames/#footnote_0_2419" id="identifier_0_2419" class="footnote-link footnote-identifier-link" title="Updated numbers to be clear: There are 13788&nbsp;people signed up as of right now, 1081 registered (I invited more in after this post), 2430 invites total sent, for a 44% registration rate&hellip;I&rsquo;m still inviting people in slowly until I get the lobby scalable.">1</a></sup> Here are their usernames:</p>
<p><span style="font-size: xx-small;">aaboyles aaerox abettega ablemonkey aca15 acbgardner aces acron actionpotential adam adman aerox2 aethyr agent agentmidnight agradula ahalay ahnimal aisugirl akuda alessandro ali alkapwn almostpeaceful74 amagineer amar amassingham amonjura ancillas andrew andy angellion01 angryamish angryjaffa angrywombat ankhwatcher annoyedguy anotheruselesspwn antoine antonio applemint apreche arbaard arbiter ardonite aresbece arthol ashenke astroblack astrognat attackslug atticusfinch austin authoralden avian avianflame awakewise awayman awesome awesomex awkwardhero azazoth azeltir bambu85 bassguitarbill bater bbrocker bc5389 bclose bdgolfer81 beanopolis beavens beckett007 beffy belak beljerd ben benny bepedos berbank beric berndie90 berryalice bespectacledgent betabetamax billiechar binarychimp biodork bishop bizzkaj bjorngylling bkyoops bl00dw0lf blabber blackheart blacklamb blaine blargnblarg blimeycow blind blindcicero blindprophet blinkity bluenator bluewins bnetter12 bobbesmcgee bobo bogger bom351 boner bracken brackhar brawnyfanta braytek brettbot bronn browncoat768 bslinger bubonulus bucksexington bukit1 bull bungeeman bunnyfoofoo bureaucromancer burgly burguois buxx cafebagels caincc calitar callguinness camgoeswild camperbob canadianbacon candafilm caphector capnlee captainduh captainquincy captgadianton carrington casey castor cathlin causeofdeath15 caustic cba123 chan charliechan charliehuge checker chris chris2c2 chrisbell chrisfranklin christianwins chuckgriff chvyvele cian cigumo ciminator ckarinja clarkson101 click cliff cloversquirrel clovest clownbaby clownzilla clubley2 codarl codyme1 cogbyrn cokelogic colemip coleopteran coleslawesome colinnorthway collinc colourmeindigo commandercup commandersven communistbloc compositeredfox congodave cornpalace corvon coubs38 cpsabotage crackilator cramill creasy cru31ty cskilbeck cube cukydoh curieous cyberpirate cyclpsrock d1a2r3k d4rkfr4g dacool561 daddykingkong daeke daenrr daethwing188 dagstur daheadhunter dak722 damatman dan danblanchard dandyshlonglegs danger dangerskew danicia danielben danigold dariusk darkcephas darkernukes darkswordmaster darktrooper darrentorpey darthbutternutz dash daveycraney david2crazy dbfclark dbltnk dead deadbolt deadjoe deep9x defendership deftmunky dekuplatformer desdewd develin devp dexan dfan diamondae dieffenbachj dienekes00 diffusion digikrynary dinozzo dios dippyt dirk dizzyduke dj2scoop dla26 doc docace doctorwhy docyahoo doddzy39 doenan domofizz doneval dookie dorkus1218 dotreptile doug drdos dredgman drmuncie drqqq dukrous dumbitdown duncank dungeonduck dutchsanta duwilli dvorak eddie edgeblend eggs eight einhandr eirabben eklyptz ekombokom elcarlito elendira eljenso ellbent elpicante emanon emil emmick4 emuspawn emwurst engagequadlaser enragedviol ento ephargy erariga escapecharacter esperento esuark06 etran1 etsd eversoslightly evolved exayden ezithau fain faithbleed falcon9xr falconred fancyfrog fapsauss faryn fatheroctopus favocado feenox fenir fetthesten finners fischl flamingarms flanjygo foo forspareparts fortninety foxblock fredthedeadhead french fringe frondo furay fuzzyspo0n fweez fyre gabriel gabrielsp gabumon2 gahd gamersdg garythellama geekgrrl geekynerd geibys geiko generalchaos gerardi getaway geveeso gimmefiction ging girricane gkyl gmandm goat6star6of6astaroth goblineat2 godfatherbrak godfire godzchozenson goinbananas golds golzernurf goodboy goofus gordon gordonfremen gouda graywolf greger grempkin griffinmcelroy grifta67 grimwtf grinspork gryphus1 grythean gummikana guncannon gunner1905 gunslingernz guylum haganehylian hajav halcyon hansoneg harrisonthefan harrylee hasmarth hatebobbarker havochq hawk haze hdfisise headbuddy hehateme henningdc hermituk hernismall herrdoctor hinges hobbes2424 hollygolightly holodoc homeless hop3less hothmonster hulkmaster hunter4hire hutty211 iamcarneiro iax ibis idrisguitar iiatlas ikubiakius ilzxc imaginarydinosaur immortalwombat imthedude ineffable ininja132 inker intero iocat iou1username ironhive isamu itsatrap itsmyyard itt0 j0be jacksonaces jake james1221 jaqenhghar jason jasonbourne jcmoore jedexodus jedimasta jeffatrad jeffnosanov jello jennylynn jentaro jeremiahbritt jetlizard jguzikowski jjb273 jleet82 joakim joeaverage joeprunz johnnemann johnnyr jonmm1776 jonoabbo jorispz joset305 jrmtz85 ju34u julian justin justinliew jvnane kai kalei50 kami kapit karabinerkarl karaidon karibou kate katya kb0m kbd keith kelemvor kellywallick kennyal kevin keysosaurus kidko kifil killercow kkriller klinefelter0 kmonarq7 kmuncie knifeninja kobolt kramff krow007 krpiper kujiun kunstbanause kwhiggs8 kyarourin kyoun lackey lagarathan lala lameduck lamzor lancelake larakin launchbay07 lavinashfield lawiin lawlercaust lazz leaveittoreaver leichelle lemming liamh101 lightishred linkz lioninacoma liquidindian liquience ln3000 loddy logomorph loki lopert lordking loshon lovick lrrr ltfragbox ludusvan lusomai m0r7if3r maarch macintoth mackinder maddragon maedas malbios malfunktionv2 maniacalv mantis marcham93 marciantobay marcnotmark markfnd markrestart markus marlowe mathewcrichton matt mattseligman maverick21xx maxemoe mazazon mbrewski mcc meanbrian megadestructo megapixel23 mehall merc mercuryn mercy metaregress metronome49 mevica michael michibuenkosai micole miggins mike mikecruz mikegbrown mikejmika mikella mikey minecraftboy3437 mistadad misterfix misterpanky mizzrie mmcdhoward mmorier mmustapic mogri moo moof moomanibe morfran morganic mosse motorcrash mrbanana519 mrcompletely mrhoten mrmember mrpants mrphatjared mrprice33 mrpropellerhead mrssaurbaum msarge mtb mukow murray mvandevander mylene namelesssniper nara nateventure nayon7 nayrev naysayer nazzerac nefiorim nelsormensch nemesiscw nemo nerdmancer neuman nexai nicemissmayonnaise nickmac863 nicktheflanders nicta nille niteowl noc noclip noethis nogon noise noisia noonan noonchild norskov northernlion nosferatastic notbengilbert notch nuhuhnoway nukednova nunix nyo odeyin odie ohsickkbro okibi olninyo omar omgmyface omnomonsouls onik onyxmoron opticalshadow or1g1nal1ty orahpajo ossy otakushi overlordror ozzyj88 paarre palm partario pastorpeej patrick patyrick pauzle pawng pax217 pbobzebuilder penumbra peter phaazoid phase phatjax pheranova phobon phokal pickard12 pigo pikes piratepwnsninja pixelfenix pixeltitan pixistick pnomoth pobblepop poison5htp pokeadot polycube popabawa porousnapkin postgoodism powerlanguage pparks76 prall prelone prios proverbialbones psponge psytog pubtongue puppet purplevanes pylons qq qualityjeverage quanimal queenkid1 quickfics quirken rachel rachet20 radchek rage ragegoblin rager ragtag21 rainault raphaelo rapscallion ravi ravie77 raypaik rebeleleven redbeardedowl redivy redrocket redrum remy remzo rennix resdog reusablebox revdancatt reverendanthony rex rick rift rinzai ripply rj rnw159 robinaire robotpants rockliao rocky rodomont roel1976 rogermoore rook rorrim ross rowdynun ruhmann rullerr rune ryan rym sabones sachiarias sagemichael samuel samuelcole sarahnorthway sardiax sarge sargonas sarrick saucepain saurbaum sawchuk1 sb00ya scnigey scopesofter scottish scotto6uk screwjack scud seansf seeking segolectrick seiromem senisan sentry sepherimorth sequel seregrail7 sgtfury shakewell shatter1313 sheepofheight shurp sigmatheta simon2776 simonbob sirblastalot sirlaitier sk82712 skapig skrymir skuchachii skuzeeii slack slats sleepyendymion slyvr smacktubby snakeaes sneakypeaky89 snoken snowmaneater soda solidplasma someguy sonicblastoise sonicboom soolseem spencerrohan spikerama spinnermaster spinyeti spliter splodeybaloney splodn spouwny spy spychao999 squirtle srjenkins starkiller2000 stdizzie steakeatsbabies stents sterling steve stockdogfactory storm stu stuart superdraw swenson sylver symlink syrinx t3kk taco taker talensis tam tantfarbror taquelli tautology teddydief telephis tembac tenebrous tenjouutena tenochtitlan terrytate testing texanfromin thealmightydan thebogdan thechicken thecze thedirt thedoctor52 thedoomsmith thegeckoj thegerf theharribokid theinfamousnolan thejoshfox thelaughingman themantis themightyyeti themosb thename thenew110 theotheralex ther thesocialsolipsist thief thirdordeal thornsten thoseposers thsandman tiefooz tigerseye85 tigershark tilton time5 tlabresh tobybot tokion tomeng1997 tomqbui toomuchpete tpwt tralan trauts travis treadd tree treythalomew trickery triplelex trouserdemon tubybuny tulse twie twinbolt twistamafoo twoflower tylerthedesigner undeadnub undisclosed uneddy vadid37 valir valuedrug vanlo varanas venom veturi vidvandre vincentdpc vinnyboombatz vorrik vortexmortinghan voxhumano vs vutdevuk wantedbob warlock watertower webjr weebee886 weeble wesley weteor whatthegeek whispous whitewolf whizkid wiggy wikzo willdude william wilson2k4 wiltwin wirapuru wish wkerslake woodensplint woof woomstick woseseltops wubang3r wubby wyrmling x13ucketheadx x1pyvr84 xav xemu xenoacid xlom3000 xspjigo xtreyu xzalander yanndos yazzflute yegg yobani yokelbloke yorudy yourdad zacaj zachwlewis zafo999 zaktilu zapdsl zen zerooneinfinity zerosyphon zerotka zerozshadow zgilly zhiwiller zoink2000 zoneghost zynux</span></p>
<hr/><ol class="footnotes"><li id="footnote_0_2419" class="footnote">Updated numbers to be clear: There are 13788 people signed up as of right now, 1081 registered (I invited more in after this post), 2430 invites total sent, for a 44% registration rate&#8230;I&#8217;m still inviting people in slowly until I get the lobby scalable.</li></ol>]]></content:encoded>
			<wfw:commentRss>http://www.spyparty.com/2012/06/12/the-first-999-beta-tester-usernames/feed/</wfw:commentRss>
		<slash:comments>42</slash:comments>
		</item>
		<item>
		<title>Deductive Depth Jamming, Beta Balance, and Megabuild Todo List</title>
		<link>http://www.spyparty.com/2012/06/05/beta-update-and-megabuild-todo-list/</link>
		<comments>http://www.spyparty.com/2012/06/05/beta-update-and-megabuild-todo-list/#comments</comments>
		<pubDate>Tue, 05 Jun 2012 08:58:34 +0000</pubDate>
		<dc:creator><![CDATA[checker]]></dc:creator>
				<category><![CDATA[beta]]></category>
		<category><![CDATA[design]]></category>
		<category><![CDATA[metrics]]></category>
		<category><![CDATA[programming]]></category>

		<guid isPermaLink="false">http://www.spyparty.com/?p=2355</guid>
		<description><![CDATA[Wow, I have been busy, and the Early-Access Beta todo items are piling up! I will just get right to talking about stuff, no introduction necessary! The Depth Jam and Deduction Recently, a small group of us did an intensive game design retreat we called the Depth Jam, during which I dug into the deductivity [&#8230;]]]></description>
				<content:encoded><![CDATA[<p><div id="attachment_2362" style="width: 310px" class="wp-caption alignright"><a href="http://cdn.spyparty.com/wp-content/uploads/2012/06/DSCN0872.jpg"><img class="size-medium wp-image-2362 " title="DSCN0872" src="http://cdn.spyparty.com/wp-content/uploads/2012/06/DSCN0872-300x225.jpg" alt="" width="300" height="225" /></a><p class="wp-caption-text">I was killed.</p></div>
<p>Wow, I have been busy, and the <a title="Sign Up for the SpyParty Early-Access Beta!" href="http://www.spyparty.com/beta-sign-up/"><em>Early-Access Beta</em></a> todo items are piling up! I will just get right to talking about stuff, no introduction necessary!</p>
<a name="The+Depth+Jam+and+Deduction"></a><h3>The Depth Jam and Deduction</h3>
<p>Recently, a small group of us did an intensive game design retreat we called the <a href="http://chrishecker.com/The_Depth_Jam">Depth Jam</a>, during which I dug into the <a href="http://en.wikipedia.org/wiki/Deductive_reasoning"><em>deductivity</em></a> of the Sniper gameplay.  I want there to be some deductivity on the Sniper side, so the player has some method or process he or she can step through to eliminate suspects and not just completely drown in information, but I don&#8217;t want you to be able to completely grind out who the Spy is by a series of rote operations, like in a game of <a href="http://en.wikipedia.org/wiki/Cluedo">Clue</a>.  Since one of the <a href="http://www.spyparty.com/faq/#What+are+your+aesthetic+goals+for+the+game%3F">aesthetic themes</a> for <strong>SpyParty</strong> is about &#8220;making consequential decisions with partial information&#8221;, it&#8217;s very important that the Sniper not be able be 100% sure of the Spy&#8217;s identity, unless the Spy screws up and the Sniper sees a <em>hard tell</em>.  There are a lot of places where deductivity comes into play for the Sniper, but an important one is the highlight/lowlight mechanic.</p>
<p>If you&#8217;ve <a title="RT(updated)FM" href="http://www.spyparty.com/2011/03/10/rtupdatedfm/">read the documentation</a>, you know that the Sniper can highlight and lowlight people at the party to manage levels of suspicion.  This is purely a Sniper-side mechanic: the Spy doesn&#8217;t know for sure which characters are being tagged in this way or how, except that the Sniper&#8217;s laser has to hit the character to tag them, and this is visible to the Spy.  The current version of this mechanic has two levels of highlight above neutral, and two levels of lowlight below neutral.  The original intent was that you&#8217;d want to vary the amount of suspicion or lack thereof based on how the party is progressing, and for the most part, this worked.  However, gamers are very good at optimizing things, and soon one of the elite players realized he could use these five levels as a 5-counter, using both the highlights and lowlights to count total number of visits to the statues and bookcases.  Depending on the missions selected for the game, this Sniper would have a pretty good idea of who had to be the Spy.</p>
<p>From a game design standoint, this kind of player behavior is a delicate thing to direct in a player-skill game.  Should the game help the Sniper do this bookkeeping because the player is going to want to do it anyway?  Or, does the existence of multiple levels actually encourage this kind of bookkeeping?  Should I nerf the mechanic, or just make the NPCs go to the statues more often so the bookkeeping is less relevant?  It&#8217;s very hard to know the answers to these questions, but after thinking about it for a while, and talking to the elite players, I decided I needed to deal with this in a few different ways, and it will be an ongoing iterative design challenge:</p>
<ul>
<li>I proposed nerfing the Sniper&#8217;s mechanic to a single level of highlight and a single level of lowlight.  Snipers could still use this as a 3-counter, but that&#8217;s less valuable, and each individual highlight or lowlight becomes more consequential.  On the flip side, it removes the cognitive load associated with trying to figure out if you&#8217;re going to single- or double-highlight, which is subtle but significant.  It&#8217;s also a lot cleaner controls-wise.  This was the first real nerf, and the beta community responded well to the idea.  There was some grumbling, but players posted thoughtful critiques and analyses of how they thought they&#8217;d be affected.  I realized I was going to have to test this thoroughly, but it was easy to isolate and I chose it as my question for the Depth Jam.</li>
<li>I decided against changing the NPC behavior in the short term, but in the long term, instead of simply increasing the probability that NPCs will visit the statues (for example), I might make it so some NPCs end up going to the statues a minimum number of times.  However, either of these is a very complex change, because if an NPC is at a statue, it means they&#8217;re not at a bookshelf or in a conversation, so the entire flow of the party will change.   This is a very nonlinear change, and it&#8217;s hard to predict what will happen.</li>
<li>I can also change the missions that require the Spy to be in &#8220;deductivity-susceptible situations&#8221;.  Keeping with the statue example, at the Depth Jam we ended up modifying the Inspect Statues mission so that instead of requiring three visits to the statues, the Spy could also inspect the neighboring statues if they&#8217;re not being held by and NPC.  This allows the Spy to trade off duration and number of visits, and requires Snipers to have a feeling for both these quantities.  There&#8217;s also a discount on the amount of time it takes to inspect each additional statue in a single visit, so you&#8217;re encouraged to inspect more than one.  Finally, it makes the middle pedestal the most valuable statue to visit, because it allows the Spy to inspect either side, so characters going to the middle statue are a bit more suspicious.  I love these tradeoffs, and these changes felt great when we tested them.</li>
</ul>
<p>To give you an idea of how we went about testing this stuff at the Depth Jam, here is a screenshot of a special build we played.  In it, I had the computer automatically increment two counters over the characters&#8217;s heads, one for statue visits, and one for bookcase visits, so the Sniper didn&#8217;t even need to click.  This was never something I&#8217;d release to players, but I wanted to see how the game felt if the deductivity was &#8220;turned to 11&#8243;.  Especially before the Inspect Statues changes, you could definitely just shoot (or at least watch) the person with the highest numbers.</p>
<p><div id="attachment_2363" style="width: 510px" class="wp-caption aligncenter"><a href="http://cdn.spyparty.com/wp-content/uploads/2012/06/deductive.png"><img class="size-full wp-image-2363" title="deductive" src="http://cdn.spyparty.com/wp-content/uploads/2012/06/deductive.png" alt="" width="500" height="351" /></a><p class="wp-caption-text">Shoot Danger P. Johnson, he&#39;s got the highest numbers! Except he&#39;s the Ambassador.</p></div>
<p>I&#8217;m very happy with both the single level of highlight/lowlight and the Inspect Statues changes, and they&#8217;re going to roll out in the next build.</p>
<a name="Beta+Balance"></a><h3>Beta Balance</h3>
<p>I haven&#8217;t run detailed metrics yet, since I&#8217;m scrambling just to keep things humming along for players, but I did get curious about whether the game is even roughly balanced right now.  Qualitively, I&#8217;ve been surprised by the seeming consensus in the private beta forums that the Sniper is the harder side to play.  In the private testing I did with <a title="Know Your Enemy, Especially if He or She Has a Sniper Rifle" href="http://www.spyparty.com/2011/01/27/know-your-enemy-especially-if-he-or-she-has-a-sniper-rifle/">Ian and Paul</a>, it seemed like at elite levels the Sniper had the advantage, but I wasn&#8217;t seeing people complaining about this, which I found interesting.  Even more surprising was that players had settled on Pick 4 of 5 Missions on Ballroom as the balanced game mode.  Back in the day, Pick 3 of 4 Missions on Ballroom was considered balanced, and even then we were worried about a Sniper advantage at elite levels. </p>
<p>Because <strong>SpyParty</strong> is so intensely player-skill oriented, I&#8217;ve implemented <a title="Seduction on the Balcony" href="http://www.spyparty.com/2011/02/17/seduction-on-the-balcony/">a number of </a><a title="Seduction on the Balcony" href="http://www.spyparty.com/2011/02/17/seduction-on-the-balcony/">game types</a> so players can handicap matches to make up for skill differences.  You can tune the Spy&#8217;s difficulty by choosing modes with more or less missions to accomplish, and giving the Sniper more or less information about which missions will be enabled.  Some modes allow the Spy to complete any small subset of the missions chosen opportunistically while playing.  Other modes require the Spy to divulge exactly which missions he or she will attempt.  A large skill gap in favor of the Spy can be handicapped by choosing &#8220;6 Known Missions&#8221;, where choosing &#8220;Any 3 of 6&#8243; makes up for a lot of skill on the Sniper side. </p>
<p>Pick 4 of 5 is significantly harder for the Spy than Pick 3 of 4,<sup><a href="http://www.spyparty.com/2012/06/05/beta-update-and-megabuild-todo-list/#footnote_0_2355" id="identifier_0_2355" class="footnote-link footnote-identifier-link" title="Pick modes require the Spy to choose which missions to accomplish before the game starts, while Any modes let the Spy opportunistically choose during play.">1</a></sup> yet people were playing it as the default mode once they graduated from Beginner Ballroom, so I decided to run some quick numbers.</p>
<p>The first thing I did was looked at the results of all games ever played in the beta.  There are four possible outcomes of a game of <strong>SpyParty</strong>:  the Spy can accomplish the missions, the Spy can run out of time, the Sniper can shoot the Spy, or the Sniper can shoot a civilian.  We call the first and the last a Spy Win, and the middle two a Sniper Win.  The results were even more balanced than I thought they&#8217;d be!</p>
<p style="text-align: center;"><strong>Total Games: 13238, SpyWins: 6494 (49.1%), SniperWins: 6744 (50.9%)</strong></p>
<p>Whoa.</p>
<p>Okay, so this is great, but it&#8217;s pretty silly, since this includes every newbie game, every game where somebody said &#8220;shoot me because I&#8217;m stuck on the briefcase&#8221;, and the like.  So, next I decided to look just at the elite games.  I defined &#8220;elite game&#8221; as one played between the people on the leaderboard with more than 20 hours of game time.<sup><a href="http://www.spyparty.com/2012/06/05/beta-update-and-megabuild-todo-list/#footnote_1_2355" id="identifier_1_2355" class="footnote-link footnote-identifier-link" title="Important note: this is actual game playing time, not time spent in the matches overall, or time in the lobby, both of which are way higher than this.&nbsp; A lot of people like to hang out and chat in the lobby!">2</a></sup>  That part of the leaderboard looks like this:</p>
<p><div id="attachment_2360" style="width: 610px" class="wp-caption aligncenter"><a href="http://cdn.spyparty.com/wp-content/uploads/2012/06/leaderboard-top-20120604.png"><img class="size-large wp-image-2360" title="leaderboard-top-20120604" src="http://cdn.spyparty.com/wp-content/uploads/2012/06/leaderboard-top-20120604-600x140.png" alt="" width="600" height="140" /></a><p class="wp-caption-text">The top 8 players on the leaderboard by time.</p></div>
<p>I did a few different queries here.  First, all games these guys played in the last three weeks:</p>
<p style="text-align: center;"><strong>Total Games: 1794, SpyWins: 826 (46%), SniperWins: 968 (54%)</strong></p>
<p>This includes teaching games, which these guys do a lot because they&#8217;re awesome, and everything else, so then I narrowed it to all games they played versus each other in the last three weeks, so these are the most advanced games going on in <strong>SpyParty</strong> right now&#8230;and these guys are very good&#8230;I know this because they beat me routinely:</p>
<p style="text-align: center;"><strong>Total Games: 266, SpyWins: 128 (48.1%), SniperWins: 138 (51.9%)</strong></p>
<p>Wow, again, that&#8217;s just incredibly balanced!?  I couldn&#8217;t believe it when I ran these numbers, but I&#8217;m super happy with them, and it goes a long way towards explaining why buxx said this to me in chat:</p>
<blockquote>
<pre>checker: "so, you've played 43 hours or whatever, do you feel like there's a
ceiling for you yet, or still going strong?"

buxx: "Skillwise, not even close, I still feel like I have a ton of room
for improvement"</pre>
</blockquote>
<p>I just feel incredibly lucky to have a game this deep, this early in its development.  I&#8217;m so excited to take it even farther&#8230;people in competitive game design talk about &#8220;300 hour&#8221; games, and games like Go and Poker can be played for a lifetime.  I hope to take <strong>SpyParty</strong> to those levels, or as close to them as I can get.</p>
<p>Finally, I looked at these players versus each other at a finer grain.  I&#8217;ll just post some rough numbers for buxx and ardonite, because they&#8217;re an interesting contrast:</p>
<table style="width: 600px; height: 70px;" border="0" cellspacing="0" cellpadding="0" align="center">
<tbody>
<tr>
<td style="background-color: #ffffff;"> </td>
<td style="background-color: #ffffff; text-align: right;"> </td>
<td style="background-color: #ffffff; text-align: right;"> </td>
<td style="background-color: #ffffff; text-align: right;"><strong>As Spy</strong></td>
<td style="background-color: #ffffff; text-align: right;"> </td>
<td style="background-color: #ffffff; text-align: right;"> </td>
<td style="background-color: #f0f8ff; text-align: right;"><strong>As Sniper</strong></td>
</tr>
<tr>
<td style="background-color: #ffffff;"> </td>
<td style="background-color: #ffffff; text-align: right;">Total Games</td>
<td style="background-color: #ffffff; text-align: right;">Wins</td>
<td style="background-color: #ffffff; text-align: right;">Win%</td>
<td style="background-color: #f0f8ff; text-align: right;">Total Games</td>
<td style="background-color: #f0f8ff; text-align: right;">Wins</td>
<td style="background-color: #f0f8ff; text-align: right;">Win%</td>
</tr>
<tr>
<td style="background-color: #f5f5f5;"><strong>buxx</strong></td>
<td style="background-color: #ffffff; text-align: right;">32</td>
<td style="background-color: #ffffff; text-align: right;">23</td>
<td style="background-color: #ffffff; text-align: right;"><span style="color: #000000;">71.88%</span></td>
<td style="background-color: #f0f8ff; text-align: right;">34</td>
<td style="background-color: #f0f8ff; text-align: right;">20</td>
<td style="background-color: #f0f8ff; text-align: right;"><span style="color: #000000;">58.82%</span></td>
</tr>
<tr>
<td style="background-color: #f5f5f5;"><strong>ardonite</strong></td>
<td style="background-color: #ffffff; text-align: right;">24</td>
<td style="background-color: #ffffff; text-align: right;">12</td>
<td style="background-color: #ffffff; text-align: right;"><span style="color: #000000;">50.00%</span></td>
<td style="background-color: #f0f8ff; text-align: right;">22</td>
<td style="background-color: #f0f8ff; text-align: right;">14</td>
<td style="background-color: #f0f8ff; text-align: right;"><span style="color: #000000;">63.64%</span></td>
</tr>
</tbody>
</table>
<p>buxx is the top player right now, and he wins most of the time even against the other elite players, but especially as Spy.  ardonite also wins over half the time against the elites, but moreso as Sniper.</p>
<p>I can&#8217;t wait to delve into the metrics more in the future.</p>
<a name="Todo"></a><h3>Todo</h3>
<p>Due to the Depth Jam and other distractions, the todo list has been building for weeks.  I&#8217;m finally cranking through it, but I&#8217;ve dubbed this next build the &#8220;megabuild&#8221; because of all the stuff I&#8217;m shoving into it.  This is not the right way to develop software.  You should do more small releases, so you can test and isolate bugs, but I&#8217;m kind of on a roll and don&#8217;t want to slow down to make a build until I get some more stuff fixed.  I won&#8217;t do this entire giant list before releasing, but I will do a couple more days worth of work.  I&#8217;m afraid there are going to be bugs due to the number of changes, so it&#8217;s really the build after the megabuild that is going to be totally awesome!</p>
<ul class="tightlist">
<li>misc</li>
<ul>
<li><del>added matches and total match time to leaderboards</del></li>
<li><del>alpha punchthrough for binocs and timed text&#8230;draw after</del></li>
<li><del>kill &#8220;sleep too long&#8221; log message</del></li>
<li><del>put graphics and cpuid info in log, at least glext</del></li>
<ul>
<li><del>pack cpuid into string in journal too</del></li>
</ul>
<li><del>don&#8217;t make duplicate logins delete cache and crash, stupid and annoying</del></li>
<li><del>hittest laser to world for marking, except for window muntin bars</del></li>
<li><del>mission text in practice sniper mode buggy</del></li>
<li><del>terrible lowercase in username entry on login page</del></li>
<li><del>s/affordance/action/g in code</del></li>
<li><del>highlight/lowlight nerf</del></li>
<ul>
<li><del>ctrl/shoulder slam all the way to highlight/lowlight</del></li>
</ul>
<li><del>hysteresis on actions</del></li>
<ul>
<li><del>if it&#8217;s present in the current batch, set the index (unless it&#8217;s been manually set)</del></li>
<li><del>if it&#8217;s not present, grey it out and fade it away</del></li>
<li><del>this needs a lot of testing, it was a complex change</del></li>
</ul>
<li>clean up timeouts, wins, blah text everywhere in game</li>
<li>remove debug_state_transitions</li>
<li>check for pdf file association in setup program</li>
<li>need network test during greeting</li>
<li>invite beep in practice mode?</li>
<li>don&#8217;t hide mouse during sniper menus</li>
<li>put stats on match menu</li>
<li>mouse sensitivity config prop and readme.txt</li>
<li>do in-game update with a popup window instead of modal!</li>
<li><del>sell subliminal advertising in the crowd walla</del></li>
</ul>
<li>endgame and results</li>
<ul>
<li>not top suspects, just high/lowlights, filtering down cast</li>
<li>escape confirmation on results screen with settings</li>
<ul>
<li>can take out the eat stuff and timer</li>
<li>make Last Results on the match menu? possible?</li>
<li>test invite cancel</li>
</ul>
<li>OT confirmation, OT happens on sniper machine for a second</li>
<li>output game result into log</li>
<li>shoot after timer runout still records shot person</li>
<ul>
<li>Shot person on sniper screen but not on Spy screen</li>
</ul>
<li>escape from invite to game needs confirmation?</li>
<li>if sniper escapes while spy is waiting for results, game doesn&#8217;t end</li>
<li>need to record actions for spy when ai before control</li>
</ul>
<li>briefcase</li>
<ul>
<li><del>make briefcase a heavy pathing weight, not infinite</del></li>
<li><del>briefcase making characters float off ground</del></li>
<li>moving while putting down briefcase</li>
<li>stuck in COURIER_WAITING_FOR_SCIENTIST_HOLDING when ambassador has a book&#8230;not moving</li>
<ul>
<li>need to put down the briefcase when the timer runs out!</li>
<li>dispose is broken, courier won&#8217;t put it down!</li>
</ul>
<li>add pauses</li>
</ul>
<li>conversations</li>
<ul>
<li><del>fix interruption stuff to occasionally finish talk animation</del></li>
<li>people in convo circles by themselves can read book until somebody else comes</li>
<ul>
<li>hmm, one frame of Read Book action, and immune to pause?</li>
<ul>
<li>ahh, the listen stomps it, need some kind of &#8220;interruptable&#8221; flag on anim, or priority</li>
</ul>
</ul>
<li>can interrupt while taking drink&#8230;need to block</li>
<li>people need to leave during listen idles?</li>
<li>people going through middle of convo circles?</li>
</ul>
<li>double agent</li>
<ul>
<li>fake bbread not available when selected but not enabled</li>
<li>banana bread double agent signal&#8230;if da leaves while action testing converts to fake</li>
<ul>
<li>need to have DA check to see if spy is action testing, and not leave convo if so</li>
<li>how to do override in double_agent.cpp?</li>
</ul>
<li>double agent won&#8217;t go into circle during last minute &#8211; guarantee in last 30 seconds?</li>
<li>fake banana bread is default option over flirt</li>
<ul>
<li>drink over conversation</li>
</ul>
<li>need to have cancel behavior on bbread</li>
<ul>
<li>okay &#8211; what to do here? person starting to talk is enough? short cough?</li>
<li>bad &#8211; cough as walk away</li>
</ul>
</ul>
<li>statue</li>
<ul>
<li><del>new inspect statues design, can inspect statues next to you if not held</del></li>
<li><del>finish drink at or take briefcase to pedestal like bookcases</del></li>
<li><del>play sound when teleporting statue back, sorry ardonite!</del></li>
<li>swap wrong statue when between pedestals &#8211; /tmp/fraps</li>
<li>two people pick up same statue</li>
<li>bad statue action test is fastest, make it delay, or flicker longer</li>
<li>statue swap cancel makes it disappear</li>
<li>statue swap bad action test needs timer before swap happens</li>
<ul>
<li>maybe bad swap doesn&#8217;t actually swap, it toggles it and back?</li>
<li>or, statue swap is too hard already so don&#8217;t make it worse?</li>
</ul>
<li>good swap statue action test</li>
<ul>
<li>need to prevent run out of time between good action test and swap?</li>
<li>its obvious to elite snipers, worse than hiding body&#8230;</li>
<ul>
<li>make it swap when somebody else picks it up? too powerful?</li>
<li>make it instantly warp, not alpha</li>
<li>more than 5 seconds? cheesy way of buying time then&#8230;</li>
</ul>
</ul>
</ul>
<li>bug ambassador</li>
<ul>
<li><del>while watch check is happening, walking bug amb is not blocked</del></li>
<li>ability to bug holding book in one hand</li>
</ul>
<li>bookcase</li>
<ul>
<li><del>fix bookcase pileup &#8211; do vertical expanded shape</del></li>
<li><del>AIs wait a bit more randomly before leaving bookshelves</del></li>
<li>marking book on shelf doesn&#8217;t record character because not attached yet</li>
<ul>
<li>do I want to support this even? have all books be grey on sniper?</li>
</ul>
<li>change put book and get book to have start/end events too</li>
<li>idles in book watch animations, mix it up a bit</li>
<li>second book page turn? or just use the idle for a time?</li>
<li>make cycling books unified, right &#8211; blue, left &#8211; green, add red as double click, then grey</li>
<ul>
<li>bookcases vary in color per map&#8230;need to standardize and make maya prop type</li>
</ul>
</ul>
<li>seduction at paintings</li>
<li>chat</li>
<ul>
<li>window size, pos, borderless, maximized through chat commands</li>
<li>add @ with username completion in chat</li>
<li>color code names in chat</li>
<li>kill &#8220;bogger says:&#8221; in single line chat copy</li>
<li>history when typing chat line</li>
<li>add crlf to chat copy so notepad works</li>
<li>crash on non-text data in clipboard</li>
<li>hitting escape kills the chat message you&#8217;re typing</li>
<li>make it clear the first whisper went through even if got the away message</li>
<li>packet loss test on console</li>
<li>can&#8217;t run /fps command even without connection&#8230;but can do practice in lobby</li>
</ul>
<li>manual &amp; readme</li>
<ul>
<li>put the latest manual images up, readme, etc</li>
<li>s/noob/newb/ in manual&#8230;ugh</li>
<li>documentation change to security agent</li>
<li>remove y button</li>
<li>inspect statues</li>
<li>highlight/lowlight levels and controls</li>
<li>add note about SpyPartyHelper.exe to the readme</li>
<li>note lag is only on sniper side</li>
</ul>
<li>buxx feels watch check is underutilized&#8230;</li>
<ul>
<li>add more time?</li>
<li>at paintings? in conversations while by yourself?</li>
<li>watch check with book</li>
<li>cancelling watch check needs failure or is animation tell enough?</li>
</ul>
<li>zerotka list of helpful things for research</li>
<ul>
<li>unlimited game time endless practice mode</li>
<li>ability to see all floor pads in sniper view</li>
<li>books come out same color as shelf</li>
<li>Seduction Target in sniper view</li>
<li>inability to complete missions or way to clear them</li>
<li>no back walls in Ballroom as sniper ( or transparent if that&#8217;s easier?) ( aka, i want to be able to see in from all angles)</li>
<li>camera track be a complete circle all the way around the ballroom.</li>
</ul>
<li>bugs</li>
<ul>
<li>intel problems</li>
<ul>
<li>lobby message box bad z</li>
<li>key and mouse button drops due to shit framerate</li>
</ul>
<li>fix the rare junked game bug</li>
<li>put more debugging code into the drinks.cpp coreanim crash</li>
<ul>
<li>store things to locals to see, do memory readable check on coreanimation</li>
</ul>
<li>phatjax crash in stun code, assert in log first</li>
<ul>
<li>0.1.1983.0-phatjax-7cs4v-e9SBihV1znwyvuFw.7z</li>
</ul>
<li>lobbyserver leak &#8211; use backtrace</li>
<li>inviting phatjax after cancelled invite gives assert</li>
<li>assert !&#8221;what do to here?&#8221;, File: .\examples\lobby\lobbyclient.cpp, Line: 1798</li>
<li>fix DTScale behaviors, briefcase, waiter, have game clock</li>
</ul>
</ul>
<hr/><ol class="footnotes"><li id="footnote_0_2355" class="footnote">Pick modes require the Spy to choose which missions to accomplish before the game starts, while Any modes let the Spy opportunistically choose during play.</li><li id="footnote_1_2355" class="footnote">Important note: this is actual game playing time, not time spent in the matches overall, or time in the lobby, both of which are way higher than this.  A lot of people like to hang out and chat in the lobby!</li></ol>]]></content:encoded>
			<wfw:commentRss>http://www.spyparty.com/2012/06/05/beta-update-and-megabuild-todo-list/feed/</wfw:commentRss>
		<slash:comments>33</slash:comments>
		</item>
		<item>
		<title>A Beta Chat Log and The Current Leaderboard</title>
		<link>http://www.spyparty.com/2012/04/22/a-beta-chat-log-and-the-current-leaderboard/</link>
		<comments>http://www.spyparty.com/2012/04/22/a-beta-chat-log-and-the-current-leaderboard/#comments</comments>
		<pubDate>Sun, 22 Apr 2012 07:27:44 +0000</pubDate>
		<dc:creator><![CDATA[checker]]></dc:creator>
				<category><![CDATA[beta]]></category>
		<category><![CDATA[metrics]]></category>

		<guid isPermaLink="false">http://www.spyparty.com/?p=2296</guid>
		<description><![CDATA[One of the best things about finally having regular players in your game&#8217;s beta is you get to watch the server logs for bugs, or just snoop on your players chatting with each other before and after games.  In fact, this is so addictive, and you learn so much about how your game really works [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>One of the best things about finally having regular players in your <a title="Sign Up for the SpyParty Early-Access Beta!" href="http://www.spyparty.com/beta-sign-up/">game&#8217;s beta</a> is you get to watch the server logs for bugs, or just snoop on your players chatting with each other before and after games.  In fact, this is so addictive, and you learn so much about how your game really works with players by doing this, that I had to basically stop myself because I was sitting there all day watching the logs scroll by rather than, you know, fixing bugs.</p>
<p>That said, I thought you&#8217;d like a taste of this server voyeurism yourself, so I asked arbaard and dbfclark if I could repost their chats from a series of games a few nights ago.  These chats are great, because they show two very good players (both were at <a title="PAX, Day 1" href="http://www.spyparty.com/2010/09/04/pax-day-1/">PAX 2010</a> when I first showed the game, arbaard was in the <a title="PAX, Day 3, ZOMG" href="http://www.spyparty.com/2010/09/06/pax-day-3-zomg/">tournament</a>, and dbfclark is the person who noticed that the line watching the games was getting implicit Sniper practice).  You can see them helping each other after each round, discussing what went right and what went wrong, changing the game types as they handicap the games, and sharing information so they both can improve.  Plus, they&#8217;re funny.</p>
<table class="graph" align="center" width="400px">
<tbody>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>hey</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>hia</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>*hiya</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>what would you like to play?</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>anything&#8217;s fine</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>have you played much?</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>yeah; you?</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>yeah a bunch</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>should be interesting</td>
</tr>
<tr>
<td colspan="2" height="30px" valign="center" align="center"><b>Saved Game Results.</b></td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>saw the tell</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>alas</td>
</tr>
<tr>
<td colspan="2" height="30px" valign="center" align="center"><b>Saved Game Results.</b></td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>got me!</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>what tipped you off? time dilation?</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>I was pretty sure when you time dialated</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>haha</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>5/6 is really, really hard!</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>feeling lucky?</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>sorry, what did you do? gotta make it fair</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>5 of 6, right?</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>yeah I did any 5/6. I was just playing around though</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>I don&#8217;t think it&#8217;s very balanced for even skill levels</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>ok, off we go</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>I was playing that with my friend who just started playing.</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>alrighty</td>
</tr>
<tr>
<td colspan="2" height="30px" valign="center" align="center"><b>Saved Game Results.</b></td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>heh</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>sigh</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>what tipped you?</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>you were very interested in sculpture.</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>yeah</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>it was either that or seduction.</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>5/6 is a little too hard for spy, methinks</td>
</tr>
<tr>
<td colspan="2" height="30px" valign="center" align="center"><b>Saved Game Results.</b></td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>well done</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>thought you had me when I bugged the amabassador</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>you were definitely a top suspect, but I didn&#8217;t have my eye on</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>you enough</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>not nearly as sneaky as I should have been.</td>
</tr>
<tr>
<td colspan="2" height="30px" valign="center" align="center"><b>Saved Game Results.</b></td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>damn it</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>I thought it was that black guy</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>thought I was doing so well there</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>were you purposely trying to get me to shoot him?</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>no</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>you got real close, making me think the AI glitched out on him and was just</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>invading his space</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>so I thought he was the guy</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>banana bread just killed me? was it two double agents in convo?</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>because both you and him walked away from the window after I noticed we were at 4:00</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>ah</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>greed</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>that&#8217;ll do it</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>no you were talking to the only double agent in a convo</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>ah</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>plus, you were marked high suspicion</td>
</tr>
<tr>
<td colspan="2" height="30px" valign="center" align="center"><b>Saved Game Results.</b></td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>god I love being fat.</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>argh</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>being fat and a spy</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>thought I saw a bug go down, didn&#8217;t</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>huge plus</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>hides those animations so well!</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>what did the yellow guy do?</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>ok, something wonky is happening; my sound is gone</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>uh oh</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>gonna restart</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>restart?</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>have you joined the steam group by the way?</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>not really</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>It&#8217;s better than waiting around in the lobby</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>well, I&#8217;m in the steam group, but I don&#8217;t have the overlay set up</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>oh</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>well that&#8217;s fine</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>that was only like a side-not</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>e</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>nope</td>
</tr>
<tr>
<td colspan="2" height="30px" valign="center" align="center"><b>Saved Game Results.</b></td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>that was super unfortunate</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>caught the tell?</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>she ducked while you bugged</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>oh, crap</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>and your hand went in the wrong direction</td>
</tr>
<tr>
<td colspan="2" height="30px" valign="center" align="center"><b>Saved Game Results.</b></td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>EVERYBODY</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>WAS IN MY WAY</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>I must have bumped into 5 people</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>and that damn waiter&#8230;</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>yeah, but that was the second funny walk twitch I caught</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>that was me cancelling a failed banana bread</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>twice.</td>
</tr>
<tr>
<td colspan="2" height="30px" valign="center" align="center"><b>Saved Game Results.</b></td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>nicely done.</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>hah!</td>
</tr>
<tr>
<td colspan="2" height="30px" valign="center" align="center"><b>Saved Game Results.</b></td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>oh darn</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>sorry</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>that wasa supposed to be a perfect banana bread</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>it actually was a pretty good one, I just had my eye on you</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>you ran away</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>haha!</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>nobody picked up on that until now</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>I usually banana and run</td>
</tr>
<tr>
<td colspan="2" height="30px" valign="center" align="center"><b>Saved Game Results.</b></td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>oh no!</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>no idea where that came from</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>why&#8217;d you shoot him?</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td> hunch</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>fair, happens</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>he was marked</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>ah</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>then went blue, statues green</td>
</tr>
<tr>
<td colspan="2" height="30px" valign="center" align="center"><b>Saved Game Results.</b></td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>haha</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>crap</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>that was intentional.</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>?</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>perfect banana</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>when he came in.</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>ah</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>wasn&#8217;t why I shot him</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>oh really?</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>though it helped</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>what did he do?</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>drank too fast, I thought</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>oh, weird!</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>sigh</td>
</tr>
<tr>
<td colspan="2" height="30px" valign="center" align="center"><b>Saved Game Results.</b></td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>nope</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>changin it up high?</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>fair</td>
</tr>
<tr>
<td colspan="2" height="30px" valign="center" align="center"><b>Saved Game Results.</b></td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>ugh, so close</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>I should have played it cool for a min</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>that&#8217;s why I shot you ;-)</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>probably</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>yeah but did you see my awesome ambassador bug?</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>that was cool</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>gave him the briefcase and a bug.</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>hahaha</td>
</tr>
<tr>
<td colspan="2" height="30px" valign="center" align="center"><b>Saved Game Results.</b></td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>nice try</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>that was sneaky</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>cheaty cheaty &#8212; it&#8217;s usually a good opening move</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>I was nervous about it &#8212; good to see my ners were justified</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>you just happened to beeline across the empty room.\</td>
</tr>
<tr>
<td colspan="2" height="30px" valign="center" align="center"><b>Saved Game Results.</b></td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>heh</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>sigh</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>check out how I transferred the microfilm</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>never trusy my instincts</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>you see how I transferred the microfilm?</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>as the general?</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>it was pretty cool.</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>I&#8217;m not sure I get it</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>(I did the book swap as your char model)</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>oh, I see</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>:)</td>
</tr>
<tr>
<td colspan="2" height="30px" valign="center" align="center"><b>Saved Game Results.</b></td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>f!</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>jow</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>joy</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>\</td>
</tr>
<tr>
<td colspan="2" height="30px" valign="center" align="center"><b>Saved Game Results.</b></td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>WHY?</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>I WAS JUST SIPPING MY MARTINI!</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>yep. sipping and watch checking</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>sorry</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>that&#8217;s funny, I didn&#8217;t even add time</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>yeah</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>I was just chillin</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>I know</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>the statue mission bugged and I couldn&#8217;t complete it</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>you were chillin&#8217; nervously ;-)</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>I was gonna play it cool</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>ah</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>I also saw the woman with the briefcase go stationary</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>sorry, I gota go do household things</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>alrighty</td>
</tr>
<tr>
<td align="right"><b>arbaard</b></td>
<td>nice playing with you!</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>thanks for the games! I learned a lot</td>
</tr>
<tr>
<td align="right"><b>dbfclark</b></td>
<td>another time</td>
</tr>
</tbody>
</table>
<p>I&#8217;ll do more posts from the beta soon!  I&#8217;m slowly inviting folks from the <a title="Sign Up for the SpyParty Early-Access Beta!" href="http://www.spyparty.com/beta-sign-up/">beta sign-up</a> in now, and it&#8217;s working out great so far.  Lots of <a title="How to Report Bugs the SpyParty Way" href="http://www.spyparty.com/2012/04/12/how-to-report-bugs-the-spyparty-way/">bugs reported</a>, lots of bugs fixed, and people are playing a ton and seem to be having a great time!  Here&#8217;s the leaderboard as of right now, look at some of the play times and remember a lot of these people have only been in there a few days!</p>
<div id="attachment_2313" style="width: 416px" class="wp-caption aligncenter"><a href="http://cdn.spyparty.com/wp-content/uploads/2012/04/leaderboard-20120422.png"><img class="size-large wp-image-2313" title="leaderboard-20120422" src="http://cdn.spyparty.com/wp-content/uploads/2012/04/leaderboard-20120422-406x600.png" alt="" width="406" height="600" /></a><p class="wp-caption-text">Seventeen and a half hours played in four days!? So. Awesome.</p></div>
]]></content:encoded>
			<wfw:commentRss>http://www.spyparty.com/2012/04/22/a-beta-chat-log-and-the-current-leaderboard/feed/</wfw:commentRss>
		<slash:comments>75</slash:comments>
		</item>
		<item>
		<title>The Near-Term Early-Access Beta Rollout Plan</title>
		<link>http://www.spyparty.com/2012/02/17/the-near-term-early-access-beta-rollout-plan/</link>
		<comments>http://www.spyparty.com/2012/02/17/the-near-term-early-access-beta-rollout-plan/#comments</comments>
		<pubDate>Sat, 18 Feb 2012 04:12:00 +0000</pubDate>
		<dc:creator><![CDATA[checker]]></dc:creator>
				<category><![CDATA[beta]]></category>
		<category><![CDATA[metrics]]></category>
		<category><![CDATA[playtests]]></category>

		<guid isPermaLink="false">http://www.spyparty.com/?p=2105</guid>
		<description><![CDATA[I still do not have my $14.26 from PayPal.  But, I have fixed most of the bugs that Jonathan Blow—the first official SpyParty Early-Access Beta invitee—found, so I guess I&#8217;m not going to wait for PayPal1 and hope things are working now. There have been a lot of questions here on the blog, on Twitter, [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>I still do not have my <a title="The First Early-Access Beta Invite Is Away" href="http://www.spyparty.com/2012/02/16/the-first-early-access-beta-invite-is-away/">$14.26 from PayPal</a>. </p>
<p>But, I have fixed most of the bugs that <a href="http://the-witness.net/news/">Jonathan Blow</a>—the first official<strong> SpyParty</strong> <em><a title="Sign Up for the SpyParty Early-Access Beta!" href="http://www.spyparty.com/beta-sign-up/">Early-Access Beta</a></em> invitee—found, so I guess I&#8217;m not going to wait for PayPal<sup><a href="http://www.spyparty.com/2012/02/17/the-near-term-early-access-beta-rollout-plan/#footnote_0_2105" id="identifier_0_2105" class="footnote-link footnote-identifier-link" title="Give me my $14.26, PayPal!&nbsp; And yes, I am going to provide other payment provider options soon.">1</a></sup> and hope things are working now.</p>
<p><img class="alignright size-medium wp-image-2107" style="padding-left: 10px;" title="rollout" src="http://cdn.spyparty.com/wp-content/uploads/2012/02/rollout-300x199.jpg" alt="" width="300" height="199" />There have been a lot of questions here on the blog, on <a href="http://twitter.com/spyparty">Twitter</a>, and on <a href="http://facebook.com/SpyParty">Facebook</a> about how the beta rollout is going to work, and a few misunderstandings based on ambiguous statements I&#8217;ve made, so I figured I&#8217;d try to clear all that up in this post.</p>
<p>First, and most importantly, <strong>everybody who <a title="Sign Up for the SpyParty Early-Access Beta!" href="http://www.spyparty.com/beta-sign-up/">signed up</a> will get invited into the beta</strong>, it just might take a while.  Just getting to the point where I could send a single invitation email was way more work than I thought it would be, so it is going to take some time to get things to where I can invite in all 11,147 people who have currently signed up (as of this blog post).</p>
<p>I can, however, say how the very near future of the next few weeks is going to go&#8230;</p>
<a name="Near-Term"></a><h3>Near-Term</h3>
<p>Today, I need to finish writing up some basic documentation, including the Welcome Message in the private beta forums, the How to Report Bugs post in the forums, the Beta Test FAQ, and I need to update the game&#8217;s README.txt.  Hopefully I will grind these out in the next few hours.</p>
<p>Then, this evening, I&#8217;m going to send out a small number of invitations to my hardest-core and longest-term playtesters, folks like <a title="Know Your Enemy, Especially if He or She Has a Sniper Rifle" href="http://www.spyparty.com/2011/01/27/know-your-enemy-especially-if-he-or-she-has-a-sniper-rifle/">Paul and Ian</a>.  <strong></strong>This will be sent to only about <a href="https://twitter.com/#!/spyparty/status/170795131514011648">8 friends</a>, all of whom are game developers and who have played a lot of <strong>SpyParty</strong> already, and who are very comfortable playing with very early software.  This group&#8217;s job is to play a lot, to find more subtle bugs—especially if I&#8217;ve broken any tuning since I haven&#8217;t playtested since <a title="PAX West 2011 Report &amp; Pics" href="http://www.spyparty.com/2011/09/23/pax-west-2011-report-pics/">PAX West 2011</a>—and to make sure all the new score ranking and player database stuff works.  I&#8217;ll be fixing the bugs they report and implementing the features they request on a daily basis, in addition to playing a lot myself so I can stay even remotely competitive in my own game.</p>
<p>While these folks are testing, I&#8217;m going to finish the Invite-a-Friend feature, so they can test that as well, which will mix a few more people into the beta.</p>
<p>At this point it&#8217;s probably around March 1st, and I&#8217;m hoping the extremely-low-hanging bug/feature fruit will be mostly picked and everybody will be having a fun time playing and I&#8217;ll be ready to invite some more people in, including people I don&#8217;t know personally.  Unfortunately, this is also when <a href="http://gdconf.com/">GDC</a> hits, which means I lose a week of productivity, so I&#8217;m going to hold off on new invites until after that&#8217;s over.  I&#8217;ll be doing a little <strong>SpyParty</strong> press at GDC, but I&#8217;m going to keep it pretty mellow.  I also might set up the laptops at some parties again, not sure.</p>
<p>After GDC, or maybe just before GDC if I&#8217;m feeling really confident and things are going swimmingly, I&#8217;ll invite in a small group of the earliest and hardest-core fans from the invite list, the blog, Twitter, Facebook, GDC, and PAX, and a small hand-picked batch of press who&#8217;ve been covering the game closely since the beginning.</p>
<p>Then the real bulk invites will happen shortly after that.  I&#8217;ll start with 20 people from the beta sign up list, mostly in order from the beginning, but also probably randomizing 25% of them from the whole list so at least a few people won&#8217;t have to wait quite as long to get invited.  Then, once the dust settles from those 20, I&#8217;ll invite 50, and then 100, and so on.</p>
<a name="Less-Near-Term"></a><h3>Less-Near-Term</h3>
<p>At some point, my server will start melting from too many simultaneous players, but I don&#8217;t expect that to happen until I&#8217;ve got 1000 or more people invited in, so this first group should get in pretty quickly.  I will look at the bottlenecks and optimize the server, and that might get me to 2000 invited, or around 200 simultaneous players.<sup><a href="http://www.spyparty.com/2012/02/17/the-near-term-early-access-beta-rollout-plan/#footnote_1_2105" id="identifier_1_2105" class="footnote-link footnote-identifier-link" title="That is, if 10% is a reasonable estimate for peak concurrent users relative to registered users, which it seems to be.">2</a></sup>  By that point I want to have my server infrastructure scalable and running in the cloud, but that&#8217;s going to be a significant chunk of programming, so there might be a delay at this point in getting more invites out.</p>
<p>Finally, once the backend is scalable, I can start inviting larger groups of people in at the same time.  When I can send out 2000 invitations and everything runs smoothly as those people sign up, download the game, and start playing, it will be time to invite the rest of the beta list in, shut down the list, and let people join the beta directly without an invite. I think I&#8217;ll probably have a couple week period where I invite all the confirmed signed up people in, but don&#8217;t allow new signups, as a way of saying Thank You for signing up.</p>
<p>But, that&#8217;s all months and months away.  The Near-Term section above should be reasonably accurate, but this part is mostly me making stuff up right now.</p>
<hr/><ol class="footnotes"><li id="footnote_0_2105" class="footnote">Give me my $14.26, PayPal!  And yes, I am going to provide other payment provider options soon.</li><li id="footnote_1_2105" class="footnote">That is, if 10% is a reasonable estimate for <em>peak concurrent users</em> relative to <em>registered users</em>, which <a href="http://massively.joystiq.com/tag/peak-concurrent-users/">it seems to be</a>.</li></ol>]]></content:encoded>
			<wfw:commentRss>http://www.spyparty.com/2012/02/17/the-near-term-early-access-beta-rollout-plan/feed/</wfw:commentRss>
		<slash:comments>61</slash:comments>
		</item>
	</channel>
</rss>

<!-- Performance optimized by W3 Total Cache. Learn more: http://www.w3-edge.com/wordpress-plugins/

Object Caching 1015/1036 objects using apc
Content Delivery Network via Amazon Web Services: CloudFront: cdn.spyparty.com

 Served from: www.spyparty.com @ 2014-04-13 03:56:59 by W3 Total Cache -->