This past week, despite being very busy, I managed to be a little more productive than previous weeks.
I closed a few bugs in Wiz:
- Fixed SNES debugger symbols to work again, so now you can get nice named labels and variable names in Mesen-S and bsnes-plus when debugging SNES programs.
- Someone reported a Mac OS crash in the compiler! It was caused by C++ argument evaluation order being unspecified. This means that the order depends on the compiler vendor (usually dictated by the calling conventions of the target system).
Usually this is easily avoidable, however
std::moveis a core feature as of C++11, which will transfer ownership of resources (eg. using unique pointers to enforce single ownership and prevent memory leaks, or for avoiding expensive copies of heap-allocated data structures). For unique pointers such as
std::unique_ptr(or Wiz's equivalent-but-better-inlined
wiz::UniquePtr), moving is necessary, and the copy assignment and copy constructors are deleted to enforce move semantics.
There is is a bit of a hidden "foot-gun" waiting to go off though:
std::movehas the side effect of moving its contents to the destination and nulling out the source afterwards, to preserve the single ownership of a piece of memory. This is how
std::moveis meant to work, so nothing unexpected here so far. But often, there is the need to move a value AND access some its members in the same call.
f(std::move(ptr), ptr->member);is easy to slip into the codebase by accident, especially as compilers don't generate any warning for this. On x86-based Windows machines, where the argument evaluation order is right-to-left, this code will compile and run as expected. On Mac OS, this will compile too, but it will always crash at runtime, because it evaluates function arguments left-to-right -- this means the
std::move(ptr)is called before
The fix to this crash is to store things into locals before a move occurs, so that you don't use-after-move. But the nature of this being "unspecified" by the C++ standard means that the compiler never warns about this bug, and it may even appear to work if the vendor/platform-specific evaluation order just happens to works in the program's favor. As a result, any change to the code could introduce this bug without notice. This reliance on the evaluation order is never desirable when wanting to write portable cross-platform programs in C++ that run reliably (and without crashing).
clang-tidyis a useful static analysis tool that can detect these sorts of problems by running with the
bugprone-use-after-movecheck enabled. It caught the crash and a number of others within the codebase.
VS2019, for all of its bloat, even comes with clang-tidy. This would be great, but MS unfortunately messed this up somehow and shipped an update that breaks being able to do static analysis -- I had to manually patch my VS2019 install's version of
intrin.hto get things working. Sigh. Anyway. A long journey later and things are working better.
- Fixed the issue with nested struct access, Now you can declare an alias for register, and use as a pointer-to-struct by going
var monster : *Monster in ix;and then use it like
monster.stat.atk = 10;
With that weight lifted, I started to get planning out the battle system and monster encounters for my NES RPG.
I thought that it might be useful to design the encounters in a sort of "top-down" kind of way. having families/categories/taxonomies of monsters that all share common traits and behaviour. Within each family, the monsters would be divided into tiers, a sort of "evolutionary hierarchy" (for monsters) or "chain of command" (for humanoid types), where each tier is a higher difficulty and more powerful.
Then, monsters families themselves belong to different environments. Things would be designed such that there's a nice variety of different encounters spread across the environments -- gradually increasing in difficulty, or introducing a new challenge or mechanic to the fights to keep things interesting.
My hope is that this sort of approach will make it easier to get a general image of how the game should "look", and allow me fill in boxes as I go. This should give a more coherent idea of the big picture of how things flow across the game area-to-area, something which usually gets lost when doing in a bottom-up method, one-map-at-time.
Monsters themselves would have distinctive behaviour, and use a combination of some state-machine driven logic, turn-based countdowns, and reactions (counters). Each state would be capable of producing a list of actions that it wants to consider, as well as a preference score to each one. States could transition depending on specific conditions like HP %, being hit X many times, detecting the player doing something, etc. Monsters themselves could also track grudges, prioritize protecting other monsters and other stuff.
But I am thinking of implementing monster AI in a way that it's mostly driven by the monster's state and a few other scratch variables. I think this method of monster design could be pretty versatile despite being fairly simplistic.
I also had some thoughts about the battle system itself. I had originally made a mockup in FF6 style, but I was thinking it might be nice to use something a little different from ATB battles. I was thinking it could use initiative-based turn orders with a combination of randomness + character speed to help determine the order. Combatants would decide and act at the same time when their turn comes up. Some actions could potentially have a charge time (that resolves as its own turn between combatant turns). Actions could also have a cooldown that affects the timing of a combatant's next turn.
I also drew this little mockup of Wandering Magic if it were a game for the MSX 1:
The mockup tries to follow MSX "Screen Mode 2" restrictions -- 256x192 resolution, a fixed 16 color palette, backgrounds are composed of 8x8 tile patterns that can have 2 possible colors per 8x1 area, sprites are 1-color and 16x16 in size.
That's all for this week!