Before the fall,
as camps swell,
as walls rise,
as wars rage,
Will we fight?
against bombs, bullets, & batons?
when all that's left are bricks & bones?
Will we still yield?
our rights?
our principles?
our beating hearts?
Working around this with the design of Allopoeia and my homesite would have been very annoying, as I supplied configuration defaults within layout metadata for pages, to say nothing of dealing with Jekyll again. So, instead, I started looking around for an alternative system. Surely, by now, there would be a site generator without crippled templating!
After struggling with Hugo and its atrocious templating via Go's text/template package, I gave up, and decided it would be far more worthwhile to make my own system.
The result is Pickle (the same name I came up with years ago), which took just under two weeks of focus before it was functional enough to start refactoring my websites. It uses togo and the tiny mmx web server library for the internals, but the majority of it is written in Lua.
The distribution of language sits at 1,216loc of Lua & 711loc of C++, just under 2,000loc total. That's rather slim for a site generator, I'd imagine. Of course, this doesn't say how much is saved by using external libraries, but it's no different from the situation with Jekyll and Hugo, which make extensive use of their language ecosystems.
togo had yet to see Lua integration, and was lacking on the filesystem interface, so significant upstart work was needed alongside Pickle, accounting for the span of time before Pickle was functional. In fact, more code was touched in togo: 2,559loc across 58 files.
Some of the discovery work for Lua integration was already done with Quanta (forthcoming), but was rudimentary. Taking the time to do it right within togo has improved the situation greatly.
The templating in Pickle is straight-up Lua, the syntax of which is inspired in part by lua-resty-template, which itself is similar to Mustache and is inspired by yet others, I'm sure. The parser is a meager 221loc of C++, and the whole Lua interface is only 185loc.
Lovely, but not the greatest implementation. Layering like I was with Jekyll isn't really possible. Ironically, I didn't even design my system with the very feature that I left Jekyll for removing.
If you have a metatable on a Lua chunk redirecting top-level indexes to the provided template context, you only ever see the first branch. From x.y.z
, you supply context[x]
and never hear if the terminating index resulted in a nil
. This is fine, that's just how Lua works.
I imagined using a proxy all the way until it terminated could be possible, but there are immediate red flags at the suggestion. There's no metamethod for "evaluate", so there'd be no way to tell when it terminates. At the last access, the call site would just receive the proxy, leaving no option to try alternative paths from the root.
The other obvious route is rewriting the indexes during template transformation, but that frankly sounds like too much hassle & too much complexity for a feature that isn't really necessary.
Instead, in refactoring my websites, I just split out layouts that were layering and supplied the defaults directly in the content constructors during filtering. This makes much more sense, and centralizes the properties of a page. This was not even possible in vanilla Jekyll (as far as I'm aware), so I still have the upper hand despite lacking its 2.0-style layering.
There are other benefits, as well. With Jekyll, I had to create "includes" for basic often-used functionality: in essence, plain old functions. This was an absolute mess. With Pickle, I can write all of that in pure Lua, if I wanted to, though the unbridled power of Lua right in templates means I don't have to.
Liquid, Jekyll's template language, was simply too restricted to be useful. It results in verbose and ugly code.
Once the disgust for Jekyll was out of the way, I finally did some much-needed maintenance on my websites. My homesite is now more cohesive, and Allopoeia is less janky. Overall, I only shaved off maybe 200-300 lines from the combined changes to the websites, but the quality is greater.
Pickle takes a different approach from most static site generators I've seen. It gives you the basic functionality, the fundamentals, but makes the user build the scaffolding. Stuff that would be considered core functionality in other systems is nowhere to be seen. There is no concept of a "layout" or even a "page". It has filters, which produce outputs, and that's it. In this way, it's not really a "site generator" in the true, it's just a complicated data transformation system that happens to have a web server attached.
I've already thought of other uses for it outside of websites. I often write emails in Markdown on local storage, with an informal metadata syntax that lives only in my head. I usually don't send emails in HTML, but it could be useful for transformation of other properties (and statistics & analysis, which I love).
Back to websites, though, this means you have to write some code upfront, specific to the structure of your site. Mine is a meager core at 307loc, shared between my homesite and Allopoeia. Not much baggage, and I don't have to wade around the mire of instituted structure by the generator itself. If something isn't working for the website, I can change the structure without disrupting anything else that uses Pickle.
All in all, a good adventure, not just for my websites, but for togo as well. Pickle has some sore spots left to mend, but it is a much welcome reprise over the previous state of affairs.
]]>I can't make this game, and, technically, I haven't been making a game. The vast majority of these three weeks has been spent wrestling with the tool pipeline. That's not what I should be doing. It's not even what will make the game what I want it to be. That's the biggest realization I've come to: I can't make this game the way I want it to be.
Only three weeks? That's a really fast burn rate. Truth is, I took a silly thought and turned it into something it didn't want to be. I'm largely making the same mistake I made with Hord & Onsang — and so soon! It's a painful realization, but one I should have had from the beginning. I let myself run along without stopping to consider the feasibility of it.
Tentuk won't work as a small game. I'm shoe-horning a perfect circle into a square slot orders of magnitude smaller.
Here's the week up to this bit of rational thinking:
The last one is where I realized all I was doing was fighting the pipeline. The rotation caused the scene to take two times the space along the Y axis, meaning the ground had to be twice as long along Y, meaning I had to unscale the axis, meaning the complexity rose, in turn causing renders to be unaligned in-game, causing me to be unhappy, causing me to actually start thinking, causing this.
So yeah, a fast burn rate, if you want to call it that. I'm happy, though. There was something unsettling about Tentuk, and this has basically been it. I was almost half-way into the 8 weeks I somehow decided was reasonable (and what the hell was that!?), and all I had been doing was fighting.
I would be far better off to use 3D and apply some shaders to get the look I wanted. It's 2015! I don't need to bake 3D to 2D. Obviously, this means I wouldn't be able to continue using LÖVE. There wouldn't be much loss, because there was hardly anything there in the first place, but that is not something I should have to do when I decide a game is not realizable under the systems I'm using. I should already be using systems that allow me to make that decision and continue using what I've already built.
If I were making this with togo (were it reasonably complete), all of this would be "oh, okay, this is not going to work, let's load the whole scene in from the source instead", and off I would be rolling.
I am reverting to the previous, actually well-thought-out plan: continue working on togo and start designing a game. That word, designing, is very important. In design I am much more flexible to change & explore; there are no systems I am working under that prohibit my choices, there are only the possible. The stage after/during that is where I decide how feasible the design is under the systems I want to use. Design also prevents me from falling into a project I don't actually want or know how to make. Rational thought is more easily found to me when I am not busied by tooling, which I actually like, apparently. I like making systems, but systems are not fruitful unless they are applied, and they inevitably increase the complexity (however little). I can't keep applying systems. At some point I have to put down the arsenal and actually make something tangible.
Then (on Wednesday, this was only Wednesday!), I actually played some games. First, Lost Constellation, followed by lisa. I realized there's so much in both of them that I loved; so much that I wasn't even thinking about with Tentuk. (I mean, let's be honest, I wasn't even thinking.) Lost Constellation is brilliant. They call it a “supplemental” to Night In The Woods, but it felt like a whole, complete game. I loved it, and I didn't even get to see all of the content! And lisa, wow. It almost feels wrong that we should see & play this game rubna made for their friend. It's a cute little game, with charm beyond what should be reasonably possible. I sat basking in it for a while.
For all I can see, lisa is full-3D, with some clever post-processing for a beautiful pixelated look, which surprisingly has a lot of character. I've no idea how the detail is pulled off, but it's proof to me that my footing was completely wrong. I would never be able to pull off its liveliness by baking 3D to 2D.
I realized these games do things I love; things I want to be able to do. The witty, grasping writing in Lost Constellations, and the mechanics & style & charm of lisa. I'm not saying I'd make a cross between these games if I could (and all signs point to a different kind of inner style). They inspire me, and make me want to do better.
I feel ridiculous. I'm disappointed in myself. I'm sorry, me. I can do better than this.
]]>I spent 62.3 hours working on the game this week. That's 10.2 hours greater than last week, with the longest uninterrupted sequence running 4 hours and 21 minutes on February 13 (yowzers).
Here's a quick look at the progress:
I didn't get to play around with audio. There wasn't enough time with the other goals in the way. I did most of what I wanted to do, but I don't think I spent enough time feeling out the process of making scenes and props. I came to ideas about how it should work from the bit of work on the test scene and extrapolated it from there, which might end up being a detriment. In any case, the Blender tooling is certainly more flexible now. The last major tooling notches to fill in are packing textures over multiple atlases (to avoid massive texture sizes) and entity animation rendering. I have a grave suspicion that the latter one will be very bothersome due to the way Blender handles poses, and it's reasonable to assume upgrading the texture packer will probably give me a decent bundle of trouble.
I'm still unhappy with the lighting, and I want to rework the camera controller to support arbitrary global lights instead of the fixed ones in the scene config. That way all of the global illumination can be directed from the tooling. I could just change the properties of the existing lights, but then their names don't make any sense, and it'd be a jumbley mess debugging it in post. I'm likely going to create light objects instead.
I've already come to dislike rendered 3D. I had a lot of trouble trying to get rendered entity frames to line up properly (because I wasn't thinking, compounded by separation of shadows and geometry), and I nearly threw a fit when I thought LÖVE wouldn't let me render from textures with arbitrarily-oriented texture coordinates. Indeed, it turns out it's not possible with Quad, but, thankfully, Mesh saved the day. I have as much control as I need with Mesh, and I can get into optimization by packing all render data for an entity into a single mesh. Fancy.
Here's what scene rendering looks like in-game (with debug mode enabled):
It's not very striking, but a lot of work went into it behind the scene. The frames drawn around the props are based on world space from the Blender data, which doesn't include the shadow. The frame on the "test" prop is incorrect because it's not calculating from the origin. It's a simple fix, and it was initially correct because it was using the pack data, but now all of that is bundled up and discarded at runtime after building meshes.
Using the same axes in Blender as in game space has proved very worthwhile, even when Blender's Y-up is in the opposite direction. Camera manipulation is a bit funky, but it's definitely better than trying to mentally juggle Blender Z -> game Y and so on.
Getting the perspective just right is going to be tricky. If I could make renders and shadows work nicely under rotation, I'd like to just rotate the whole scene a tiny bit along the X axis. It'd make the perspective cohesive instead of disparate (i.e., without rotation, movement and the background (will) suggest terrain depth, but the entities are always front-facing to the screen). These two issues are linked, since entity rendering needs to take into account casting shadows on other entities (which are masked out) and framing invisible-but-not backdrops for casting realistic(-ish) shadows on a terrain that isn't really there.
3D-to-2D is woe.
On the bright side, the asset system is pretty slick. I can reload the manifest at runtime, which automatically reloads existing assets. The build tools automatically rebuild the manifest, so it's only two steps to reloading any changes. Since scenes are assets, and due to the way the scene instance is stored, if the scene is modified, it will reload the definition and (as of soon) keep any runtime state.
The repository is sitting at 150 commits right now. That's actually surprising to me. I didn't think I was committing that much, and it seems like a very rapid growth in comparison with my other projects. There're huge deluges of commits on the game code, though, which is probably running at a faster pace.
I don't actually plan on working Sunday, nor most of Saturday, but these first two weeks have essentially lead to it. Everything I intended to do is done, but it took until the last hours of Sunday to pull it all off and leave it in a functional state. It takes a lot to put in 8-10 hours every day, and pushing all the way to the end of the week without a break just feeds into repeating the cycle the following week.
I've found that the weekend is naturally less efficient for me, so it's actually wasteful to spend that time working, even if it's to meet a deadline or complete a task list. It should be more beneficial to break during that time, and come back the next week with a refreshed psyche.
Another issue I've noticed is that I will go down rabbit trails or completely avoid rational thinking about a problem until I've hit a sufficiently large wall when trying to make it work the way I want. So, for the weeks that follow, I'm going to start by making the task list and conceptualize/explore all of the tasks before I start working. And when I come up against a sizable problem, I will leave the computer and work it out on paper. When I force myself to consider the problem without fumbling around in the code to make it do what I want, I oftentimes come to better solutions. It is likely that I could have avoided a lot of refactoring time spent on tooling this week if I had preconceived that props & actors would share all of their properties and would be easier to handle as a shared construct.
Live and learn.
This week's timelapse includes data from February 6-8 of last week. Unfortunately, I don't think YouTube kept the original video size, so it looks worse than the actual frames.
I've settled on recording at 15s/frame, and the video is running at 10 frames/s (i.e., 2.5 min/s). I think it runs a bit fast, but it'd probably be unreasonably long otherwise. I might consider overdubbing later on at a lower render rate, but who knows what'll happen. I can only do so much meta work.
That's it. That's everything. Now I must sleep. I will definitely have a skewed schedule as a result of ending so late.
]]>The idea for Tentuk came in January only as “hey plash, hey plash, how about a game where all the sound is human voice?”. I blame Kasamatsu Kouji, my vocal folds, and also my brain. I dropped the qualification that all audio must be voice-derived, because that'd take a lot of work that I don't want to spend on a “small” project (oh, who am I kidding…). The idea mingled a bit with ixili (Nahuatl for "to pierce"), a short-lived project I was co-opted to and spearheaded during the summer of 2013, which held Mesoamerican inspiration. Tentuk takes no immediate real-world cultural inspiration except some Middle Eastern clothing and architectural styles. I don't know how strongly that will come out in the end, and I expect a ton more to seep in that will make no sense.
My vocal experiments of late have leaned towards creatures — experiments I've wanted to play with and manipulate, but with my pre-occupation on Onsang, I haven't had the time. I should be able to manage a rich soundscape through that and my escapades in SunVox. This will be the first time I've used my voice for a project, and I hope to have voiced dialogue, but I won't commit to that unless I can manipulate my voice to convincingly sound like different people of any gender. I suspect this isn't possible, so I will likely apply spoken language only sparsely. It's possible I'll be able to get away with it through some mechanical means, like the characters of the world not having identifiable genders, and using non-human forms of speech… or something. I dunno, it's all very theoretical at this point.
I'm trying to avoid direct real-world appropriation, so the language(s) in the game will be constructed. I don't want to talk too much about the story until I've laid the blueprints, but the player character will initially not be able to understand the language of the first people they meet. Analytic players, on the other hand…
This first week (starting February 2), I explored technical possibilities and made some decisions on the game world. Immediately thereafter, I got swept up in Blender, exploring the feasibility of using it for all of the props and scene layouts. I think I'm firmly in that seat now, whether it turns out to be a detriment or not. The complete script-ability of Blender gives it a lot of potential, and I think I've made an interesting decision, nevertheless.
The majority of this time was spent coding the toolchain, working out the scene configuration, and figuring out how to get what I wanted with the projection. It took a lot of tweaking and prodding to get passable shadows and clean, pixel-perfect axonometric lines. I was undecided on the projection until today: front-facing with Y as depth. This makes the shadowing a little trickier, but makes simple model texturing and shading a lot easier. I have yet to white-box a test scene, so I'm still uncertain whether a slight horizontal rotation (10° or so) is desirable.
To get an idea of what the projection will look like and feel like mechanically, see Superbrothers: Sword & Sworcery EP. I guess this sort-of tagged along with ixili, which was straight-up copying the art style of S&S. I don't want to do that with Tentuk, and I learned with ixili that pixel-accurate animation in 3D is really tricky. My initial desire is to use few colors and few shapes for the art style, as that will hopefully be within my poor artistic capacities. Pixel art is probably not a reasonable style for my desired timeline, and it will be easier on my psyche to go rough, even if I end up using a pixel art edge. Scene backdrops will be painted, with background props baked in. The majority of props should be fully capable 3D models so that they can be used in different orientations. It's more work individually, but it'll hopefully pay off through reuse.
As of writing, the toolchain has mocked-up backdrop baking and individual prop rendering, with separation of the backdrop shadow and prop-to-prop shadowing baked in with the prop (I could make better sense of this if I weren't so tired). I've not worked with any scene backdrops yet. That's on the agenda for next week, along with packing prop imagery and rendering a scene in-game. If all goes well, I will have a test scene up and running by the end of the week. If there is time, and I suspect there won't be, I'm going to start playing around with audio.
Starting next week, I will post a dev timelapse with the weekly journal. I had this idea only a few days ago, so there's barely any recorded data, which is why I'm holding off until next week. If I manage to finish the game in 7 weeks (the target deadline, ugh), the recorded data should run about 100,032 frames and weigh around 20GB (half of my initial estimate, woo!), projecting from 52.1 hours this week (yes, very scientific).
It turns out game development is really hard, and really fun. Ever done a gamejam? Perhaps Ludum Dare? This week was like Ludum Dare for me, but a whole 5.3 days long. Just so you understand the gravity of that, I was awake for 37 hours last time. I developed a daily schedule during January, and it has proven very difficult to hold this week. I completely lose track of time and am detrimentally affected by deviation from the schedule — which is meant to keep my diet rigid, exercise consistent, and rhythm flowing.
It proved to be very beneficial during January, when I was working on Hord & Onsang. I think it's because that work always felt like drudgery, even when it was interesting or challenging. I was always aware of time because my mind wasn't engaged or focused. It's so bizarre to see the shift in time perception while my efficiency seems unchanged. I'm still unsure if it's because I'm excited to be trekking new ground, or if it's because I've just been really slow. Maybe I'll have a better idea when I'm not so darn tired.
]]>… Okay, I have some words.
First, an overview. My software repackaging contract finally ended on May 31, after 3 years and 5 months. I sacked murk and replaced it with Cacophony. I significantly advanced the whole of Beard (most notably, focus handling). I moved AM‽'s linear operators out-of-class and restructured the hash interface. I went through two command schemes in Hord, developed an initial table storage model, and reworked the fundamental object structure. I broke Clang at least three times and found an error in libc++. I started togo on June 22. I playtested the Linux port of Antichamber and reported two bugs in the Linux version of Among the Sleep.
I lived with my brother in Berkeley, California from August 2 to October 31 (90 days). While there, I made Onomo for Ludum Dare 30, had a battery fully discharge into my arm over 24 hours, attended GameDevDrinkUp in San Francisco (twice), attended QGCon, and failed to acquire employment. I observed that several factors, especially environment and social space, are dwarfed by discipline and motivation when it comes to doing. My discipline and motivation were largely not positively affected by the (little) improvement of these factors, and my continued lack of them was as ruinous as ever. There is undoubtedly a strength in harmony between environmental & contextual factors and discipline & motivation, and I've found that the positive presence of one side does not necessarily bring about the same to the other.
Back in Ohio, nothing of much note occurred beyond my attendance to the Texts and Contexts Conference in Columbus. Oh, and a copious display of privilege during the holidays. We are afforded too much.
That may seem like a lot, but it still doesn't feel like it, even when I write it all out. I was distraught between the high points at best, and rarely determined enough to do anything consistently.
As for the interests jotted out in my post for 2013, none have been thoroughly advanced. I've done very little with SuperCollider, and I didn't learn any music theory. I forget the exact causes, but it's most likely from the lack of motivation and, in the latter case, because I had just begun developing togo.
I feel most of the year was wasted, despite having spent the largest amount of time on projects since 2011. My greatest difficulty is applying what I know about… well, everything. Design and execution, primarily. Premature optimization, eternal black holes of design, unbridled habits. I know these mechanics on a deep level, but I don't utilize the knowledge to prevent them. At least I now know (quantitatively and practically) where the wasteful hot spots are in the gamut of my behaviors.
My immediate goals for 2015 are: 1. eradicating waste, 2. improving discipline (especially as motivation has proven unreliable), and 3. developing a game. I will construct a weekly schedule and test variations as I push Onsang to the point of reasonable usability. By either that point or the end of January, I will allot time towards togo and a game.
With all of this in hand, the only deciding factor in my success is willpower. Given my abusive, hyper-exhaustive approach to design, the best motto for me is Just Do It™.
So I will.
I tracked a total of 8753.3 hours in 2014 with 16177 entries (1827 greater than in 2013). 6.7 hours were (probably) gobbled up by micro discontinuities and weird software. On a bright note, that's only half the time lost during 2013!
One speculative lesson in my 3 years, 9 months, and 28 days of time tracking is that the mere amount of time you sleep has no effect on productivity (provided it is at least healthy). Proper segmenting does, for sure, but there are tougher nuts to crack.
Here're some numbers:1
Existence (selection of ~3797.1 hours):
2816.7 hours: sleeping. Segments (422, trimmed): mean: 6.60, min: 1.67, median: 6.91, max: 12.48. Monthly: mean: 234.72, min: 209.30 (April, surprisingly), median: 235.25 (November, July), max: 278.20 (January). Compare to: 2831.9 (+15.2) in 2013 (mean: 235.99 (+1.27), min: 213.50 (+4.2, February), median: 236.75 (+1.5, July, September), max: 251.60 (–26.6, December)), and 2782.5 (–34.2) in 2012.
~395.6 hours: eating. Segments: primary: 265.9 (1: 136.4, 2: 108.0, 3: 21.5); auxiliary: 25.6 (1: 22.8, 2: 2.8); misc: 86.6 (munching (including unsegmented): 39.8, snack: 28.4, liquid: 18.4). Compare to: ~370.0 (–25.6) in 2013.
204.8 hours: preparing food. Compare to: 253.6 (+48.8) in 2013.
78.0 hours: cooking. Compare to: 19.1 (–58.9) in 2013.
Study, projects, and work (1354.6 hours):
1106.5 hours: projects (~1287 public commits). Segments: programming: 1034.1 (tagged: 1045.0, inefficient: 65.8, severely inefficient: 20.1), design: 65.9 (inefficient: 3.0), and misc: 6.6. Compare to: 876.6 (–229.9, 616 public commits) in 2013, 823.1 (–283.4) in 2012, and ~1149.5 (+43.0; mixed segments, inaccurate) in 2011.
343.5 hours: working (4m17d span). Compare to: 591.6 (+248.1) in 2013, 239.4 (–104.1) in 2012, and 140.9 (–202.6) in 2011.
81.8 hours: writing. Compare to: ~121.3 (+39.5) in 2013.
74.2 hours: learning (non-directed, mostly media and filler). Compare to: 304.9 of coursework in 2013.
21.9 hours: maintaining/building websites (inefficient: 2.9).
21.7 hours: playtesting/QA. Compare to: 48.4 (+26.7) in 2013.
13.6 hours: drawing. Compare to: ~19.6 (+6.0) in 2013.
12.8 hours: voice acting.
8.8 hours: sound crafting. Compare to: 0.8 (–8.0) in 2013.
Entertainment (901.6 hours):
470.4 hours: watching media; anime: 257.9, talks & miscellaneous: 121.7, western television: 41.4, films: 29.4, web series: ~20.0. Compare anime/television to: 224.3/43.9 (–33.6/+2.5) in 2013, and 255.8/40.0 (–2.1/–1.4) in 2012.
214.3 hours: gaming. Compare to: 189.1 (–25.2) in 2013 and 537.3 (+323.0) in 2012.
151.8 hours: reading. Categories: misc: 96.8, books & writings: 26.8, comics: 16.1, manga: 12.1.
65.1 hours: listening to podcasts. Compare to: 81.7 (+16.6) in 2013 and ~244.7 (+179.6) in 2012.
Miscellaneous:
1078.0 hours: netloop. Tags: twitter: 643.4, reddit: 378.0, feedread: 11.9. Compare to: 658.9/208.9/425.2/91.7 (–419.1/–434.5/+47.2/+79.8) in 2013.
973.8 hours: bork (i.e., nothing in particular). Compare to: 1313.5 (+339.7) in 2013.
918.8 hours: tagged IRC (overlap). Compare to: 941.4 (+22.6) in 2013.
59.7 hours: in Windows, 50.5 of which were for gaming. This doesn't include work, which was almost entirely under Windows in 2014. Compare to: 143.4/102.3 (+83.7/+51.8) in 2013.
33015 scrobbles. Compare to: 36350 (+3335) in 2013, 23534 (–9481) in 2012, 25030 (–7985) in 2011, and 31303 (–1712) in 2010.
Top five albums (8540, 25.86%): FEZ (3327, 10.07%), one (1758, 5.32%), Ghost in the Shell: ARISE (1653, 5.00%), Assassin's Creed: Revelations (912, 2.76%), and Tamako Market (890, 2.69%).
Top five artists (10282, 31.14%): Disasterpeace (3375, 10.22%), Jesper Kyd (1836, 5.56%), C418 (1781, 5.39%), Cornelius (1653, 5.00%), Balún (1637, 4.95%).
Buys:
Games: 32, only 15 of which I have played. Compare to: 35.5/16 (+3.5/+1) in 2013.
Game bundles: 5. Humble: Amnesia Fortnight 2014, Sid Meier, Indie #11, RPG Maker (weekly), Eye Candy (weekly). Compare to: 8 (+3) in 2013.
Kickstarters backed: 5. Categories: animation (1), art (1), comic (1), game (1), podcast (1). Compare to 17 (+12) in 2013.
Music: 22 albums, 1 EP. Compare to: 5 albums (–17), 1 EP, and 1 single in 2013.
Books: 0. Compare to: 7 in 2013.
Hardware: wired X360 controller, Sager NP8278-S laptop.
Lifewear: 5.11 Rush 24 backpack, SE Bikes Draft Lite bike.
Etc.: pulse oximeter, new model of digital thermometer, long socks, hair brush, hair ties.
I made a… thing in 48 hours for Ludum Dare 30. This thing, named Onomo, resembles a game, but lacks a win state and any effective mechanics.
During development I recorded a time-lapse that will hopefully be illustrative to the folk that want to see how jammers operate. There are certainly some hours missing as I forgot to start my script several times, but it's mostly all there.
Herein I go through the timeline of this jam from my perspective and talk about the development of Onomo.
I forgot Ludum Dare was happening this August and was only reminded of it by my brother before I flew to California, where I have now been living for a hair over a month.
My plan was to furnish togo with enough capability to be usable. This did not happen. I had a pre-existing deficit of willpower and inspiration that prevented progress. Despite this, I managed to fold 69.3 hours of staring into the engine over the two weeks before Ludum Dare, although productivity and system relevance only returned during the second week (considering time alone: 45.1 hours, 65% of the time spent working on the engine over those two weeks).
Realizing that was futile, I shifted to renovating my project kernel for LÖVE during the week of Ludum Dare. These 27.5 hours were spent on 71 commits:
The previous (and first) time I used LÖVE (and my kernel) was during Ludum Dare 26, which resulted in Prisma. LÖVE already proved to be valuable and easy to distribute for the desktop trio of OSes. Automatic module reloading made it extremely powerful and eliminated iteration delay. I don't want to work without automatic code reloading now.
Even if togo were usable, I would've been in porting hell or never even bothered to port (which would've been bad for publicity and unfortunate for fellow Ludum Darians not yet initiated into the dark cult of Linux). This was apparent to me at the time, but I 'spose it just wasn't enough motivation to switch to the kernel immediately. The week before Ludum Dare would've been better spent intimating with my media tools.
LÖVE continues to be a good choice for game jams, and I suspect I will use it for many future jams until togo topples it.
I had inceptions for three themes during the final round: Connected Worlds (the highest voted among all previous rounds), Lost in Space (my personal favorite), and Isolation.
Connected Worlds predictably won, and I ran to catch the 18:07 Millbrae-bound BART train. I worked out the platforming system and explored some mechanics in transit.
I met my brother in the city and we headed off to a meetup hosted by Apportable. There were few groups at the meetup, and we only stayed for 3 hours. In retrospect, as my motivation for visiting was primarily networking, it would've been better to stop by after the Compo had finished (the meetup included the Compo and the Jam, so there would've been 24 hours remaining once I completed my entry). Regardless, I don't work well on solo things around other people or in unfamiliar environments, so it was probably best that I didn't stay for any significant period of time during development.
Once we scuffled home through the crowded subways, I spent a decent while complaining that Lost in Space wasn't the theme. This involved making spacey whale-like sounds in SunVox, shifting through different degrees of slack-jawedness, and possibly some annoyed moaning. (I was very attached to my SPACE SEA idea.) I then (somewhat unintentionally) slept for 7.3 hours. This was the only time I slept throughout the Compo.
After waking, with truly stalwart discipline, I cast aside my attachment to Lost in Space and charged forward into the dark, ominous lands of Onomo.
The premise of Onomo came verbatim from the inception: two parallel universes (in representation), with otherworldly characters that the player can switch between. The characters were the first to be sketched, and the final top character is the closest to the original drawing; only the arms were removed. The original bottom character had four legs and was more like a mouth with appendages rather than a beetle-like thing (the final design).
Preliminary mechanics were very unoriginal: move objects around to effect change in the opposite world. The top character would've been slower and unable to jump, but could climb on top of things (butchered by removing its arms!). The bottom character could jump and move faster. I hadn't decided on using puzzles, but I had many ideas for mood. I thought the puzzles would turn out uninteresting, so I looked for ways to make the game interesting in an exploratory sense.
Hmm… A lot of the changes to each world could be subtle instead of explicit, requiring the player to pay attention and connect non-trivial dots. This was more interesting to me, but I suspected most people would not have the patience. Considering both that Ludum Dare is a short-form game jam and my lack of artistic vigour, the anchor of aesthetic was possibly a misstep. Despite this, everything I did was valuable in that I was breaking new personal ground. It was the first time I built a platforming system, the first time I tried character animation, and the first time I crafted coherent music for a purpose.
I'm unsure when, but eventually I decided the characters would be able to swap positions. That definitely would've been useful for the platforming aspect (e.g., one part of a world would only be traversable by a certain character due to size), but I think I was still viewing that avenue as uninteresting. This feature didn't make it into the game, and it would've required some work on top of the existing monoliths to make the transition clean.
Without having decided what to really do about mechanics, I set about building the platforming system, world editor, and aesthetic. I wasn't granular with my time logging during the jam, so I've no exact numbers on how much time I spent on code vs. media, but the latter took surprisingly little, especially audio (judging by the time-lapse). I think I spent only a few hours in the final day creating the music, and a disproportionate amount of the time on art was spent unconstructively seeing what would happen with the tileset under certain blend modes. It felt like the monoliths (especially the large ones) took forever, but the time-lapse proves otherwise. (P.S. I used an actual script for the monoliths; they're probably legible enough for a linguistic adventurer to decipher.)
Speaking of monoliths,1 they were a late final-day idea that initially had no purpose. I realized the lighting was going to be the driving force for the mood of Onomo, so I spent a lot of time toying with it. The monoliths came from wanting a lit world feature that would draw attention. Glowing glyphs were an obvious choice, and the idea to link them came even later in the day. It was fairly obvious at this point that I was not going to be able to fit in any concrete mechanics, so I just focused on making the world look consistent and slapped on (spoilers!) activation of linked monoliths by triggering their chimes together. That's the only thing you can do in Onomo.
Code-wise, I probably spent the most of my time tweaking the lighting and figuring out how to do character animation, character movement, and platforming (all of which I had never done in this form before). I included in-game world editing from the very beginning because I didn't want to have to convert or read data from a tile editor (which I hadn't scoped out beforehand). The in-game editor proved to be very useful and I think that they are generally a good idea. However, the save function was broken for far too long due to my ill-informed attempt to pre-optimize. I was attempting to avoid outputting rows that were empty, but I neglected that table length in Lua is zero if items are not continuous or have non-integer keys. The optimization was hardly even an optimization, and it wouldn't've mattered in the end since there were only a few rows of tiles in each world. Pre-optimization is the root of evil!
Another large mistake I made was not separating game controls from editor controls clearly enough. I changed the controls for the editor mode on the final day to absolve this issue, but I failed to adapt to them. This is probably the gravest misstep as the sudden change was the largest barrier to world building, which I had already been neglecting. Also, since the monoliths were a late idea, I was only able to place them in code. The biggest takeaway I have from this is that you should plot out the control schemes and don't tangle the two in code.
The name “Onomo” came about in the last few hours. Its origin or inspiration is utterly inexplicable. I spent the last 30 or so minutes building a Windows package and submitting the entry.
All told, I worked on Onomo for 34.7 hours.
In the following hour or two after the Compo ended, I built and uploaded Linux and Mac packages. I also managed to play and rate a few entries before the 37 hour mark without sleep, which was curtailed by questions such as “how are you still awake?” and realizing that I was still awake.
After 11.5 delicious hours of sleep and some business in the city, I went on my usual spree of critical analysis, punctuated by asking for Linux-supporting entries on the Ludum Dare IRC channel. There were a few entries that simply blew me away (stay tuned).
Anyhow, I feel it's a bit disingenuous to be critical of others where evermore could be piled on my own work, so I will now offer a critical analysis of it. (I'm too honest for my own good.)
On the surface, there are a few issues of aesthetic and motion:
As for the monoliths, the chimes mostly stand out, but at least two of them blend in too well to the background music. These chimes can be more easily heard during quieter sections of the music, but their corresponding monoliths should perhaps not have been placed so close to the spawn points. Giving the player a reason to stay (alas, this is a jam) in the game world for a longer period of time, to see what it has to offer, should've been more important.
The monolith activation mechanism is unclear and tedious. It practically requires the player to position both characters next to the linked monoliths and move one immediately after the other to ensure the second chime occurs while the first is playing (yes, this is exactly how it works). It would've been better and easier to understand if there was a continuous sound as the player stood in front of the monoliths. That way the player would not have to scramble between worlds and time the chimes.
Obviously this is a jam “game”, and I only worked on it over 48 hours, but these are all valid criticisms. I make these points for all of the entries I rate. My reason for doing so is to help developers improve their skills and hopefully observe things about their work that they may have never considered. Of course a large part of learning in game jams comes from the process of creation, but I think hearing different perspectives is also very important. If more Ludum Darians offered their thoughts, the experience would be all the better.
Onomo has become yet another concept I'd like to expand and explore, just as with my Ludum Dare 26 game, Prisma. I've been building togo not only to learn about data-oriented design and engine structure, but also to have a scaffolding on which I'd feel comfortable placing a proper Prisma (and now a proper Onomo). togo will remain my focus for a long while into the future, and I think Prisma will still be its primary motivator, though I envision other ideas will come along as it matures. No concept is perfect or inherently better than any other, so I'm not worried about this prospect. Every concept can be “better” than another with enough attention and development. When I feel that I can build something complete, I will also have a better picture of my abilities and what ideas are most compelling.
I suspect I will be writing about excellent Ludum Dare entries around the time the ratings are posted. Until then, go forth and critique, fellow Ludum Darians!
]]>I've been meaning to revisit the engine I wrote for that RPG, and now I have the time to do it. The code is horrific, though, so I'm practically starting over from scratch. It's better that way (no, really). I've also been planning to remake & expand Prisma, so this fits in nicely. I can fuel design patterns by what is needed in Prisma.
Onsang is still going (and hey, it kinda looks like it's actually evolving), but it makes more sense to focus on game system knowledge right now as I'm planning on poking people through July-August in California for employment. I might end up pushing the QA side of things, so who knows what'll happen. Regardless, this is double-fun and good mental exercise.
Even since that scaffold of an RPG, I've been interested in building a rendering system that used command buffers, but not so much about the things that seem to implicitly come along with it (until recently). To achieve maximum speed in such a system, you want to minimize the amount of data you're throwing around, and you definitely don't want rampant allocations during a frame. This amounts to using compact data structures and contiguous arrays to minimize cache misses. This approach to systems is often called “data-oriented design” (DOD) or “data-driven design”, where the benefits are not just efficiency, but facilitating task distribution and modularity.
I'm going to take that to heart with the new engine. I've been reading articles of DOD proponents since last weekend, and the patterns make a lot of sense. bitsquid, for example, decouples systems to encapsulate them and to allow them to manage their storage as optimally as possible. Their “low-level” systems expose IDs instead of pointers so the system is free to store & re-organize its storage as it sees fit. By allowing the system to re-organize its memory, it can (e.g.) ensure its objects are sorted and adjacent, without affecting any IDs to those objects.
This means they can iterate through the objects without wasting CPU time or cache misses on empty slots or memory hopping through distant dynamically-allocated objects (the latter of which you are usually stuck with in something like std::map
). For some systems, that might not be necessary, but you still get the benefit of not having dangling pointers, and you have a better guarantee that external systems aren't going to unduly modify the object (because they can't access it without asking for it). In most cases, you don't mutate resource data once it's loaded, so you really don't need mutable access, but if you do, it's O(1) away!
There are downsides, though. It takes some careful thinking to ensure the IDs are unique and cannot be used once the object dies (e.g., when an object is removed, and another added, the new one could have an internal index equal to the index of the removed object). It's not very difficult, and you can usually define a sane upper bound on the number of objects the system will ever possibly use (e.g., do you really need 4 billion sounds?), so you can use a bunch of bits of the ID just for the unique part (e.g., an incrementing counter that wraps around), and leave the rest to store the internal index. An upside to having an upper bound means you can reduce a ton of allocations, and you can form a better understanding of the resources required by a system through its use. I think such deep understanding is going to be extremely valuable.
I'm going to harp on a lot about bitsquid throughout this article, so be prepared.
Another aspect that emerges from DOD is simplicity. Because structures are open and don't prohibit or protect use, whatever wants to use them can do so in the most direct manner. In contrast to OOP, DOD necessitates that anything can do anything to everything that's open. In OOP, you're often hiding data and placing an interface on top to mediate access. Why? Because everything is afraid of being malformed or misused. This might make some sense for software that really, really shouldn't crash and should catch misuse as soon as possible through its design, but in games we just want to run fast and get the code over with. Always having to plaster on a layer of interface & dumb-dumb security slows us down by making things rigid, so why do it? We're not interested in the preemptive assurances that get in our way and slow the game down.
Not having a protective interface over everything doesn't prevent us from crashing hard. We should crash hard when we encounter an error, and we should be vigilant to test for errors, because otherwise they're going to go unnoticed or ignored. It's better to fix the system early before everything depends on its broken behavior. This is, naturally, also something bitsquid does. Instead of removing that “pesky” triggering assertion that's due to malformed input, just fix the malformed input. Is the error too strict? Evaluate it and decide whether it should be removed or turned into a warning instead. For example, we may want to error when we encounter an animation that has a bajillion bones, but it could be totally legitimate, so a warning would be more sane until we have a real reason to prohibit it — e.g., an upper bound on the animation system's support for bones.
Back to decoupling. How do you share data if everything is disparate and in its own little universe? More data! Low-level systems can have an output stream of events or a way to poll per-frame (whatever makes more sense for the system). To connect low-level systems, you can use a higher-level system that knows about all of the systems involved. For example, the Game
might want to connect HIDs to the Player
, so it could poll the input system and send off to the player, the binding manager, whatever. By patching things together, we can design the interfaces to only deal with what they know, which reduces their complexity.
You might say that puts a lot of strain on the high-level system to connect things. True, but it's much better than distributing the interface to so many systems that you end up with an unmaintainable spaghetti of references, global objects, and code fluff. It moves the complexity away from the low-level systems and into the high-level systems where we can be more intelligent about the way they are connected. By connecting in a higher-level system, we reduce the complexity of the lower-level systems and increase their modularity. You can take any system out of the equation and replace it with another as long as it's not intrinsically attached to any other system.
What does this mean? We can replace systems at compile time (or even at runtime) to handle specific hardware for optimality, without resorting to expensive virtual calls on interfaces (which is too often only used to facilitate system swapping or so-called extensibility — booo). The internals could be implemented to avoid such nonsense (i.e., since we expose an event stream or polling, that's most of the interface, and it can differ internally), but still give it the modularity to support different hardware/capabilities efficiently. Most of the time, what you end up removing due to the removal of a system should only be in the high-level system. That will be a good indication that your systems are decoupled.
DOD obviously has a lot of implications for the code. Data is nearly always public, interfaces stay out of structures for POD traits (and really for uniformity when you want to extend the interface for dealing with a type), and abstract classes mostly disappear, but they are still valuable. For example, data streaming is already a costly operation, and we want to be able to use it on-demand from different kinds of storage media. We could hide this behind a little system that takes requests and gives back blobs, but it'd end up being global, since a lot of stuff is going to need data. We could keep it encapsulated and pass along streams from the high-level systems, but it's very roundabout, and adds more interface to our systems. In reality, you're going to have some resource system to fill up those systems, so the low-level storage media access should be disparate even from that. It doesn't fit into our system model. It's much easier to give it an abstract interface and allow different implementations of it for memory/file/whatever access and use it appropriately within the resource system.
Of course, you may only ever need standard filesystem access, so maybe you don't need the abstract interface. That's fine, but its consequences should be considered early on. What if you move to a server-client model (for tooling, the actual game, etc.)? You're probably going to want to stream over the network, or at least into memory for the network system, so your streams are going to then need direct memory support or support for a network buffer. What about platform support? You could handle that by using PIMPL, including different code, or by using a bunch of macros to determine the platform & change the code accordingly, but then you end up with some manner of code spaghetti and have to either switch the type data or duplicate the whole interface for each platform.
If you foresee anything of that nature with something used in many places or heavily dependent on the platform (in this case, storage media access), you probably want a virtual interface. It separates the different implementations and makes the code simpler. You still have to duplicate the interface, but with an abstract class you have a constraint on what the interface is supposed to provide. This helps in maintainability when you change an interface. You don't have to guess whether a change has made it to all of the platform-specific implementations because the compiler tells you.
The core of my engine is going to be modeled around bitsquid's foundation library. Having the interface separate from most types isn't very critical for something I'm authoring, since any extension to the interface I would make would be core and could thus be in-class if the interfaces were designed that way, but I'm going to do it anyways. Style shifts are fun!
It has other benefits, such as moving the recompilation factor to the interface and the code that includes it — away from the type, which rarely changes. These types have public data to facilitate the built-in functionality (since the interface consists of non-friend free functions), which happens to enable anyone else to add functionality. In addition to reducing recompilation from monolithic headers, code only includes what it uses and doesn't incur extra cost from the stuff that it doesn't need. This means types that only need a type definition (i.e., none of its interface) don't incur the cost of the interface.
Granted, you can design code to work like this without separating the interface from the class, but you still end up with inclusion spaghetti due to dependencies. That's what causes infectious recompilation, especially with class templates, which still costs more time due to the interface being needlessly included. You could move the interface definitions out of the class and still have the problem simply because the declarations are still a part of the class, and you then additionally have the maintenance cost of syncing the declaration and implementation files.
Like the foundation libray, my types are grouped together by … type. Core types, collection types, math types, etc. Interface headers (obviously) include the required type headers, so you only ever need to include the interface header for a type if you need to use functionality for it. If you need types, you have fewer headers to include since they're grouped. Because types rarely change, the recompilation factor of doing this is minimal. Interfaces change far more often, and it's less work to maintain & include few headers (which, again, are small because there's no interface in the type headers).
A downside I've already noticed is the boilerplate caused with free functions for class templates. The template specification has to be repeated for each function. If I were doing this in my own style, that would be unnecessary because everything for a class template would be declared and defined in-class (which is undesired here for aforementioned reasons). It's not that bad, though. At least I don't have to repeat the entire declaration like you have to do with non-header implementations (or with separate declarations and definitions). It does have a nice side-effect for the objects the functions operate on: data member prefixes/suffixes are not needed because you always have to go through the object to get at its members, and thus there are no name confusions.
I'm again taking bitsquid's cue here by using a single prefix underscore for internal data members, just to indicate that something should only be mutated if you know what you're doing. My motto here is “the user should protect the user from the user”, but I'm still going to furiously flip tables via assertions if they misuse the existing interface. I'm still going to have (free function) accessors for these internal data members, because the data in a type might change (and thus have a significant effect on anything that directly accesses its members), because it's more consistent with accessor-like functions that make small calculations, and finally because writing array._size
is kinda bleh. This is in contrast to my usual internal data members with m_
as a prefix. Arguments of readability against something like that don't work on me (because I can read it just fine and “m_this m_is_hard m_to m_read” is never a real occurrence in code), but here I'm making an exception just to see how it goes. The rest of the design isn't really in my style either, so why not.
One thing I'm definitely not doing is repeating declarations just so the documentation is in a smaller area. Documentation is best represented outside of code, and editors these days can jump to functions if you really need to look at the code instead of the documentation (and why do you need to see just the documentation in-code if you already have documentation elsewhere?).
I'm very tempted to use the facilities from duct++ for the engine, but I really want to avoid using the stdlib if I can help it (which duct++ uses somewhat significantly). This is pedantic for someone that isn't doing “AAA” games where performance is critical because of all the stupid pixels, but I see it as a way to learn how all of this works. Making a game is the motivation to write the engine, but it has its own values and intrigue that will keep me plenty happy even if I don't get to actually making the game. I'm a system designer, which games have, but first and foremost need before they can be games, so I have plenty of fun in the backend.
I might end up using a few specific things from duct++, like compiler detection and endian handling, which don't include any (or little) of the stdlib.
Pedantic motivations justified, there are still good reasons to avoid the C++ stdlib. I can have a more intimate understanding of the algorithms and structures I'm using and can tailor them specifically to the needs of the engine. Everything in the engine can be consistent in style and (hopefully) behavior. I'm not forced to deal with the mental and (supposedly minor, now) performance cost of exceptions (which are unnecessary since I want to crash hard) and I have less platform-/implementation-specific junk to deal with (case in point: Microsoft broke cos()
, granted cos()
is not something I'm going to write).
I have yet to seriously dig into the rendering system design, but it's also going to be bitsquid-inspired. Ideally, rendering will be distributed into tasks, merged for sorting, then distributed again to the hardware. Sort keys are really neat, surprisingly simple, and I can't wait to play with them.
I need to stop expounding on this article and get back to the code. Hopefully I'll have something interesting to write about on this topic in the near future. Here's a list of what I've been reading:
Very valuable information on how DOD is used in their engine, and a lot of real-world examples in design and methodology.
A very good article on the essentials, benefits, and drawbacks of DOD. “Where there’s one, there are many.”
A collection of links for existing command-buffer rendering engines and an FAQ on the design of such rendering.
Specifically about command buffers and sort keys.
A series on the design of the engine for Secrets of Rætikon (and Chasing Aurora?). This contains some useful links, but they use a typical OOP design, and a lot of virtual inheritance as far as I can see. See Ginkgo's Game Loop for time management in the main loop.
Let's see here.. overview first. I spent 31 days house-sitting and subsequently 71 living with my grandmother (from the end of March), the latter while suffering from a severe chronic headache and paired mental impairment. This obliterated focus, efficiency, and progress on everything.
After July, the headache got somewhat better (presumably due to diet and return to the homestead), but it took until late November to develop a functional theory: fructose malabsorption. Tweaking saccharide consumption resolved the issue (yet another slow downramp), although there were undoubtedly other factors (especially from my grandmother's house).
(And yes, I did visit a doctor in June. They had no theories. Nutrition tracking in Onsang is going to be glorious… *drools*.)
During the first month I was with my grandmother, I somehow managed to make Prisma for Ludum Dare 26. It's amazing this thing is even coherent.
For study, I planned on taking Algorithms, Part II (which started in April), but I didn't have the health nor time for it. I later took and "completed"1 the second run in November. I also took Algorithms, Part I again for good measure and crammed in Calculus One, Introduction to Mathematical Thinking, Functional Programming Principles in Scala, and Automata. The last one seemed to be low-quality, so I didn't do any of the material. I also dropped the Scala course very early because I deemed it not worthwhile.
Yet the remaining workload was too taxing for the weekend, so I ended up dropping the rest of them (five or so weeks in), excepting the Algorithms courses. For Part I, I revisited material and revised code for assignments, so there wasn't much workload there (and I only intended to skim over it anyhow, since I had already completed the run back in March).
Once I was back home, work kept me busy & interrupted. The headache and mental impairment made effort on projects inefficient and slow (even when at its lowest potency). Hopefully now that it's gone I can finally get some Serious Time rolling.
Earlier in the year I sketched out my goals for 2013. Most have not happened, and the rest that "have" are not complete.
Static site generator. I haven't even touched the design since January. Jekyll has gotten somewhat better (speed-wise) and I know the way my sites are setup is disgusting (yes, that's Jekyll's fault), but I've forgotten precisely why, so I don't feel the urgent desire to replace it. I'd still like to roll my own at some point.
Log keeper rebuild. This is very much WIP, but I've got an initial API (Hord) working and theoretically-correct networking complete for the server/client (Onsang).
So far I've created three libraries to assist the project: Trait Wrangler (trait testing), ceformat (constexpr printf-like format parser and stdlib I/O library writer), and Beard (terminal-based UI). I hope to have something up and running once Beard is usable — and I can't wait. I have wondrous, ridiculous, and 100% obsessive ideas for Onsang's use.
Android time tracker. I'm putting this one off until Onsang matures. I intend to move all of my time tracking into Hord nodes so that the various logs aren't disparate (as is currently the case).
ØMQ broker. I hoped to have a use for this in Onsang, but now I don't think I'll be using ØMQ, so it's on the back-burner until I find it interesting again.
Game engine. It needs a redesign.. which I haven't dug into. It'll probably happen once I'm done with my current contract work, since I'm still aiming to make Prisma into a Real Game™ (although that's reciprocal because I'm going to use Prisma as a guinea pig to flesh out the engine).
There are too many awesome things in our universe. Here are some of my interests coming into 2014.
I learned of SuperCollider just last week from Jeremy Zuckerman, and it's probably one of the coolest language-environments I've ever seen. This reignited my interest in audio, and I'm going to devote some time to learning SuperCollider for generative/responsive music creation and sound foley. I'm also tacking on some music theory.
On the visual side of things, I've been mulling over how to maximize visualization via imagination in a non-interactive medium. Books (and writing in general) are great because of the imagery and atmosphere you can conjure from them. I still vividly remember scenes from books like Down and Out in the Magic Kingdom, One Thousand and One Nights, and Twenty Thousand Leagues Under the Sea — far more vividly than I can remember any of the countless comic pages I've read.
It's not just a static representation, either. I can step through parts of the scene and see its angles and people and creatures. I find several reasons for this.
Books have longer spans of engagement, and the only tool at the reader's disposal is their imagination. Length of engagement certainly affects the retention of the imagined scenes and the play within it.
On the flip size, the engagement in a comic is very low unless the reader is going through entire chapters in a single sitting. For web comics, the reader will spend a very small amount of time reading new pages (once they've gone through the backlog). Comics take all of the written imagery and turn it into literal imagery, but in doing so remove the reader's imagination from the equation. This is inherent, and the shorter length of engagement reduces even the retention of the speech.
BRAINS.
I enjoy reading and writing stories where the reader's imagination is only nudged part-way with descriptions and left to conjure the form and dynamics. To me, giving the reader a unique experience of the story is a lot more exciting than setting the imagery in stone with art. The annoyance is that I find it difficult to convey imagery in a succinct and natural manner with written English. I'd like something more expressive, but I can't think of a decent way to expand into existing media without losing the imaginative element.
Simply using cued audio could work, but I'd have to be really careful with timing and flow. As soon as the reader wants to read the previous page, the mood is generally upended. And the text gets chopped down into bits that can actuate an audio effect, meaning there's a lot of page turning and smaller chunks of text… meaning the reader is upending flow a lot more often. It gets ugly.
Actually, audio is really only complementary. The text still has to describe things. The audio will only ever be a layer plastered on to help with the nudging.
It's possible I'm just a poor writer (I am) and haven't yet found the secret formula. I imagine this desire will remain for years unless I either 1. figure out how to expand; or 2. improve my modus operandi to the point where it doesn't bother me. I should probably keep writing.
I tracked 8747.7 hours total in 2013 with 14350 entries. 12.3 hours are mysteriously missing.
Here're some numbers:
Existence (selected ~3474.6 hours):
2831.9 hours: sleeping; monthly: mean 235.99, min. 213.50 (February, duh), median 236.75 (July, September), max. 251.60 (December). Compare to: 2782.5 in 2012.
~370.0 hours: eating.
272.7 hours: preparing food.
Study, projects, and work (~2121.6 hours):
876.6 hours: project programming (616 public commits), 966.0 total tagged as coding. Compare to (more efficient): 823.1 in 2012 and 1149.5 in 2011 (for which only 10 months were tracked!).
591.6 hours: working, 59.5 of which were spent writing packaging documentation. Compare to: 239.4 in 2012 and 140.9 in 2011.
304.9 hours: coursework (mostly programming) and study.
178.8 hours: designing games.
~121.3 hours: writing (non-work; probably overlaps a bit with game design).
31.8 hours: playtesting.
16.6 hours: video game quality assurance (related to above, but specifically: writing bug reports, investigating, and gathering data).
Entertainment (579.7 hours):
268.2 hours: watching anime (224.3, eep) and western television (basically The Legend of Korra × 2 and Avatar: The Last Airbender) (43.9). Compare to: 255.8/40.0 in 2012.
189.1 hours: gaming. Compare to: 537.3 in 2012 (235.5 of Diablo 3 — eeeee).
81.7 hours: listening to podcasts. Compare to: ~244.7 in 2012 (lot of backlog).
21.4 hours: watching films.
19.3 hours: reading comics and manga.
Miscellaneous:
941.4 hours: tagged IRC (span-wise; does not accurately represent time spent solely communicating in IRC).
658.9 hours: netloop; 425.2 tagged reddit (… terrible!), 208.9 tagged twitter, 91.7 tagged feedread (RSS, blogs, comics and such).
143.4 hours: in Windows, 102.3 of which were for gaming. This doesn't include work, which is 100% Windows for software packaging.
36350 scrobbles (nearly no podcast scrobbles). Top three albums: FEZ, Tamako Market, and the Legend of Korra. Top three artists: Jesper Kyd, 野見祐二 (Yuuji Nomi), and Disasterpeace. Kyd and Yuuji are top artists because their share is distributed amongst multiple albums. If the albums were grouped, the top three would be: Assassin's Creed series, Nichijou, and FEZ.
Buys:
Games: 35.5, only 16 of which I have played. The 0.5 is Age of Empires II HD: The Forgotten, which is an expansion.
Game bundles: 8. Humble: Indie #7, Mojam #2, Android #7, Double Fine, Indie #8, Indie #9, and Daedalic (weekly). Indie Royale: Spring Sun.
Kickstarters backed: 17. Categories: animation (4), art (1), comic (4), game (5), music (2), podcast (1).
Music: 5 albums, 1 EP, and 1 single.
Books: 5 physical: The Art of Computer Programming (box set, counted by volume) and The Design and Implementation of the 4.4 BSD Operating System. 2 digital: Records of the Grand Historian: Qin Dynasty (aka Shiji — or parts of it, at least) and Mapping and Visualization with SuperCollider.
Hardware: CM QFR keyboard, AT-50M headphones, CO detector, Gigabyte GA-990FXA-UD3 mainboard, 16GB flash drive, Grifiti Fat Wrist Pad 14 (terrible), AT2035 LDC microphone, Focusrite Scarlett 2i4, On Stage MS7701 stand, miscellaneous others.
Etc.: glass beaker, replacement tea infuser (how dare you break that!), chair (finally), assorted teas (the knolled survivors), and a digital thermometer.
Despite its numerous flaws, Prince of Qin is one of few video games I hold in high regard. It's also a game that I am irrationally attached to. It won't leave me in peace. Or vice versa.
This article is sort-of documentation for the game and a chronicle of my time with it. I give a history and overview of Prince of Qin & its related games, my experience with the game, and its possible futures in my care.
I supply Simplified Chinese for introductions, as well as pinyin and IPA where appropriate; if the pinyin is absent, its transliteration is roughly equivalent to the Chinese. If you want to learn more about the Qin Dynasty, the Shiji (史記) covers it extensively. I cite specific passages from Burton Watson's translation Records of the Grand Historian: Qin Dynasty (2013, 3rd edition) as SHIJI in the footnotes, although a fair majority of the storyline section is derived from it.
Fair warning: the next section is probably very boring. Skip past it if you're not interested in the time period in which Prince of Qin was released.
Alright, let's get at this tiger-fish!1
Object Software began development on 《秦殇》 (Qin War; pinyin: Qín Shāng; IPA: /tɕʰin ʂɑŋ/) in 2000 and released in China during July 2002. They partnered with Strategy First to release in the United States market under the title Prince of Qin and brought a beta of the English version to E3 2002.
It was released to the US market in August 2002 with a full English dub. Reviewers criticized clunky mechanics, poor voice acting, and poor translation, but often praised the aesthetic, history-derivation, and music.2 These latter factors generally saved it from utterly dismal reviews. Western media often compared it to Diablo II and Baldur's Gate.
Object Software also partnered with Capcom to release in the Japanese market under the title 「天覇光芒記 ~プリンス・オブ・シン~」 (difficult-to-translate Japanese-ey head-title – Prince of Qin; romaji: Tenpakoubouki – Purinsu obu Shin). The release was pushed to August 2003 due to the localization team's pre-occupation with Blizzard Entertainment's Warcraft III and, unlike Strategy First, Capcom did not dub their release.
Online multiplayer began in China and the US shortly after release with a capacity of 500 players. The online service was called “Battle.Net” despite not having any relation whatsoever to Blizzard's online gaming service.
Also after the Chinese and US releases, Object Software shifted to developing online-only games and made public an online-only standalone “expansion” called Prince of Qin Online - The Overlord of Conquerors (later called World of Qin) near the end of 2002. This shift was likely due to Blizzard's World of Warcraft announcement at E3 and the surrounding hype over massively-multiplayer online gaming.
In January 2004, Strategy First partnered with GMX Media to release in the United Kingdom market. Official English multiplayer servers died down through the year as patches reduced server stability and broke some fundamental mechanics.
In the same month, Object Software released its final (to date) singleplayer game: 《复活》 (Resurrection; pinyin: Fùhuó; IPA: /fuxu̯ɔ/), a more mythologically-derived “prequel” to Prince of Qin. GMX Media published the game in the UK in December with a full English dub under the title Seal of Evil. Strategy First later published the game in the US market in April 2006.
Seal of Evil illustrates some experiments they would later incorporate into World of Qin 2, which utilized realtime 3D for actors. Object Software later grew into first-person 3D MMOs and hasn't looked back since.3
Prince of Qin features a history-derived story set in the Qin Dynasty (221–206 BCE) surrounding Fusu (扶苏), heir apparent of the First Emperor (秦始皇帝; pinyin: Qín Shǐ Huángdì), after the First Emperor's death.4 What follows is an overview of the period and the events surrounding the story in real-world history.
Near the end of the Warring States period, the Qin state conquered and unified the six major states. The king of the Qin state, Zhao Zheng (趙正), became the First Emperor of this unified China.
The Qin Dynasty standardized the units of measurement and currency, formed extensive trade routes, and unified the Chinese script. The First Emperor undertook massive projects, such as the Great Wall of China and his tomb, which is famous for its collection of life-sized terracotta warriors and horses.
However, the First Emperor used cruel law to control and subdue the people, and most of his tomb's workers were killed to ensure secrecy (by some counts, 300,000–720,000 people). He ordered the mass burning of books to suppress scholars (who were accused of raising dissent) and later buried alive more than 460 scholars5 after being deceived by two alchemists in his search for immortality, actions that drew criticism from his eldest son, Fusu.
Due to his numerous remonstrances, Fusu was exiled (ca. 212 BCE) to the north to supervise General Meng Tian (蒙恬), who was in charge of the construction of part of the Great Wall (as it was later known). In the subsequent years, Fusu was very successful in protecting the northern border against the Huns and was universally liked among the troops.
In 210 BCE, the First Emperor died on a trip to the far east of the empire in search of an elixir of immortality. As the First Emperor insisted on touring in secrecy, only prime minister Li Si (李斯), advisor Zhao Gao (趙高), son Huhai (胡亥) (who was the only member of the royal family traveling with them), and five or six trusted eunuchs knew of his death. Li Si feared news of the emperor's death would trigger an uprising, so he concealed his coffin and returned with the convoy to the capital, Xianyang, all the while concealing the emperor's rapidly decomposing body (due to the summer heat) by ordering carts full of rotten fish be carried behind and ahead of the emperor's wagon.
After two months journey back to the capital, Zhao Gao conspired with Huhai (while only convincing Li Si to play along) to forge an edict claiming various crimes committed by Fusu and Meng Tian and ordering them to commit suicide. Meng Yi (蒙毅), Meng Tian's younger brother, sentenced Zhao to death for a crime he committed as a minor official, but the First Emperor pardoned him. Because of this, Zhao held a grudge against the Meng family. He was also afraid Meng Tian's prowess and closeness to Fusu would threaten his position if Fusu ascended the throne.
When the messenger arrived with the edict, Fusu retired to weeping in his inner quarters, intending to commit suicide. Meng Tian stopped him from doing so, saying:
His Majesty has been residing away from the capital and has not yet designated an heir apparent.6 He has put me in command of 300,000 men to guard the border and ordered you to act as supervisor. Such assignments are among the most crucial in the whole empire. Now one messenger appears and immediately you prepare to commit suicide. How do you know that this is not some sort of deception? I urge you to ask for confirmation. If the order is confirmed and you then take your life, no one can say you acted tardily.7
Being too loyal8 for his own good, Fusu responded with “If a father instructs his son to commit suicide, how can he ask for confirmation of the order?”9 and proceeded to take his life. Meng Tian, however, was unwilling to commit suicide. The messenger turned him over to the officials, who imprisoned him at Yangzhou.
After the messenger returned, Huhai ascended the throne (210 BCE), taking the title of Second Emperor (秦二世; pinyin: Qín Èr Shì), with Zhao Gao as the chief of palace attendants constantly by his side in the inner palace to tend to affairs of state. Zhao spoke ill of the Meng family “day and night” to Huhai. In the same year, Meng Yi was put to death, Meng Tian was forced to commit suicide by poison, and the remaining Meng family was killed.
Zhao exploited Huhai's view of him as a teacher and superior to impose his own agenda, out-running the First Emperor's cruelty, having Li Si executed,10 and eventually cornering Huhai himself into suicide through a rather rash move in 207 BCE. After Huhai's death, Zhao tried to install Ziying (子嬰), one of Huhai's uncles,11 as the next emperor.
However, Ziying (of royal blood), suspected Zhao was going to have him killed to appease the rebels, so he feigned illness and declined to attend state affairs. He plotted with the eunuch Han Tan (韓談)12 to kill Zhao. Zhao eventually came in person to his quarters to inquire about his illness and was killed by Han Tan as he arrived. Ziying then ascended the throne as the King of Qin13 and executed Zhao Gao's three sets of relatives. He reigned for a short 46 days before surrendering (along with the entire royal family) to a Chu (later Han) rebel leader, Liu Bang (刘邦),14 in late 207 BCE.
Liu Bang handed him over to another Chu rebel leader, Xiang Yu (项羽), who killed Ziying and the entire royal family. This effectively ended the Qin Dynasty after only 15 years of existence. Liu and Xiang fought over the coming years in the interregnum known as the Chu–Han Contention, with Liu eventually defeating Xiang at the Battle of Gaixa in 202 BCE. Liu then established the admired Han Dynasty (206 BCE–220 CE), becoming Emperor Gao (高皇帝; pinyin: Gāo Huángdì).
At least, that's how it goes in history.
Prince of Qin explores what could have happened if Fusu heeded Meng Tian's logic — or if he was at least as suspicious. The story up to the false edict is the same as real-world history. Fusu is stopped by Meng Tian and flees as Meng Tian and his men hold off the soldiers. A manhunt ensues for Fusu, who has embarked on a journey to Xianyang to discover the truth.
The 11-chapter narrative branches with seven total endings and explores the Qin state. Many of the NPCs encountered in the game are based on their real-world characteristics and known history. Zhao Gao is the main antagonist throughout the game, although there are many obstacles in Fusu's path.
Seal of Evil, the so-called “prequel”, takes place in the Warring States period surrounding the Baiyue peoples as the Qin state was rapidly conquering the surrounding area. It largely stands alone and incorporates more Chinese mythology than Prince of Qin does. I have plenty of criticisms about it, but I will not share them presently.
Prince of Qin is a mixture of ARPG and traditional CRPG (with emphasis on the latter, I will argue) with realtime combat (i.e., not turn-based), partially-interactive pausing, and on-demand game saving. In singleplayer mode, the player can recruit and control up to five characters with permadeath (except that when Fusu dies, it's game over).
The first two sub-sections are somewhat detail-heavy to serve as a reference of sorts to fundamentals and build theory, but cover some of the core mechanics and show the classes in action. Most specific values are relative to patch 1.30. I planned on including a gameplay video (as all that I've seen are quite crude), but the forces of evil15 were having none of that AV synchronization business. Fortunately, there're plenty of screenshots.
The Five Elements, based on a real-world concept, are a fundamental mechanic. The elements and their corresponding attack power (AP) and defense power (DP) abbreviations are:
I abbreviate elemental AP and DP as XAP and XDP, respectively (i.e., when referring to elemental AP and DP in an abstract or encompassing sense). There is also so-called “common” attack power (CAP) and common defense power (CDP), which act outside of the elemental system (i.e., they are neither strong nor weak against XDP and XAP).16 Two mechanics control the relationships between these elements.
Promotion is a mechanic used to activate “hidden” attributes on items. As seen in the illustration above, the promotion loop is (clockwise, starting from the top): Metal → Water → Wood → Fire → Earth → Metal → …. To activate hidden attributes on an equipment item, a specific equipment item type of the promoting element must be worn (e.g., belts promote chest pieces and rings promote weapons & shields).
Restriction is a mechanic controlling the strength and weakness of elements against other elements. As seen in the illustration above, the restriction loop is: Metal → Wood → Earth → Water → Fire → Metal → …. Somewhat defying logic, Fire does not restrict Wood. You'll just have to deal with that.
In addition to promotion and restriction, the elements have an offensive chance to inflict short effects:
Leveling gives the player 10 points to spend on six different attributes and 1 skill point to spend on skills, with skill tiers unlocking periodically up to five tiers. The six directly modifiable attributes are:
Increases CAP and maximum weight capacity. Stock and trade for purely offensive variants.
Increases CDP and HP regeneration. Tank attribute.
Increases Dodge Rate (DR) and Shoot Rate (SR) (accuracy). Generally required for all non-casters.
Increases rate of experience gain. I am not aware of a sane use for this attribute outside of multiplayer.
Increases value of own items and decreases cost of store items. This has some uses in singleplayer.
Increases MP restoration speed and XAP. Stock and trade for casters and element-driven characters.
Health and mana have the typical abbreviations: HP and MP.
Most skills can reach a maximum level of 9, with others limited to 3–6. Certain tiers of a specific skill are only unlocked once the character reaches the level of the tier. Characters can reach a maximum level of 99, although this takes an obscene amount of time – even with decent Savvy – and will never be reached by any sane person before they complete the singleplayer storyline.17
There are five classes in the game:
Each class has three variants with different traits. All effects of a trait are amplified with the character's level. Variants with an elemental trait increase XAP of the element and XDP of the element it restricts, but decrease XDP of its restrictor. For example, for every level an Earth-traited character gains, their EAP and WDP increase, but their NDP decreases. XAP gain varies with the variant, but XDP increases and decreases by 10.
The Paladin uses swords, shields, dagger-axes (like a sword attached to an axe handle), and bows. Because the skillset is geared towards swords, bows are not very useful and tend to be a waste of time (especially in singleplayer, where Fusu generally needs to be the strongest character).18 The skillset includes an EAP buff and its forging skill (covered shortly) allows the Paladin to craft items instead of paying gold to a blacksmith. Base attributes are: 30 Str, 30 Con, 25 Dex, 30 Sav, 40 Cha, 25 Wis. Variants:
Gains XDP, CDP, and extra HP. Should focus on Str and Dex or Str, Con, and Dex. With good block rate and defense skill utilization, this variant makes a good tank.
Gains CAP. Often used for PvP; should focus on Str and Dex.
Earth-traited. Rarely used in multiplayer due to dangerous Wood (and thus poison) weakness. Should focus mostly on Wis.
Fusu is a Paladin of the first variant, which also happens to be the most over-powered class variant in the game.
The Muscleman is an offensive, sluggish brute employing the use of polearms, clubs, axes, and maces. The skillset includes MAP & FAP buffs, beast summoning, and some crowd-control. Base attributes are: 40 Str, 35 Con, 20 Dex, 30 Sav, 30 Cha, 15 Wis. Variants:
Gains extra MP.19 This variant is effectively useless.
Metal-traited, but also gains CAP. Should focus on Str, Dex, and Wis or just Str and Dex. Plenty of shock skills available.
Fire-traited, but also gains CAP. Should focus on Str, Dex, and Wis or just Str and Dex.
The Assassin is an offensive trap-laying, stealth-employing dagger, bow, and crossbow wielder who can attack very fast with all weapons except for the crossbow (outside of rapid-fire skills). The skillset includes WAP & NAP buffs, is almost equally divided between daggers & bows, and can be critical in singleplayer due to trap detection and disarm skills. Base attributes are: 25 Str, 25 Con, 35 Dex, 40 Sav, 35 Cha. Variants:
Gains CAP and extra HP. Should focus on Str and Dex.
Water-traited; gains SR. Should focus on Dex and Wis.
Wood-traited; gains extra MP. Should focus on Dex and Wis.
The Wizard is a support and offensive caster. The skillset includes an MP-stealing default attack, cures, heals, defensive buffs, a quick-regen flash buff, item identification, and teleportation. Base attributes: 15 Str, 30 Con, 25 Dex, 35 Sav, 30 Cha, 40 Wis. Variants:
Gains extra HP and MP. Should focus on Con and Wis. Good support variant.
Earth-traited. Should focus on Con and Wis.
Metal-traited. Should focus on Con and Wis.
The Witch is an offensive caster which uses staves. The skillset includes an HP-stealing default attack (which is very useful with the offensive variant), a CAP buff, and some crowd-control & debuffs. Base attributes are: 10 Str, 25 Con, 30 Dex, 30 Sav, 35 Cha, 45 Wis. Variants:
Gains CAP and extra HP. Should focus on Str and Dex. Can be quite dangerous.
Wood-traited. Should focus on Wis.
Fire-traited. Should focus on Wis.
The gameplay itself is not too different from Baldur's Gate. Anyone familiar with RPGs should find it welcoming. The elements make for a fun build challenge, and your characters' restrictors can become serious sources of fear.
Keys Q, W, E, and R can be bound to attack skills for the primary slot (default attack; left mouse button), whereas keys A, S, D, and F can be bound to all skills for the secondary slot (right mouse button).
Unlike most ARPGs, the camera is independent of the character(s) position and the player can pan around the map freely. Multiple characters can be selected by clicking and dragging over the characters in the game world, or individually by selecting portraits from the character bar or with keys 1–5. Each character has their own inventory and the player has a single stash. Items can be moved directly between characters by dropping them on portraits in the character bar.
Characters can be moved individually around the map, similar to Baldur's Gate. Due to fog of war (black & soft), it can be useful to send a scout ahead.
Once a character is attacking an enemy, they will continue to attack automatically using the current skill in the primary slot. In singleplayer, the character will also pick a new target automatically, which is very necessary when juggling up to five characters. The game can be paused to queue up the next action, although there is no indication of which action will be performed once the game is unpaused (much unlike Star Wars: Knights of the Old Republic).
Combat is initially slow due to low-grade weapons with low attack speed and doesn't become too fast as the player advances (especially in singleplayer). Some classes, such as the Muscleman, are slower than others, but make up for it in offensive or defensive power — or, conversely, are faster but more weak. The Paladin, of course, ignores all of this balance nonsense and runs around like he owns the place.
Narrative choices are presented in a textual dialog box which unfortunately requires use of the mouse. The sometimes-broken mission log stores accepted quests and completed quests, and the story log book stores a list of events per chapter. Cutscenes are fully voiced, sub-titled, and dialogue is skippable.
Another fancy mechanic is crafting, which is only accessible through NPC blacksmiths and Paladins (with the ‘Art of forging’ skill). Crafting combines materials to create equipment of nearly any type. In singleplayer this extends to some unwearable items for quests.
Flesh-based material – tendon, bone, and skin – can be looted from nearly every non-human mob, whereas ore and wood have to be gathered from sparsely-marked rocks and trees. High stat (Dex especially) and movement speed materials were especially coveted and endlessly hunted down.
Prince of Qin skips the part where materials have to be processed (much unlike Ultima Online) and simply combines the attributes of the materials (with a chance of extra random attributes being added). The item level of the crafted equipment depends on the item level of the materials and the skill level of the crafter, which is only a maximum of 6 for Paladins. Due to this, players would use the blacksmith in multiplayer Xianyang, which is slightly better than a maxed Paladin.
Crafters can also inlay gems in main equipment items. Crafted equipment are limited to one socket whereas store-bought equipment can have a maximum of three. An early-patched bug with crafting was exploited to inlay an unlimited number of materials in an item, leading to exceedingly powerful equipment.
Prince of Qin is commonly considered an ARPG, but I find some fault with this label — or at least in its ‘pure’ sense, even though ARPG is not tightly defined. Rather, Prince of Qin is somewhere in-between ARPG (such as the famed Diablo series) and traditional CRPG (such as Baldur's Gate20) with its pacing. Auto-attack really reinforces that point. Players coming from a fast-paced game like Diablo II can cause Prince of Qin to feel sluggish. On the other hand, it employs powerful items and attributes (especially through crafting), following the nature of ARPGs from its time period (which undeniably includes Diablo II).
Several classes can move about swiftly through skills, but it only really becomes ARPG-like in multiplayer, where movement speed is more accessible and where PvP garners a different kind of attention and discipline. On that note, multiplayer might've never happened if it weren't for crafting, which is notably absent in any ARPG from that period (as far as I'm aware). Prince of Qin's real downfall comes from its bugs and neglected player base. Diablo II, on the other hand, still has a large following to this day; its purity and innovation in the way of the ARPG has certainly served it well.
Despite all of its problems, it still stands as an excellently-crafted RPG in my eye. The artwork direction, music, story, and fantasy are infallibly fitting to its setting. Had it simply received better translation and voice acting, it would stand a bit more prominently as a relic today — although it certainly deserves a crusade against the more heinous bugs.
So, how did this attachment begin?
For me, it all began during the latter snow-covered months of 2002. My brother and I were scouring the net for demos to play (as was our wont) and found the multiplayer demo. It only had access to two classes up to a level cap of 5, which restricted us to the starting city (Yangzhou) and two enemy maps.
We were obsessed. We played numerous characters to the level cap with excitement, despite there not being anything to actually accomplish or explore in our limited play zone. The true factors of interest escape my memory, but they were likely due to the excellent ancient Chinese aesthetic and my love of role-playing (which Prince of Qin is very responsible for extending).
The control over agency and narrative branching in RPGs has always been appealing. When combined with attribute and ability customization (as the label "RPG" now almost universally assumes), these games can become incredible time sinks, despite often lacking any actual designed goal beyond the story (if there is one). In Prince of Qin's multiplayer (which dominated our time), there certainly wasn't one.
The aesthetic captured ancient China like I'd never seen before — and since. My exposure to MMORPGs at the time was very minimal, if at all, and in the later years I was always disappointed by the aesthetics of Asian-themed MMOs. They were tacky and lackluster. Even Object Software's later games – specifically, Seal of Evil and World of Qin 2 – began to slip away. As soon as realtime 3D was utilized, the magic was lost.
Many polygons have to be pushed to contend with the quality of pre-rendered 3D art and tailored 2D art (especially highly detailed background art, such as Prince of Qin's). Back then, consumer hardware certainly couldn't handle such density, so developers of 2.5D games were limited to high-quality (but inflexible) pre-rendered 3D or low quality (but flexible) realtime 3D. I didn't understand the move to realtime 3D when its quality was so severely limited, but I digress.
Looking back, it's strange that I got hooked so easily. Nowadays story tends to hold more weight than aesthetic as a requirement of my interest. This is possibly due to disappointments over the years more than anything else.
Christmas of 2002, we asked for and received the full game as a gift. We peeked under the wrapping and were driven mad because we weren't allowed to open it until Christmas day. There's a lesson for you, kids: don't peek.
Despite numerous bugs and exploits, the official multiplayer servers were a blast. “Stacking”, a prominent exploit, involved the combination of equipment through a bug with the stacking mechanic intended for medicine items. The combined attributes of the stack would apply to your character when you wore it.
One particular attribute, movement speed, was a big target for stackers. This attribute was rare to come by, and the store-bought items were low level and otherwise of low grade. Because the exploit utilized items that required attributes above the character's base attributes, it was a challenge to find and create items to reach the movement speed cap — but reach it we did.
Movement speed was of particular interest to hunters, who wanted to do “mat runs” faster. There are very few hunting maps for end-game players, and they were often a point of contention in the community due to the relatively long refresh cycle of material sources.
Soon after we started playing, I sucked two of my cousins and a friend into the game. For all its glory, multiplayer only lasted about two years. The game undoubtedly suffered due to the release of World of Qin. Patching stopped near the end of 2004 with version 1.30 as Object Software jumped ship to online-only games. In quelling exploits like stacking and the demo level glitch, the patches ruined some fundamental mechanics.
The probabilities of accuracy and dodging were effectively useless somewhere in patches 1.22-1.28, and chat message length was reduced to 30 characters in patch 1.30, effectively ruining interaction. Server instability rose and most of the players moved on.
Strategy First kept the servers up for a short while into the end of 2004, but there were very few consistent players. Once the servers shut down, we started our own server.
Our first dedicated server started in December 2004. I can't recall the patch we used, but stacking was certainly possible — I remember having “server maintenance” where we inspected the players' characters for signs of stacking.
This died down after only a few months. Stability in general was always rather poor for non-Battle.Net servers (and even then…). Unlike Battle.Net, private dedicated servers kept items in the world indefinitely instead of deleting them after some matter of minutes. Because of this, our server had to be restarted frequently to avoid bogging down the game.
In October 2006, we came back as the “Legend Server” and were a bit more clever. Utilizing my adventures in programming (which I only started in February 2005), we released a user patch (1.0, map fix, 1.1a) with a terrible hand-written installer which mixed various official patches to quell map issues. We also created a launcher to check server status and to login using a password (albeit in a fake manner — to this day the actual password handling has not been figured out).
I have no idea how long this run lasted, but it was likely around the same amount of time as the first one. It's possible that it extends into what I'm calling the third run in 2007, but due to no activity in my archives during the interim, I find that unlikely.
In November 2007, we started up the Legend Server again. This saw LS patch 2.0 and an updated launcher, but probably only lasted a few months. There was another likely-short run in October 2008 which saw no increase in the level of cleverness.
Finally in March 2009, after a culmination of over 4 years of programming knowledge, I built a passthrough server to handle authentication and we started our fourth run. We also updated the LS patch to 2.1 & 2.2 and rebuilt the launcher. If I recall correctly, we were using a base patch that fixed stacking, but the LS patch was still incorporating multiple official patches.
Since then, we have not run another server. You might've noticed a pattern with the months we were running: mostly late in the year. I think this is due to the mood we had when we first started playing the game and the ever-vehement nostalgia we felt when reminiscing. It is no coincidence that I am writing this at the end of September.
Throughout 2010, I was deeply invested in deciphering data formats and the network protocol. During my investigations on World of Qin, I eventually got my hands on actual production server software, although it took a few long nights to decipher the poorly-translated Chinese instructions to get it working. This led to the discovery of the command system in Prince of Qin (as well as World of Qin) and the set of XOR ciphers for data and the network protocol (yes… XOR).
With that information, I was designing passthrough servers to inspect the actual messages the client and server were sending to better implement authentication, but these weren't completed. I moved on to greater things and am currently invested in sorting out my own future.
It's been nearly eleven years since our first encounter. In the past three years alone I've grown my knowledge more than in the remaining eight of that span. In 2010, everything started to click together and I finally feel that I've become a decent programmer. I don't know where I would be without Prince of Qin as a source of inspiration and as an enigma to study. I suspect something else would have taken its place – to largely waste my youth – but such theorizing is often fruitless.
The least I want to do is enshrine the game (especially its media) on the net. I have a wealth of information about the game and its various backend systems that can show where Object Software failed and where they succeeded. For now that consists of this article; later I will collect the information on my homesite.
I don't quite know what lies ahead for the game itself, but I have plenty of ideas. Ideally I'd just remake it. There're so many issues that it'd just be better to start over. But time is against me. An obsession such as this doesn't pay the bills; I have employment to worry about before I can do anything substantial, and even then…
As far as that unrealistic future goes, I'd start with UI handling and scene rendering (the latter of which I've done before). The largest remaining mess of an enigma is animation, so I'd tackle that and move on to actor rendering and dynamics. It's probably a good idea to maintain legacy support, so I'd have to implement the command system as well. The network protocol carries the commands verbatim, which explains lag with more than a handful of players (over, you guessed it, TCP/IP), but it shouldn't be hard to translate to a more compact serialized form later on.
Actually, all of the data formats are rather creepy. It's probably a good idea to rework the asset system and just upconvert everything to new formats.
Or I could ignore the presentational stuff and go straight to the logic with a server and local translator. The server could implement its own compact network protocol and the translator would sit locally and convert between the legacy protocol and the new protocol. That'd be a cheaper way to handle authentication, actually.
But I'm not too interested in preserving multiplayer at this point. Being able to play singleplayer on modern systems without annoyance is more important. On that note, I'd also love to do an LP of the game. It'd force me to actually complete singleplayer and I'd get to document the entire game. That'd be a lot of fun. Maybe I'll do it when I'm old and grumpy.
In the meantime, I have enough information on hand to make easy mods. I've been fantasizing about making multiplayer obscenely difficult — maybe increase the enemy count, increase their stats, elemental effect chance, etc. Just for kicks. Maybe dabble in some custom maps, too. It'd also be nice to hack in a windowed mode.
Or… perhaps no future? All this thinking about Prince of Qin is giving me more ideas for an RPG I've been mulling over. My attachment to Prince of Qin fueled a major growth period through 2006–2010 (although dwarfed by 2010–present), but I think I've just about reached the limit of gain.
Remaking anything with a critical eye can be instructive and full of lessons, but not too many people care about the game. I'd just be gratifying myself. All of my game ideas are more compelling than spending countless hours peering into the bit ether and making divinations. Not to mention designing my own systems would be faster than deciphering existing ones.
It doesn't help that the law frowns upon such things. Frankly, it's all rather irrational. Yet here I am.
Which is to say, who knows?
]]>