Wednesday, March 18, 2020

The pains of being flexible

I ran a build with some pretty simple refactors as the only changes. I expected it to pass. The only reason the build even ran was that I checked in my changes.

Yet the build didn't pass. It failed with a bizarre error. SpecFlow was failing to generate code-behind files.

Some research made it clear that this was really a function of SpecFlow not working very well with .NET 3.1.200. The surprising thing about that was that I didn't remember switching to 3.1.200.

The fault actually was mine. At least, it was in a circuitous way. It was an artifact of a decision I made while defining my pipeline:

  - taskUseDotNet@2     displayName'Use .NET 3.1'     inputs:       version3.1.x       installationPath'$(Agent.ToolsDirectory)\dotnet\3.1'

I intentionally chose to allow updates automatically with that little ".x" and it burned me.

Sure enough, a tiny change "fixed" my broken pipeline:

  - taskUseDotNet@2     displayName'Use .NET 3.1'     inputs:       version3.1.102       installationPath'$(Agent.ToolsDirectory)\dotnet\3.1'

I'll definitely leave it that way until I have a reason to change it.

What I don't know, at this time, is if I'll go back to allowing the maintenance version to float, when I finally do make a change.

Is it better to have the stability of a known version or to get the fixes in the latest version without having to do anything?

Right now, my inclination is toward faster updates, still. For an indie game-development shop with a disciplined developer who jumps on problems right away, it's probably better to get the updates in exchange for the occasional quickly-fixed disruption.

That said, if I get burned like this a few more times, I might change my mind.

Tuesday, March 17, 2020

Making an SVG shape specification easy to interpret visually.

In my most recent post, I deferred describing how I parsed an SVG document to another post.

There are multiple subtopics:

  1. How to make the SVG easy to interpret visually.
  2. How to convert the SVG into a set of rules.
  3. How to select the right rules given a query string.
  4. Generating an SVG that explains what went wrong when a test fails.

I will attempt to address them all in separate posts. Like the rest of the world (at the time of this writing), I'm recovering from illness. So I'll do an easy one, now, and the rest will have to wait.

First up, how to make the SVG easy for a person to understand.

This part is all about SVG, itself. I started out with a single document that looked pretty raw - just some white rectangles on a black background. Over time I accumulated more documents and evolved them into something more palatable.

Those mostly involved the use of stylesheets and definitions but also, after much experimentation, I discovered that polyline was the most effective tool to create the shapes I wanted. I'll explain why in a bit.

First, let's look at a single polyline element:

<polyline id=".inner" points="80,192 80,1000 1860,1000 1860,192 80,192 80,193" />

That's a rectangle with its first two vertices repeated. For the test, I only need the first three points - the rest of the parallelogram is inferred.

However, to create the visual effect of a bounding box with little arrowheads pointing inward at the corners, I needed the extra points. At least, I couldn't figure out how to do it without the extra points.

I could only get the orientation of markers on inner vertices to be correct. Everything else pretty much just looked like a random direction had been chosen. As a result, I needed 4 inner vertices, which means I needed six of them, total (start, inner x 4, end).

The other structures I needed were some defined shapes to use as vertex-markers.

<defs>
    <marker id="marker-outer" viewBox="0 0 10 10" refX="5" refY="10"
        markerWidth="5" markerHeight="5"
        orient="auto">
        <path d="M 5 10 L 2 0 L 8 0 z" class="label" />
    </marker>
    <marker id="marker-inner" viewBox="0 0 10 10" refX="5" refY="0"
        markerWidth="5" markerHeight="5"
        orient="auto">
        <path d="M 5 0 L 2 10 L 8 10 z" class="label" />
    </marker>
</defs>

Once I have a rule-definition (my polyline, in this case) and the definition of the marker, I can use a stylesheet to marry the two and create the visual effect.

<style>
    *[id='inner'],
    *[id$='.inner'],
    *[id='outer'],
    *[id$='.outer']
    {
        stroke-width: 5;
        stroke: white;
        stroke-dasharray: 30 5;
        fill:none;
    }

    *[id='inner'],
    *[id$='.inner']
    {
        marker-mid: url(#marker-inner);
    }

    *[id='outer'],
    *[id$='.outer']
    {
        marker-mid: url(#marker-outer);
    }

    /* SNIPPED: Stuff used at runtime for other purposes */
</style>

Finally, to create a frame of reference, I converted a background image to base 64 and embedded it in the SVG document as the first element.

All of those steps create an effect like this:


Thankfully, most of those steps don't need to be repeated.

Sadly, it seems that SVG pushes you in the direction of redundancy. You can externalize your stylesheet but not every renderer will respect it. I couldn't find a way to reliably reuse the markers, either. The background image could be externalized but then I'd be relying on hosting for the specification to render properly.

There's a bunch of copy and paste but it's not on the critical path for the test. It just affects how the test looks to developers. So I tolerate it.

I could write a little generator that runs just before build time but then I wouldn't be able to easily preview my work.

C'est la vie.

At least, this way, I can quickly interpret my specification just by opening it in a browser or editor.

Wednesday, March 11, 2020

Pinning down layout for a Unity game

As I work to find an audience for Dwindle - the hardest problem I've ever worked on, by the way - I'm experimenting with different layouts. Specifically, I want games run on PC (including the browser) to look more like one would expect.

This means that I need to support both landscape and portrait layouts.

Tool Trouble


I've found an asset to help with this but it doesn't work perfectly. Especially: sometimes its behavior is a little surprising to developers and it was not set up very well for Unity's nested prefab feature. I use nested prefabs very heavily.

The result is that the tool works but, if I'm not very careful (which is most of the time), it can destabilize quickly.

I need tests that let me know what I've done something that triggers the layout asset I'm using and causes it to go berserk.

What to Test?


I have two lines of defense here: switching from invoking buttons programmatically to simulating clicks and directly testing the layout.

The former ensures that the buttons I'm invoking are actually clickable in the viewable area of the game - an assumption I could make up until recently.

The latter is a gigantic pain in the ass but was ultimately worth the effort.

I'm going to start laying out some of what I did on that front, here.

Defining a Meaningful Layout Requirement


It starts with a good specification and that means I need to know what a good specification for layout actually is.

Not being a UI/UX expert, I settled on an SVG graphic that can be used to codify the specification and can be inspected visually.

I first had to decide what my requirements for the requirements were. The game is playable on a variety of Android devices as well as in a web browser. Additionally, it can be configured differently within those different form factors (for instance, there are no embedded ads when played on Kongregate).

So the tests I write need to either be (a) very flexible around changes in device form-factor/configuration for a channel or (b) finely tuned to a specific execution environment.

Neither of those is particularly appetizing options - UI testing never is. However, given the choice between too much flexibility or too much coupling, I'll take the former.

Right now, I define layouts in terms of two kinds of constraints. Not every constraint is defined for every requirement I specify. The two kinds of constraints are:
  • Inner boxes - boxes that must be contained by the UI structure being examined.
  • Outer boxes - boxes that must contain the UI structure being examined.
Note that this still gives me the ability to have a rigid definition if I want: just make the inner and outer boxes the same box.

Remember, I'm not trying to set up a test-driven layout, here. For a small project with one developer who is also the product owner and product manager, that would probably be overkill.

What I'm trying to set up is a system of alarms that lets me know when my layout asset has gone haywire and borked my game. That's a much less demanding task and the above constraints suit it nicely.

Codifying Layout Requirements


To represent these constraints, I chose to define a narrow convention within the SVG format:
  • Anything can be in the document.
  • Only rect, polygon, and polyline elements are actually parsed and used as specification directives. Everything else is disregarded.
  • Dot-delimited id attributes represent a logical path within a specification file to any given constraint.
  • The final step in a constraint's path represents the kind of rule applied to the shape in question.
  • Additionally: For polylines and polygons, the first three coordinates of the path are treated as points on a parallelogram that defines the corresponding constraint. (I never use anything other than a rectangle but that's a kind of parallelogram, right).
For instance:
  • A rect element with an id of "cheese.tacos.halfoff.inner" represents a box that must be inside whatever UI element is being tested.
  • A circle element with an id of "important.control.outer" will be ignored because it is a circle.
  • A polyline element with an id of "handle.bars.notsupported" will not be treated as a rule because the system cannot map "notsupported" to a constraint.
This gives me a fair amount of freedom to create a specification that, visually, tells a person what are the rules for a particular shape while allowing a test to use the exact same data to make the exact same determination.

Blogger makes it pretty hard to host SVGs - as do most companies, it seems. However, below is an embedded Gist for a specification. You can grab the code and look at it, yourself if you like.


Also, here's a rendering of the image, in case you don't want to go to the trouble of grabbing the code, saving it, and opening it up in a separate browser window:

I had to do a screen grab to do this...why does the world hate SVG?
A jpg-ified version of the SVG
If you look at the gist, you can see what I did to make the little wedges without having to handcraft each one.

This leaves much to be desired but it also allows me to solve a lot of my problems.

Parsing it and computing the expected positions for an actual screen's size is pretty straightforward arithmetic. This entry is long, so I'll save that for another time so I can focus on a more interesting problem.

Defining a Test


Of course, this is just raw data. It's not a test. I need to add some context for it to be a truly useful specification. Here's a pared-down feature file.

Feature: Layout - Portrait
Background:
Given device orientation is portrait
Scenario: Position of application frame artifacts
Given main menu is active
# SNIPPED: Out of scope specifications
Then shapes conform to specifications (in file "Frame Specification - Buttons - Portrait.svg"):
| Layout Artifact | Rule |
| application return to main menu | home |
| main menu new single player game | start single player |
| main menu new multiplayer game | start multiplayer |
| main menu resume game | start resume |

What I've described, so far, allows me to set up the expected side of the tests. How do I bind it? How do I get my actuals?

It turns out the quest to do this gave me the tools to solve other problems (like using real taps instead of simulated ones).

Like everything that isn't painting a pretty picture for a first-person shooter, Unity doesn't make it easy to do this. It's not that any of the steps are difficult to do or understand. It's difficult to find what the steps are and string them together.

I take that to mean I'm doing something for which they have not planned, which is probably because not very many people are trying to do it.

For one thing, there are two complementary ways of determining an object's on-screen shape. One way works for UI elements. The other works for game objects that are not rendered as part of what Unity calls a UI. That's another problem, I'll set aside for the moment, though. Let's just stick with UI.

I already have a way of getting information about the game out of the game. That's probable another blog entry for later. Anyway, it allows me to instrument the game with testable properties and then query for those properties from my test.

Getting the Shape of Onscreen Objects


First, I need something to pass back and forth:


[DataContract]
public struct ObjectPositionData
{
  [DataMember]
  public Rectangle TransformShape;
  [DataMember]
  public Rectangle WorldShape;
  [DataMember]
  public Rectangle LocalShape;
  [DataMember]
  public Rectangle ScreenShape;
  [DataMember]
  public Rectangle ViewportShape;
}

[DataContract]
public struct Rectangle
{
  [DataMember]
  public Vector Origin;

  [DataMember]
  public Vector Right;

  [DataMember]
  public Vector Up;
  /* SNIPPED: Mathematical functions */
}

[DataContract]
public struct Vector
{
  [DataMember]
  public float X;
  [DataMember]
  public float Y;
  [DataMember]
  public float Z;
  /* SNIPPED: Mathematical functions */
}

Then I needed a way of populating it. For UI elements. That is about turning a RectTransform into its onscreen coordinates. Most importantly, I need the "viewport" coordinates, which mapped to the square (0,0)-(1,1). I could also use the screen coordinates, but both the screen coordinates and the viewport coordinates need a little massaging to be useful for the test infrastructure and the transformation for the viewport coordinates is trivial: ynew = 1 - yoriginal.

Extracting these coordinates is, of course, an obnoxious process. Everything has to be done through the camera and everything has to be treated like it's 3D - even when it's not. So, it starts by finding the "world corners" of a rectangle.

var WorldCorners = new Vector3[4];
Transform.GetWorldCorners(WorldCorners);

This seems like a weird way of doing it, to me, but I'm sure they have their reasons. It's probably something to do with performance. All the access to these kinds of properties must be from the same thread, anyway, so it's probably safe to do something like keep that array around and reuse it from one frame to the next.

Anyway. The world corners aren't good enough because those are in a 3D space that is in no way associated with the actual (or virtual) device being used in the test.

So we have to convert those corners to viewport corners and that has to be done using a camera object.

new ObjectPositionData
{
  /* SNIPPED: Other shapes */
  ViewportShape = MakeRectangle(WorldCorners.Select(V => ToTrueViewportPoint(Camera, V)))
}

static Vector3 ToTrueViewportPoint(Camera Camera, Vector3 V)
{
  var Result = Camera.WorldToViewportPoint(V);
  Result.y = 1 - Result.y;
  return Result;
}

Things like how I marshal and unmarshal the ObjectPositionData struct between the game and my tests are going to be discussed in a different article.

Binding the Test


Now I can pull that out in my test bindings. So let's start looking at those.

[DataContract]
public sealed class ShapeFileRow
{
  [DataMember] public string LayoutArtifact;
  [DataMember] public string SpecificationsFile;
  [DataMember] public string Rule = "";
}

// This one is called for the feature file shown above
[Then(@"shapes conform to specifications \(in file ""([^""]*)""\):")]
public void ThenShapesConformToSpecificationsInFile(string SourceAssetName, Table T) //5
{
  var Rows = T.CreateSet<ShapeFileRow>();
  foreach (var Row in Rows)
    ThenShapeConformsToLayoutSpecificationFrom(Row.LayoutArtifact, Row.Rule, Row.SpecificationsFile ?? SourceAssetName);
}

[Then(@"shape (.*) conforms to layout specification (.*) from ""([^""]*)""")]
public void ThenShapeConformsToLayoutSpecificationFrom(string ShapeKey, string SpecificationKey, string SourceAssetName)
{
  SpecificationKey = Regex.Replace(SpecificationKey, @"\s+", "."); // 1
  var ShapeSpecificationsContainer = TestAssets.Load(SourceAssetName, ShapeSpecifications.Reconstitute(Artifacts)); // 2
  var Requirement = ShapeSpecificationsContainer.GetViewportRequirement(SpecificationKey); // 3
  var Actual = Client.GetViewportShape(ShapeKey); // 4

  Requirement.Validate(Actual); // 6
}

There are several things of note (corresponding with numbers in comments, above).
  1. Because I'm not sure if SVG IDs allow spaces, I build my SVG ID scheme around dot-delimitation. However, that's ugly in a feature file. So I swap whitespace with dots et voilà.
  2. I grab test assets like the SVG spec out of assembly resources using a helper. That helper is omitted from this entry for length.
  3. Getting the shape specification object out of the document is, as already mentioned, omitted for length.
  4. Getting the viewport shape involves using the test-command marshaling system to exercise the code mentioned earlier.
  5. The table-based assertion is just syntactic sugar over the top of an individual assertion I use elsewhere.
  6. The various validate methods enforce the constraints mentioned earlier: inner boxes must be contained by actual boxes and outer boxes must contain actual boxes.

That's It, for Today


This feels like a really long entry, at this point. I've tried to be a lot more detailed, as some have requested. However, there's just no way to include all the details in one entry - any more than there would be in a single section of a single chapter of a book.

So I'm going to have to drill into more details in subsequent writings.

Tuesday, December 17, 2019

Dwindle major effort: Multi-bound tests part 2, introducing handles

In my previous post, I made a small inroad to discussing the multi-binding of tests. In that conversation, I glossed over a lot of details.

One of the most important details I skipped was how I abstracted everything about a sophisticated system so that variations in binding points were invisible to the tests. This discussion will probably span several entries.

Saturday, December 14, 2019

Making my tests multi-bound

To multi-bind a test is to make it so that a single test enforces the same semantic requirement against different portions of a system.

This has been going on for a long time but not everyone noticed it. Teams that use the Gherkin syntax to define a behavior are likely to exploit those definitions in two ways:
  1. Bind automatically with Specflow or Cucumber.
  2. Treat as a manual test script.
This is an early form of multi-binding.

I'm talking about something more than that and it's the foundation of stability in all my pipelines. So we need to discuss it, first.

In my environment, any given test can be executed all of the following ways:
  1. As a unit test.
  2. As an integration test with no networking (everything runs in a single process).
  3. As an integration test for the backend with mocked data.
  4. As a gate between the blue and green environments of the backend.
  5. As an integration test for the Android client, with a local server and a mocked database.
  6. As an integration test for the WebGL client, with a local server and a mocked database.
  7. As a gate between the blue and green environments of one of my WebGL deployments.
There's nothing to stop me from adding more ways to run tests if I need them. This seems pretty good for what amounts to a one-man show.

Thursday, December 12, 2019

Building Dwindle, a list of challenges



This entry is more like the establishment of an initial backlog of topics to discuss than an in-depth discussion of a particular topic.

Wednesday, December 11, 2019

Dwindle released on Kongregate

Dwindle has been released on Kongregate. You can play it here. This deployment interoperates with the existing Android deployment.

This release coincides with a major UI overhaul. Here are some screenshots:



Here's a cute little animated gif, demonstrating gameplay.



Here's a video of me beating the AI on the "normal" (1 star) setting, using the Simple board.


It took a lot of behind the scenes work to make this happen. In addition to everything you see and what you may easily be able to imagine (like the back end server), I also needed to build a very reliable pipeline to guarantee that what I build flows out to production in a well-controlled way.

The pipeline is summarized in the following dashboard:


It seems like there's months worth of writing on the subject of getting to this milestone.