A Late Postmortem of "Monster Defense"

May 18, 2019 code rambling

A few months ago, I uploaded the full source code and assets I used in an unfinished project I worked on back in 2011/2012 to Github. However, as is typical of me lately, I forgot to take the opportunity to write about it here. This is going to be a very informal postmortem that is probably better described as me writing random things in an unorganized manner about an old project of mine.

"Monster Defense" was to be an arena shoot-em-up / bullet-hell type of arcade game where you are supposed to survive an onslaught of monsters that come at you in waves. Monsters you kill can drop power-ups and other random power-ups will spawn periodically throughout the level as you go. There were two different planned modes of play, "timed" and "endless" which are probably fairly self-explanatory.

The artwork is all placeholder. Because I opted to use Quake 2 MD2 models for reasons discussed later in this post, I found that using the wealth of character models from Egoboo (whose assets are freely available under a GPL3 license) was helpful in getting reasonable placeholder artwork going. The problem is I of course never got "real" artwork in the end to replace the placeholders. D'oh.

Not exactly a project worthy of any awards in unique design, heh. Good thing I don't fancy myself a game designer.

Anyway, I suppose I might as well start with a little background on how this project came to be.

Background and Timeline

For most of my side-projects, especially game projects, I find I tend to have a very strong Not Invented Here syndrome approach where I rather enjoy building up the groundwork myself rather then using some preexisting framework or whatnot that may be available. For game projects, this means that you will probably never find me using something like Unity or Unreal Engine or something like that. Lower-level libraries like SDL are a different story, especially if you want to target multiple platforms. In fact, I think something like SDL is really a sweet spot for people like me in that it provides just enough of an abstraction that I can easily target multiple platforms but build up my own engine from scratch if I so choose. In recent years, I've found myself becoming more accepting of using something for game projects that provides a little bit more abstraction, such as libGDX, while still not going to all-out Unity-levels of abstraction.

But back in 2011, I had been working on my own C/C++ framework for my game projects and it was just based on SDL for desktop PC builds. It was coming along well, and in early 2012 I actually left my job to start working on the project that would become Monster Defense full-time. I had grandiose ideas that I was going to somehow make it as an indie game developer. Heh. Well, that was part of the reason I left my job. The other big part was that I was getting a little bit fed up and noticed that I was really just coasting along at work so I guess this was my own way of shaking things up for myself. I think I knew in the back of my head at the time that there was absolutely no chance I was going to make it as any kind of remotely successful indie game developer. I have barely any artistic talent (at least, not enough to be able to draw my own complete set of game art assets) and as you can tell from the fairly uninspired "design" of Monster Defense described previously, I'm not exactly a game design genius.

Actually, I should point out that I was originally going to work on a completely different game. I even mocked up a promo image for it.

I don't now remember why I switched the project to Monster Defense instead. I think the reason might have been that I thought it would be an easier project to finish or something like that. Of course that wasn't true. Both project ideas were quite involved. But hindsight is (usually) 20/20...

I remember that within two or three months of quitting my job, I had fully accepted the fact that Monster Defense was going to just be a portfolio piece I would use to get a job in the games industry and that there was no way it was going to become a fully polished and published game that would make me money. Didn't really take long for reality to fully set in, right?

I continued working on it and around late fall or so of 2012, I think I had come to realize that I actually didn't want to work in the games industry. Instead, I was fine with game development just being a hobby. This was actually the second time in my life I had come to that same conclusion. The first was in 2005 after I had finished a two year college diploma and was about to commit to moving out west to attend the game development course at the Art Institute of Vancouver (apparently this has all changed significantly in the past decade or so, I don't think it exists in Vancouver anymore?). I re-thought the decision and ended up getting a job as a "normal" software developer instead.

Anyway, during summer and fall of 2012 I had started to notice that I was becoming less enthusiastic about working on Monster Defense as time went on. A big part of it was the realization that I was not ever going to be able to release it as a finished product using placeholder art assets as I was currently using. And I was still unsure exactly what was the best way to solve this problem. You could say that, obviously, recruiting an artist was the solution here. I was not so enthusiastic about that idea because the last time I worked on a game project in a team with an actual artist, the experience left a really bad taste in my mouth.

Around 2006/2007 or so, I was working on a Zombie survival / puzzle game for Nintendo DS (using homebrew development tools, like devkitARM) in a team of friends, one of whom was an artist and two others who also had some varying artistic talents as well. That project ended abruptly with our artist seemingly just disappearing (I actually never met him in person, but he was a friend of a friend and lived in Vancouver). In the end no artwork had actually been produced. At least, none that I ever saw. In fact, the only thing that had ever been produced during this failed project was a fairly basic 3D engine for the Nintendo DS (coded from the ground-up by yours truly) and a very, very overly ambitious game design document that detailed a plan we were honestly never going to come close to completing. The overly ambitious design is a different problem altogether and I think is a very common problem, but the artist randomly disappearing was hugely problematic and ruined whatever trust I may have had in a team composed mostly of friends being reliable. Actually, now that I think about it, this was the second time that I've had an artist disappear on me (the first was for a QBasic game project around 2001/2002 or so). Maybe the problem is me? Heh. *shrugs*

Very early test build of the Nintendo DS project. Bits of this code ended up being used in Monster Defense too!


But regardless, even if I had wanted to try finding an artist again to see how it went, I did not have much money left to pay for one and I was skeptical of being able to find one who would volunteer to help me out on the promise of splitting future profits once the game was ready to be sold. Especially since I no longer really had that end goal in mind anyway. I didn't want to be dishonest, recruiting someone on that premise when I no longer believed it would happen at all.

As a result the project basically ended right there during fall 2012 and in early 2013 I found a new full-time job. I continued working on Monster Defense in bits here and there during early 2013 but more just as "something to do."

The Project Itself and How I Built It

As mentioned earlier, Monster Defense was built from the ground up using SDL for desktop PC builds. I developed it using C/C++, in a "C with classes" coding style, targeting OpenGL ES 2.0 (or the equivalent desktop OpenGL feature-set). I built my own framework and game engine for this project. I also was targeting mobile devices through the use of the now defunct Marmalade SDK (formerly Airplay SDK).

Before SDL 2.0 really stabilized and ironed out their mobile device support, Marmalade SDK was really quite awesome I think. It provided an environment where you could code your game using a quite familiar feeling game loop, C-style, in your main() function. It had some higher-level graphics/audio/input abstraction support but you could also opt to just use OpenGL directly and ignore their own graphics library which is what I did.

I updated my game framework to support different platforms mainly via an abstraction provided in two interfaces, OperatingSystem and GameWindow. I had two implementations, one for SDL for desktop builds, SDLSystem and SDLGameWindow, and the other for Marmalade SDK for mobile builds, MarmaladeSystem and MarmaladeGameWindow. This platform abstraction carried on a little bit further with interfaces like Keyboard, Mouse, Touchscreen, File, and FileSystem.

I was pretty happy with this architecture. Maybe it was not perfect, but it seemed to work well enough for me. The game loop was handled in a base class called BaseGameApp which the game project itself would implement (what I always just called GameApp). The main() function just served to instantiate the OperatingSystem, GameWindow and GameApp classes and then called GameApp:Start() which would get the game loop running.

BaseGameApp provided access to the system objects mentioned above as you can probably imagine.

OperatingSystem* GetOperatingSystem();
GraphicsDevice* GetGraphicsDevice();
Keyboard* GetKeyboard();
Mouse* GetMouse();
Touchscreen* GetTouchscreen();
ContentManager* GetContentManager();

The ContentManager was an idea I took from XNA although I'm sure many other game frameworks use a similar approach. The content manager object took care of caching of loaded assets and loading them if they were not already when assets were requested.

Texture* texture = GetContentManager()->Get<Texture>("assets://textures/mytexture.png");

The above snippet would return a Texture* for the texture in the specified file. If it was not yet loaded it would be right then and there. Thusly, asset file paths were used as a unique identifier. In hindsight perhaps using numeric IDs to identify assets via the ContentManager might have been better. At any rate, what I ended up doing in an attempt to simplify re-use of common assets was to have a singleton class ContentCache that had methods like this on it:

Texture* GetUISkin();
Texture* GetGamePadButtons();
SpriteFont* GetTitleFont();
SpriteFont* GetFont();
SpriteFont* GetSmallFont();
SpriteFont* GetUIFont();
TextureAtlas* GetEnvironment();
TextureAtlas* GetParticles();
TextureAtlas* GetItems();
Texture* GetShadow();

Looking at it now, I feel like the addition of ContentCache highlighted perfectly how ContentManager was not providing the right level of abstraction and I probably should have merged the two. ContentManager just ended up being a fancy general-purpose thing which I did not really need. At least, not in that fully general-purpose way (I definitely did need the caching and re-use of loaded assets obviously).

Anyway, back to the game loop, which was implemented via callbacks in GameApp that were called every frame. Additionally, other important system event callbacks could be invoked for certain things, such as the window being minimized or the app running on the mobile device being paused.

void OnAppGainFocus();
void OnAppLostFocus();
void OnAppPause();
void OnAppResume();
bool OnInit();
void OnLoadGame();
void OnLostContext();
void OnNewContext();
void OnRender();
void OnResize();
void OnUpdate(float delta);

At the time I remember that this style of game loop, implemented via callbacks (in this case, the main callbacks being OnUpdate() and OnRender() of course) seeming to be "obvious" to me in a "well, duh, why wouldn't you do it like this" kind of way. I had been doing game projects (such as the aforementioned Nintendo DS project) in this style for a while already. Nowadays, I'm not so certain. It might be because of my recent return to "old-school coding" with MS-DOS, but I feel like today I would prefer (if at all possible) to just put everything into a simple while(true) { ... } type of loop. No fully separated update and render operations. I would still separate update and render calls so that updates generally all happen before rendering each frame as that only makes sense. But I feel like sometimes there are some grey areas and having the full, forced, separation makes you sometimes do silly things to work around the 1-5% of the time you need to do something weird. Plus I guess that I just like the simplicity of a simple loop. Meh.

However, one plus of using this game app object callbacks architecture was that it made it pretty simple to plug in a game state/process system which still allowed each state and process to react to the very same types of callbacks/events.

The idea with the game state/process system was that in a typical game there are multiple different states where the player needs to interact with the game in a very different way. For example, they launch the game, and a main menu appears. They make some choices and then the game starts. Now they are playing the game. They can pause the game which maybe brings up a different menu. Or they could open some status/inventory display during game play which requires them to interact with a UI of some sort.

Each of these things can be represented by a different state, and all of the states together are represented by a stack. And that is exactly what StateManager and GameState are for. StateManager has a bunch of useful methods for manipulating the stack of game states.

template<class T> T* Push();
template<class T> T* Push(const stl::string &name);
template<class T> T* Overlay();
template<class T> T* Overlay(const stl::string &name);
template<class T> T* SwapTopWith();
template<class T> T* SwapTopWith(const stl::string &name);
template<class T> T* SwapTopNonOverlayWith();
template<class T> T* SwapTopNonOverlayWith(const stl::string &name);
void Pop();
void PopTopNonOverlay();

The reason for the use of templates in most of these methods is so that the class type of the game state is passed but not the actual object instance itself since I guess I didn't want the caller to have to instantiate the object itself (I honestly don't recall my reasoning for that decision now).

GetStateManager()->Push<MainMenuState>();

The code in StateManager always felt overly complex to me but I do feel like the end result worked quite well. In addition to normal game states that could be pushed/popped or swapped, you could also use "overlay states." This essentially let you have the current "main" state running in the background while overlaying another state over it to allow some limited processing to continue happening in the "main" state, but where the overlay state was the one that was really the current state. The best example of this is where the player is in game and they open their inventory. You might want to overlay a menu UI over-top of the game screen which still continues to be rendered and even maybe with animations still continuing in the background, but you want to pause game logic (so enemies cannot attack the player while they are in their inventory, for example). This type of thing could be implemented by having the inventory game state be an "overlay state."

GameState objects implemented all the same basic callbacks/events that GameApp had in addition to having a few extras to allow them to react to manipulation of the game state stack.

void OnPush();
void OnPop();
void OnPause(BOOL dueToOverlay);
void OnResume(BOOL fromOverlay);
BOOL OnTransition(float delta, BOOL isTransitioningOut, BOOL started);

OnTransition() in particular was neat because it allowed game states to very simply do things like implement their own fading in/out or similar type of effects.

Now, game states were not by themselves enough. At least I didn't think so at the time. Very often you may want to do multiple things each frame while a game state is running and they must run together. The best example of this I think is during a GamePlayState where the player has full control over the gameplay, you probably also want to show some kind of game status (e.g. score, lives, health, etc). You could obviously just call whatever code to show this in your GamePlayState or you could do what I did and have each game state have it's own ProcessManager which containers a list of GameProcesses, and then have a StatusUI process do the game status display.

ProcessManager worked quite similarly to StateManager except that instead of a stack of processes, it was just a list. Each GameProcess added was always processed each frame in sequence.

Circling back to what I was talking about earlier with regards to the game loop and the pros/cons of implemented via some really simple while(true) { ... } with all the code right there in the body of the loop, or by doing it how I described here using callbacks in GameApp... I think that (at least to me) my StateManager and ProcessManager setup made a pretty strong case for sticking with this approach. In fact I liked this architecture so much that I kept it for the next game project I worked on in 2014, even porting it all to Java/libGDX in the process.

Another really important thing for a game to have is some sort of events system. I'm not actually talking about events from the operating system or input devices, etc, but game logic events. Such as when the timer runs out, the player gets hurt or even dies, the player picks up an item, etc. My framework had support for this too, and this was another aspect that I think worked well in practice but had some little things to cleanup in the future maybe.

In my event system, classes can subscribe to events by implementing EventListener and then registering themselves with the EventManager (which was managed by BaseGameApp). I can remember it was very important to me that game states and processes (and, well, really anything else) should be able to subscribe to events via code like the following:

ListenFor<IntroFinishedEvent>();
ListenFor<OptionsUpdatedEvent>();
ListenFor<QuitGameEvent>();
ListenFor<DespawnedEvent>();
ListenFor<TimerRanOutEvent>();

And then unsubscribe from events via StopListeningFor() called in a very similar way. And this is exactly the way it worked. Event handling happened in a Handle() method that any class implementing EventListener would need to provide.

BOOL GamePlayState::Handle(const Event *event)
{
    if (event->Is<QuitGameEvent>())
    {
        // do stuff
    }
    else if (event->Is<IntroFinishedEvent>())
    {
        // do other stuff
    }
    
    // ...
}

It was a very minor bit of syntax sugar, but I really remember the use of templates here to provide the information about what type of event to subscribe to, or here with the Is() method so it knows what to compare against, was a really important feature I wanted the API to have. *shrug* Looking back at it now, I do have to admit that I quite like the way it reads so I guess it was worth the effort.

Events were used quite a bit in Monster Defense. I had defined 35 different event types at the time I stopped working on the project. Definitely there would have been more had it continued. There were events for things like animation updates, player buffs, entity state changes, healing, damage, movement, score, weapon switching, timers, and other things.

Which brings us to entity management. I remember at this time there had been a number of articles written on the subject of "entity/component systems." I was initially intrigued by the idea as I was not liking the idea of going with some object hierarchy approach. I remember reading this article (and a number of others by the same author) and really liking the approach but that it clearly seemed to be missing some necessary extras and what I deemed "basic functionality." As well, I didn't like the idea of using ID's to represent entities and figured I'd instead just use an Entity object (but purely as a code convenience... it still would not be meant to contain any data directly).

class Entity
{
public:
    Entity(EntityManager *entityManager);
    virtual ~Entity();
    
    template<class T> T* Get() const;
    template<class T> T* Add();
    template<class T> void Remove();
    template<class T> BOOL Has() const;
    
    template<class T> BOOL WasCreatedUsingPreset() const;
    BOOL WasCreatedUsingPreset(ENTITYPRESET_TYPE type) const;
};

The Get(), Add(), Remove() and Has() methods all operated on Component objects which you attached to entities and these were the things that contained the actual data that belonged to the entity itself.

class Component
{
public:
    virtual ~Component();
    virtual void Reset();
};

As you can see from this, a base Component is incredibly simple and itself contains no data. The specific component types would actually have the data defined. For example, a PositionComponent to hold an entity's position in the game:

class PositionComponent : public Component
{
public:
    PositionComponent();
    void Reset();
    
    // the actual data this component holds
    Vector3 position;
};

ComponentSystem objects are registered with the EntityManager. Then each time during the game's OnUpdate() and OnRender() callbacks, each component system is called in turn.

When a component system is called to do it's processing (either during OnUpdate() or OnRender()), it queries the EntityManager for a list of all entities that contain a component it cares about. For example, the PhysicsSystem would fetch a list of entities that currently have a PhysicsComponent.

void PhysicsSystem::OnUpdate(float delta)
{
    EntityList list;

    GetEntityManager()->GetAllWith<PhysicsComponent>(list);
    for (EntityList::iterator i = list.begin(); i != list.end(); ++i)
    {
        Entity *entity = *i;
        PhysicsComponent *physics = entity->Get<PhysicsComponent>();

        // do stuff
    }
}

Generally, component systems would do their main processing loop based on entities having one "main" component type that that component system cared about, but component systems could very well retrieve other components from a given entity if needed (for example, the PhysicsComponent will need access to the entity's position via the PositionComponent in addition to whatever current physics state is present in its PhysicsComponent).

The entity system and event system also were integrated. Component systems were also EventListeners. For example, the PhysicsSystem would respond to JumpEvent and MoveForwardEvent amongst others. These types of events were EntityEvent subclasses which required an Entity object to be set so that the thing that listened to those events (almost always a ComponentSystem) would manipulate the source entity in response to the event.

MoveForwardEvent *moveEvent = new MoveForwardEvent(entity);
GetEventManager()->Queue(moveEvent);

Most of the entities that were created and managed in Monster Defense fit into the same categories and were re-used a lot. For example, there were a few types of zombie monsters that were constantly re-used. Same for power-up objects that the player could pick up. To handle this standardized creation of new entities, the addition of new entities via EntityManager needed to be passed off to an EntityPreset.

An EntityPreset had a very important Create() method which would be called by EntityManager when an entity was being Add()ed. It was then up to that Create() method to actually add the entity to the EntityManager and then return it. The MonsterPreset and ZombiePreset together give you an idea of how much manual component adding and manipulation was required to create an entity in Monster Defense (ZombiePreset is a subclass of MonsterPreset so when a zombie monster entity is added, both of their Create()'s get called).

Entity* MonsterPreset::Create(EntityPresetArgs *args)
{
    Entity *entity = GetEntityManager()->Add();
    
    entity->Add<AffectedByGravityComponent>();
    entity->Add<AnimationComponent>();
    entity->Add<AutoAnimationComponent>();
    entity->Add<BlobShadowComponent>();
    entity->Add<BoundingSphereComponent>();
    entity->Add<ColorComponent>()->color = COLOR_WHITE;
    entity->Add<LifeComponent>();
    entity->Add<NPCComponent>();
    entity->Add<OrientationXZComponent>();
    entity->Add<PhysicsComponent>();
    entity->Add<PositionComponent>();
    entity->Add<PushableComponent>();
    entity->Add<PusherComponent>();
    entity->Add<StateComponent>();
    entity->Add<BuffsComponent>();
    
    return entity;
}

Entity* ZombiePreset::Create(EntityPresetArgs *args)
{
    Entity *entity = MonsterPreset::Create(args);
    
    ContentManagerComponent *content = GetEntityManager()->GetGlobalComponent<ContentManagerComponent>();
    
    KeyframeMesh *mesh = content->content->Get<KeyframeMesh>("assets://characters/zombie.mesh");
    
    entity->Get<AnimationComponent>()
        ->AddSequence("idle", *mesh->GetAnimation("IDLE"), 0.05f)
        ->AddSequence("walk", *mesh->GetAnimation("WALK"), 0.02f)
        ->AddSequence("dead", *mesh->GetAnimation("KILLED"), 0.10f)
        ->AddSequence("punch", *mesh->GetAnimation("BASH_LEFT_2"), 0.1f);
        
    entity->Get<AutoAnimationComponent>()
        ->Set(ENTITYSTATE_IDLE, "idle")
        ->Set(ENTITYSTATE_WALKING, "walk")
        ->Set(ENTITYSTATE_DEAD, "dead", FALSE, TRUE)
        ->Set(ENTITYSTATE_PUNCHING, "punch", FALSE, TRUE);
        
    entity->Get<BoundingSphereComponent>()
        ->bounds.radius = 0.49f;

    PhysicsComponent *physics = entity->Get<PhysicsComponent>();
    physics->friction = FRICTION_NORMAL;
    physics->maxWalkSpeed = 6.0f;
    physics->walkingAcceleration = 4.0f;
    physics->SetBoundsRadius(0.49f);
    
    entity->Add<RenderOffsetComponent>()
        ->offset = Vector3(0.0f, -0.5f, 0.0f);

    KeyframeMeshComponent *keyframeMesh = entity->Add<KeyframeMeshComponent>();
    keyframeMesh->mesh = mesh;

    CanBeAttackedComponent *canBeAttacked = entity->Add<CanBeAttackedComponent>();
    canBeAttacked->byPlayer = TRUE;
    canBeAttacked->byNPC = TRUE;
    
    return entity;
}

Monster Defense had 65 different component types. Some of these were complex (like PhysicsComponent) and others served no purpose other then as a simple "marker" or type of glorified flag on the entity (such as AffectedByGravityComponent).

Now, a decision I am in hindsight a bit bothered by was that EntityPresets also sometimes subscribed to events and did processing on EntityEvents only when the entity in question was created by that same EntityPreset (when an entity was added using an EntityPreset the EntityManager would automatically attach an EntityPresetComponent to that entity to mark the EntityPreset type that was used to create it). Anyway, EntityPresets subscribed to stuff like DespawnedEvent, HealEvent, HurtEvent and KilledEvent so that common functionality that should occur for entities when these events occurred could be customized per entity-type. For example, when a bullet entity dies (KilledEvent) different things should happen then when a zombie entity dies. In the bullet's case, some particles should be created where it was last located at and nothing else. When a zombie dies, some smoke particles are created and a random chance for a power-up entity to be created needs to happen.

Looking back at this, I like the idea of the EntityPreset::Create() but I think that the other additions to EntityPresets made it a bit too complicated in practice. It's hard to put my finger on it exactly though, but I know if I were to start a new project today using this same entity/component system, I would probably spend a good long while thinking about a better way of organizing this.

However, in general I really liked the whole entity/component system concept. It felt very powerful to me. Especially with the physics system I had, I liked how easy it was to add full physics processing to basically anything. Added some smoke particles and didn't like them flying through solid walls? Fixed by adding a PhysicsComponent to it. Want to attach a "blob" (stupid simple black circle) shadow to some entity? Add a BlobShadowComponent. Want to make it so that one entity can push other entities around? Add a PusherComponent.

That all being said, I do feel like this also made the code feel a little bit more like spaghetti in some ways. It could be a bit harder to fully figure out how an entity was going to work once all it's components were added because the code for fully processing that entity was now spread out over (at most) 19 different component systems.

I won't be able to talk about every entity subsystem (of which there were many), but I do want to talk about the physics system a bit more because I spent so much freaking time on it. I think this took up at least half of my development time over the entire project, but it is hard to say for sure.

I remember initially investigating things like Bullet Physics and Open Dynamics Engine (ODE) and coming away from each of these feeling incredibly stupid. I never did particularly well in math 'nor physics classes in high school. I was at best a 'C' student in both subjects overall. Some years I did better then others. Of course, this is why things like the aforementioned two physics libraries exist... to make it possible for math "dummies" like myself to not have to fret over the scary details as much. Note the emphasized part of that last sentence. You do still need to know some stuff. Anyway, personally I felt like the documentation and examples for both of these libraries just flew over my head and left me almost clueless. I remember having a weak moment after investigating both of these and coming away empty handed where I almost decided that I really should have gone with something like Unity 3D instead from the very beginning ("Things like this must by one of the major reasons why these frameworks are so popular! I cannot possibly be the only one so stupid!"). I remember reading discussions at the time how the Bullet documentation was not the greatest so at least I didn't feel totally alone there.

Anyway, I decided to pick up a prototype I had worked on a few years earlier for a physics system that was based on the old Peroxide "Improved Collision detection and Response" article from 2003. I had it half working back then and figured I might see if I could fix the remaining problems and integrate it into my engine here for Monster Defense. I think after about a month of futzing about with this I became a victim of the sunk cost fallacy, unable to pull myself away from this and admit defeat. I just had to get it working.

I rather "enjoyed" spending lots of time working on problems with ramps. Of course, the single final level included in Monster Defense doesn't even have ramps in it, but hey, let's just ignore that fact. In particular I remember there was some bug that would only occur sometimes, where an entity that climbed up to the top of the ramp could suddenly disappear (in reality, they would be launched either upward or downward – I forgot which – at incredible velocity). This was due to some floating point inaccuracy that could cause a calculation to result in NaN and propagate into other calculations and cause mayhem. Took me a looong time to narrow that down.

Lots and lots of time was spent tweaking the "feel" of the physics system too, from the player's perspective. And I still don't think it's tweaked quite right. I remember reading something from Twitter at the time where an indie developer was saying something like how typically when a game's physics system feels just right, it's usually not the most accurate (real-world accurate that is). Like it's kind of "cartoon physics" in a way or something. I really agree with this. I definitely never nailed this either, heh. A lot of my time was spent on jump physics. Things that you never thing of, like how it should react when you jump up and hit a flat surface. Or you jump up and hit the corner of a ceiling edge. Or when you jump up and just as you're falling you catch the edge/lip of the top of a wall (there's a lingering "bug" in Monster Defense where you can double-jump in this scenario if you time it right, but the single level in it doesn't give you any opportunity to test this).

Test level for working on ramps, steps and other physics bugs.

One thing I did learn was that debugging physics problems by overlaying wireframe geometry onto the display was incredibly helpful. Looking at tons of numbers (so many you have to use 2 letter labels as in the above screenshot) is difficult. As well, stepping through with a debugger is not always the most helpful either.

The game world was represented using 3D tiles. Kind of Minecraft-inspired, where the world is subdivided into 16x16x16 TileChunks which are all part of a larger TileMap. Each grid component in the map being represented by a Tile which had a number of different properties. Special, optimized processing was present for Tiles which were just simple cubes. But it was also possible for tiles to be represented by arbitrary 3D meshes (ramps could be done in this way for example).

TileChunks needed to be turned into 3D meshes with a ChunkRenderer of which there was also an optional subclass, LitChunkVertexGenerator which could apply lighting effects, based on tiles that were defined to be light sources and would "spread" light to adjacent tiles (if there was empty space for light to pass though, or a tile was marked as not solid).

The full TileMap could be rendered by simply looping through all of the TileChunks in the map and rendering their pre-generated 3D meshes (which would each just be a VBO).

The use of a 3D tilemap for the game world made integration with the physics engine easier. Scanning the world for nearby world geometry to test for potential collisions with was made incredibly simple due to this. Once I did solve the seemingly insurmountable task of getting the physics engine to work decently enough, it really did feel like it all came together well. I remember when I finally got it all working, I just spent a day moving around one of my test worlds, running past the same set of walls and ramps and whatever other objects again and again, marveling at the fact that it actually worked. If I had to choose one single thing in my "programming career" that I worked on to date that gave me the most satisfaction or that I was most proud of, it would be this thing. Not this project (Monster Defense), but just getting this physics engine working as well as it is now, despite whatever bugs remain. I truthfully did not even think I would end up getting it working this well when I first began working on it.

There are a bunch of other things in Monster Defense that I could talk about in detail but nothing else feels like it is super worthy of writing lots of words about. One thing I feel that is missing that probably would have been added had I continued working on it was some sort of scripting integration, probably with Lua in particular since it's fairly easy to integrate with C. I think that using it to define entity behaviours and even creation of entities (versus the previously described EntityPreset system) would probably have been the best approach. Assuming of course that I could have arrived at a decent abstraction through Lua for interacting with the entity/component system. I suspect that adding/manipulating entity components through simple maps would have been the best way and is very likely something I will explore in future projects.

The last thing I think I will talk about is the graphics and math engine used in Monster Defense. There was a fair bit of inspiration taken from XNA here.

A GraphicsDevice is used to manage an OpenGL context, and allows Texture, Shader, VertexBuffer and IndexBuffer objects to be bound.

The integration between Shaders, VertexBuffers and IndexBuffers is something I was pretty proud of at the time, but am not sure how well it might hold up today (I've not really kept up to date on graphics APIs so I cannot say honestly).

VertexBuffer and IndexBuffer objects are both subclasses of BufferObject which handles the OpenGL details of VBO's or client-side buffers. Each of these two buffer subclasses had nice, easy to use methods for programmatically manipulating the buffer's contents, such as Move(relativeOffset), MoveTo(absoluteOffset), MoveNext(), MovePrevious(), SetCurrentPosition3(Vector3), GetCurrentPosition3(), etc. If the buffer was initially set to be backed by a VBO, using these "setter" methods would set a dirty flag and the next time the buffer was to be rendered by GraphicsDevice it would know to upload the buffer's data via OpenGL. A lot of this convenience functionality was definitely not efficient, but damned if it wasn't convenient. For pure performance, you obviously could just pre-set the buffer data and upload it once and then render it many times after that.

Example usage, initializing a VertexBuffer (part of some code to set up a wireframe grid mesh).

VERTEX_ATTRIBS attribs[] = {
    VERTEX_POS_3D,
    VERTEX_COLOR,
};

points = new VertexBuffer();
points->Initialize(attribs, 2, width * 2 + 2, BUFFEROBJECT_USAGE_STATIC);

for (uint i = 0; i < height + 1; ++i)
{
    points->SetPosition3((i * 2), -(width / 2.0f), 0.0f, i - (height / 2.0f));
    points->SetColor((i * 2), 1.0f, 1.0f, 1.0f);
    points->SetPosition3((i * 2) + 1, width / 2.0f, 0.0f, i - (height / 2.0f));
    points->SetColor((i * 2) + 1, 1.0f, 1.0f, 1.0f);
}

And then rendering via GraphicsDevice

SimpleColorShader *colorShader = graphicsDevice->GetSimpleColorShader();

graphicsDevice->Clear(0.25f, 0.5f, 1.0f, 1.0f); 

graphicsDevice->BindShader(colorShader);
colorShader->SetModelViewMatrix(modelViewMatrix;
colorShader->SetProjectionMatrix(projectionMatrix;

graphicsDevice->BindVertexBuffer(points);
graphicsDevice->RenderLines(0, points->GetNumElements() / 2);
graphicsDevice->UnbindVertexBuffer();

graphicsDevice->UnbindShader();

You can see here a Shader is used (a preset Shader instance in this case, one of several that the GraphicsDevice provides, but custom shaders can be used too). Missing is some of the boilerplate that you might see in other OpenGL code to map vertex attribute to the shader (like the position and color attributes we previously set up above). This is handled automatically through the VertexBuffers use of "standard vertex attributes" which Shader objects are aware of and know how to automatically bind. This is accomplished per-shader due to the nature of the actual GLES shader source code being able to use completely arbitrary names for the attributes. But it's still easy to set up, like in the constructor of the SimpleColorShader:

SimpleColorShader::SimpleColorShader()
    : StandardShader()
{
    BOOL result = LoadCompileAndLinkInlineSources(m_vertexShaderSource, m_fragmentShaderSource);
    ASSERT(result == TRUE);

    MapAttributeToStandardAttribType("a_position", VERTEX_POS_3D);
    MapAttributeToStandardAttribType("a_color", VERTEX_COLOR);
}

Where a_position and a_color are the actual attribute names used in the GLES source code.

An additional convenience that Shader objects provided was the ability to let you pre-set uniform values (even before the Shader is bound). If a uniform value is set on a shader object, it is simply cached, and the cache flushed (uniforms set via OpenGL) when the shader is bound. This is a minor thing, but it ended up being handy in a few places where I suddenly didn't need to care about this kind of thing. Probably not the best idea for performance-reasons, but I was not (and still am not) working on anything remotely cutting-edge.

While my game framework did have some limited support for skeletal animation with 3D models, it was half-baked and only ever worked with one or two test models. I fell back to vertex interpolation for animation (via good ol' Quake 2 MD2 models) as a result of giving up on trying to extract skeletal animation data out of Autodesk FBX models via their SDK. The FBX file format feels like PDF in that it is a bit of a "kitchen sink" format and as such, it hard to grasp fully for the uninitiated.

Final Thoughts

So, Monster Defense was of course never finished and probably won't be, at least not in it's current form. It was fun working on, but I am disappointed that it was never finished.

Technology-wise, some final notes:

  • While it was fun building everything myself from scratch, it ended up taking a ton of effort and this was probably the single biggest reason it was never finished.
  • Having said that, if I could it over again, I would still build everything from scratch. I learnt a lot.
  • Inefficiencies galore! I could get away with it here because I was by no means pushing any boundaries, but it's still (in my opinion) important to recognize where you are taking inefficient shortcuts. Some of these have been noted above, but one thing that still bothers me is that I was lazy about memory allocations. In particular I was all too willing to just arbitrarily new() up memory whenever I needed it.
  • Organization of the code, in hindsight after not looking at it for six years, feels messy. I find myself jumping around a lot trying to trace through how things worked.
  • I had some terrible STACK_TRACE macro I used everywhere which is a holdover from the Nintendo DS origins for a lot of this code where the debugging facilities are much more crude. I don't know why I kept this around here in Monster Defense. Also, ASSERT()ing after every allocation was a bit silly (again, another holdover from the Nintendo DS).
  • I will probably get shunned or banished as a programmer for this next point: I would probably consider using global singletons for a lot of "system"-type objects. For example, my ContentManager, StateManager, EntityManager, EventManager ... almost anything with "Manager" in the name I guess. I just don't see the value in having to pass these things around when they are actually global state. I don't see the value in hiding from this fact.

In late 2013 and through 2014 I ported much of this game framework code to Java using libGDX and put together the beginnings of a worthy successor.

Monster Arena Defense

I cannot currently release this as-is because the 3D model assets used are paid assets so throwing it up on Github would not be a good idea (I could do it with the code, but you would not be able to run it). However, I did release a sample demo of the underlying engine in 2014.

In this project, I did not resolve all my concerns with this game framework as noted in this post. But I did confirm a lot of them, heh. In particular the spaghetti code feel of a lot of the entity system architecture. It felt a lot worse in this project (but there were a lot more entity types...).

Perhaps there will be a "take 3" on this project in the future. Or I may just bring it back to MS-DOS. Maybe!

Bad eBay Seller Experience: szubaark

June 7, 2017 rambling ebay

I've just concluded a pretty terrible experience with an eBay seller szubaark who is either known as Wes Szuba or Arek Szuba and is either from Florida or Michigan (the confusion is because my PayPal transaction was with an "Arek Szuba", but the return address on the two packages I ended up getting from him was from a "Wes Szuba." Both shipped from Florida, but the eBay auction's location clearly said Michigan. I don't really know what's going on there to be honest. Maybe two people, possibly family members, sharing the same eBay account?). This individual sold me some a Commodore 64 system that was not as described (it didn't work properly right from the get-go) and ended up blaming me saying that I had someone broken it. And to top it all off, he ended up getting away with it.

I don't like to write stuff like this that has such a negative tone, but this experience has left such a sour taste in my mouth and the fact that he ended up getting away with it scot-free... I feel like I have no other recourse now but to write about it. In the very unlikely chance that someone in the future is considering dealing with him and happens to google his name first and finds this, well, I hope it helps them out. Since he essentially runs a shop on eBay (over 1000 feedback), consider this a review of his shop just like any other review of a store you may find elsewhere.

On May 6th, I bought a Commodore 64 "complete system" package from szubaark on eBay. This was fairly expensive for such a system, but when you look at Commodore 64 systems for sale, they very typically are not "complete" and even if they are, they often have not been tested at all, or at the most someone has plugged it in and verified that the power light turns on... not even verifying functionality by plugging it into a TV. These types of auctions go for far less than the one I bought. But I was willing to pay a premium for a fully working and tested unit. There weren't many on eBay at the time and this one from szubaark was even modded with JiffyDOS which seemed like a nice perk (saves me the trouble from doing such a mod myself down the road!).

I can hear you now: "Yikes! That's expensive!" Yup, but again, when you look at complete systems for sale that have been tested and verified working, they are all typically around that ballpark (though this was definitely near the top-end of that range).

On May 17th, it arrived. Packaging all looked good to me, no need to believe it had been beaten around during shipping or anything of the sort. I opened up the package and everything that was mentioned in the auction was present and exactly as pictured.

I proceeded to plug it in (just the Commodore 64, I actually to this day have not tried the floppy drive at all). It powered on and looked to be all good. I had previously bought the Commodore 64 user's guide and programmer's reference guide and had them both in hand and excited to be able to try fiddling around with old school BASIC for the first time in many, many years I began typing in some simple examples from the user's guide. I was in the middle of typing in one such example (to do with sound) and it froze. The screen didn't change, it just froze completely. Uh oh. I power cycled (waiting several seconds between powering off and then on) and it booted up fine again. I once again tried typing in the same BASIC program. Froze again. Much quicker this time (within 30 seconds or so). Though this time the screen cleared to a simple blue with blue border screen without any text or other characters visible at all. After several more crashes like this and power cycles, it began booting up to a plain blue with blue border screen and nothing else.

Annoyed, I contacted szubaark via eBay:

Hello,

I just received this today and it's become quite clear to me that it's not in working order (packaging was fine btw)

It powers on and sometimes seems to work alright (I was able to run a few short BASIC programs successfully after typing them in). But somewhat frequently it seems to crap out and freezes on an empty screen as seen here: http://imgur.com/IBO4Ezg

It doesn't crash consistently. Once it did while I was in the middle of typing a small program in from the user guide and I simply turned it on and off again and re-tried to type it in and was able to finish it successfully the second time.

When it does crash like this, I have no option but to power cycle it. Nothing else (pressing any keys, etc) has any effect whatsoever. However, sometimes when it has crashed and I power cycle it doesn't power on correctly. It boots up to that same blank screen as shown in the picture I linked above. In fact right now it's doing that.

Anyway, let me know your thoughts.

Thanks

A couple hours later I got a response:

hi

I was testing it all week and worked every time but this is 35+ year electronics and anything can go wrong at anytime..

Do you have a cartridge? can you put it in and see if boots?

also plug/unplug Video cable and PSU

Not exactly an encouraging way to start out a reply from a seller who claims to have sold me a "tested and working" Commodore 64. Sure, it's quite old, but you supposedly had tested it. It randomly failing a mere 2-3 minutes after I take it out of the box seems unlikely to me. I would suspect that the simpler and far more likely explanation is that it simply was not tested very thoroughly (if at all).

We exchanged a few more messages and he reiterated that he had tested it and mentioned that "never had any complaints until now from you...so im curious what could have gone wrong with it and right out the box"

In between waiting for responses, I had been googling for more information about common failures with Commodore 64's and what possible fixes there might be and really just trying to get a better idea of what was going on. Of course, there's a wealth of information out there and I discovered many helpful sites and forum posts, etc. In my case it was hard to be 100% certain because I unfortunately did not have a cartridge to try booting from, but it did seem like either the BASIC ROM or PLA was the culprit and would need to be replaced. To quote from one such source

U3 901226-01 BASIC ROM
Blank screen w/ border. Cartridge works.

...

U17 906114-01 (82S100PLA) PLA
Blank screen, no border. It can produce colored screen or flashing color garbage instead of startup screen. It can cause intermittant loss of cursor, screen freeze and/or program crashes after warmup and it can put random characters on screen. This chip normally runs hot. It is the most common chip to fail in the C64 because it runs hot normally, like the SID.

I've bolded the parts that were relevant to what I had seen.

I sent him the following:

Given that I had to pay a $50 customs fee which I wouldn't be able to get back without a lot of time and effort (I looked up the process just now), plus the cost of re-shipping it back to you, PLUS the fact that the C64 at least does power on and the sound does seem to work (the little power on "chime" is audible), I've been considering the feasibility of replacing something that is broken / gone bad inside it.

I did some quick googling about possible problems when you get a blue screen on power on and it seems like some likely culprits are the BASIC ROM or PLA IC's might need to be replaced?

I'm curious if you know anything about this? Figured I'd ask you since (I guess?) you did the JiffyDOS mod on this? Figure you know a thing or two about repairing C64s.

It's been a while since I've done any soldering but it would probably come back quick, but I'd want to practice first.

Obviously goes without saying that if I were to go the self-repair route, that is all at my own risk. Still considering the idea anyway.

First off, regarding that last paragraph, I want to emphasize right now that I never even got around to trying to replace the ROM. Even now as I write this. The Commodore 64 system he sold me has sat entirely unmodified by me from the time he sold it to me until today as I write this very post. The most I've done with it is plug it in and power it on to try using it normally.

Second off, I want to say that I realize now in hindsight what an absolute mistake it was for me to send this. I should have requested a return and refund instead and have been done with it. All the googling and reading I had done though while waiting for his responses had unfortunately planted the idea in my head that it would be a simple repair and that it would be no problem at all and certainly a lot less effort for both of us. Also at this time I did not know what eBay's policies were regarding returns. I wish I had spent the time to look it up, but I incorrectly assumed that I would have to pay return shipping (which would be expensive and a pain in the ass). Thus it really did seem like the most logical course of action was to do a simple ROM replacement. How wrong I was...

Anyway... he seemed positive about this course of action and even offered to send me a replacement BASIC ROM and Kickman cart. Note that I didn't ask for this at all, he was the first to offer it. In fact, when I received his message offering to send me that stuff I was in mid-search looking for places where I could buy those items. I accepted his offer and that was that... for a while.

I continued to do a bit of reading on the side now and then while waiting for the package to arrive. I eventually read about diagnostic cartridges that people had created for diagnosis hardware issues with Commodore 64s. I ended up buying one from COREi64 figured that it would probably be much more useful to have then having to guess at what is wrong from weather a Kickman cart does or does not boot when it arrived.

On May 25th, I received another smaller package from him, but didn't get a chance to open it until the 27th. The diagnostic cart I had bought also arrived that same week luckily. When I opened the package szubaark sent, I found 3 EPROM chips, a BASIC ROM chip and a Kickman cart. There was a note included which said "some spare EPROMs for you :)".

I have no idea why he would send me spare EPROM chips. I don't own an EPROM programmer, nor had either of us even mentioned anything about EPROMs in any of our correspondence to that point. Him sending me EPROMs was ultimately not helpful at all and served no purpose.

Confused, I plugged in the Kickman cart and turned on the Commodore 64. It booted straight to Kickman as one would expect, though the audio was not working 100% right. There was a somewhat loud constant tone that never ceased right from bootup. The game's sounds would continue to play over it, but clearly it was not working perfectly.

Now somewhat annoyed, I plugged in the brand new diagnostic cart I had bought. I became even more annoyed. It indicated that the Kernel ROM was bad, that the CIA chips (U1 and U2) were bad and that the SID (U18) was bad. As if that wasn't bad enough, over the course of five or so minutes each time I re-ran the tests some new chip came up as bad. This was the final test result that it seemed to "stabilize" at:

Ignore the "bad" test results shown on the right. They show bad because I do not have a test harness hooked up that is required to test those ports. The only test results that really matter are the ones on the left and bottom.

So we can see that eventually the BASIC ROM did also come up as bad, and that now also the PLA was coming up as bad. Also the fact that the tests seemed to "get worse" as the system was turned on for longer seemed to prove that at least some components were probably failing at least in part due to heat (maybe?). At least that was one of my theories. Certainly heat does seem to be a common cause for a PLA failure. And if szubaark's lousy testing was only comprised of a 30 second power on test or something similar, he definitely would not have noticed a problem. Thus bringing me back to my original conclusion that he did not at all thoroughly test a 35+ year old "tested and working" Commodore 64 before selling it to me.

I sent the following:

Hello,

The cart and rom chips you sent arrived the other day and I finally had a chance to try it out today and the cart did boot fine, though the audio seems off compared to what I'm seeing from looking at Youtube videos of the same game. There is a constant loud tone that never goes away (and is there immediately from power-on), but the rest of the game's sounds play fine despite this.

A couple days after you sent that package, I came across forum posts mentioning a "C64 Diagnostic Cart (586220)" and was able to find someone selling one near me and bought one. That also arrived the other day too, and this is the result: http://i.imgur.com/TgMJSqd.jpg

Ignore the casette, control, serial, and user ports as from what I understand they'll show as "bad" simply because I do not have the diagnostic hardware hooked up that is required to test those.

However, the remaining tests are pretty grim. The Kernel and BASIC ROMs are bad, in addition to the PLA, CIA and SID.
(Please note that at this point I've NOT touched the replacement ROM chip you sent, I've done no modifications to the C64 at all and it has simply sat in a box since the day it first arrived and I discovered the problems with it originally).

Frankly this seems to be a lost cause. It's obvious that there are numerous issues with this C64 you sold me and I'm sorry to be blunt, but I seriously call into question the quality of your pre-sale tests. I'm going to have to scrounge around for other C64's looking for replacements which is not something I really wanted to be doing from the get-go at least. That was the whole reason I was willing to pay your somewhat-above-average price for a tested and working unit.

Please confirm for me if the return address on the package you sent is ok for me to send back the Kickman cartridge to.

He sent me two replies in a row. The first:

Hi
The diagnostic cartridges are barely 50\50..and if you have a bad chip other chips like kernal,pla,mcu will show bad also
How can u have bad pla when the cartridge boots?

U just need to replace the chip i sent you and you will be fine

Just ignore the diagnostic cartridges they are worthless in my opinion

And the second:

The sound issue will be fine once u replace the basic chip and load from disk drive

Again how can u have bad pla.kernal.cia when the computer boots fine now with kickman cartridge?

At this point I was seriously starting to question his knowledge level when it came to Commodore 64 repair and modding. He had some correct information, but even I knew he was saying some incorrect things, probably because he was starting to realize he had sold me something that he had not tested that well (or perhaps not at all).

I turned to the Lemon64 forums and posted the full story so far and asked for a second opinion. I got replies saying roughly what I expected, people logically suggesting to verify the power supply and mentioning that socketed IC chips could potentially cause problems. I proceeded to open up the Commodore 64 simply to see what I was dealing with internally, not to make any changes yet:

Not exactly what I had expected. A very "homebrew" looking modding job. When I posted this picture to the forum topic on Lemon64, I got a couple replies which confirmed my fears:

That board shown has a (imo crappy) EPROM PLA replacment as well as a SID replacement. I'd be quite mad if I got that in an ebay sale for a fully working unit. It basically has a old junk SwinSID SID chip replacement and a not-fully-compatible PLA replacement chip in it. The Kernal ROM is also a hacked replacement. This may explain the problems you are having. You may want to contact the seller about getting a proper PLA chip (or PLAnkton) and a new kernal at the very least. That SID chip will work but not 100%.

And:

The KERNAL is hacked up for the JiffyDOS install. I bet the 1541 looks much the same.

It's extremely shady to sell a C64 without disclosing that it has a fake SID.

Given your situation with shipping, I'd open an "item not as described" dispute and ask for enough money back to get a real SID and good quality repro PLA.

sigh

I was really angry at this point. I slept on it and woke up the next morning, still upset and wrote this message to szubaark (based partially on the advice I had gotten from the users on Lemon64) that I'm not particularly proud of. Re-reading it now, it has a waaay more negative and demanding tone then I really intended:

Hello,

After talking with multiple people knowledgeable about C64 hardware and modding, I've received unanimous agreement that I should open an eBay case for this as an item "not as described" (defective) and get a full refund so there is overwhelming evidence that this is the case. I was not aware that eBay's policy was that the seller pays full shipping in this case (see http://pages.ebay.com/help/policies/money-back-guarantee.html).

However, I don't think that's necessary IF AND ONLY IF you're willing to pay for replacement chips that are all obviously not working. Since the diagnostic tests (which are not worthless as you claim, but do need to be taken in proper context) indicate that the Kernel ROM, PLA and SID have problems, I would ask that you provide a partial refund to cover the costs of getting replacements for these chips. This comes from multiple recommendations from my discussions with other people about this issue.

Replacement chip costs (all in USD):

PLAnkton - $17.50
JiffyDOS Kernel ROM - $20.00
Original SID - $40.00

Total $77.50 US

Keep in mind that YOU advertised this sale as for a "tested and working" Commodore 64. It is QUITE CLEARLY not working. Not only that, but you explicitly advertised working sound and shipped it with a SwinSID without mentioning so, which is pretty sketchy honestly. SwinSID is not well regarded and I suspect that that is exactly why you didn't mention it in the auction description.

I'm not willing to do a bunch of back and forth with you on this. The solution I've provided you with is cheaper than you paying for my return shipping. I am probably foolish for offering you this alternative, but I figure it will save us both a lot of time.

If you provide a partial refund for the amount indicated above, I will leave you positive feedback and that will be that.

If you refuse I will be forced to open an eBay case. If I don't receive a response from you in a couple days, I will also then be forced to open an eBay case.

Again, this is really not one of my better moments. It reads more like a demand then anything. I don't think what I was asking for was unreasonable at all considering that if I had done what I should have done right from the start, he would have had to pay more then what I was asking (the return shipping would have been a fair bit more than the amount I quoted in my message). However, there is a correct way to ask for that and then there is what I wrote above... not good.

Unbeknownst to me, this message of mine was actually against one of eBay's policies: feedback extortion. We'll come back to that later.

I received three replies in a row a few hours later:

Look im trying to be nice here. I already send you extras such as extra chips and cartridge. Now you asking me to send you even more chips(money).

I never did any modifications to that computer except FIX It and added new power supply. The Sid or as you call it swinsid and pla were already there. All i know you could have another defective commodore 64 and you trying to fix with me paying for it.

I have 100% feedback on my sales so when you started complaining i realized you could he trouble.

At this point i dont know what you did to this computer or what chips you switched and stop blaming me
That computer wad 100% functional when i shipped it to you and you just trying to get it for free
You keep asking me to ship you extra chips which i did and now asking for refund money..

I really just want to be done with you and move on

I'll give him that he did always come across as nice-ish. I was the only one of us writing mean sounding messages, though frankly, given that he had sold me a broken Commodore 64, I feel at least a tiny bit entitled to have a bit of a bad mood. I think any disgruntled customer would feel the same. But even still, I was going overboard in some of my messages and I do feel badly about that.

That said, this first reply of his does not inspire me with confidence. He now claims that he didn't do all of the modifications that are present in this system and seems to not even know what one of the modified chips (a SwinSID) was. Which is obviously a lie I found out as a bit later a user on Lemon64 found an old post of his which clearly shows he does in fact know what a SwinSID is and seems to just be trying to play dumb here.

Then he continues on now trying to blame me for breaking it. Wow. I should have honestly expected it from the way his very first message to me started (quoted near the beginning of this post).

He mentions his 100% feedback rating, and honestly based on my experience with him and how he was trying to deflect blame, I honestly had no idea how he has maintained that rating. Luck, I imagine.

Second response from him:

Also you never asked to open up this computer nor did i state anywhere in my description anything about modifications or different chips...

Well, actually, he did really give me permission to open it up. Even though I never actually got to the point where I was going to do the BASIC ROM replacement, we had both discussed it and agreed that that was the correct course of action and he had offered and then willingly sent me a BASIC ROM chip so I could replace it! If I was not supposed to open the Commodore 64 up, then what was I supposed to do with the ROM replacement had sent me? Just sit there and look at it? There isn't exactly any complex mental gymnastics going on here to arrive at this conclusion, this is all pretty simple stuff I think!

And actually he is kind of wrong when he says he didn't state anywhere in his description about modded chips. The auction was clearly for a system with JiffyDOS installed, which is by definition a mod and must be installed via a modded chip. Again, this is not rocket science here.

He's really reaching low to try and shove the blame back on me.

Third and final reply from him:

This is a hobby for that allows me to buy other Commodore hardware like cmd harddrives and you really need to stop sending me messages every week asking for more stuff or money..
I see the game you trying to play...
Im done with you..

I sent him exactly one message asking for money. He also sent me the BASIC ROM and Kickman cart for free and without me requesting it. When he offered to send it to me I was already googling around for places I could buy them from. And aside from an overly nasty tone as I've mentioned previously that I'm not proud of, I actually think that my request for a partial refund was not unreasonably considering that I was well within my right to request a full refund which would have cost him more. The only things I ever asked him explicitly for was a partial refund to repair the broken chips that were not working in this "tested and working" Commodore 64 and also for confirmation of his address so I could return the Kickman cart to him! (see a bit above if you missed that part in one of my responses to him). I never got that confirmation by the way.

So I ended up opening a case for an "item not as described."

Then about 40 minutes later I stupidly closed the case. I had mulling over it and was feeling really angry overall at how szubaark had been trying to shift the blame back on me to weasel his way out of this. It suddenly occurred to me that even if I did initiate a return he would probably eventually receive the returned package and make up something along the lines of how I broke it somehow or switched it with another broken Commodore 64 or some other made up thing and initiate a claim himself with eBay. Basically, I figured a return would just start a whole bunch more issues. So I closed the return.

I then left him negative feedback and even managed to squeeze in a URL to the Lemon64 forum post I had made describing the whole ordeal. And that was that... or so I thought.

Every couple days, I took a peek at his eBay feedback page to see if he ever posted a response to my negative feedback, figuring that he would and try to claim I had broke it instead and it was all my fault or something.

Well, yesterday, I discovered the feedback I left him had been removed! What? I called eBay to find out what had happened. I found out that he had reported me! The irony! I don't know exactly what he reported me for, but eBay's findings were that I had violated their policy on feedback extortion. I guess technically I had given that really terribly worded message I sent to him, but I find it really quite disappointing that eBay will essentially allow a seller like szubaark who had obviously sold something "not as described" get away with it scot-free. I don't know how eBay looks at user reports such as in this case, but it does seem like cherry picking key phrases out of context and "resolving" the case prematurely.

Well, that is that then. Nothing more to be done. As I said above, I can only hope that at some point in the future someone either a) does a random search by name for this guy and finds my post before decided to engage in a transaction with him and ultimately decides not to, or b) learns something from my experience about how to better deal with cases like this as a buyer: When you buy something and it arrives broken or otherwise "not as described" don't entertain the idea of fixing it or any other alternative. Return it and be done with it. Save yourself the headache!

This post has been extremely long and I honestly hope to never post anything like it again. I've been an eBay user since 2003 and this is the first bad experience I've had on it. Strangely, I actually do think I feel better for having written this all out even though it ultimately will accomplish nothing. Or maybe lightening my mood was the whole point after all. Heh.