SNG:Rules Description Language
Stars! Supernova Rules Description Language
by Jim Lane Jan 17, 2000
Is a primary race trait too strong? Is Cheap Tech too costly? Want to create a new type of armor? Or change the number of parts that a ship hull slot can accept? You'll be able to do this in Stars! Supernova. Using a simple but flexible Rules Description Language (RDL) and a text editor, you can quickly modify these and other low-level game details for any scenario, then test the effects on game balance without hacking the Stars! Supernova program.
The RDL is not the scenario editor
The RDL is not a substitute for a scenario designer. Stars! Supernova will also include a graphical scenario editor that allows you to locate and name star systems and players, edit system statistics, create alliances and teams, introduce artifacts, and so on. The RDL is used to change the values and behavior of low level elements of the game, and to balance game play.
Game details that can be changed using the RDL are described in files called rule sets. We'll ship the play-tested rule set developed for Stars! Supernova with the game. We expect (and hope) that the player community will both modify this rule set and create alternate rule sets: no matter how hard we try, there's no way for the Mare Crisium team to match the ingenuity of thousands of players searching for clever loopholes in game play. When a too-clever trick is discovered, scenario designers may be able to compensate for it without waiting for Mare Crisium to make changes to the game's source code.
The rule set used by a particular game is contained in the universe definition files for that game. This allows hosts and players to have any number of games in progress, each with its own rule set, with no interference between them.
The rule set used for a particular game cannot be changed in any way once the game begins. A host could choose to start a game using a very unbalanced rule set, but nobody will be able to cheat by changing the rules during the game. Players will easily be able to tell if a non-standard rule set is being used.
Knowledge and caveats
Using the RDL will require a certain degree of technical knowledge. It is a simple language; much easier than C, but a limited exposure to programming will probably be needed to understand it.
The RDL has limits; it can't change everything. The way scanners interact with cloaks; the way missiles interact with jammers; the possible range of warp speeds; these are all fixed by the Stars! Supernova game engine. The RDL can change the type(s) and number of parts that can be mounted in a particular slot in a ship hull, but the number of slots for a given hull type cannot be altered.
(In programmer terms: the algorithms that determine how things act, and some of the fundamental design elements, are written into the game. The RDL can change many, but not all, of the parameters used by those algorithms.)
Does this all sound too good to be true? It isn't, but there is a catch: the RDL compiler is a Mare Crisium internal tool. It will work with the rule set source supplied with Supernova, but it won't be guaranteed to be bug free. Mare Crisium is not a large enough company to handle technical support for even a simple computer language. Anyone with the technical skill will be able to write a new RDL rule set, but Mare Crisium will disavow any knowledge of your actions. If your changes don't work, you'll be on your own. If working without a net worries you, stick to the rule set that is provided with the game.
Get a small sample of RDL source to read with this article. This example is created just for demonstration purposes, and it's based on Stars! game elements, not Stars! Supernova. Please don't nitpick the details; the general idea is correct, but many of the specifics are not.
Game Balance and the RDL
by Jeff McBride Jan 25, 2000
After many years of balance changes and feedback on Stars! we are still not satisfied with the balance of the game. Even back in the Stars! 1.x and 2.0 timeframe, when we were putting out new releases every couple of months, each cycle of feedback took far too long.
The whole evolutionary process that created Stars! left behind a lot of special case code for different racial traits and technologies. This provided, in essence, a hostile environment for balance changes. In other words, as the program got older and older it became harder and harder to make balance changes.
We designed the Rules Description Language (RDL) as a response to this problem. For Stars! Supernova, the turn generation engine is very simple. It knows nothing about racial traits or technologies. There are a fixed set of basic object types (like hulls, beam weapons, etc). Each object type has a fixed set of possible properties and behaviors that might apply. For example, a tech part could potentially have both an armor value and a scanner range but can never have a spectral class or an espionage skill.
Each member of our Q/A team and every beta tester will have the opportunity, not just to make balance suggestions, but to actively try them out themselves. This cuts the proposal/implement/test cycle down from days or weeks to minutes. When you combine this with the battle simulator and scenario editor you get a development platform that is idea for tuning the rules and balance of this kind of game.
We expect the balance of the initial release of Stars! Supernova to far exceed the balance of any release of Stars!. We also expect the stability of the initial release to far exceed the stability of any release of Stars!.
The source code for the core game engine will be cast in stone long before Supernova ships. This will reduce the number of bugs that could be introduced during final balancing and will tend to reduce the severity of any bugs that do get introduced.
For example, the RDL language is non-procedural. This means that, while it is possible for the rule set author to write an expression that comes up with the wrong answer, he can't get stuck in an infinite loop. Improper loop termination is a classic cause of "hanging" bugs.
Quite a few bugs that were fixed by releases of Stars! over the years have dealt with inconsistencies between different areas of the code. For example, the tech browser says that this part is available to PRT x but the ship designer only gives to PRT y.
By eliminating all of the special case code and relying on the same simple interface to the rule set everywhere a particular piece of information is accessed the vast majority of these kinds of bugs can be avoided. However, it will still be possible for the rule set author to write textual descriptions for things that do not match the rules they define.
The added security that RDL gives us, has allowed us to consider and play with hypothetical new features and behavior changes which would not have occurred to us before or would have been immediately rejected. For example, Alternate Reality ships obey different laws of physics in combat than other races' ships. I believe that Jim Lane gave a few hints about this in a post couple months ago. This is possible because the laws of physics are defined by the rule set not the battle engine.
Perhaps this will give you a better idea:
The turn generation engine knows that something of type fleet may have a property of "passive scanner range". The rule set determines if this fleet has such a thing or not and, if so, what that value is.
In Stars!, the scanner range of a fleet is equal to the maximum of the scanner ranges of each ship design in the fleet. In contrast, the scanner range of a ship design uses a form of addition on the scanner ranges of each tech part in the ship design.
For Stars! Supernova, it would be trivial to do it the other way around. The rule set author could choose to have the scanner range for a fleet be the sum of the scanner range of each ship type in the fleet or even of every individual ship. You could also decide that the scanning range of a ship design is determined by the scanning range of the single best part in the ship design. You could just as well decide that the scanner range of a fleet is dependant on the owner's electronics level and the distance to the nearest star. Scanners work better farther away from gravity wells? That's plausible.
In Stars! this kind of change would have required a major rewrite of large chunks of code in multiple locations and risk introducing many bugs, some of which might have nothing to do with scanner ranges.
For Stars! Supernova, it would be easy to make it work one way for most races and another way for Alternate Reality.
This is not something we're likely to do in the standard rule set that ships with Stars! Supernova. It is, however, something that is trivial to do with no code changes and virtually no risk to the behavior of features not directly associated with scanner ranges.
The short version of this is that RDL does not eliminate the need for testing. It does, however, reduce the code/test cycle time and improves the overall stability of the product.
Opening up the language for general consumption is pure gravy.
Rules Description Language example
by Jeff McBride Nov 8, 2000
The RDL language is object oriented, polymorphic and non-procedural. The game engine asks the RDL questions like what is the passive scanning range of this fleet? or what is the change in population of this system for this year? The RDL author writes algebraic formulas that answer each of these questions.
In the following RDL example, a number of simplifications have been made. Assume that any line that looks like this:
is an indication that a large number of additional clauses (lines) have been cut.
This example explains how the RDL author has chosen to answer the question "How cloaked is this fleet?"
First, we define a cloaking behavior. The game engine supports a predefined set of behavior types. One such behavior type is "Cloaked". Behaviors of type Cloaked contain a clause that computes the mass that this behavior is capable of cloaking to 50%. In this example, we've defined the Cloak value (mass) to be the constant value 200 (kT). We could just as easily have put in a complex formula that referred to the Owner or the Builder of the object such as "Owner.TechLevel3 * 10". You can define as many behaviors of type "Cloaked" as you want. We have named the behavior "Cloak200" so that we can use it later.
Cloaked.Cloak200 begin Cloak 200 ; Mass in kilotons. This behavior can cloak to 50%. end Cloak200
Behaviors by themselves don't do anything. You have to attach them to an actual object. Let's define a technology called a Shadow Shield, which acts as both a shield and a cloaking device. This can be accomplished easily by just attaching more than one behavior to the technology like so:
Technology.ShadowShield Begin cost 2 ; All of these values could be formulas mineral1 1 ; and in fact will have to be in order to mineral2 0 ; make miniaturization work correctly. mineral3 1 mass 2 ; It could easily affect mass as well. SlotType ShieldSlot ; Slot types are defined by the game engine BMP idbSHLD_SPCL_02 ; Id numbers for bitmaps and strings are Name idsShadowShield ; defined in another file Description idsShadowShieldDesc ; as are the strings themselves. Tech1 7 ; Tech fields not listed are presumed to not be needed. Tech5 3 ; It would be possible for even these to be variable. behavior Shield550G5 ; Source is not included in this example. behavior Cloak200 ; BINGO! This is what we are talking about. end ShadowShield
In the old version of Stars!, every technology that did more than one thing or that had variable behavior (based on racial traits, etc) required a bunch of exception code in multiple locations. Inconsistencies in this special case code caused us a lot of problems (and bugs). In Stars! Supernova Genesis the game engine does not contain any special case code to deal with technologies that have multiple or variable behaviors. The RDL author is free to add any number of behaviors to any technology and it all just works.
Okay, so we have at least one technology defined that includes a Cloak200 behavior. Now we need to define how a ship design makes use of the Cloaked behavior of each of its components. We're going to do that by defining an Iteration Function or Iterator. When an object contains more than one of something the RDL author can access those "somethings" as a Collection. An Iterator is a function that acts on a Collection to perform a calculation. In this case, we are looking at a technology which includes a Collection of Behaviors. We want to add up the cloak values (masses) of each Behavior in the Collection.
Iterators are built out of three clauses. Init clauses allow you to set counters to some starting value. Loop clauses allow you to perform some computation for each element in the Collection. Return clauses allow you to compute the final result. In this case, we start off with a cloaking value (mass) of 0, add the Cloak value of each Behavior and then return the sum.
Iterator MassCloakOfTech() b over Cbehavior Init.c 0 ; Start our counter "c" at zero. Loop.c c + b.Cloak ; Add the cloak value of each behavior to c. Return c ; We're done, just return c. end MassCloakOfTech
Now we have a function that will give us the cloaking value of a particular technology (part). Ship designs are built out of multiple parts so we will need another Iterator to walk over the collection of technologies (parts) in a ship design adding up the cloaking value of each part. A ship design includes a collection of components. Each component consists of a TechPart and a Count. In other words, what and how many. Each TechPart is a reference to a Technology like the Shadow Shield we defined before and includes something called the BehaviorSet which is the Collection of the Behaviors attached to that Technology.
Iterator MassCloakOfShip() c over Ccomp Init.s 0 Loop.s s + c.TechPart.BehaviorSet.MassCloakOfTech() * c.Count Return s end MassCloakOfShip
Notice that for each component we add the result of the MassCloakOfTech() Interator over the BehaviorSet of the TechPart multiplied by the count of parts of that type. If you wanted to make multiple cloaking devices in a single ship design add up in a non-linear fashion, such as the way that scanners ranges add up, this is the place you would do that.
We still need to define how to get the cloaking value of a ship design. That happens in the default ShipDef template. There are a huge number of other clauses defined in this template which have been cut from this example for clarity. A ship design includes a collection of components. The "percent cloaked" of a ship design is determined both by the capabilities of the parts in that design and by the mass of the ship. Our goal was to make a ship with 100 kT of cloaking and a mass of 100kT be cloaked by 50%. The formula that gives us that is 100 * C / ( C + M) where C is the cloaking value of the ship and M is the mass of the ship. We can get the value C by using our MassCloakOfShip() iterator over the Components Collection in the ship design. As this function is pretty complex we don't really want to do it twice. It would be nice if we could compute that value once and use the result in two places. We accomplish this with something called "expression local variables". They are placeholders for values you intend to use more than once in an expression that happen to be computed by complex formulas. To define a local you just type its name followed by a colon and the expression you want to assign to it followed by a comma and the expression that uses the local.
Shipdef.default begin ;... Mass Components.MassOfShip() Cloak c: Components.MassCloakOfShip(), 100 * c / (c + Mass) ;... end default
Notice that the Cloak clause starts out by defining an expression local variable "c" to be the result of Components.MassCloakOfShip(). It then uses "c" in the remainder of the expression "100*c/(c+Mass)". Also notice the reference to the Mass clause which was defined just above the Cloak clause. The MassOfShip() iterator looks very much like the MassCloakOfShip() iterator and has been purposefully left out of this example.
So, given an arbitrary ship design we can now determine how cloaked it is. We still haven't answered the question, how cloaked is this fleet? For the purposes of this example we'll use the formula currently in playtesting. It is not necessarily the formula we will use in the final rule set. For now we have defined the cloaking value of a fleet to be the minimum of the cloaking values of each ship design in the fleet. In other words, it does you no good to mix cloaked and uncloaked ships in a single fleet. As stated above, this is almost certainly not going to be the final formula. It is important to notice how easy it will be to change it during play testing .
A fleet includes a Collection of Fleet Components. Each Fleet Component is made up of a ship design, a count of ships of that type and other useful information. As in the original Stars!, no information is stored on a per ship basis. First we have to be able to iterate over the collection of fleet components and find out what the minimum cloaking percentage is.
Iterator CloakOfFleet() fc over Cfc init.c 100 Loop.c Min(c, fc.ShipDesign.Cloak) return c end CloakOfFleet
Finally, we have to define the Cloak clause in the default Fleet template itself. Again, a large number of additional clauses have been removed for clarity.
Fleet.default begin ;... Cloak FleetComps.CloakOfFleet() ;... end default
This may seem like a lot of work just to answer the question "How cloaked is this fleet?" In fact the above is less complex than the final rule set will be. For example, if we wanted ships built by races with a particular trait such as Super Stealth to have an inherent cloaking value we would have to make the Cloak clause of the default ShipDef template take that into account.
Example: If we wanted fleets to be 50% more cloaked when in a nebula then we would have to do something like this to the Cloak clause of the default Fleet template:
Cloak c: FleetComps.CloakOfFleet(), \ 100 - ((100 - c) * (2 - InNebula) / 2)
This would cause a fleet that normally had no cloaking to be 50% cloaked and a fleet that normally was 50% cloaked to be 75% cloaked. The "\" at the end of the first line tells the RDL compiler that the expression continues on the next line. It has no effect on the result of the formula. InNebula is an intrinsic clause that returns zero if the object is not in a nebula or one if it is in a nebula.
We do not expect the majority of players to be able to effectively modify the rule set. We will be putting into place a moderated forum where players can discuss possible rule changes and a place for experimental rule sets to be stored by the moderator and downloaded by the community at large. When the player community as a whole settles on a stable new rule set it we will take a look at it and if we don't find any problems we'll "authorize" it. We will not accept bug reports for problems found in any games using non-authorized rule sets. The RDL gives you a lot of power but it also gives you the opportunity to really screw things up. There is absolutely no way for us to guarantee that every arbitrary set of rules will work correctly with our game engine. Instead, we'll provide support for any problems that are reproducible under any rule set authorized by us. We will authorize a new rule set proposed by the player community when we feel that the player community has fully tested it (run enough full games).
Note. We cannot promise to fix problems in the Rule Description Language itself. The RDL compiler and interpreter has been fully tested only to the extent that the standard rule set(s) use it. There are bound to be things that players would like to do, that the language was intended to support, that just don't work. These will not likely be fixed due to time constraints.
- Special thanks to The Internet Archive for preserving the Crisium website.