So yeah. As mentioned in the previous couple posts here, HS1 has a
pair of
major deckbuilder bugs. They went unnoticed because I played Adventure Mode very little while developing HS1 (since I didn't change it at all except to interface with the new deckbuilder), the one bug was caused late in development by a typo, and the other appeared to work in a superficial test. The first is in an area of the program that I'm very confident in, and is thoroughly fixed.
The other,
#1149, is in an area that I'm not at all comfortable with, and where I've already screwed up verification once. I've posted a fix for it at the bug report, and it could really use some more thorough testing than what I've given it so far. Failing that, because I
really don't want to have to put out another emergency fix, I'll probably delay until I'm ready for a bugfix release of Manalink as well. (Manalink's most prominent current breakage is with
cycling and other abilities that activate via the Rules Engine.) That's probably not going to be ready until at least this weekend, and at that point I may put it off a couple days more until
Conspiracy 2 is up in Gatherer.
---
Before becoming aware of those - in fact, well before releasing HS1 - I began work on effects that add or remove subtypes from cards. This turns out to be relatively difficult in this engine. There's three places where data about a card is stored: in a card_instance_t data structure ("struct" in C parlance), which stores data about the current status of a particular card that's in your hand, on the stack, or on the battlefield; in an immutable card_ptr_t struct, which stores everything that can be determined from a physical printing of a card; and in an intermediate card_data_t struct, which contains mostly AI and optimization data, but also mirrors certain fields from card_ptr_t like its types, base power and toughness, and static keyworded abilities. None of the three can be easily enlarged, since accesses to them from Magic.exe and Shandalar.exe (as appropriate) are pervasive.
The stuff in card_instance_t is very easy to change - so much so, that every byte of unused space in it has been earmarked for something else in Manalink, and even a good amount of the space that
is used (which is why, for instance, things break very, very badly if a permanent's ability has more than ten targets, and subtlely badly if a spell's does). The stuff in card_data_t and card_ptr_t, though, are shared by every card of a given name, so can't be edited directly. Of the two, there's space for 16 card_data_t's to be created at runtime, and - since a card's types
aren't one of the things stored in card_instance_t, but is in card_data_t - that's how things like
Ashnod's Transmogrant and
Living Lands work: for each kind of land on the battlefield that has subtype
Forest, it makes a temporary card_data_t that's copied from that land's card_data_t and then edited to add TYPE_CREATURE and to set its base power and toughness to 1. However, subtypes aren't one of the things mirrored from card_ptr_t to either card_instance_t or card_data_t, there's no existing facility to make a temporary card_ptr_t at runtime, and it's not practical to add such a facility.
"But wait, Korath!
Mishra's Factory is an original MicroProse card, and
it gives itself
Assembly-Worker! Why not just copy that?" Glad you asked. What the original
Mishra's Factory does, instead of adding type Creature and subtype
Assembly-Worker, is to change the card to an entirely different one, which already has that type and subtype, and is named
Assembly-Worker. (In all fairness, that's what the card
said it should do in its Antiquities and Fourth Edition printings.) This is how most manlands are implemented in Manalink, too, but it
- requires a second card_ptr_t slot for each such card, and until very very recently we were almost out of those;
- makes the secondary version of the card have a different name ("Creeping Tar Pit Animated" vs "Creeping Tar Pit"), so things like Eye of Singularity or Remembrance or Bifurcate or Wake of Destruction won't see them as the same unless you're very careful (and happen to remember this mostly-unrelated problem when implementing them);
- doesn't carry over previous changes to card_data_t when it changes a card - turn that Creeping Tar Pit into an artifact with Liquimetal Coating, and if you're lucky, nothing at all will happen when you try to animate it; if you're not, it'll turn into something completely random or just crash;
- is completely incapable of changing the subtypes of an arbitrary other card, like Angelic Armaments or Oni Possession or Nameless Inversion have to do;
- in the specific case of Mishra's Factory, anyway, keep us from getting the standalone Assembly-Worker card from Time Spiral, or the textless assembly-worker tokens created by Urza's Factory, quite right. With effort, they can be made not to be lands, not to activate for mana, and (for the latter) not to activate at all, but rules text and the text for the type line are also stored only in cards_ptr_t, so their fullcards will always look wrong.
To implement the stuff from #4, Manalink also assigns two values in card_instance_t - which, as mentioned before, are in
extremely short supply - for "add this subtype" and "replace all subtypes with this". That works out tolerably well for isolated cards (and things like
Oni Possession can set both, so it replaces all subtypes with Demon and then adds Spirit), but can't accommodate more than one of these effects on a given card at a time: you can't use
Sensei Golden-Tail and
Mephidross Vampire to make an army of Vampire Samurai. Plus, there's quality-of-implementation issues with it, since these values are added or removed at discrete times, and not continuously recomputed like, say, power and toughness are - hence problems like
Dragonsoul Knight staying a Dragon forever are pervasive, and keep getting found years after the card's programmed.
So a general solution means one of:
- Make it possible to create new card_ptr_t's at runtime, which is difficult and clumsy (it'd have all the same problems as changing to a temporary card_data_t).
- Copy subtypes into card_data_t and create those at runtime. That's still clumsy, for all the reasons above; also, it'll break for effects that add a subtype to a lot of different objects, since only 16 different run-time-created card_data_t's can exist a time and expanding that limit is difficult; and space within card_data_t is fairly limited. (The last isn't insurmountable - 35 bytes are earmarked for a copy of the card's name, which is never overwritten and is redundant to the card_ptr_t data. While that copy isn't accessed much from Magic.exe, though, it's accessed pervasively by Shandalar.exe, and all those would need to change to go to the card_ptr_t version instead.)
- As in Manalink, assign an itsy amount of space within card_instance_t to deal with most cases, and special-case the subtype-checking functions for effects that need to either add several different subtypes (like Figure of Destiny) or to many different cards (like Rimefeather Owl - supertypes other than legendary (which has its own problems) are internally treated identically to subtypes). Those special cases haven't, as a rule, worked out very well in Manalink.
- Find room in card_instance_t (or expand it) to store a reasonable number of subtypes, and compute them continuously.
D is by far the most attractive, assuming space
can be found. But it really couldn't, and direct expansion of the struct is even less practical than in Manalink. So way back in October during development of AS1, I held my nose and created an auxiliary data structure to hold all the stuff that should go into card_instance_t but just can't fit. This is a parallel collection, which is a well-known antipattern - the sort of thing they've been teaching you in school not to do for decades. The biggest problem with this antipattern is always being sure that the auxiliary structure you're looking at is the correct one for the card_instance_t you want to know about, and in particular keeping the two structs in sync when a card_instance_t is initialized, destroyed, reused for a different card, backed up for the AI, copied to the other player's territory for a change-of-control event, synchronized to another thread for display, stashed in a window's private data, you name it. This all provides a lot of room for error, including a subtle bug in initialization that I didn't discover until after HS1 was released and I began to (finally) add subtype data in there, like I'd meant to all along. But I still think it's the least of this choice of evils.
Compared to all that, making cards look in card_aux_t for their subtypes instead of card_ptr_t (or, in some legacy cases similar to
Mishra's Factory, card_data_t) is trivial.
In any case, it's working now, and the space I have for data in what's effectively part of card_instance_t is limited only by how long it takes for the AI to copy it back and forth while speculating, and my only real regret is that the prettiest and most popular of the cards I've been able to add with it were already in Manalink so they don't fit my self-imposed constraints for making them into my posting avatar here.
- Manlands and changelings and animation, oh my | Open
- Ambush Commander
Amoeboid Changeling
Angel's Tomb
Angelic Armaments
Angelic Destiny
Anthousa, Setessan Hero
Assembly-Worker
Atarka Monument
Aurification
Avian Changeling
Awaken the Ancient
Awakener Druid
Azorius Keyrune
Balduvian Frostwaker
Blades of Velis Vel
Boldwyr Intimidator
Boros Keyrune
Call to Serve
Celestial Colonnade
Chainer, Dementia Master
Chameleon Colossus
Changeling Sentinel
Chimeric Coils
Chimeric Egg
Chimeric Idol
Chimeric Sphere
Chimeric Staff
Chronatog Totem
Corrupted Zendikon
Creeping Tar Pit
Crib Swap
Crusher Zendikon
Cyclone Sire
Darksteel Brute
Daxos's Torment
Dimir Keyrune
Dragonsoul Knight
Dralnu's Crusade
Dread Statuary
Dromoka Monument
Ego Erasure
Elvish Branchbender
Embodiment of Fury
Embodiment of Insight
Ensouled Scimitar
Ever After
Faerie Conclave
Fendeep Summoner
Figure of Destiny
Fire-Belly Changeling
Forbidding Watchtower
Game-Trail Changeling
Genju of the Cedars
Genju of the Falls
Genju of the Spires
Ghitu Encampment
Ghostly Changeling
Ghoulflesh
Glint Hawk Idol
Golgari Keyrune
Grimoire of the Dead
Gruul Keyrune
Gruul War Plow
Guardian Idol
Guardian Zendikon
Halcyon Glaze
Haunted Plate Mail
Hidden Ancients
Hidden Gibbons
Hidden Guerrillas
Hidden Herd
Hidden Predators
Hidden Spider
Hidden Stag
Hivestone
Hydroform
Ignition Team
Inkmoth Nexus
Izzet Keyrune
Jade Idol
Kolaghan Monument
Living Terrain
Lumbering Falls
Lurking Evil
Lurking Jackals
Lurking Skirge
Mirror Entity
Mistform Ultimus
Moonglove Changeling
Mothdust Changeling
Mutavault
Nameless Inversion
Nantuko Monastery
Noyan Dar, Roil Shaper
Ojutai Monument
Olivia Voldaren
Olivia, Mobilized for War
Omnibian
Oni Possession
Opal Acrolith
Opal Archangel
Opal Avenger
Opal Caryatid
Opal Champion
Opal Gargoyle
Opal Guardian
Opal Titan
Orzhov Keyrune
Paragon of the Amesha
Phyrexian Totem
Rakdos Keyrune
Rise from the Grave
Runed Stalactite
Rusted Relic
Selesnya Keyrune
Shambling Vent
Shields of Velis Vel
Silumgar Monument
Simic Keyrune
Skarrg Guildmage
Skeletal Changeling
Skinshifter
Slumbering Tora
Soul Separator
Stalking Stones
Still Life
Stirring Wildwood
Taurean Mauler
Testament of Faith
Thunder Totem
Treetop Village
Vastwood Animist
Vastwood Zendikon
Veil of Birds
Veiled Crocodile
Veiled Sentry
Wall of Resurgence
War-Spike Changeling
Warden of the First Tree
Warden of the Wall
Weatherseed Totem
Wind Zendikon
Wings of Velis Vel
Woodland Changeling
Woodwraith Corrupter
Xanthic Statue