Friday, December 21, 2012

Just Tactics on OS X?

People keep asking if and when Just Tactics will be available for OS X. And it's a fair question. I figure folks deserve a straight answer.

When we started Just Tactics, we planned to support Mac OS X. We chose Java, with its cross-platform strength, for just that reason. But, things didn't quite come together as we had hoped.

First off, a Macintosh capable of running Just Tactics is a pricey machine, since the game needs a 1GB graphics card. The cheapest option available today is the $2,000 27-inch iMac. That's almost three times as much as my development workstation on Linux, and almost twice as much as one of our artists' Maya workstations running Windows 7. So we didn't buy one early on in the project.

My assumption was that, out of all the libraries and frameworks we were using, OpenGL would be the one to give us the least trouble. Especially if primary development was happening on Linux. And so we planned to buy one of those pricey iMacs when we got closer to our main quality assurance phase. I figured that we'd have plenty of time to fix the little bugs I expected in the periphery.

So we worked on the game for a year. I fiddled to get the rendering system doing identical stuff on Linux and Windows, with both ATI and nVidia cards. I wrote a bunch of GLSL shaders, and modified some of the stock JME shaders. All of that stuff worked fine in the office.

And then we tried Just Tactics on a Mac. Black screen. Didn't work.

The exact reasons why are arcane. But, the short version is this: on Linux and Windows, you can freely mix OpenGL 2.x and 3.x features. On OS X, you cannot. Due mainly to my own inexperience, Just Tactics is not cleanly OpenGL 2 or 3, but a mix.

Fixing Just Tactics so that it's properly OpenGL 3 requires some sophisticated modifications to the JME rendering system. It also requires that we translate a bunch of shader code. And this has to be done in such a way that it doesn't break Linux or Windows support, and in such a way that hundreds of special effects scripts don't have to also be rewritten. Then all of that has to go through quality assurance on three different operating systems. This is at minimum several months of work during which it's virtually impossible for me to maintain or improve the game experience for existing users.

So while it's possible to make Just Tactics work on OS X, we came to the conclusion that it wasn't the best use of our limited resources.

Out of all of the different computers Apple sells at the moment, only 4 iMacs and laptops have a graphics card that will run Just Tactics. It is true that the Mac Pros all have a good enough card, but I don't know anybody with a Mac Pro. None of the machines at the low end of the price range have an acceptable card, and those are the machines that people seem to buy.

For some less anecdotal evidence, have a look at the Steam Mac hardware survey. Only about 14% of Mac gamers have 1024MB or more of video RAM. Given that OS X only makes up only a tiny percentage of the gaming market (about 1% of Steam), and given that only about 14% of those Macs meet our minimum system requirements, it's pretty difficult for us to prioritize the several months of work it would take to bring Just Tactics to OS X.

It isn't 100% off the table. If there's enough demand, we're certainly willing to reconsider the issue. But before you start emailing us, keep in mind that fixing Just Tactics so that it runs on OS X isn't going to change the minimum system requirements. If you have an Apple laptop, and it isn't a top of the line MacBook Pro, OS X support isn't actually going to help you.

Tuesday, December 4, 2012


Yesterday and today we've been putting the finishing touches on our Live Beta release. Here are the highlights.
  • Initial price for a paid account set at $5 US. This will go up as we add more content.
  • Snazzy professional website.
  • Payment integration, so we can take your credit card. We're using Stripe, which makes it as effortless as possible to do safe and secure credit card transactions.
  • Added some initial sound effects. You'll notice movement sounds on a few of the units, as well as a few sounds on card effects.
  • All of the area of effect cards now have visual effects.
  • A base coat texture was added to all the dark units.
  • Several dark units have concept preview versions of the sweet style we're working on for the dark units. Check out the Atropos, Wheeler, and Keres.

Thursday, September 20, 2012

Giving Back

Our first game, Just Tactics, is built on a lot of open source technology. From our graphics engine (jMonkeyEngine 3) to our networking library (kryonet) to our chat client (Spark), Just Tactics would not have been possible without untold hacker-years of labor.

So, we've been sure to give back when we could. Our fork of the jme3 game engine will be available soon (although I'd recommend you go get the real thing instead of ours anyway).

But, for now we have three useful modules available on GitHub. All are available under the BSD license, meaning that you may use them in both closed- and open-source projects.



Alamode is a super-lightweight continuous update framework for Java applications. It requires no server-side infrastructure beyond a standard web server. It quickly scans indexed local files at startup and downloads those that differ from the server's version.

We use alamode to push out updates to the Just Tactics match and chat clients.



Simmons is a thin Java wrapper around native exec()/CreateProcess() functionality. The Java version of exec (Runtime#exec) requires that the parent process manually drain the stdout of the child process. If the parent JVM exits, then the child process will eventually stall out when its output buffer fills up. Simmons offers an exec() that truly separates the parent from the child.

We use simmons to chain between the alamode updater and the Just Tactics chat client. (Bonus nerd points if you catch the joke in the name.)



Sploosh is a pure Java implementation of game-grade fluid dynamics. It uses a vorticity model to compellingly simulate vortices and swirls in the fluid medium. Since the problem is embarrassingly data-parallel, it makes use of all available processor cores to update fluid state in real time. Seriously, you should get hundreds of frames per second with thousands of vortons.

You can see sploosh-based effects throughout Just Tactics, especially in explosions.

Wednesday, September 12, 2012

Customizing a Tinycore Linux Image

In preparing the backend network for our new turn-based strategy game, Just Tactics, we architected it to be able to easily scale up as the game becomes more popular. Step one was designing an automated netboot operating system environment, to allow us to bring up new servers without having to sit through an install procedure, and remove our dependence on mechanical storage devices.

As such a minimal Linux environment, Tinycore provides a great starting point for a low-overhead server, but some applications will inevitably need to be added to the base install. They distribute packages as Tinycore Extensions in compressed .tcz files, and provide some great methods to dynamically load them on boot or as needed. This works great for a desktop or virtual machine appliance, but for a constantly-running server, this provides an unnecessary level of complication and overhead.

Initial Attempt

Initially, we started using the recommended Dynamic Root Filesystem Remastering method. This method decompresses the extensions into a second initial ramdisk, which is loaded by one of the Syslinux bootloaders alongside the tinycore ramdisk. This is how it worked in our environment:
  • TFTP load and boot the tinycore ramdisk
  • mount an nfs share inside the tinycore ramdisk
  • load the secondary ramdisk from the NFS share
We were having problems with consistently mounting the NFS share inside the tinycore ramdisk, so we decided to simplify, and integrate the extensions directly into the primary image.

Final Config

We ended up using the method outlined in this guide to decompress the tinycore image and all relevant extensions, and rebuild it with the additional packages integrated. This way, everything is integrated into the root filesystem, just like it would be with a standard local install.

Download necessary extensions:

All tinycore extensions for the current release are listed here. I manually downloaded extensions for all necessary packages, including the dependencies listed in .dep files.

Unpack extensions:

After installing squashfs-tools, you can unzip each .tcz extension using the following command:
unsquashfs -f filename.tcz
The extensions will be unzipped into a filesystem tree in squashfs-root in the current directory, for example /tmp/squashfs-root

Unpack tinycore:

You will want to unpack tinycore into it's own directory, for example /tmp/tinycore. Unpack the tinycore ramdisk using this command:
sudo zcat tinycore.gz | cpio -i -H newc -d

Merge everything:

To integrate all of the extensions into the base install, you simply copy the uncompressed files into the right place. This will give you one filesystem tree with tinycore files and extensions in the same places. Sudo is necessary to preserve permissions in some of the system files.
sudo cp -Rp /tmp/squashfs-root/* /tmp/tinycore

Recompressing the Tinycore Image:

I use the following commands to update the dynamic linker and build the image. The advdef command  is optional - it simply compresses the image a little more.
sudo ldconfig -r /tmp/tinycore
sudo find | sudo cpio -o -H newc | gzip -2 > /tmp/tinycore.gz
cd /tmp && advdef -z4 tinycore.gz

Additional References:
Integrating Extensions into an ISO

Thursday, August 23, 2012

Building a Diskless Tinycore Linux Cluster

A major part of the network for our new turn-based strategy game, Just Tactics, is the pool of game servers. As the user base grows, this will have to scale up first. In designing our network, one important consideration was the ability to add new machines to the pool of game servers as easily as possible. To assist with this, they are all booting the same shared linux image over the internal network. With no configuration needed on the new server, it will look for a DHCP server, which will supply network configuration, and information on where to look for files necessary to boot. It will then download and boot a minimal operating system, and start the game server.

This document is not a complete step-by-step, and assumes some basic knowledge of the linux command line.



One major goal is to make it as easy as possible to add additional game servers as our user base grows. By booting a blank machine directly into the OS over the network, we can bring new machines online without having a per-machine installation process.


As the number of installed machines grows, time spent administering them becomes more important. All game servers are booting from the same linux install. Because of this, changes only need to be done in one place for all machines, which can then be rebooted into the new image as soon as the time is appropriate.

Since we are booting the same linux install on all machines, some things must be configured at boot time. We statically assign the public interface's IP address from the internal DHCP server, and set the hostname on each boot.


Since we boot a read-only ramdisk install, any filesystem changes made to the running machines are lost when the machine is rebooted. Because of this, in the event of a hacker gaining access to the machine, it will be easily restored to the previous state by a reboot.


To keep overhead from the operating system minimal, we are using Tinycore Linux, an 8MB linux installation based on busybox. It is very minimal, but is appropriate for a single-purpose server such as these game servers.

Ease of Updates:

To let us quickly update the game server software, it can not be part of the base operating system install. We start the server software from an NFS share, which will allow us to update the software quickly, without rebooting the base machine.

The Basic Steps:

  • The bios must be set to boot from network, or "PXE Boot"
  • The bios broadcasts for a DHCP server
  • DHCP will provide IP information, and the location of the PXELinux bootloader
  • The bios downloads the PXELinux bootloader via TFTP
  • PXELinux downloads its configuration from the TFTP server
  • PXELinux downloads the specified kernel and Ramdisk from the TFTP server
  • PXELinux mounts the ramdisk, and boots the downloaded kernel
  • The Tinycore install loads an NFS share, and runs the game server software from the share


Server Software Needed:

The server hosting the files for this netboot setup is running Ubuntu 11.10 server. We needed to install the following packages:


rsyslog is also necessary, but it's installed by default

Client Software Needed:

To minimize operating system overhead, we decided to use tinycore linux to boot the game servers, and run the server software from an NFS share. We needed to add a few packages to the 8MB image, which is documented here.

To help with large memory machines, tinycore has a 64 bit kernel available to be used with the 32 bit binaries.

The 64 bit kernel and ramdisk, are core64.gz and vmlinuz64. For the current release of tinycore, it is located here:


DHCP Server

These config options needed to be added to the global section of /etc/dhcp/dhcpd.conf to enable PXE booting. The filename command gives the location of the pxelinux.0 bootloader relative to the TFTP server's path

allow booting;
allow bootp;
filename "/pxelinux.0";

To allow us to physically locate game servers, IPs must be assigned by MAC address. To avoid running two instances of dhcpd, we are statically assigning the secondary external IP addresses via dhcpd.

we need to define a custom dhcp option in the global section to pass the second IP to the client. This statement creates an option called second-ip of type ip-address:

option second-ip code 187 = ip-address;
this is the host stanza that defines the host name, and both IP addresses based on the MAC address of the second interface (eth1)
host server1 {
                hardware ethernet ab:cd:ef:12:34:56;
                option host-name "";
                option second-ip;

DHCP Client

To assign the secondary IP and default route, we needed to use the ISC DHCP client's exit-hooks feature

the second-ip option must be defined in /etc/dhclient.conf, and the option name needs to be added to the 'request' statement

option second-ip code 187 = ip-address;

request subnet-mask, broadcast-address, time-offset, routers,
        domain-name, domain-name-servers, host-name, second-ip;

/usr/local/etc/dhclient-exit-hooks is also used, to configure a few machine-specific commands. This is where the external IP is assigned

#configure second IP address
ifconfig eth0 ${new_second_ip} netmask broadcast up
#change the default route to the external network
/sbin/ip route del default via dev eth1
/sbin/ip route add via dev eth0
#update the Nagios NRPE client to only listen on the internal interface, and start the daemon
sed -i "s/server_address=.*/server_address=${new_ip_address}/g" /usr/local/etc/nagios/nrpe.cfg
#set the hostname based on the one received from DHCP
/usr/bin/sethostname ${new_host_name}
/usr/local/sbin/nrpe -c /usr/local/etc/nagios/nrpe.cfg -d &

TFTP Server

the tftp server must be installed and running. In our case, we updated it to serve files out of /tftpboot, but the default config should work fine. Any files served must be world-readable

PXELinux Config

The PXELinux bootloader's default config is located  at /tftpboot/pxelinux.cfg/default. Similar to a Grub config file, you specify paths for the kernel, initial ramdisk, and any arguments to pass to the kernel.

All paths are relative to the tftp server's location. For example: /file.txt will have a full path of /tftpboot/file.txt

#this specifies which kernel is the default
LABEL linux
#this specifies the relative path of the kernel
KERNEL /vmlinuz64
#this specifies the relative path of the initial ramdisk
INITRD /core.hts.gz
#kernel arguments are specified here
APPEND base nodhcp cron

the PXELinux config can get significantly more advanced - this is a very straightforward example

Log Management

rsyslog is used to collect logs from the PXE booted servers, since they have no local storage. It is installed by default on ubuntu, but needs modification to enable it to listen for other machines.

in /etc/rsyslog.conf, these lines must be uncommented to enable listening on TCP and UDP port 514:

# provides UDP syslog reception
$ModLoad imudp
$UDPServerRun 514

# provides TCP syslog reception
$ModLoad imtcp
$InputTCPServerRun 514

We added the following config to /etc/rsyslog.conf to sort logs into different files, based on the hostname of the sending machine:

$template DynaFile,"/var/log/remote/%HOSTNAME%.log"
*.* -?DynaFile

the game server software running on the server1 machines uses the logger command to send errors to the syslog host using the local0 facility. rsyslog will then separate out these messages into their own files, to make sure they get noticed.

if $syslogfacility-text == 'local0' and $hostname == 'server1' and $programname == 'master' then /var/log/remote/server1-err.log

if $syslogfacility-text == 'local0' and $hostname == 'server2' and $programname == 'master' then /var/log/remote/server2-err.log

if $syslogfacility-text == 'local0' and $hostname == 'server3' and $programname == 'master' then /var/log/remote/server3-err.log

Tuesday, August 21, 2012