A Delayed Update

Wow, it’s been awhile. I apologize to anyone who visited the page and it wouldn’t load; it was offline nearly a month due to an Amazon update that rebooted the server.

So it’s been nine months, and I haven’t gotten a lot done on any projects. My roguelike is still in a design phase. Part of what is fun about developing games is when you can make it your own, and I’m still kind of seeking how to do that.

I also ran into an unexpected problem which was when I started playing various Roguelikes to get a feel for them I… wasn’t really having fun with them. Some were TOO random and others the sameiness of every iteration wore me down. It’s possible I’m just playing the wrong ones, but it made it difficult to imagine the kind of game I’d want to play. Maybe I’m just NOT a fan of roguelikes?

So as a result, a few months back, I got distracted by another project idea. I fired up my TI emulator and played a full 10-level game of Tunnels of Doom. As I played, I noticed flaws that always irked me. A mistaken keystroke causes you to lose your turn. Canceling a ranged attack ends your turn, rather than letting you do something else. When monsters try and fail to attack or use special attacks and they still “flinch” on screen or you briefly see the description of the attack despite it not happening. And worst, the incredibly slow generation of the 3D view, with visible “building” going on.

Then I found a guide on the file structure for Tunnels of Doom, written by Michael Vepraukas. This proved to be a gold mine, showing exactly how the game file (which is really just a 12k database) was structured. And it got me thinking, what if I could write a new engine that could replicate the original game engine but with refinements and corrections?

The trick with this is that Tunnels of Doom is not, in fact, written in TMS9900 assembly.

The TI-99/4a was originally envisioned as a console game machine. So to protect their cartridges, Texas Instruments decided to develop their own 8-bit proprietary language called GPL. (Graphics Programming Language)

No actual documentation on it was ever released to the public but 99’ers backward-engineered it awhile ago. The language is much more compact since it’s built for the specific hardware in the TI, namely the video chip. For an example, there’s a literal two-byte command that will fill the screen with the character of your choice. But it runs much slower than assembly.

So I started work on a new cartridge, which would require the 32K expansion, and with my own interpretation of the engine. (My forum post on AtariAge shows the progress.) I got the opening title screen and music playing, loading the game file into memory, and basic menu construction. But then I got bogged down…

Just a bit too busy…

It turned out that for memory reasons, the game file stores all potential ten levels of the dungeon but with no actual corridors, only room locations. Each time you go up or down a level, the corridors for the map are procedurally generated. This is cleverly concealed by playing the title music’s 2nd voice by itself as you transition levels so the long pause seems more like a dramatic scene change.

Kevin Kinney spoke with someone on a blog about the algorithm awhile back. He used empty set logic to try and ensure every room was connected to another room, but the exact algorithm is unknown. Vertical aligned rooms are always connected but how horizontal were determined was a mystery. While it wasn’t necessary to perfectly implement it, I didn’t want it to be so significantly different as to alter the look of the game.

I managed to come up with a decent algorithm (better than the one in the shot!) but my interest waned. I do want to pick this one back up again but I find trying to interpret other’s work to not be as fun as making your own.

I did do some experimentation on a full-screen scrolling view, with 16×16 pixel size blocks. If I ever do a sequel for Realms of Antiquity, I’d like to have bigger characters to align with the classic Ultimas as well as give me better graphic resolution. This is a bit of a challenge on the TI-99/4a’s video architecture.

To add a bitmap mode to their existing video chip, TI engineers basically just expanded what was already there; the ASCII character table was expanded to three, and a matching set of three color tables is in the top half of video memory. The screen table still exists, but each of the three table’s 256 characters are only applicable in their section of the screen.

So that means a full screen bitmap scroll is VERY hard to do. Because video memory is only written to/read from a single byte port, you can’t keep the screen static and only update the tables. Pushing 12K in two separate sequences would be visually terrible. (All video RAM on the TI is only accessed by the CPU through a port.)

So I came up with a method. First I shrunk the window to 26×18, using border characters and space around it, which means I would have enough spare characters per section to use the ones NOT on screen as buffer characters to put the new character patterns into. That way it would be a clean transition. So besides an initial screen build, the only updated tiles are those on the edge of the direction you move. The screen characters are also updated since an existing character also moves accordingly.

The result? Pretty good! The fact it’s only moving one character and not two at first I thought a problem, but I realized it looks smoother to do that, and you could even do a 2-step move every time you move. Without any time delays it moves pretty fast as well, although vertically a bit slower.

There are still issues to solve though. How to do animation? What about moving objects like NPC’s? Line of sight? Lighting? The prototype works well enough but there’s a lot of work to get it right. Like other projects, this one fell by the wayside.

So, basically, Ive been gaming rather than making games. Fallout 76 is a favorite, and every new season gets better. The recent surge of popularity thanks to the TV show was also good to see. And after Rings of Power season 2 I got the urge to play Lord of the Rings Online again. I had purchased a lifetime account almost twenty years ago, and quit when they went free-to-play. After resurrecting my account and downloading the client, I found my VIP status was intact and I had received 500 store points a month for over a decade. So I had over 80k points! So been playing that again as well.

But, I recently discovered a new C64 CRPG in the works, by Sarah Jane Avory. She’s posting updates on Twitter about it frequently, and I’m amazed at how quickly things are moving along. It inspired me to get thinking again about coding games and making things.

So stay tuned!

Posted in Assembly, Coding, CRPG, Design, Gaming, Personal, Roguelike, TI-99/4a | 1 Comment

Character and System Developing

Happy 2024 to all! Another year, another effort to get myself back into developing games over playing them. 🙂

I received Baldur’s Gate 3 for Christmas, which is VERY good. I’ve also been a steady player and fan of Fallout 76, which continues to add new content and make small changes to make the game more accessible to new players. (The most recent update added small faction bases near the starting location to introduce you to the various groups early on.)

And I recently started playing Animal Crossing again after around two years; my villagers acted like they had seen a ghost when they saw me! That usurious little bastard Tom Nook at least had no new things to put me in debt with… (Anyone think AC needs an enemy? Maybe an evil counterpart to Tom Nook, who’s plans and projects constantly fail, but he’s too much the narcissist to stop. And his name will be… Elon Muskrat!)

But, I did play enough Roguelikes to come to some conclusions on things character and system related with my TI-99/4a Roguelike:

  • Moldvay’s D&D Basic Book

    Pretty much all of them directly rip off D&D or AD&D rules sets from the early to mid 80’s. This makes sense; they were using what everyone was familiar with. There is small adjustments here and there to accommodate the computer but it’s otherwise the same design: six ability scores, hit points, classes, levels, experience, etc.

  • No matter how much variety of procedural generation you put in, sooner or later your system becomes predictable. You can even see this in modern PG games like No Man’s Sky. After awhile there’s no real surprises and it’s a lot of same-old same old…
  • Curiously, the early Roguelikes stick to a linear “floor 1 to floor 2” approach. I’ve decided to have the option of forking levels. Which is, funny enough, easy to generate as unbalanced binary trees. This creates some new stress where you may go down several levels on one fork and have to traverse back again. (This entry edited due to Ontor, see below)
  • Almost all Roguelike’s interactions with NPC’s are combat. It’s not easy to envision, but I like the idea of having monsters who aren’t immediately hostile that you can negotiate and carry a short conversation with. And even engage in trade and barter. More on that later…
  • Related to above, money is usually only used to gain XP, if the system is using the old-school “gold for XP” approach every edition of D&D used prior to 2nd Edition AD&D. Having it be a useful resource for trade and other things would be cool.
  • All Roguelikes are turn-based. I think I’ll do the same. I had a moment of “Well maybe real-time would be kind of interesting…” but generally if you go RT, then the game becomes more about quick thinking and being good with a controller or keys, rather than a game of tactical thinking and consideration. I much prefer the latter.
  • Perma-death… Again, this is a staple of Roguelikes. But I am not a fan of this. As a middle-aged gamer, my time is valuable. I don’t want to spend it playing a game where a single random factor can end it and waste hours of play. Plus, most gamers cheat by backing up saved files. So why have the mechanic if it can be circumvented? What I may do instead is have a “limited” number of reloads based on difficulty level, with standard being unlimited.
  • Dungeon/cavern generation. I’ve some working models on both of these, but I realize I need the character system defined to really give the game the depth it needs first.

So I have tentatively started mapping out my “character system”, by which the player can advance and get more powerful. As part of this, the algorithms for attacking, defending, damaging, and so forth get created as well.

Roles (or Classes)

Right now, my plan is to have four roles:

  • Warrior
  • Wizard
  • Rogue
  • Adventurer

The first three are self-explanatory, they encompass the typical range of abilities for fighting, spell-casting, and skill usage. The last one, the Adventurer, is a blank slate which the player can mold into the character of their liking.

What, no cleric? Yeah, I’m not a fan of divine magic, clerics, or gods in my computer games. You can see this in Realms of Antiquity; I prefer to keep magic as a secular force. That doesn’t mean the classic “altars” of Roguelikes won’t make an appearance, though.

This is by no means the final list; I may add additional roles in later design.

Races (or Lineage, Species, ???)

Wait, you ask, what about races? Elves, hobbits, dwarves, orcs, and whatnot?

Well, my initial plan was not to have them at all. When Ultima removed all the Tolkien monster references in Ultima VI onward, I found I agreed with the logic that Tolkien races are VERY overdone.  It’s derivative and boring. In Realms of Antiquity, the only playable race is human; other well-known fantasy races are referenced but not encountered or playable. (With one exception…)

But, with a Roguelike, I think I can make it work. The important thing is that whatever race you choose should have significance with the game-play, beyond just some statistic adjustments.

D&D before 3rd edition was an interesting design. Most of the “demi-humans” (elf, dwarf, halfling) had significant advantages over the baseline “human”, such as infra-vision, immunity to various effects, better XP bonuses for high ability scores, bonuses to armor or attacks against certain foes, better abilities to detect secrets and traps, or in the case of the elf in classic D&D, the ability to cast spells like magic-user but wear full armor and use weapons like a fighter!

So with all that, why would you EVER play a human? Well, for one, experience level costs for demi-humans were higher. The aforementioned elf, for example, required double the XP a normal human fighter did to get to the next level. There was also a level cap for demi-humans so that they’d eventually stop leveling and get no more powerful, where humans had no limit.

So after some thought, I think I could add a lot of little special traits and things for non-human characters that make them both advantageous but also a disadvantage at other times. Plus possibly level limits as well, which means an easy ride in the upper levels but an increasing challenge in the depths. Could be fun!

Ability Scores (or Attributes)

Classic D&D character sheet

Ability scores are, in most tabletop gaming design, the statistics that exist for all living creatures in the game. I want to shake things up a bit with some changes to the typical D&D baseline:

  • Strength – As expected, used for hitting, damaging, carrying capacity, opening stuck doors, and all that jazz.
  • Dexterity – Used for ranged attacks, dodging, picking locks, disarming traps, etc.
  • Fortitude – Used for resisting physical ailments like poison and disease, hit point calculation, and so forth.
  • Intellect – Used for spell casting, energy calculation, reading mysterious writings and interpreting scrolls, magical trap disarming, resisting spells, etc.
  • Perception – Used to spot traps, secret doors, searching, avoid gaze attacks, insight into negotiations, determine item values, and similar.
  • Chutzpah – Used for reactions from NPC’s, trade and bartering, intimidation and deception checks, resisting mind effects, etc. (Won’t lie, I stole this one from classic Paranoia.)

Other Roguelikes copy D&D by having ability scores run 3-18, with more advanced ones like Nethack using the percentile system for exceptional Strength introduced in AD&D. I’m not doing that. The current plan is to have ability scores run from 1-32, although that’s subject to change. Also, sticking with integer math is just a good idea on older vintage systems unless you have a real crucial need for that degree of accuracy.

A fun side note: During 3rd edition D&D’s development, the question arose whether or not the actual ability scores (3-18) should be abolished and only bonuses kept. If only the bonus is ever used for any calculations, why have the original ability score at all? After some debate they decided the ability scores were a “sacred cow” that they couldn’t remove without angering the fans. (They had a similar debate on hit points.)

So I’m also considering why not have every point of an ability score matter? The main reason to retain it is if you want fractions; that you have to gain more than one ability score point before it impacts algorithms. I’ll need to work out all the various calculations and see which scales best to what I need.

And one last thing is how often ability scores change. I want them to be a bit more flexible than my past game designs, where they remained largely static most of the game with only end-game items letting you raise them. Old school D&D expected the DM to take some latitude and have pools or magical encounters that could raise and lower attributes permanently.

Potions (or… Potions)

A common staple of Roguelikes is that a lot of items in game aren’t immediately identifiable. You have to find out, through identify magic or trial and error, what things are and what effect they have.

With potions, the design is often to give them a color which is randomized for each game. So a “blue potion” may be a potion of healing in one game, and a potion of poison in the next. Some more advanced Roguelikes let you label a potion after you’ve tested it so you can remember what it does.

For my own, I’ll use 12-bits (1024 possible combinations) with a potion’s color, appearance, and smell as the random descriptors. Some examples:

  • Bubbling crimson potion with a citrus note
  • Steaming teal potion with a sour note
  • Clear maroon potion with a metallic note

I could probably just use color, but I like having fun with it. 🙂 Whether or not you can re-label them after discovery I’m still considering.

I’m also debating if I want to introduce an age-old idea from AD&D, the “potion miscibility table”. (As usual, Gygax uses a ten-dollar word where a simple one would do. Miscibility refers to the ability of one liquid to completely dissolve in another liquid solvent.)

The idea is if you already have a potion’s effect on you and you consume another, what happens? The table of old would allow for them both applying, one canceling each other out, one having their effects doubled, or worst case the character exploding. Could be fun, could be frustrating. Worth testing!

 Scrolls and Spells

The original Rogue game used a random spew of characters for scrolls, which looked kind of like magic words. I had a better idea…

Runes of Power from another CRPG

Borrowing from Ultima V, I thought it would be cool to define spells in the game as a series of verbs and nouns, describing the spell’s effects. Then, I could randomly generate syllables for each verb and noun and have a magic language constructed for each game, giving the player a chance to interpret a scroll based on the words.

  • Fireball – “Create Fire Movement”
  • Heal – “Create Life”
  • Vanish – “Alter Creature Light”
  • Charm – “Alter Creature Knowledge”
  • Detect – “Sense Danger Knowledge”
  • Cure Poison – “Remove Toxin”

My experimentation in this area proved quite humorous… computers generating random syllables can easily get out of hand and make some very strange and funny combinations. You can read a tech story about this here:

The Automated Curse Generator

Some samples I generated:

1st Set:

  • Spell: Fireball – HU FUHON GWIN
  • Spell: Heal – HU CRAM
  • Spell: Vanish – TARO BPAK CEROD
  • Spell: Charm – TARO BPAK TNED
  • Spell: Detect – BA YEWEK TNED
  • Spell: Cure Poison – MA TAROT

2nd Set:

  • Spell: Fireball – CE FOTI YOKE
  • Spell: Heal – CE PEMU
  • Spell: Vanish – JI DOYO WAHU
  • Spell: Charm – JI DOYO GOZI
  • Spell: Detect – KI CUPEW GOZI
  • Spell: Poison – CE ZAFA

3rd Set:

  • Spell: Fireball – MI KUEB GE
  • Spell: Heal – MI ZU
  • Spell: Vanish – HI RAY HEW
  • Spell: Charm – HI RAY MAN
  • Spell: Detect – TOD YO MAN
  • Spell: Poison – MI YEAH

After playing around with different algorithms to generate words, I realized that there was NO WAY, on a vintage system at least, to prevent a potential bad combination of letters making weird, funny or offensive words. So the best approach is to use my generator to create two, three, four, and five letter random words in large sequences, pick the best ones for data tables, and  use those to generate magic words.

The Display

Roguelikes started as text-only games, using the ASCII table (and later the extended ASCII table, usually CP437 which is the IBM’s set) to represent all objects, structures, and mobiles in the game. The player was always the ‘@’ symbol, walls were a mix of ‘+’, ‘-‘ and ‘|’ characters, monsters were always the first letter of their name, money was ‘$’, and so forth.

Even later games like Nethack continued this tradition, only grudgingly offering a graphics mode at times. And inspired games like Dwarf Fortress embraced the ASCII graphics, albeit with the option to add actual graphics. (It comes as no surprise to me that Dwarf Fortress for Steam finally left this behind; we’re not far from a point where ASCII will be just something grumpy old men talk about.) Games like ADOM start with graphics but can be switched to ASCII at anytime.

The problem with this approach? Every graphic tile in the game also needs an ASCII equivalent.

Some Roguelikes overcome this limitation by using the extended ASCII set which includes a lot of variant vowels and characters like arrows, simple graphic shapes, and so forth. Or they use colored characters to differentiate between types. Neither of these options are easy to implement on the TI-99/4a for these reasons:

  • As a computer developed in 1981, the TI-99/4a has no extended ASCII table. It could be introduced, of course, but it means deciding WHAT extended set to use, and then making sure it fits the available size. (6×8 in text mode)
  • The TI-99/4a 40-column text mode is monochromatic; there are only two colors available, the text color and the screen color.
  • Using colored text is somewhat rude to those with color-blindness; the CRPG Addict has ran into this before with other games including Nethack, and I don’t care to contribute to the problem.

I plan on using full graphics for the game, but I’m debating on how to assign letters for a “text only” mode. We’ll see if that happens.

Monsters (or Abnormal Entity of Inconsistent Temperament)

And finally, we have the monsters.

Unlike my past project, Realms of Antiquity, a monster’s appearance will be just a single character, as opposed to a colorful 16×16 or 32×32 graphic. This means I need to give them a lot more depth than just a set of combat statistics.

Much like bending a little on classic fantasy races, I think that I will also have some more classical fantasy monsters, such as goblins and hobgoblins. (Not orcs. Still too Tolkien-esque.) As these terms for monsters predate fantasy gaming, it’s easy to fashion them into your own. Monsters from mythology such as medusae, minotaurs and similar are good too.

One aspect of older school gaming I want to bring in is the reaction roll. If you read old D&D manuals, it was not assumed that every encounter with monsters was hostile. There was always a chance of a conversation, negotiation, and even a peaceful conclusion. As computers couldn’t really simulate this, early CRPG’s and Roguelikes had to focus on just the combat aspect.

So I’m exploring the idea of having individual or even small groups of monsters being amenable to conversation. They may let you pass them unharmed in return for a bribe. Or be willing to barter and trade items.

And possibly, the player starts to develop a reputation that makes future negotiations easier or more difficult. Although there it will be fun to use the races as potential sources of trouble. Maybe being TOO friendly to one group makes you anathema to another.

Conclusion

So there’s where I’m at so far! I’m going to continue to define and work on system design until I have a good idea of everything I want to implement. Still a ways off from an alpha engine.

Posted in Blog, Coding, Design, Gaming, Roguelike, TI-99/4a | 3 Comments

Review: The Legend of Zelda on Switch

For Father’s Day, I got an unexpected gift, Legend of Zelda: Tears of the Kingdom for the Switch. And for my birthday a month later, I received Breath of the Wild.

I’ve always enjoyed The Legend of Zelda series. Back in the late 80’s, the only opportunity I had to play it was when I was visiting my cousin’s who owned a NES. (They also had an Atari 2600 which was the only time I could play THOSE games as well.) We finally got our own NES for Christmas in 1990, so shortly after I played and won both of the first two games. And later I bought, played, and won Link to the Past on the SNES.

Then I had a long break, until I was in my 2nd stint of college pursing my C.S. degree and I saw someone playing Wind Waker. Unlike many of the time, I loved the cartoonish look of the graphics and I bought a GameCube just to play it. It remains my favorite Zelda of all time. (And I am SUPER annoyed it has not been made available on the Switch.)

I also got a copy of Ocarina of Time for the GameCube, which I played about halfway before losing interest. When Twilight Princess was announced, I was annoyed that they groveled to the haters and promised the game would have an “adult” look, and so I skipped that one and left the series behind, until now. (I may dig around for a GameCube version of Twilight Princess, if I can find one cheap.)

So, I’m in a unique position to review the Zelda titles on the Switch, as I started with Tears of the Kingdom and then played Breath of the Wild afterwards. Just to be clear: Both are EXCELLENT games, and well worth playing. The details on that follow.

Gameplay

The descriptions you hear the most about both of these games is “open world” and this is absolutely true. After you have left the tutorial area of each game, you are literally left on your own to go anywhere you can reach. And the “dungeons” in each game can be done in any order, although some are easier than others.

Both games feature “shrines” which act as a means to increase your health and stamina and provide means for fast travel. Neither game lets you max out both hearts and stamina; you have to choose the balance you want. In Tears of the Kingdom I found stamina slightly more valuable to have, so I maxed that one out over heart containers.

Both titles embrace what I like to call “the Dark Souls model” of action game-play. Namely, you’re expected to watch and observe your enemies in combat and time correctly your attacks and defenses. As Wind Waker was my last Zelda, I was unprepared for this. I spent most of my time just hacking away ignoring tells and getting knocked down or hurt, because I was expecting to be told WHEN to push a button to defend. If I had started with Breath of the Wild I’d have learned this lesson sooner, as some combats can only be successful there if you time defenses right. (Namely, deflecting Guardian laser blasts.)

Both games also leave behind the “gimmick tool” approach that was used in prior Zelda’s. You’d always get a special “tool” like the boomerang or the grappling hook in a given dungeon that then was the key to defeating the dungeon’s boss. If anything, these games largely diminish the value of the boomerang; I almost never used it in either game via throwing because it just wasn’t worth the effort.

Both games introduce equipment decay, Tears of the Kingdom goes further by having most weapons being covered in gloom and even more brittle than usual. This is an interesting concept, as it requires you to have a steady supply of replacements always available, and it keeps combat interesting. The Master Sword in both games doesn’t break, it just runs out of energy and requires a real-time recharge wait to reuse. I’m not sure it’s a system they should return to in a sequel as by the time I finished both games I was done with it.

Both games have “special” abilities, in Breath of the Wild provided by your Sheikah Slate (which gee, looks EXACTLY like a Wii U or a Switch Lite) and in Tears of the Kingdom by your replacement right arm. Of the two, Tears of the Kingdom clearly benefited from a lot of feedback. The Sheikah abilities are a little clunky in comparison. And despite the fact it offered unlimited bombs, I actually liked that Tears of the Kingdom returned the bomb flower of older games.

A big selling point of Tears of the Kingdom was their physics engine. And it is VERY good. I could tell Breath of the Wild is just “faking” the physics in various puzzles. Combined with the ability to build and assemble machines, it gives the game a huge amount of flexibility to puzzle solving and even travel.

Both games feature an interesting method of replenishing monster encounters. A “blood moon” rises every couple real-time hours of play that regenerates monsters all over the game. You can’t force it to happen, as it tracks to real-time play. But if you are trying to farm specific creatures (Particularly in Tears of the Kingdom, when you need material components) it helps you know when you can hit certain spots again.

One minor complaint about both games I have is the increase in power for monsters. Each monster type has four variants, each one with more hit points. The strongest are the “silver” variety, and their hit points are just ludicrously high. After a certain point silvers can re-spawn anywhere in game, which means you end up with very lopsided fights where you kill most of the enemies in one blow but end up in a grind on the last one or two. Knocking them into water is one of the best tactics as no matter how strong they are they drown almost immediately. (Doesn’t work with Lizafos, who can swim.)

In Tears of the Kingdom, you gain the aid of the various sages who then have an “avatar” that you can summon that will fight with you. This makes the game easier but also a bit frustrating. The AI for them is maddeningly stupid at times; I can’t count how often I’ve seen them approach an enemy but NOT attack. Probably their best feature is drawing attention. When fighting Lynels, they often distract or redirect his targeting to them, which is very useful. The best of them is Tulin, the Rito archer, he does seem to hit often with his bow at the right moment.

Both games feature an elaborate “cooking” system, by which you can create food and elixirs that give temporary ability boosts as well as recover hearts. I never experimented a lot with this, but I could see a younger version of myself really getting into that. I do like the fact that Link just recovers all his health with a good night’s sleep. Past games were harsh on heart recovery methods.

Rupees, or money, is not easy to come by in either title. The only enemies who drop money are the Yiga clan; monsters only drop weapons, shields, and materials. There are mini-games you can win money at, and you can sell materials and crafted food/elixirs. So it’s not too bad. The biggest challenge is not accidentally selling something you need for armor upgrades.

Tears of the Kingdom has a LOT more stuff than Breath of the Wild. In fact, it’s almost too much. You need material components to upgrade armor at the Great Fairies, and you may spend a lot of time hunting and farming very specific things. It also has more monster types, which drop unique materials for the level of the creature. This plays into your Fuse ability which lets you fuse monster horns, claws, etc. onto weapons to increase their attack power. You can also find unblemished weapons in the Depths, held by what I presume is shadowed spirit of a lost hero.

Breath of the Wild also has DLC content, which adds additional armors to the game and a challenging “Champions Trial”. I found the DLC content to be incredibly difficult; too much so. It starts with you using a special weapon that can kill anything in one hit, but drains you to a quarter-heart so a single hit can kill you. You then have to eliminate four camps of monsters with this limitation (thank goodness it lets you use your bow!) and solve shrines that are ludicrously hard with deathtraps everywhere.

After you’ve done this it sets up three additional shrines in the four main regions of the divine beasts that you must uncover and solve. Once that’s done, you can approach the divine beast and are forced to fight the bosses of each again, but this time in a strange “dream” realm using the weapons the original pilot used. It’s clearly meant as a metaphor; they lost their fights a century ago and now Link is fighting and winning them. Again, far too difficult. But after winning each sage’s powers recover twice as fast, which makes it worth it.

The final trial is another divine beast dungeon, at the end of which you fight one of the ancient monks that are in the shrines everywhere. He fights using the various Yiga clan techniques, and is, not surprisingly, very hard. But if you win, you get your own “divine beast” which is, wait for it… a motorcycle. 🙂 By the time I got this I didn’t need to go cruising around Hyrule, but it’s pretty cool.

The other part of the DLC is the Trial of the Sword. Normally the Master Sword has a base attack power of 30, but in dungeons or the final end-game, it’s power is doubled to 60 and it’s indestructible. Completing the trial of the sword lets you raise the base power of the Master Sword by 10 for each trial completed, so that you could have the sword at maximum power at all times.

The problem? The trial is stupid difficult. You’re dropped into a dungeon with zero equipment, all of it has to be salvaged as you go. You have no access to sage abilities, only your sheikah slate powers. And if you die, you have to start over from the beginning. The problem with this trial is it’s SO hard, you pretty much have to already be maxed out on hearts to get through, but at that point in the game, having a maxed out Master Sword everywhere has very little value.

Story

The Legend of Zelda has always been cyclic in nature. There is always a Princess Zelda, there is always a Link, and they are always fighting Ganon, in one form or another.

The interesting part of these two games is that they decided to embrace it, and make it part of the story. Essentially, Zelda is the mortal avatar of the goddess Hylia, who created everything. Ganon is a demon/fallen god who wanted her power, and also followed her into the mortal world to possess it. Link represents the mortal races, the ones who have the choice between good and evil. So this is why the battle between them happens over and over.

Interestingly, neither game features the Triforce, which was a prominent artifact of the older games. In fact, the last time we saw it was in Wind Waker, which is now considered a variant timeline. You can see the Triforce symbol in artwork and on the Master Sword itself, and there are springs of Power, Wisdom and Courage to be found, but the Triforce itself isn’t mentioned otherwise.

Both of these games are also set long after every prior Zelda game, and it’s been clearly established that the timeline has started anew from here. We probably won’t see any further “prequel” style games from here on out.

Since I started with Tears of the Kingdom, it was old hat for me to see Zelda and Link together, although I was a little disconcerted to see Link reduced to a shadow of his former self. It wasn’t until Breath of the Wild that I realized that Link and Zelda are both literally out of their time; the kingdom of Hyrule was more or less destroyed a century ago.

I liked that in Tears of the Kingdom there was sign of rebuilding and people everywhere; Breath of the Wild has far too many stark and empty landscapes for me. In fact, there’s really only two Hylian settlements and the Sheikah village in the game. Where did everyone go?

Because I started with Tears of the Kingdom, I spent a lot of time starting from the sky and floating down to various places to open up the map. As a result, I really didn’t get a feel for the landscape at all; it was all a chaotic jumble that I just used shrines to jump back and forth on. Breath of the Wild definitely grounds you (pun intended) and forces you to actually walk places and get a feel for the regions and areas.

Since Tears of the Kingdom starts with Zelda and Link, I didn’t have all the background that had already gone on. So when I started Breath of the Wild it was an “ah ha!” moment; Link’s been in a bacta tank for a century which explains why he (and the player) are so unfamiliar with things.

Both games play out a very similar story. In Breath of the Wild you need to take control of the Divine Beasts back from Calamity Ganon, each of which are in a different region of Hyrule. After you’ve done this it’s a lot of side quests and building up your strength for the final battle in Hyrule Castle. In Tears of the Kingdom, after acquiring all your new abilities and literally falling back to the surface of Hyrule, you’re tasked with investigating “problems” in the same four regions.

Tears of the Kingdom feels more like a classic Zelda game. It has true dungeons, it has caves, and Ganon is an actual person not just an elemental force. The boss battles for each dungeon feature a “giant” version of a normal monster, which is pretty cool. Each also requires a specific sage’s abilities to really fight effectively. The Depths are also very cool, a well done representation of an “Underdark”.

That’s not to say Breath of the Wild isn’t bad, it’s just weird that the only “dungeons” are giant gundams, er, divine beasts. The bosses are a weird mix of malice material and tech, and aren’t very distinctive from each other. Ganon is just a big clockwork spider thing which eventually manifests into a giant boar. It just feels like they deviated a bit too much from the base formula, and righted the ship with the sequel.

My main takeaway after finishing both games was, where do they go from here? It will definitely be on whatever future console Nintendo is developing, but are they going to continue the story of THIS Zelda and Link, or are they going to fast-forward again to the future and a new incarnation of everything? We’ll have to wait and see…

Notes

Here’s my list of miscellaneous notes and observations on both games.

  • Both games really seem to like putting Link in a position where he’s running around in his underwear a lot. Even the end-game in Tears of the Kingdom removes his armor entirely.
  • I like the fact that any “romance” between Zelda and Link is kept subtle and to the gamer’s imagination.
  • On that note, more than a few NPC’s in the games are enamored of Link. Especially the Great Fairies.
  • If I was shipping anyone with Link, it would be Purah! In Tears of the Kingdom, specifically.
  • Calamity Ganon? Seriously? The english translator must have despaired to find a good match for whatever adjective was used in the original Japanese. Demon King works a lot better.
  • Did anyone consult a cultural advisor when it came to the Gloom resistant armor? A conical cap like that has some uncomfortable connotations in the USA…
  • In Tears of the Kingdom, I never got a numerical report of monster hit points. But in Breath of the Wild I do… but inconsistently?
  • Why aren’t you told the Master Sword’s base attack value in Tears of the Kingdom? Or it’s strength after you fuse material to it?
  • It’s funny how both games feature advanced cultures (Sheikah, Zonai) who build machines. It definitely contributes to the feel that Tears of the Kingdom is just a redo of Breath of the Wild with more stuff.
  • Where did all the shrines and towers of the Sheikah go between the two games? Did they dismantle them?
  • Both games feature horses and fighting from horseback. That’s cool, but I almost never used them.
  • The Master Sword was “The Blade of Evil’s Bane” in Wind Waker. Here it’s the “Sword that Seals the Darkness”. It went from being a relatively normal magic blade in Link to the Past to a full-fledged artifact.
  • An interesting note is that Link only “kills” monsters, who vanish in purple/black smoke. If he fights Yiga clan members, they don’t actually die, they just retreat leaving spoils behind.
  • I liked the preview of the Depths in Breath of the Wild with the “bottomless” chasm in the Yiga clan hideout, which Master Korgha falls into at the end of your battle with him. This explains how the Yiga clan has such a strong presence there later, and are familiar with Zonai technology.
  • Korok seeds and inventory expansion was a lot easier in Tears of the Kingdom. It was way too much grinding in Breath of the Wild, I’m glad they reduced the overall number required to max out.
  • I was amused that Breath of the Wild had “hearty durians”. As anyone who watches the Food Network knows, this fruit has a… unique flavor and scent that is very much an acquired taste. But everyone loved it because cooking even a single one creates a meal that restores ALL hearts and gives temp hearts as well. It is conspicuously absent from Tears of the Kingdom.
  • The Shrine puzzles are a lot more consistent and friendly to mistakes in Tears of the Kingdom. In particular, if a shrine has to be “created” from a fragment, you never have to solve a puzzle inside.
  • I hated the “motion sensor” puzzles in Breath of the Wild. I use a detached controller to play and hook up my Switch to a TV. I have to keep the controller plugged into USB as the battery is bad/dead, so I couldn’t twist it in the proper directions easily. On some puzzles I ended up using the switch side controllers which were easier to manipulate. I’m glad they were not in Tears of the Kingdom.
  • Man, Hyrule castle just can’t catch a break in either game…
  • Love that Koroks have returned from Wind Waker! They were one of my favorite characters in that game.
  • Love all the callback armors and weapons to prior Zelda’s. I think my favorite is the White Sword from the very first game.
  • Didn’t like how in Breath of the Wild it tracks how many of certain items (like arrows) you have and won’t restock merchants until you drop under a certain number.
  • Tears of the Kingdom in general seemed to have a lot more stuff in crates and barrels overall. It was a lot easier to stockpile various things.
  • Lynels are hella-tough opponents! The memory cutscene of Link having just defeated several of them is total bull. It takes all your energy and focus to defeat just ONE.
  • The Gloom hands are nasty… also another callback to Wind Waker, where they lacked the eyes but they could grab you and force you to restart the dungeon from the beginning.
  • Ultrahand is by far the most awesome power in the game. I was seriously missing it in Breath of the Wild, when I was forced to roll boulders by force rather than just easily pick them up.
  • Call me blind, but it wasn’t until I was nearly done with Tears of the Kingdom that I noticed that the light roots in the Depths align perfectly with the surface Shrines.
Posted in Blog, Gaming, Personal, Review, Screenshots | Leave a comment

Digging Dilemmas

Hello all!

It’s been awhile since my last post, things have been busy! I started a new job at the beginning of the year, so I’ve been really focused on that.

I also participated in a Retro RPG Roundtable discussion on YouTube, link below! We’re working on the details to do a second episode as well.

For my new project, it’s proceeding slowly. One thing I realized was I needed to actually play some Roguelike’s to get a feel for the games and the various versions that are out there. If I just wanted to make a perfect clone of Rogue, that wouldn’t be difficult as the source is freely available. But what I want is something unique and my own, and that’s where the challenge lies. So I’ve downloaded and played Rogue, ADOM, Brouge, and Zorbus, to name a few…

I’m also working on a cave generation map system. It’s very different from the dungeon generator, which plotted corridors first followed by rooms off the corridors with doors. Instead it generates random cavern rooms at first, then plots passages between them. At present I use pre-made bitmaps for cavern layouts, as it’s much faster than trying to generate them on the fly with a random plot system.

Determining which caverns to connect is trickier; I already discovered in my current one that it’s possible for caverns to get connected to each other but not the rest of the map, so I need to work on that. 🙂 My goal is to potentially combine both methods and have a map that has both dungeon and cavern rooms at the same time.

Random Cavern

Posted in Blog, Coding, Design, Personal, Screenshots, Video | Leave a comment

Update: New Project!

Hi, sorry for the long quiet!

As we all know, 2022 didn’t turn out to be much better than the prior two years… I’ve also been mostly occupied with patching and maintaining Realms of Antiquity in hobby time.

But at last, the end of the line is in sight. I feel like the game is “done enough” and at most another bug patch may be needed if someone finds something. But otherwise, it’s time to move on to the next project!

And what is the next one? Well, still assembly. Still on the TI-99/4a. And something the system never got in the heyday. A rougelike. 🙂

I posted about it before (Processing Progressions), but now it’s got my full attention. A roguelike for the TI-99/4a. I actually got a lot of the prototyping done already, so now it’s full speed ahead on development.

The #1 thing I want to do is make it my own. I have no interest in re-creating the original Rogue or even Nethack on the TI. I want my own game with my own unique twist on things. As part of this, I’m utilizing a resource I chose not to use with RoA: other people’s feedback. As I have a Discord with many players of my game, it makes perfect sense to leverage their opinions to make sure I make the best game that I can.

Because, if there is one thing I learned from RoA, it’s that you can work and make a game that you’d like, but it benefits you to hear from others as to how to make it better.

I don’t have a timeline or expected date on this project as yet; probably no earlier than 2023 at the best. But rest assured I’ll keep everyone apprised of the status.

Posted in Assembly, Blog, Coding, Gaming, Roguelike, TI-99/4a | 5 Comments

Making a CRPG Part 2 – Maps, Scrolling and Line of Sight

In this, part 2, we will be looking at scrolling maps, a pivotal element of top-down 2D computer role play games of yore.

I think something that is lost on many modern gamers, who didn’t grow up in the 80’s. The majority of games on the early 8-bit systems were limited to a single screen of play. Really good games may have multiple screens but when you moved off the edge it would load a new screen.

It’s bigger on the inside!

So it was utterly FANTASTIC to see a game screen that was a view-port on a much larger world. When I first saw Ultima II, I was in total shock. There was (to my viewpoint) no limit to what could be beyond the borders of the screen! It both was thrilling and increased my curiosity and expectations of what the game could have.

A scrolling map was also well beyond what most BASIC languages could do in those days. Some were better than others at high-speed video display, but anything close to full screen was a challenge no matter the platform. So knowledge of assembly language was needed to implement one.

So how to render a map that is bigger than the screen? Let’s dive in…

Storing Maps

The first thing to figure out how you are encoding your maps internally. How much memory are you devoting internally to a map? Is the data compressed on disk and must be uncompressed when loaded? How many unique tiles can be present on a map? Are tiles global or specified for the map?

Early Ultima’s like II and III had 64×64 size maps. Both had less than a byte’s worth of tiles (64 and 128) so they would use up 4K of RAM to store the map, uncompressed and using a full byte per tile. Ultima IV uses 32×32 size maps and some clever coding to load a continuous world map. As you walk around, new 32×32 chunks are loaded. This creates some challenging edge cases (literally) when you approach corners and the game will need to load up to 3 new chunks to get the data needed. Ultima V goes to 16×16 chunks for the world map.

The issue with doing continuous map loads on early 8-bit systems is most of them aren’t that efficient with loading data continuously. Until hard drives came along, loading even a few kilobytes from floppy disk could take a second or so. And it got worse on systems like the Commodore 64 where the continuous music would suddenly get stuck on a single note as it loaded fresh data. My own experiments with a “big map” showed the problem, as every time I approached an edge it would take seconds to load fresh data.

So I decided for my own maps to just have singleton maps that are a maximum of 4K in size, and if you left them you’d just load a new one as a self-contained area. For tiles, I have 128, and multiple character sets. So for a world map, there is a “world” tile set that includes mountains and other features only found on world maps. The leftover bit is used as a lighting mechanic; it indicates if this tile is “lit” or not naturally on the map.

The nice thing about a map buffer is it can take any shape you want. Just because I have 4096 tiles doesn’t mean it’s automatically 64×64. By specifying a height and width parameter for each map, I can have them in many different sizes. In practice I found that 32×32 was a decent size for most towns and dungeons. 48×48 was nearly perfect, just big enough to have a lot of interesting areas. 64×64 was almost TOO big, there was a few cases where I split maps into multiple maps because a super large map wasn’t actually ideal, especially if they had a lot of mobs (mobile objects) on them.

Some game maps are compressed on disk so they take less room. With a lot of older CRPG’s this makes good sense; you don’t have a lot of unique tiles and you tend to have long horizontal stripes of them, which screams “compress me!” The most common form of compression is RLE, or run-length-encoding. RLE defines the data as either a singleton (one tile) or a count of whatever comes next of values to repeat. There’s usually also a terminator value as well to indicate map processing should end.

For example, if you have 64 unique tiles, the top two bits could be used to indicate a few different ideas:

  1. The top two bits are control bits. If both are 0, it’s a terminator for the map data. If 01, this is a singleton tile. If 10, it’s two consecutive tiles (or whatever the most common count of consecutive tiles is in your maps.) If 11, use the tile value as a count and that’s how many of the next tile to produce.
  2. The top two bits are count bits. Value 0 means it’s a terminator for the map data. Otherwise produce 1-3 of the given tile.

Let’s look at an example, here is a 7×7 map, showing an island. Uncompressed, if you used a full byte per tile, it would take up 49 bytes.

With method #1 above, it would take two bytes for any length of greater than 2 to store. Crunching the numbers, it would take 25 bytes (including a terminator byte) to store. Almost 50% compression!

With method #2 above, while we are limited to a maximum of 3 repeated tiles, it actually comes in at 21 bytes, over 50% compression. Nice! And the algorithm to decompress is a little simpler as well.

Of course, this example only has two tile types, and the map is rather straight-forward. Any CRPG map is likely to have a lot richer of a data set. And there is a point where RLE won’t be as effective. My own maps utilize a lot of “two pair” tiles where they are the same type (grass, for instance) but are in fact different tiles. This breaks RLE, which expects a lot of the same tiles to be repeated in a horizontal line. For such maps, using a more complex pattern-oriented compression technique like LZW and Huffman makes more sense.

The main value of compression is to reduce disk size, though. Is disk size really a problem to solve? In the old days, the answer was unhesitatingly yes. Most 8-bit systems had 160-180K disks, and every disk was an expense to replicate and put in a box. Data compression saved money. In the modern era, though, with digital distribution and USB sticks that hold more memory than the entire production run of a computer system’s RAM added together, it’s not as big of a deal. Even retro enthusiasts these days tend towards modern storage solutions like emulated disk systems or even cartridges with megabytes of space available. So I figured, why bother?

Towards the end of my development work, I did consider that it would be better to have dynamic tiles. In other words, store a key list of the unique tiles used on the map, assign them unique values, then the map data itself using those values. It’s a nice idea, as each map then basically has it’s own unique tile set, and you wouldn’t have a lot of needless replication. But it adds a lot of overhead towards map loading, and would require a complicated editor for maps. Something to consider for the future…

Viewing Maps

Okay, so you have your map loaded in memory. How to get it on screen?

Well, the first thing is to determine the number of tiles you want to appear on the screen. Depending on how many pixels per tile, how large the screen, etc. And is your “avatar” character always at the center? If so, an odd number of tiles per side makes sense for balance.

Using a ‘view-port buffer’ is a best approach. Don’t try and pull each tile individually from your map data, use an in-between buffer to store it. Using map height and width and a position offset, it’s not hard to create a double loop to copy out map data to your view-port buffer. But what do you do when your view-port goes over the edge of the map?

Different games handle it different ways. If you want your map to just stop scrolling at edges, Gauntlet-style, that’s easy to do; you just make sure offsets never go past a certain value. This does create the sense of reaching a “map edge” though, and may remove the player a little from immersion.

Other games just have a default “overflow” tile that is used, and interacting with it will pop the character off the map. This also works but still clearly illustrates that you’ve reached a map edge.

For my own game, I had two versions. One is “repeat the edge tile” which takes whatever tile was along that edge and repeats it indefinitely. This creates a less obvious map edge. The other version is “wrap-around”, which creates a continuous map by wrapping to the other side. I use this one sparingly, usually for maps that are either “warped magic” in nature or in a more clever fashion to create non-square style caverns.

No wasted space!

One other feature I introduced with my own map system is slanted maps. Since map projection is up for interpretation, I can slant map data by row to make a more natural style map for coast lines that aren’t going in cardinal directions. Very useful, and very difficult to debug!

One additional task is to place mobs (mobile objects) on your map. This would be your monsters, points of interest, and other items that aren’t part of the static map. I store these in a separate data set, so they must be placed in the view-port area if they are visible. This puts a practical limit of how many mobs per map, since every extra calculation is costing you processing time.

So now we have our view-port, with our set of tiles. Now (finally) it’s time for line of sight!

Line Of Sight

Back in the early 90’s I tried to create the LOS algorithm in BASIC, using sine and cosine functions. I thought of it as I was flinging light out from the center and traversing a circle, and that if I struck a barrier that blocked line of sight, everything after it on the path would be dark.

So… it kind of worked. Except that even on a 11×11 size screen and going 1 degree at a time, it still didn’t cover all the squares. Plus given it was in BASIC it ran VERY slowly, taking ten minutes to complete.

Years later, I got a hold of the Ultima III LOS algorithm, and was able to see my error. I was thinking in reverse! What you do is trace a path from the tile you want to check LOS on and traverse back to the center.

Diagonal then Cardinal

The algorithm is pretty simple:

  • Hide all tiles in the view-port, except the center tile, where your avatar is.
  • For each tile in the view-port, plot a path back to center. The original algorithm uses two arrays to achieve an offset. It moves diagonally towards the center until it hits a cardinal direction, then continues the rest of the way on the cardinal.
  • If at any point you encounter a “blocking” tile, move on to the next tile.
  • Otherwise, if you reach center, uncover the target tile and move to the next tile.

Simple indeed, but processor-intensive. Besides processing the view port in start-to-end order without taking the 2D nature of the data into account, it also ends up reprocessing a LOT of tiles unnecessarily. If a tile is blocked, then wouldn’t any tiles between it and the blocking tile also be blocked? And if so, why calculate those at all?

I introduced some optimizations to reduce unnecessary calculations and it worked pretty well. However, the LOS algorithm in Ultima III was rather heavy; a single tile creates a huge swath of diagonal shadow behind it. I noticed that Ultima IV on the PC seemed to have a better algorithm so I took a copy of the source from XU4 (sadly now an extinct project) to analyze.

Cardinal then Diagonal

The algorithm is VERY different. It starts by tracing in the cardinal directions from the center outward. If it encounters a blocking tile, it blocks only the cardinal tiles behind it.

After the four cardinal plots, it then does four quadrant calculations, which start on the edge and go towards the center on a horizontal or vertical direction first then diagonal. This has the effect of not blocking so pervasively. It’s a much more efficient algorithm because it’s actually taking the structure of the view-port into consideration.

Light

Light Map

So, remember that light bit on the tiles? That’s used to determine dark and light areas. But, if the player has a light source going, how to determine if a square is lit or not?

I use a light map, which has concentric circles of numbers, matching the size of the view port. The center area is value 0, and slowly increases as it goes outward. A light source has a strength (or radius) value, which the light map is subtracted from. If the value is less than zero, it’s not lit. If it’s equal or greater, it’s lit.

The light map is always applied after LOS has been calculated. A square that’s already blocked remains blocked.

Elevation

This was a late-development feature, which came about when I was out on a ridge one day looking over a magnificent set of waterfalls in the distance. I realized that despite being in a forest and having a deep valley of forest between us, I could still see the falls clearly. And that got me thinking, what about elevation and LOS?

The elevation map is a separate data set from the map’s data, as not every map uses elevation. The data is also stored in a compressed format (run-length encoding!) so it doesn’t take up much disk space. There are four levels of elevation, from 0 to 3.

The math is easy. Whatever elevation your avatar is at, everything below it is not a blocking tile. Everything at same level respects the LOS calculations. And everything above your level is considered a blocking tile automatically.

I mostly use elevation on world maps, but a few special maps use it. I think my favorite in this regard is a sewer dungeon, with both upper and lower levels. The biggest problem to solve with elevation was coming up with clear boundary delineation.

The Code

Below is ROA’s map view algorithm, in TMS9900 assembly. It uses multiple buffer maps for each stage and then combines them at the end for the finished mapview.

* Map Building Routine
* Extract map from map buffer into VMAP
BLDMAP MOV  R11,*R10+
       LI   R0,MOBVIS
       LI   R1,32
BLDMP0 CLR  *R0+                       * Clear mob visibility array
       DEC  R1
       JNE  BLDMP0
       LI   R3,2
       BLWP @PAGE1                     * Set >2000 to page 2 (map buffer)
       LI   R3,1
       BLWP @PAGE2                     * Set >3000 to page 1 (elevation)
       MOVB @SLANT,R1                  * Get orientation into R1
       SRL  R1,8
       MOV  @DIRY(R1),R9               * Set R9 to -1 (left), 0 (none), or 1 (right)
       MOV  @DSLANT(R1),R8             * Set R8 to 0 (left), -6 (none), -12 (right)
       MOV  @Y,R1                      * Get Y value into R1
       MOV  @X,R2                      * Get X value into R2
       AI   R1,-6                      * Set starting y position
       A    R8,R2                      * Add slant start to x position       
       LI   R3,13                      * Row count
       LI   R4,13                      * Column count
       CLR  R5                         * buffer index
       MOVB @EDGES,@EDGES              * Check if edge or repeating map
       JNE  BLDMPW
* Build map with edge
BLDMPE MOV  R1,R6                      * Copy R1 to R6
       MOV  R2,R7                      * Copy R2 to R7
       BL   @EDGCHK                    * Check if over edge
       MOV  R0,R0
       JEQ  BLDME1
* Edge correction
       COC  @W1,R0                     * Vertical?
       JNE  EDGEM2
       MOV  R6,R6
       JLT  EDGEM1
       MOV  @VWIDTH,R6
       DEC  R6
       JMP  EDGEM2
EDGEM1 CLR  R6
EDGEM2 COC  @W2,R0                     * Horizontal?
       JNE  BLDME1
       MOV  R7,R7
       JLT  EDGEM3
       MOV  @HWIDTH,R7
       DEC  R7
       JMP  BLDME1
EDGEM3 CLR  R7
* Get tile
BLDME1 MOV  R7,R0                      * Copy R7 to R0
       MPY  @HWIDTH,R6                 * Multiply Y by hortz width
       A    R0,R7                      * Calculate map index into R7
       MOVB @MAPBUF(R7),@VMAP(R5)      * Copy tile
       MOVB @MAPENV(R7),@EMAP(R5)      * Copy elevation level
       MOVB @VMAP(R5),@LMAP(R5)        * Copy light level
       SOCB @B128,@VMAP(R5)            * Set high bit on active tile
       SZCB @B127,@LMAP(R5)            * Filter light array to top bit only
       MOVB @B1,@LOSMAP(R5)            * Set LOS map to blocked
       INC  R5                         * Increment the buffer pointer
       INC  R2                         * Increment the column index
       DEC  R4                         * Decrement the window width count
       JNE  BLDMPE
       INC  R1                         * Increment the row index
       A    R9,R8                      * Add slant change to R8
       MOV  @X,R2                      * Move X back into R2
       A    R8,R2                      * Add slant to x position
       LI   R4,13                      * Reset the window width to 13
       DEC  R3                         * Decrement the window height count
       JNE  BLDMPE
       JMP  BLDMP2                     * Jump to next routine
* Build map with wrapping
BLDMPW MOV  R1,R6                      * Copy R1 to R6
       MOV  R2,R7                      * Copy R2 to R7
       BL   @EDGCHK                    * Check if over edge
       MOV  R0,R0
       JEQ  BLDMW1
* Wrap correction
       COC  @W1,R0                     * Vertical?
       JNE  WRAPM2
       MOV  R6,R6
       JLT  WRAPM1
       S    @VWIDTH,R6
       JMP  WRAPM2
WRAPM1 A    @VWIDTH,R6
WRAPM2 COC  @W2,R0                     * Horizontal?
       JNE  BLDMW1
       MOV  R7,R7
       JLT  WRAPM3
       S    @HWIDTH,R7
       JMP  BLDMW1
WRAPM3 A    @HWIDTH,R7
* Get tile
BLDMW1 MOV  R7,R0                      * Copy R7 to R0
       MPY  @HWIDTH,R6                 * Multiply Y by hortz width
       A    R0,R7                      * Calculate map index into R7
       MOVB @MAPBUF(R7),@VMAP(R5)      * Copy tile
       MOVB @MAPENV(R7),@EMAP(R5)      * Copy elevation level
       MOVB @VMAP(R5),@LMAP(R5)        * Copy light level
       SOCB @B128,@VMAP(R5)            * Set high bit on active tile
       SZCB @B127,@LMAP(R5)            * Filter light array to top bit only
       MOVB @B2,@LOSMAP(R5)            * Set LOS map to blocked
       INC  R5                         * Increment the buffer pointer
       INC  R2                         * Increment the column index
       DEC  R4                         * Decrement the window width count
       JNE  BLDMPW
       INC  R1                         * Increment the row index
       A    R9,R8                      * Add slant change to R8
       MOV  @X,R2                      * Move X back into R2
       A    R8,R2                      * Add slant to x position
       LI   R4,13                      * Reset the window width to 13
       DEC  R3                         * Decrement the window height count
       JNE  BLDMPW
* Retrieve data into state and sensing arrays
BLDMP2 MOVB @VMAP+84,@CTILE            * Set current tile value
       MOVB @EMAP+84,@CELEV            * Set current elevation level
       MOVB @B247,@VMAP+84             * Set player graphic for permissible space
       MOVB @B1,@LOSMAP+84             * Set center of LOS map to visible
       SETO @SURRND                    * Clear the surrounding tile contents
       SETO @SURRND+2
       SETO @SURRND+4
       SETO @SURRND+6
       MOVB @VMAP+97,@SURRND           * Copy the down tile
       MOVB @VMAP+83,@SURRND+2         * Copy the left tile
       MOVB @VMAP+71,@SURRND+4         * Copy the up tile
       MOVB @VMAP+85,@SURRND+6         * Copy the right tile
* Mob processing
       LI   R3,4
       BLWP @PAGE2
       CLR  @WORK2                     * Clear WORK2 (in map mob count)
       MOV  @MOBCNT,R0                 * Copy total mob count to R0
       JEQ  BLDMP3                     * If 0, skip to next phase
       MOV  @MOBADR,R1
       CLR  @WORK                      * Clear @WORK (Mob #)
       LI   R6,WORK2+2                 * Set R6 to WORK2+2
BM2    MOVB *R1,R2                     * Copy mob type into R2
       JEQ  BM2B                       * If zero, skip, no counter decrease
       CB   @B23,R2                    * Check if inert
       JEQ  BM2B                       * If so, skip but decrease counter
       JMP  BM2C
BM2A   AB   @B1,@WORK
       DEC  R0                         * Decrement mobs processed
       JEQ  BLDMP3                     * If finished, move on
       JMP  BM2
BM2B   AB   @B1,@WORK
       AI   R1,8                       * Go to next mob
       DEC  R0                         * Decrement mobs processed
       JEQ  BLDMP3                     * If finished, move on
       JMP  BM2
BM2C   MOV  *R1+,@MAPMOB               * Get mob data
       MOV  *R1+,@MAPMOB+2
       MOV  *R1+,@MAPMOB+4
       MOV  *R1+,@MAPMOB+6
       BLWP @MOBWIN                    * Calculate window positon
       MOV  R3,R3
       JLT  BM2A                       * Not visible, skip placement
       INC  @WORK2                     * Increase mob count
       MOV  R3,*R6+                    * Copy index to WORK2 array
       MOVB @MAPMOB+1,*R6+             * Copy pattern to WORK2 array
       MOVB @WORK,*R6+                 * Copy mob index
       MOVB @MAPMOB+1,@VMAP(R3)        * Copy pattern to VMAP for LOS calculations
       LI   R4,4
       LI   R2,MOBSEN                  * Load mob sense data
BM2D   C    R3,*R2+                    * Check if position is next to player
       JNE  BM2E
       MOV  *R2+,R5                    * Get address into R5
       MOVB @WORK,*R5                  * Copy mob to state array
BM2E   INCT R2
BM2F   DEC  R4                         * Loop all four locations
       JNE  BM2D
       JMP  BM2A
* Update sense counter for traps/secrets
BLDMP3 CLR  R1                         * Clear R1 for sense counter
       LI   R0,SURRND+1                * Set R0 to SURRND array, mob area
       LI   R2,4                       * Set R2 to 4 (4 directions)
BM3A   CLR  R3
       MOVB *R0+,R3                    * Copy mob # to R3
       JLT  BM3B                       * if negative, skip
       SRL  R3,5                       * Make 8-step index
       A    @MOBADR,R3
       MOVB *R3,R4                     * Copy mob type to R4
       SB   @B16,R4                    * Subtract 16 from mob ID
       JLT  BM3B                       * If less than zero, not a hidden mob
       SRL  R4,8                       * Shift value to make index
       MOVB @SENSEV(R4),R5             * Copy from character array
       JEQ  BM3B                       * If 0, skip
       SOCB R5,R1                      * Set bit
BM3B   INC  R0                         * Increase to next tile position
       DEC  R2                         * Decrement counter
       JNE  BM3A
       MOVB R1,@SENSEC+1               * Copy R1 to SENSEC (Counter)
* Check party light level, map onto tiles
LGTMAP MOV  @MAGEYE,R0                 * Check if magic eye is active
       JEQ  LGT1
       BL   @MEVIEW                    * Fully open map
       B    @PCME4A
LGT1   MOVB @LIGHT,R0                  * Check if map is fully lit
       JEQ  LGT1A                      * If so, skip to LOS algorithm
       MOVB @PARTY+30,R0
       ANDI R0,>2000                   * Check for Radiant Pharos
       JEQ  LGT1B
LGT1A  BL   @MEVIEW                    * Fully open map
       JMP  LOS
LGT1B  CLR  R1                         * Set buffer index
       LI   R5,169                     * Set buffer counter
LGT2   MOVB @ALIGHT(R1),R2             * Copy window position light value into R2
       SB   @LGTLV+1,R2                * Subtract the current light strength from window value
       JGT  LGT3                       * If greater, unlit
       MOVB @B128,@LMAP(R1)            * Otherwise, mark the tile lit
LGT3   INC  R1
       DEC  R5
       JNE  LGT2
* Map line-of-sight on the map
LOS    LI   R8,4                       * Number of directions to process
       CLR  R9                         * Direction index
CLOS1  LI   R7,6                       * Count of tiles
       LI   R0,6                       * Column position
       LI   R1,6                       * Row position
CLOS2  CLR  R12                        * Set tile to copy to closed tile
       BL   @POSCLC                    * Calculate position
       MOVB @LOSMAP(R3),R2
       JEQ  CLOS3
       CI   R3,84
       JEQ  CLOS2A
       BL   @CHKTLE                    * Fetch tile's opacity level, also check elevation
       ANDI R5,>8000                   * Check if a blocking tile
       JNE  CLOS3
CLOS2A LI   R12,>0100                  * Set tile to open
* Set tile
CLOS3  MOV  @MAPLOS(R9),R2             * Copy direction vector index
       A    @DIRX(R2),R0               * Add direction vector to column
       A    @DIRY(R2),R1               * Add direction vector to row
       BL   @POSCLC                    * Get buffer index
       MOVB R12,@LOSMAP(R3)            * Copy visible/blocked value to buffer
       DEC  R7
       JNE  CLOS2                      * Loop through path
       INCT R9                         * Change direction
       DEC  R8
       JNE  CLOS1                      * Loop through rows
* Diagonal LOS
DLOS   LI   R8,4                       * Number of directions to process
       CLR  R9                         * Direction index
DLOS1  LI   R6,6
       LI   R0,6
DLOS2  LI   R7,6
       LI   R1,6
DLOS3  BL   @POSCLC
* Collect four tile indices in FRAM array
       MOV  R3,@FRAM
       MOV  R0,@FRAM+8
       MOV  R1,@FRAM+10
       MOV  @MAPLOS+8(R9),R2
       A    @DIRX(R2),R0
       A    @DIRY(R2),R1
       BL   @POSCLC
       MOV  R3,@FRAM+2
       MOV  @FRAM+8,R0
       MOV  @FRAM+10,R1
       MOV  @MAPLOS+10(R9),R2
       A    @DIRX(R2),R0
       A    @DIRY(R2),R1
       BL   @POSCLC
       MOV  R3,@FRAM+4
       MOV  @MAPLOS+8(R9),R2
       A    @DIRX(R2),R0
       A    @DIRY(R2),R1
       BL   @POSCLC
       MOV  R3,@FRAM+6
       MOV  @FRAM+8,R0
       MOV  @FRAM+10,R1
* Check three surrounding tiles
       CLR  R12
       MOV  @W3,@FRAM+8
       LI   R2,FRAM
DLOS4  MOV  *R2+,R3
       MOVB @LOSMAP(R3),R4
       JEQ  DLOS5
       BL   @CHKTLE
       ANDI R5,>8000
       JEQ  DLOS6
DLOS5  DEC  @FRAM+8
       JNE  DLOS4
       JMP  DLOS7
DLOS6  LI   R12,>0100
* Set tile
DLOS7  MOV  @FRAM+6,R3
       MOVB R12,@LOSMAP(R3)
       MOV  @MAPLOS+8(R9),R2
       A    @DIRY(R2),R1
       DEC  R7
       JNE  DLOS3
       MOV  @MAPLOS+10(R9),R2
       A    @DIRX(R2),R0
       DEC  R6
       JNE  DLOS2
       AI   R9,4
       DEC  R8
       JNE  DLOS1
* Final opening of permitted space
PCMEND CLR  R1                         * Set buffer index
       LI   R0,169                     * Set buffer counter
       MOVB @CELEV,R2                  * Copy elevation to R2
PCME1  MOVB @LOSMAP(R1),R3             * Check LOS
       JEQ  PCME3
       MOVB @VMAP(R1),@LMAP(R1)        * Set the tile onto the map
       JMP  PCME4
PCME3  MOVB @SPACE,@LMAP(R1)           * Make the tile black (invisible)
PCME4  INC  R1
       DEC  R0
       JNE  PCME1
* Place visible mobs
PCME4A MOV  @WORK2,R0                  * Check mob count
       JEQ  PCME7                      * If zero, skip
       LI   R1,WORK2+2
PCME5  MOV  *R1+,R2                    * Get mob index
       MOV  *R1+,R3                    * Get pattern
       CB   @LMAP(R2),@SPACE           * Is the space blacked out?
       JEQ  PCME6
       MOVB R3,@LMAP(R2)               * Set mob on map
       ANDI R3,>00FF                   * Get mob #
       MOVB @B1,@MOBVIS(R3)            * Set mob visibility to 1
PCME6  DEC  R0                         * Decrement count
       JNE  PCME5
PCME7  LI   R0,>F700
       SB   @BOAT,R0
       MOVB R0,@LMAP+84                * Copy the party icon to the center
       B    @SUBRET

* Magic eye/Pharos view
MEVIEW LI   R0,169                     * Set all tiles to lit/visible
       LI   R1,VMAP
       LI   R2,LMAP
MEVW1  MOVB *R1+,*R2+
       DEC  R0
       JNE  MEVW1
       RT

* Calculate mob position in viewing window
* Returns window index (0-169) in R3, -1 = not in window
MOBWIN DATA VWS,MOBWN0
MOBWN0 MOVB @SLANT,R2                  * Get orientation into R2
       SRL  R2,8
       MOV  @DIRY(R2),R0               * Set R7 to 1 (left), 0 (none), or -1 (right)
       NEG  R0                         * Negate R0
       MOV  @W7,@WMOB                  * Store 7 in WMOB
       MOV  @WN7,@WMOB+2               * Store -7 in WMOB+2
       MOV  @MAPMOB+2,R2
       MOV  R2,R4
       SRL  R2,8                       * Set R2 to mob Y
       ANDI R4,>00FF                   * Set R4 to mob X
       S    @Y,R2                      * Subtract player y from mob y
       CI   R2,7                       * Check right vector
       JLT  MOBWN1
       MOVB @EDGES,@EDGES
       JEQ  MOBWN5
       S    @VWIDTH,R2                 * Subtract wrap offset
MOBWN1 CI   R2,-7                      * Check left vector
       JGT  MOBWN2
       MOVB @EDGES,@EDGES
       JEQ  MOBWN5
       A    @VWIDTH,R2                 * Add wrap offset
       CI   R2,6                       * Check left vector again
       JGT  MOBWN5
MOBWN2 MPY  R2,R0                      * Multiply Y offset by R0
       S    R1,@WMOB                   * Adjust positive boudnary for X
       S    R1,@WMOB+2                 * Adjust negative boundary for X
       MOV  @WMOB,@WMOB+4
       DEC  @WMOB+4
       S    @X,R4                      * Subtract player x from mob x
       C    R4,@WMOB                   * Check down vector
       JLT  MOBWN3
       MOVB @EDGES,@EDGES
       JEQ  MOBWN5
       S    @HWIDTH,R4                 * Subtract wrap offset
MOBWN3 C    R4,@WMOB+2                 * Check vector
       JGT  MOBWN4
       MOVB @EDGES,@EDGES
       JEQ  MOBWN5
       A    @HWIDTH,R4                 * Add wrap offset
       C    R4,@WMOB+4
       JGT  MOBWN5
MOBWN4 MPY  @W13,R2                    * Multiply by 13 (window width)
       AI   R3,84                      * Add center offset
       A    R4,R3                      * Add X delta
       A    R1,R3                      * Add shift delta
       MOV  R3,R3
       JLT  MOBWN5
       CI   R3,168
       JGT  MOBWN5
       MOV  R3,@>0006(R13)             * Copy back to calling routine
       RTWP
MOBWN5 SETO @>0006(R13)
       RTWP

* Check tile at index in R3
CHKTLE MOVB @LMAP(R3),R4               * Check light level
       JEQ  CHKTL2                     * If not lit, is automatically blocking
       CB   @CELEV,@EMAP(R3)           * Check current elevation against target tile
       JEQ  CHKTL3                     * If equal, continue to opacity test
       JLT  CHKTL2                     * If less, go to block
CHKTL1 CLR  R5                         * Clear R5 (open)
       JMP  CHKTL4
CHKTL2 SETO R5                         * Set R5 (closed)
       JMP  CHKTL4
CHKTL3 MOVB @VMAP(R3),R4               * Copy tile to R4 low byte
       SRL  R4,8
       ANDI R4,>007F
       MOVB @TILES(R4),R5              * Copy tile code into R5 high byte
CHKTL4 RT

* Check for map edges
* R1 = Y, R2 = X
* R0 returns 0 if no violation, 1 if vertical, 2 if horizontal, 3 if both
EDGCHK CLR  R0                         * Set to 0
       C    R2,@HWIDTH                 * Check X against horizontal width
       JL   EDGCH1
       INCT R0
EDGCH1 C    R1,@VWIDTH
       JL   EDGCH2
       INC  R0
EDGCH2 RT

* Determine index on map
POSCLC MOV  R1,R2
       MPY  @W13,R2
       A    R0,R3
       RT

Conclusion

Despite the numerous calculations going on, the map view creation is pretty fast, enough that I had to introduce some artificial delays. I did notice that the more mobs on the map the more impact it had on performance. For that reason, there is a limit of mobs per map, and I actually broke up some maps into more than one map to remove performance problems.

So that’s it with maps and part 2! I’m not sure what part 3 will cover yet, I’m open to feedback.

Posted in Coding, CRPG, Design | 2 Comments

Summer Update

Hey all…

Sorry, it’s been awhile since I posted here. After the release of the game, I got crazy busy doing patches, testing, and improvements.

Thanks to the help of several enthusiastic fans, I was able to do a large substantial update to the game to address a number of balance issues in the middle tier of the game, and add several new features. I am calling it “done” now, with only bug fixes going forward.

I also decided to do one last thing, which was a 2nd printing of the Collector’s Edition. And this time use crowdsource funding to do it. If it gets funded, great! If not, I may do a small print run privately for the store to sell and then I’ll close the book on this chapter.

You can find the Kickstarter here:
https://www.kickstarter.com/projects/arcadeshopper/realms-of-antiquity-the-shattered-crown-collectors-edition/

Posted in CRPG, Personal, RPG, TI-99/4a | Leave a comment

Waiting on the Post

Stack em up!

Okay, all pre-ordered collector’s editions have been mailed! Going forward, I’ll be shipping them individually rather than waves. I just received my last order of game boxes, which means there will be around 100 or so collector’s editions. I don’t plan on doing more, so get them while you can!

If you want the custom cartridge and/or disk, there will likely be a delay in shipping your order. I have to order the cart boards and chips separately,  and with floppy disks I’ve been recycling old disks I  got a pile of (thanks Ciro!), but it’s a roll of the roulette wheel if they’re usable or not…

Part 2 of Making a CRPG will be coming next… with a focus on scrolling maps!

Posted in CRPG, Personal, TI-99/4a | 2 Comments

Making a CRPG – Part 1: Infrastructure and Platform

In this, the first part of several, I will talk about the creation of Realms of Antiquity for the TI-99/4a home computer. There will be technical, narrative, and designer content, with plenty of side-treks. Strap yourselves in!

I should add that these articles are likely to be very “crunchy” with technical detail. The intended audience would have some passing knowledge of assembly language as well as being CRPG enthusiasts. If you want more detail, program listings, algorithm definitions, by all means post and ask!

The best place to start? The platform it was built upon, and the infrastructure of how the code drives the program. And on that note, the number one best resource EVER is the TI Tech pages. They were an awesome and useful resource with my project!

The Platform

A big reason that there was a distinct lack of good software for the TI until well after the home computer division was cancelled in 1983 is the processor that drives the TI-99/4a, the TMS9900 microprocessor, released in 1976.

It has occasionally been called the “first” 16-bit processor, but that claim is disputed by IBM and Motorola. It’s VERY different from the 6502, arguably the most popular and well-known microprocessor of its era, in a multitude of ways:

  • 3mz clock speed, around 3 times the speed of contemporary processors
  • 16-bit instead of 8-bit
  • Can do register-to-memory, memory-to-register, or even memory-to-memory operations
  • Big-Endian (high byte first, then low byte, going left to right)
  • Hardware unsigned multiplication and division operators
  • No native stack implementation
  • No memory page addressing; no “zero page” concept
  • 15-bit address line, so it accesses 32,767 “words” of 16-bit size
    • Byte operations are handled via special op codes
  • 16 CPU general purpose registers available
    • Instead of hardware registers, they are relocatable anywhere in CPU memory using a workspace pointer
    • You can have as many register sets as you want; this effectively replaces a “stack” concept, as you can use registers as a means to pass values
    • Only a few registers have special uses
      • R11 is always the return address for a branch and link
      • R12 is used by the communications register unit (CRU) for special purposes; the SAMS card needs this for page swaps
      • R13-15 are used for context switches. They store the return address, workspace address, and status register from the prior context

Because opcodes are 16-bit, TMS9900 assembly uses more memory than a 6502 line-by-line, as instructions can run anywhere from 1-3 words (2-6 bytes). But you save memory because you can do in one instruction what takes several on other processors.

To use a car analogy, if a standard 8086 processor is your typical car, the TMS9900 is a Cadillac. Big luxurious driving, but kind of expensive and fuel-consumptive. 🙂

Memory

The TI-99/4a has a 16-bit addressing range, for 64k total.

Unlike other architectures, none of this addressing space is used to map video; the VDP chip has it’s own 16k of dedicated RAM which is accessed through memory-mapped ports. These ports only allow you to read/write bytes, not words. This bottleneck is the cause of much anguish and annoyance on the part of TI programmers. It can and HAS been overcome; the singular most impressive work in this area in my opinion is Mike Brent’s “Dragon’s Lair” for the TI-99/4a, which runs on the base console and renders all the original videos in a fairly decent rendition on TI’s bitmap mode.

The base console only has 256 bytes (!) of CPU RAM, nicknamed the “scratchpad”, which is fast 16-bit memory. Most of the time, it’s best to locate your register set here. Some values in the space are used by internal processes but most of it is available for your use. TI’s Parsec cartridge (which features horizontal pixel bitmap scrolling) had to locate it’s scrolling routine in the scratchpad for maximum speed.

If you have the 32K memory expansion, you get two large blocks of CPU RAM, a lower 8K block and the upper 24k block. This RAM is accessed with a slower 8-bit multiplexer, which adds wait states when accessed, so many 99’ers try and move time-critical code into the scratchpad for best performance. My personal experience has been the slower speed is not really an impediment unless you’re doing something really over the top.

So where, you ask, are memory pages, like Apple and Commodore have? Well, there aren’t any in the base TI architecture. The only page switches occur with some cartridges, which have their own 8K space. That’s where the SAMS card comes in.

Reverse-engineered from the never-released TI-99/8 architecture, the Super Advanced Memory System (SAMS) card allows you to swap out 4K pages anywhere in the addressing space that RAM exists, just using some simple instructions to configure it. These pages don’t even need to be unique; you technically could assign the same page twice in two different places. The base SAMS card gives the TI 1MB of memory, or 256 pages, which is a bounty of space to play in!

But how to write code for such a system? Well, that’s the tricky bit…

Assembly

One thing to call out is that Realms of Antiquity is written in 100% assembly language. It’s reasonable to ask why, when high-level languages could be utilized to simplify maintenance and understanding.

Well, for one, because I wanted to. 🙂

Second, If I was writing a game for modern computers directly, I would not hesitate to use a high-level language. Besides being easier to manage, the most important thing about them is they can be compiled for different architectures. If I wrote a game in Java, I know it will run on a PC, MAC, or even Linux without any problems, and regardless of what kind of chipset or hardware are present.

But for a classic retro computer? You know the hardware and how it works and how to optimize for it. If you need speed and performance, assembly is the way to go. Any high-level language may apply a software pattern that works but could have been implemented with less memory or better efficiency.

Loaders

The TI-99/4a differs from a lot of other microcomputers of the era in that assembly language isn’t readily accessible with the base console. TI BASIC completely blocks access to it, TI Extended BASIC offers some access to load and run but no assembler is provided.

Most 99’ers use the Editor/Assembler to do their work. It was the big package deal, requiring the full system (disk drives, 32K expansion) to use. It had both a text editor and assembler, and two disks of utilities. They even threw in the complete source code for one of their games, Tombstone City.

Now on the TI, there are two kinds of assembly binaries:

  • Tagged-object code
    • Can be loaded anywhere in memory
    • Can co-exist with other object code and ran independently via name
    • Can refer to each other using assembly directives
  • Fixed-binary code
    • Only loads to specific memory locations
    • Stored as “memory images” with a maximum size of 8K per image minus six bytes for a header value
    • Loaded as a chain of files to fill up the entire memory space
    • Usually called “EA5” format as they were loaded using the Editor/Assembler cartridge’s option #5 “Load Program File”

Most 99’ers write assembly programs to start with as tagged object code. They are then converted to fixed-binary files using a utility. Programs load much faster this way as it loads them as 8K segments directly into memory.

As for how to load a SAMS program, which occupies more space than the 32K RAM? We’ll get to that in a bit. First, we go into…

Modules

So in the summer of 2017, I made the decision to convert Realms of Antiquity to use the SAMS memory card. As part of this, I had to figure out HOW to use it effectively.

The only assembler ever written for the AMS was an extension of a popular macro assembler called “Ragtime”, written by Art Green. I’ll give him credit; he did create an entire assembler/linker/loader platform which could utilize the card. But I had already been using a cross-assembler on the PC for speed and efficiency so I didn’t really want to try and compile everything on the TI in emulation.

So instead I read the documentation on how it built modules that were linked to each other and I figured out the pattern.

The first thing with any program in modules to do is identify your “root” functions that absolutely are needed everywhere. These form the basis of your “root” module, which always is present in memory and is accessed by everything. Then, figure out how many other modules you need. Ideally, if the root module and another are loaded, you should always be able to fit it into the existing address space. (Which on the TI is 32k, split into the 8k and 24k blocks.)

For Realms of Antiquity, I wanted the 8K block for data pages only, utilized by the modules for various functions. So I split the upper 24k into two modules, the root module and then potentially four other modules:

  • Start Module (Contains the title screen, character creation, music player and data, and end game sequence)
  • Travel Module (Contains the code for travel mode, includes map loading and mob interactions)
  • Manager Module (Contains the code for inventory, stat screens, and complex transaction management)
  • Combat Module (Contains the code for combat mode)

I later added more modules and sub-modules:

  • Encounter Module (Contains the code to generate battlemaps, as well as end battle management such as chests, traps, rewards, etc.)
  • FX/Scan Module (Contains all the code to create FX for combat, sprite based effects, as well as the code to create the monster stat screen. Only swaps out the last 4K page of the Combat or Encounter module.)
  • AI Module (Contains all the code to determine monster actions. Only swaps out the last 4K page of the Combat module.)

The sub-modules occurred as modules got full and I didn’t want to try and create a whole new module. This made me realize after the fact that I could have done a better job splitting up functionality and making smaller modules instead of larger monolithic modules. A good lesson for future projects!

So how to compile it? I just created several batch files to execute my cross assembler at a combination of the root module files and each targeted modules files, effectively compiling them as separate binary files. I then created some utility programs that copy the binary code out into the program binary file in specific locations for each module.

Memory Map

Here’s a picture of my file and memory map:

The greyed out areas are pages that are technically assigned to ROM addressing space at start-up time. That is the default mode for SAMS; pages 0-15 are just assigned consecutively. So that means after the program has started I can freely use those pages for data.

Pages 2-3 are the 8K lower memory space which means they are switched as needed for different functions. Pages 10-12 are always the root module. Pages 13-15 are the alternate modules. Everything after those is raw data used by the game, up to page 53. I use page 64 onwards for storing saved game data in memory while you play.

So how to get this into the SAMS card? The E/A loader certainly can’t do this. Time for a custom loader…

Custom Loader

The first issue to deal with is getting a loader in the right place. The default location for most assembly programs is the start of the upper 24K block. That’s not ideal here, though, because we want the root module there and if we swap it out at any point in the loading process the loader code will be lost! So we make sure it’s located in the lower 8K RAM instead. This is achievable by using an opcode called AORG (Absolute Origin) to relocate the program there.

The loader has to be self-contained, so it contains not just the loading code itself but subroutines for reading and writing to VDP. This is necessary beyond just updating the screen; the TI device service routines (DSR) which the disk system utilizes requires you to use buffer space in the video memory. This curious design is likely because on a base TI console that was the only RAM memory any architect could rely upon being there for buffer. Unfortunately that means all data has to be read from the VDP back into CPU memory.

The loader loads 8K chunks of data from the program binary at a time into the upper RAM, which are assigned to the requisite pages. It updates the page assignments on each pass, so each 4K blocks ends up in it’s correct page. With 44 pages of data, or 176k, it takes a bit! I originally designed my loader to read in 12K blocks from the program binary, but I found in practice this didn’t work, even when I was certain the VDP memory was freed up. I have noticed that TI was biased towards 8K blocks as a maximum size.

For the cartridge ROM, I have a different approach. I use 8K ROM pages to store the program, using 2K of the space for the loader and 6K for program segments. This is necessary because you aren’t guaranteed what ROM page a cartridge starts on, so you have to replicate your root code in every page. It does a direct CPU to CPU memory copy from the ROM page into the upper 24K page space with page swaps in a similar fashion. This is one reason the cartridge is by far the fastest way to load the game, no VDP in the middle of the process!

And here ends Part 1. In Part 2, we will start looking at specific modules and routines and going into excruciating detail on them!

 

Posted in Assembly, Coding, CRPG, Design, TI-99/4a | 11 Comments

Packing, Stacking and Mailing

Been nearly a month since release, and I’m overwhelmed by the response and feedback! Thank you everyone who purchased the game, and I hope to see more as word spreads.

I did an interview with The Lost Sectors on YouTube on Friday, you can view it here:

I also received the cartridge boards and EPROMs from my source to create the custom cartridges for Collector’s Editions, so I have everything I need now to start the mailing! (Formatting floppy disks and copying data is by far the slowest part of the operation. Thank goodness my drives are holding up for the task.)

Each Collector’s Edition will have all contents inside the game box, and will also be shrink-wrapped for added protection. I purchased custom mailers from ULine that fit the box perfectly as well. I’ll be taping them up thoroughly, especially for international shipping. Customs forms will identify it as “merchandise” worth $20 U.S. to avoid high duty fees and delay. Everyone will get emailed a tracking # for it as well. I’ll start on them this week and try and get them all out by the end of the week.

As for what’s next for the blog?

Well, my next TI project at some point! I plan to do a few small-scale ones that I’ve had on my mind. I wanted to do a true assembly version of an old BASIC program I wrote “Aperture”, just to learn the ins and outs of doing a platformer. I also every year around Christmas get a bug to write a game with shopping and having to pick up gifts in a department store and navigate around rude senseless people.

My next big project will be my Gauntlet clone, which will require the SAMS card. I have some particular technical limitations and challenges I have to make sure are achievable with it. I also have a Roguelike on the brain… Something that’s a mix of Rogue, NetHack, Warriors of Ras, and similar.

I may also take some time on the blog detailing the actual coding and design work that makes up Realms of Antiquity. My blog over the years has a lot of details on it, but a linear step-by-step set of posts of “How was it made?” and “Why this and not this?” may be educational for some.

Happy gaming to all! And be sure to check out Nox Archaist, another retro CRPG for the Apple II!

Posted in Blog, CRPG, TI-99/4a, Video | 3 Comments