Thoughts about experience.

Most people know what “experience” means in the context of games, but defining a commonly-used term forces me to think about it concretely and precisely when I usually take it for granted, so I’m going to do it!

What is experience?

Lots of games have characters that grow over the course of the game, becoming more powerful and learning new abilities.  This ability to grow is usually represented by a currency called “experience.”  I’m going to abbreviate “experience” as EXP,  to indicate that it is a term with special meaning distinct from the usual meaning of the word.  EXP is gained by performing certain activities.  In some games, EXP is spent to purchase upgrades.  In others, reaching certain milestones of total EXP unlocks upgrades.

Why is EXP important?

Gaining EXP is a strong incentive. Players tend to perform activities that reward EXP over activities that don’t.  By changing which activities award EXP, and how much, game designers can influence their players’ behavior to suit the designers’ goals.

Common ways to gain EXP

Most of these examples are from video game shooters with “RPG elements”, RPG video games, and table top video games.

Individual EXP for killing enemies: In games where most situations are combat challenges, this method is obvious. The goal is to kill enemies, so reward the player who kills an enemy. This works well for single player games, but in multiplayer games, giving all the EXP to the player who lands the killing blow does not account for teamwork. If player A deals 90 damage to an enemy and Player B deals only the last 10 damage that kills it, player B will get the EXP and player A will feel cheated.

Individual EXP for assists: This is the obvious fix to the previous method.  Everyone who participates in killing an enemy gets some EXP. There are various ways to do this.

  • Full EXP for the killing blow and half EXP for anyone else who damaged the enemy.
  • Award EXP proportional to damage done.
  • Using a helpful ability on a player engaged with an enemy awards assist EXP when that enemy is killed.
  • Award assist EXP for using non-damaging abilities on an enemy, like knocking it down, pushing it out of position, and so on.

Making an attempt at fairness reveals how difficult it is to precisely define fairness.

EXP for completing objectives: This is mainly used in video game that have other things to do besides killing enemies.  Usually, most of the systems are about killing enemies, with some longer-term objectives on top, like “Control an area”, “Escort an object”, or “Capture a flag”. Accomplishing these objectives is another source of EXP, alongside killing enemies.  Some objectives (e.g. hold an area) award EXP equally to everyone involved. Others (e.g. capture the flag) award EXP to the player who accomplishes it, and maybe also to players who assisted that player.

Group EXP for overcoming obstacles:  This is common is video games and tabletop games where players form teams or parties.  Any accomplishment by the party awards equal EXP to all party members. Framing the achievement that grants EXP as “overcoming an obstacle” instead of “defeating an enemy” expands the types of situations that grant EXP: solving a mystery, navigating a hazardous area, convincing an NPC. It also handles solving a problem in multiple ways.  Players can get past a checkpoint by sneaking, fast-talking, or fighting, and get the same reward.  If combat is dangerous or expensive, players are encouraged to try non-violent solutions.

Group milestone leveling: This is used in tabletop games that emphasize story, Instead gaining EXP for every obstacle along the way, every player gaining a large amount of EXP for reaching a significant narrative milestone, like defeating a boss, or wrapping up a story arc.  This lets the GM choose how powerful the party will be at any point in the story, and less accounting is required of both players and GM.

EXP per skill: This is a paradigm shift that rewards players for their actions instead of for the effects those actions have.  Instead of one pool of EXP, characters have multiple pools, linked to skills or groups of skills.  For example, a character may have a “shooting” skill, and could gain “shooting EXP” for attempting to shoot, or for shooting and succeeding, or for succeeding on difficult shots.  “Shooting EXP” can only be used to improve shooting-related parts of the character.  This method keeps track of a lot more than other methods, so it’s usually limited to video games, where the computer can do all the math.

Ideas for gaining EXP

In team games, it’s good for players to work together and help each other.  How do we know when a player has been helpful to another?  Humans intuitively use a lot of context to decide what certain actions mean, and that’s hard for computers to emulate.  A computer would like to say “Healing a teammate is good”, but healing a tank that’s at 3/4 health while a squishy teammate dies is a mistake.  Most simple rules for what is helpful and what is not can be gamed: players who are motivated to gain the most EXP can find actions that make no sense diagetically, like standing in a fire to let a teammate get unlimited EXP for healing.

One way to answer “does this action help?” is to ask “If this action did not happen, would things be worse?”  That’s easier for turn-based games or games with fewer verbs. Predicting the future gets more expensive the more complicated each situation gets, and how far ahead one has to look.  Here’s a simple example.  In Pathfinder, a Bard gives the Fighter +3 to attack, and the Fighter’s next attack beats the enemies AC by 1.  Without the Bard’s Inspiration, the Fighter would have missed, so the Bard definitely helps!  Grant EXP!  But what if the Monk trips that same enemy, knocking it prone and reducing its AC by 4. Does the Fighter hit because of the Bard or because of the Monk?  Even in this turn-based example with chunky numbers, it’s hard to assign causes to results.

Another concern in team games is fairness. EXP is a positive feedback loop. Characters that perform better get more EXP and more power, and then perform even better.  Small differences in effectiveness are magnified over time, and it’s hard to have a team of characters with vastly different amounts of power.  Limiting that difference in power can keep players from feeling frustrated. One solution is to award EXP to the group, not to individuals, but that may lead to the “free rider problem.”  Another solution is a limit to the difference in EXP between party members. A very effective character would stop earning EXP until other characters caught up.  The powers granted by EXP could also reduce this problem by weakening the positive feedback loop. If characters grow mostly horizontally (more utility options, diversification) instead of vertically (bugger numbers), that characters that are far behind can still contribute (in a few areas) just as well as a character that is far ahead.

Bodypaint Generator: Code Clean Up

My first draft of the bodypaint generator was a bit hacky, so I went back and cleaned it up a bit.

I created “drawers” (things that draw, not parts of a dresser) that would draw different things: stars, letters, squares, etc.  I could add more than one “drawer” to a placer and fill the canvas with, say, half circles and half heart emojis.  But because I mis-used Javascript’s inheritance, I couldn’t create multiple copies of the same “drawer.” So half hearts and half smiley faces wouldn’t work, nor would half red stars with few points & half green stars with many points.  As the image below proves, I’ve removed that constraint. Now I can instantiate as many “drawers” as I like, of any type, and give them all different parameters.

The placing feature was mixed in with the top-level generator object.  I split it out to its own class, so I could make sub-classes and switch out or combine placers on one canvas just like I could use any combination of “drawers” with one placer.  To demonstrate the new placer’s extensibility, I implemented a grid placer in addition to the random placer I made earlier.

Finally I made some quality-of-life improvements to make testing easier for myself.

  1. A “regenerate” button so I can re-run the generator without re-loading the page.
  2. A “save to PNG” button so I don’t have to Print-Screen, paste into an image editor, and crop each time I want to add an image to my blog. (If you look carefully at previous entries about this program, the images are off by a few pixels.)

Fairmeadow Fair, session 4

← Session 3 | Campaign Summary | Session 5 →

When we last left our heroes, Lucia had just rid herself of a self-inflicted flea infestation with some flammable anti-flea oil. She’s not on fire or covered in fleas, but she is covered in oil.  She goes to the bath-house while Gleador goes back to the Glazers’ house to rest and level up.  The bath-house is attached to Macker’s Tavern, the other inn in town.  The bath-house is decorated in green & blue tile. Lucia washes up, then lingers for a relaxing bubble bath and massage.

Gleador goes back to his room and meditates, connecting with nature and its wisdom. He has a vision of the pony that rode away on the cart. It’s talking! It says, “Gleador. Gleador! Time to wake up.” It’s actually Lucia, who has returned from the bath-house and is rousing him.

They head back to the fair. Along the way they overhear someone say that Dandelion went on-stage to perform and croaked! They’re not sure if that means he died, or if he made frog noises. As they approach the market they hear a disturbance at the auction!

The auction stage at the Fairmeadow Fair

A metal statue, which was lined up on the platform along the other rare and valuable items, has come alive and is wrecking the place! It’s grabbing some of the auction items and throwing others aside.  Our heroes see Pepe’s pole moving through the crowd towards the commotion, but there’s no time to wait for him! The statue throws a large wooden carving into the crowd!  Gleador turns into a gorilla and catches the statue, placing it gently down next to shocked onlookers.  They were too busy looking at the statue to notice him transform, right?  Lucia commands the statue to stop, but it ignores her. She can intimidate people with her Paladin authority, but this is no person.

When Pepe arrives, he sees a gorilla, which is both a beast and black.  Surely it must be the Black Beast!  Will he engage it, or the living statue?  Gleador rushes to grab the statue & show Pepe that he’s a friendly gorilla.  He pulls it down and notices that the robot has only collected metal objects in its backpack.  Lucia rushes up to tie the statue down, but it throws her through a table of valuable items.  The statue gets back up and Pepe rushes Gleador.  Gleador leaps up onto the platform, getting between Lucia and the statue and putting the statue between himself and Pepe.  Undeterred, Pepe pole-vaults up onto the platform to threaten Gleador.  Lucia convinces Pepe to focus on the statue.

The hidden switch on the living statue.

Gleador notices a three-position switch on the statue’s back and tries to turn it, but the statue spins 180 at the waist, grabs hum, and throws him into Pepe, toppling and injuring both of them.  Lucia lunges with her sword, but it scrapes harmlessly off the solid metal statue. The statue tries to pull the sword from her grip and collect it like the other metal objects. Lucia also notices the switch.  Gleador rushes the statue again, but it throws him into Lucia. She loses her grip on her sword as they both stumble back.  That tug-of-war gave Gleador an idea.  He takes Lucia’s shield and entices the statue into grabbing it.  With its arms occupied wrestling for the shield, Lucia has an opportunity to turn the statue’s switch. Left or right? Left! The statue freezes and topples to the ground.

With the statue incapacitated, Pepe the sherriff turns his attention on the Black Beast.  Gleador flees out of town into the surrounding fields.  Although his halfling legs can’t run as fast as his quarry, Pepe can easily follow the trail of a gorilla crashing through fields of grain.  Gleador runs until he has enough of a lead to break line of sight, then switches to a rabbit and makes a right-angle turn, aiming to come back into town from a different angle.  He dashes across one of the main roads into town and a passing hunter looses an arrow after him.  Gleador is a lot tougher than a normal rabbit and just keeps going, much to the hunter’s surprise.

While Gleador and Pepe are gone, deputies arrive and attempt to tie up the statue.  All its joints are locked, so they can’t bring its wrists together to bind it in the usual way.  They do their best and drag it towards town hall, which is also the headquarters of the town watch.  People at the auction are upset by the destruction of most of the valuables, but Lucia ignores them and exhorts the deputies to place the statue in a cell, not in the evidence room. Maybe it will come to life again.  Better to treat it like a person.

Gleador re-emerges as an Elf and reunites with Lucia.  They go to the bard stage to check on Dandelion and find that when he started his performance, the tried to sing but could only croak like a frog.  He’s not dead, but he’s humiliated and unable to perform.  The back up bard is and Elf named Bill Shook. He uses Elven metaphors and references that don’t translate into Common well, so Gleador thinks he’s hilarious, but Lucia and most of the spectators are unimpressed.

There’s plenty of talk about the living statue that ruined the auction, and the gorilla that suddenly appeared and seemed to fight it.  Was it a person who turned into a gorilla?  To protect Gleador’s secret, Lucia starts a rumor about a an invisible, benevolent gorilla, who appears when people are in danger.  Her fantastic story gains her more listeners than poor Bill Shook.

← Session 3 | Campaign Summary | Session 5 →

Fairmeadow Fair, session 3

← Session 2 | Campaign Summary | Session 4 →

Our heroes are out late at night, helping Daniel, an enthusiastic villager, search for a strange beast that appeared during a fight between Lucia, our Paladin, and a ruffian named Opal. Daniel doesn’t know that the beast is right beside him, because Gleador, our Druid, is keeping his shape-shifting a secret.  It’s complicated.  Read the previous session for all the details.

Daniel is wearing a robe and slippers, with a poker in one hand and a torch in the other.  He peers down alleyways, prods piles of trash with his poker, and tells the sleepy villagers that he awakens that there’s a strange beast on the loose!  After about 15 minutes, Lucia and Gleador convince him that the beast is long gone, so they leave him and return to the Glazers’ house, where they are staying for the duration of the festival.  Once they are alone in their room, Lucia holds Gleador’s injured hand aloft, says a prayer, and the pain of his dagger wound is replaced by a feeling of peace.  They rest for the night.

Lucia has enough experience to level up, and she becomes a Cleric of the goddess of the Sapphire Islands.  She can now cast low-level Cleric spells.

The Glazers prepare a lovely holiday breakfast.  All the dishes, glasses, and mugs are beautiful multi-colored glass, because the Glazers are glass-blowers.  They tell their guests about the day’s activities: the market is open, with goods from all over the region; there’s a stage for bards & other performers; in the afternoon, there’s an auction for especially valuable items.  Our heroes accompany the Glazers to the market, where the Glazers have a stand to sell their glassware.  Our heroes act like they are going to wander the market for a bit.  They buy some fair food: fried dough & corn on the cob with mayo.  Lucia starts to feed her share to the stray dogs, but bystanders scold her.

A map of the town of Fairmeadow, including the bathhouse and the Poirot home.

Our heroes duck out to find the pickpocket’s house. (Last night, they had some dogs follow the pickpocket’s scent, and got directions to it.)  They recognize the house by the token over the door, even though the dogs described it in shades of grey and our heroes can see color.  It’s a nice, upper class house: no shop attached, a yard with stalls for horses in the back. Rich people live here. The streets are bustling with fair-goers so they are able to circle the house a few times without drawing attention.  Gleador wants to infiltrate the house and turns into a tropical hornet when he thinks no-one is looking.  A kid notices and tugs on his mom’s sleeve.

Child:  “Mommy, mommy! That man just disappeared!”

The mom looks where the child is pointing and only sees Lucia.

Mom: “That’s not a man. She just wearing armor.”

The mom thinks this might be a teachable moment, so she comes over so the kid can ask about Lucia’s armor.  Lucia would like to be left alone, so in addition to explaining the parts of her armor, she talks about dangerous quests and violent battles.  The mother looks uncomfortable. Maybe the child shouldn’t be hearing this.

Gleador flies into the open kitchen window. A servant comes in through the door to the dining room carrying three plates full of food. He grumbles that the guests left suddenly and no one told him until he had already made extra breakfast. Gleador wants to get into the dining room, but the door is shut. A young man in expensive clothing pops his head in the door to complain about something trivial: his pancake ripped or something.  Gleador buzzes past him to get into the dining room. The young man squeals and flails ineffectually as Gleador passes.  Gleador sees a table set for five. There’s an old woman at the head of the table, the young man yelling for help and looking something to swat him, and the three absent guests. Gleador leaves the dining room by another way and finds a way out of the house. The young man gets braver once the scary insect is gone and starts bragging to his mother about how he drove the awful creature away.

Gleador flies back to Lucia, who is still telling scary stories to the curious child. Gleador flies behind the mother & child to transform back unnoticed, but they turn to leave just as he does so!  Lucia claims that Gleador can turn invisible. The mother hurries her child away from these strange, dangerous adventurers.

Our heroes watch her go and see a pole with a little flag on it coming through the crowd towards them. As it approaches, they see the halfing that is carrying the pole.  He uses the flag so people know that he’s there.  He’s the Sheriff of this town, named Pepe.  He heard that Lucia drove off the strange beast last night.  Because of the attack, Pepe’s instituted a curfew tonight, and he’s collecting able-bodied deputies to search for the creature.  Pepe’s working hard to ensure the safety of everyone at the fair!  Since Lucia has fought the beast already, and her friend can apparently turn invisible, he asks them both to meet him at town hall at sundown and join the deputies.  They agree.

Lucia wants more information about the house. For all she knows, this rich old lady is the ringleader of a band of thieves! She waits for the servant to leave the house, looking for a chance to talk with him. About an hour later, he leaves through the back door with a basket under his arm, heading for the market.  Lucia follows and tries to bump into him. She underestimates her strength and sends the servant stumbling and knocks his basket to the ground. He recovers and notices his coin purse is gone!  Oh, it fell under the basket. Everything is fine.  Gleador approaches, complains about Lucia’s clumsiness, and offers to share some pipe-leaf with the servant.  He happily agrees.

His name is William and he serves Lady Poirot and her son Master Thierry.  Three guests (matching the descriptions of the pickpocket and the two ruffians) came in yesterday on a metal cart pulled by a pony. They kept to themselves.  He brought in their things and served them some food, then they went out.  When he got up today and made breakfast, the guests had already left, and no one told him.  Very frustrating! Our heroes commiserate with William and send him on his way.

Our heroes wonder how to gather more information.  They could have the stray dogs follow the scent on Opal’s cloak, or investigate the strange cart. They return to the Poirot house. There’s a shed divided into three stalls in the back yard. There’s a horse in one stall. The guests’ pony and the cart were probably stored in the other stalls.  Lucia promises to cure the dogs’ fleas if they sniff out Opal’s scent.  The scent leads to the stalls, but the horse won’t let the dogs approach.  Gleador goes to speak to the horse and asks about the pony.  The horse says that the pony and three humanoids got into the cart and the cart left. The pony was inside the cart, not pulling it!  Weirdest thing!  Some of the humanoids may have been dwarves or juvenile humans. The horse has trouble telling the difference.  Gleador thanks the horse for its help and gives it the corn on the cob with mayo they bought earlier.  Cart tracks indicate that the self-propelled cart left town going south.

Lucia lays hands on the dogs to cure their fleas as promised, but transfers the fleas to herself instead! How embarrassing and annoying.  How can she get rid of them? The bath house won’t admit someone with fleas, and the river is miles away.  Samantha the witch knows potions and remedies.  Maybe she can help. They return to The Brace of Pigs where they last saw her.  Lucia has to wait outside while Gleador goes in to inquire.  Samantha’s probably in the market, but so is everyone else. They don’t find her, but they do find a shop selling anti-flea oil.  The shop-keeper wants 3 bundles of Gleador’s pipe-leaf for the flea oil, but Gleador talks him down to two bundles, and they share some now.

They go into the shop-keepers tent. Lucia removes her armor and covers herself in the flea oil. The shop-keeper gives Gleador the pipe and tries to light it for him.  The flint sends sparks toward Lucia, who is covered in flammable oil!  She dodges, but drops the vial of oil, which catches fire . . . on the grass . . . inside the cloth tent . . . in the middle of the crowded market!  Lucia backs away from the flames and tosses a canteen to Gleador.  Gleador tries to remember if it’s OK to pour water on an oil fire.  It seems good to him, but he’s wrong!  He pours the water on the water and it splatters out further, setting one side of the tent on fire and sending flames towards a cabinet full of other potions!  Lucia tosses Gleador a container of milk. Maybe that will work. Gleador realizes that milk is mostly water and will have the same effect, so he doesn’t pour it out.  The shop-keeper drags his cabinet out of the tent and flees, yelling about the fire.  Now that they are alone, Gleador is free to transform into an earth elemental and smother the fire, but he fails and belly-flops onto the fire as a normal, flammable Elf!  He already stopped and dropped, so he rolls to put the fire out. He’s not on fire anymore, and neither is the grass. The tent still burning.  Lucia throws over a rug, which Gleador uses to smother most of the fire.  The remaining fire on the tent is just burning fabric, not oil, so Gleador douses it with the milk.  The market is saved!  Our heroes breathe a sigh of relief before the bucket brigade arrives and soaks them & everything in the tent!

Lucia goes to the bath house to wash off the flea oil. It did work. She’s cured of fleas.  Gleador goes back to the Glazers’ house to rest and level up.

← Session 2 | Campaign Summary | Session 4 →

Fairmeadow Fair, session 2

← Session 1 | Campaign Summary | Session 3 →

My players enjoyed the first session so much they asked me to come back and continue the adventure.  Since Fairmeadow Fair is usually a one-shot, we didn’t talk about character backgrounds, and I didn’t build the world much past the town and the surrounding fields.  We spent the first part of the session brainstorming about those things.

Gleador is from the Sapphire Islands and was exiled for showing Lucia a secret ritual. This means that the Sapphire Islands have an organization of Druids that have secret rituals and can exile people. The Sapphire Islands are in the south and are more tropical than the plains and forests surrounding Fairmeadow.  Lucia & Gleador are heading north through this region to reach the Great Forest, home of the Elves, where Gleador hopes he can somehow redeem himself.  The central plains are a loose group of city-states with nominal allegiance to a central government that isn’t relevant to most people daily lives.  Each city-state sends a representative to high council, which elects a leader.  The current leader of the council is a queen.  We will fill in the details of Gleador’s exile, the ritual, and what he must do to be restored at a later time, as they become relevant.

Lucia attended a Paladin Academy, so there must be a number of Paladins out there, enough to sustain a school. Gleador owes Lucia some sort of debt.

Gleador: Why would I owe you a debt?

Lucia: Because I’m so cool.

When we last left our heroes, they just returned to The Brace Of Pigs tavern with the special festival wine, and everyone in the crowded tavern is celebrating. Every room in The Brace of Pigs was reserved long before the festival, but a couple is willing find other accommodations and let Gleador and Lucia have their room as a gift for saving the day.  Fairmeadow has a tradition of hospitality, where people open their homes to travelers, especially during the fair when the two inns can’t contain all the visitors.  Guests are given a sturdy token, big enough to fit in own hand, with a unique shape, pattern, or image on it.  This token matches a placard set over the door of the house, so they can recognize the place they are staying. Bill Glazer offers his token to our heroes. He’s a glassblower & lives with his parents, Fred & Eliza. Their token is beautiful multi-colored glass in a metal frame.  Our heroes choose to stay with the Glazers and let the other couple keep their room.

The tavern is bustling, because it’s the supper rush the night before the festival begins.  Gleador looks around to see what’s happening.  Ferdinand is excited to be accepted instead of an outcast. He’s had a few drinks and is arm-wrestling all comers. He’s pretty strong in elf form, so he’s winning for now, but he’s not so good at self-control and may “hulk out” if he faces stiff competition. There’s a table in the back where a bard (a regional celebrity) is holding forth, telling stories to the delight of his entourage and fans. Bards are entertaining and also bring stories from far-off places. There’s also a kid wandering from table to table, apparently not with anyone. As he passes our heroes’ table, Gleador notices that his coin purse is missing!

Gleador alerts Lucia to the theft. Lucia keeps her purse inside her armor, so she’s fine. She grabs for the kid but only gets his cloak, which drops to the floor with a heavy jingle of many coin purses. Gleador leaps over the table, tackles the kid, and knocks the barstool out from under a large man at the bar. Lucia takes the cloak to Hobert the barkeep and asks him to return the purses, since he knows everyone. Gleador is trying to keep a hold of the kid and calm the drunk man he knocked over, but it’s not going well.  Lucia comes over and tries to take the kid from Gleador. As they scramble and claw at each other, the kid’s hand lands on the hilt of Lucia’s sword! She can secure the kid or her sword, not both. She has a plan, so she secures her sword and lets the kid escape. He runs out the door past Gleador, who has managed to knock the drunk man over a second time.  Gleador plays the bar-fight trump card and yells, “A round for everyone, barkeep!” The drunk man is won over by Gleador’s generosity.

People come up to order drinks are are surprised to receive their coin purses as well! Lucia retrieves thief’s cloak from Hobert and gives it to Gleador so he can tell the local dogs to track the thief by scent.  The two dogs (Ralphie and Jollifer) agree, but demand Lucia’s drink.  She get a drink just like everyone else, but her vow of temperance means she’s not allowed to drink it. She complies, but doesn’t like the reminder of the fun she’s not allowed to have.

A lackey from the Bard’s table gets a whole tray of drinks for the Bard and his entourage. The Bard orders a fancy, expensive drink, and when it arrives, he salutes Gleador with it (like Leo in Great Gatsby). Gleador comes over and introduces himself. The Bard’s name is Dandelion, and he brags about himself. Gleador motions for Lucia to come over and use her Charisma on him. Lucia’s not sure what he’s implying.  Paladins from Lucia’s academy are not barrred from romance, but she herself is single. Instead of flirting she is a charming conversationalist and impresses Dandelion with her wide travels. Of course he turns the conversation back to himself and his travels.

Samantha (the witch whose herbs were stolen to make Hobert’s wine) sees Lucia chatting with Dandelion and pulls her aside. Samantha warns her to avoid Dandelion, as one of Samantha’s recent customers had some nasty things to say about him.  (Samantha sells potions, herbs, and components useful for magic and alchemy, so Hobert’s theft was especially unnecessary.)  Lucia wants more details, but Samantha hesitates, because her customers trust her to keep their secrets. She draws a magical seal on her palm, thumb & index finger. If Lucia will shake her hand and accept a spell of secrecy, she will tell her what she knows.  Lucia doesn’t know the consequences for breaking this unfamiliar spell, so she declines, saying she’ll respect Samantha’s professional courtesy.

Bill Glazer has definitely had enough, so Gleador and Lucia leave the Brace of Pigs to walk home with him. It’s nighttime now.  The dogs return with a report on the thief’s trail. Gleador steps away so he can talk to the dogs without letting Bill know his secret. The dogs give directions to the house the thief entered, and describe the black, grey, and light grey symbol over the door. Gleador asks them to stake out the house overnight, but the dogs decline. Too much work. Not enough food.

A map of the town of Fairmeadow, showing our heroes’ home for the weekend, and the site of the confrontation.

Lucia and Bill continue to walk along, but two hooded figures emerge from the shadows ahead and block their path. “You’ve cost us a lot of money! We want it back!” says the shorter figure. Lucia says she doesn’t have the money, and that Bill is not involved. The ruffians have a specific grudge against our heroes and aren’t ruthless leave-no-witnesses mercenaries, so they give Bill a shove and tell him to get lost. Lucia draws her sword and shouts “Let me pass” in her most commanding Paladin voice.  One ruffian is shaken by her command and freezes, but the other (a Dwarven woman) leaps at Lucia with a club.  She tries to knock Lucia’s sword aside and tackle her, but Lucia stands firm and the sword blade jams into the gnarled club, locking the weapons together. Lucia tries to disarm the ruffian but ends up flinging both their weapons away. Gleador hears the commotion and turns into a panther. The second ruffian, already shaken, hears a rumbling growl and sees green retinas flash in the darkness. She yells, “I’m sorry, Opal!” and flees. Opal, the Dwarven woman engaged with Lucia, attempts to throw her cloak over Lucia’s face to get an opening for escape.  Lucia catches the whirling cape and moves with it. The combatants switch places and the cloak flutters to the ground beside them. Lucia is now between Opal and her fleeing comrade, but Opal is closer to the weapons on the ground. As the two wonder how to react to this new situation, Gleador pounces from the shadows, knocking Opal flat!

It’s surprisingly difficult to knock a Dwarf over, but panthers are very strong. Gleador is on top of her, big paws on her chest, claws extended just enough to touch her leather tunic.  Opal has never seen a panther before, so she does not recognize the invitation to surrender.  She panics, screams, and draws the dagger at her belt. Gleador moves one paw back to swat the dagger out of her hand before it finds his belly. The dagger goes flying, but both of their arms are now bleeding.  The commotion has rousee people in the surrounding houses. Candles are lit and heads pop out of upper windows.  Lucia retrieves her sword and points it at Gleador, “Back! Back, foul beast!” she says.  Gleador understands her implication and vanishes into the shadows. The general populace doesn’t need to know that the Paladin is friends with monsters.  Two neighbors come out of their homes.  Lucia “helps” Opal up and keeps a tight grip on her arm. One neighbor wants to pursue the beast with his improvised weapon.  Another is ready to care for any injuries.  Lucia tells the neighbors that she should take Opal home and make sure she’s alright.  Opal does not want to be alone in Lucia’s power, but can’t openly resist lest it be revealed that she attacked Lucia.  Gleador returns as an Elf, yelling about a strange beast that ran past him.  His arm is still bleeding where Opal’s dagger caught him, so he keeps that arm under his cloak.  Everyone is talking over each other about what to do and our heroes end up on the wrong side of the consensus. The armed neighbor is hustling Lucia and Gleador off to chase the dangerous beast, while the kindly neighbor is taking Opal inside to tend bandage her claw wound. Insisting would be suspicious, but our heroes are clever and subtle. Gleador says, “Get your cloak, Lucia, and let’s go”  Lucia picks up Opal’s cloak, so they now have her scent. They follow the armed neighbor on his futile search for the strange beast.

We ran out of time and had to stop there. As a GM, I am very proud of my players for creating and managing such complex social situations. The last scene, with everyone hiding their intentions and relationships and choosing words that meant different things to different people, was so tense and so fun! I’d be proud of that if I wrote it in a story, and they improvised it. I am eager to see what they come up with in our next session.

← Session 1 | Campaign Summary | Session 3 →

Fairmeadow Fair, session 1

Campaign Summary | Session 2 →

Fairmeadow Fair is a scenario that I use to introduce people to tabletop role-playing games.  My goal is to provide a wide range of activities so players can engage with the things that interest them: parties, conspiracies, thefts, fights. I don’t want people’s first introduction to role-playing games to be a fight to the death against implacable foes.  I’ve run this scenario for several groups, but this group asked me to return and make the one-shot into a campaign.

Our heroes are Lucia, a risk-averse female human Paladin, and Gleador, an impulsive & creative male elf Druid. They are headed to the town of Fairmeadow for the famous Fairmeadow Fair, which attracts people from all across the region. There will be good food, entertainment, new people, and plenty of excitement. I show them a sketch of the city, with major landmarks like the main roads, fairgrounds, town halls, and both inns.  Gleador wants to check the perimeter, so they go clockwise, right past the Brace of Pigs inn.

A map of the city of Fairmeadow

There’s a commotion at the Brace of Pigs and an Elf wearing a big backpack bursts out of the back door and runs out into the fields, away from town.  Jimmy, son of the innkeeper, gives chase with a broom, yelling, “Stop, thief!”  Our heroes try to stop the Elf with the backpack, but he turns into a bull and plows past them!  Gleador is a thing-talker, so he can shape-shift into things of the earth as well as animals.  He attempts to turn into wheat and entangle the bull, but he fails and ends up buried to the neck in dirt.  Lucia grabs a rope from her adventuring gear & attempts to lasso the bull, but is tangled in the rope and falls over.  Jimmy & his father Hobert catch up & extricate our heroes.

Lucia feels she must redeem herself from this embarrassing failure by capturing the bull.  Hobert explains that the man pushed his way into the inn’s storeroom and stole his special wine. The inn has a tavern in the front, a kitchen & storeroom in the back (that’s where the bull-elf exited) and rooms for rent upstairs.  Each year at the start of the Fairmeadow Fair, the Brace of Pigs Inn serves this special wine, and at the end of the fair, they bottle the next year’s batch.  It’s a point of pride for the inn and a tradition for the fair, so Hobert is very upset!

Gleador excuses himself (he doesn’t want everyone to know he’s a shape-shifting Druid) and turns into a crow.  He mobilizes the crows in town to fly out over the fields and look for the bull elf. They can cover a lot of area, but will take a few hours to search.

Lucia swears herself to a quest: she will recover recover the wine!  Her god grants her immunity to piercing weapons and senses that pierce lies but in return, she must demonstrate temperance. Even if the wine is recovered before the fair is over, she cannot eat any fair food or drink any alcohol (including the wine she’s trying to recover!)  Since the bull-elf knew exactly where to get the wine, Lucia suspects an accomplice, so she starts questioning people in the tavern. It’s crowded, since even people who aren’t staying here have come for lunch.  She overhears a woman excuse herself & leave the tavern & knows that her excuse was a lie: she wanted to avoid Lucia & her questions.  Lucia follows her.

The woman heads to the busy marketplace, but is unable to lose Lucia in the crowd. Lucia asks her if she knows the thief. The woman denies it, but Lucia knows she’s lying, so she grabs the woman. The woman makes a scene, yelling at Lucia & slapping her across the face. Lucia is forced to back off by angry onlookers, but later corners the woman in a more private place.  Gleador has turned into a dog & recruited other stray dogs in town by promising that Lucia will give them all her fair food this weekend. The dogs form a perimeter around the strange woman, but she turns invisible and runs off!

GM: Wait, you still have that boon that pierces illusions, so you can still see her.

Lucia: Yup. I act surprised, then follow her.

The crows report that the bull is in a swamp some miles to the east, and that’s where the strange (and apparently magical) woman is heading. At the edge of the swamp, the stray dogs balk. The swamp seems unnatural and they are scared of it.  Gleador sends them back to town to guard the inn and report any strange scents.  He attaches a note to one of the dogs explaining that he and Lucia are on the trail of the thieves and to please give these dogs some scraps.

This delay has let the woman get far ahead, so Lucia has to follow her tracks through the swamp instead of just watching her walk across the fields. Lucia’s not very good at that & blunders into some quicksand. Gleador turns into a tree with an overhanging branch so Lucia can pull herself out. As they are struggling to get free, the woman appears, having heard the commotion.

Gleador: I continue accessing the situation as a tree.

The woman fires a lightning bolt at Lucia, which misses, but hits Gleador, setting his branches on fire. Lucia grabs a burning branch and wields it against the woman, who meets her with a dagger.

Gleador: I think I attack her at this point.

Gleador turns into quicksand to overwhelm the mysterious spellcaster. The flaming tree bends and pours over her, the flames going out as the branches turn into sand. The sand engulfs her, knocking her over, and reforming into Gleador’s Elf form pinning her in a submission hold.  She knows she’s beaten, so she stops resisting physically, but yells and curses the town and our heroes. “It’s not enough that you pillage my crops and claim them as your own, but now you invade my home and attack me!”  Our heroes thought that she and the bull elf were the ones doing the pillaging, so they ask for clarification.  It turns out that the secret ingredient in Hobert’s special wine is a rare herb that Samantha (that’s the witch’s name) grows here in the swamp.  Hobert has been sneaking in and tearing out the herb, and Samantha stole his wine back as revenge.

Lucia is moved by their plight. Lucia & Gleador bring the wine, Samantha, and Ferdinand (that’s the bull elf) back to the inn. They return the wine to Hobert in the tavern, in front of all his customers, and give Samantha and Ferdinand credit for the wine’s unique taste.  The customers cheer Samantha and Ferdinand because they love the wine. Hobert can’t say anything against Samantha & Ferdinand lest his wrongdoing be revealed. But he’s got his wine back and the yearly tradition can proceed, so he’s happy too.  Everyone’s happy, except Lucia, who was rewarded with a bottle of excellent wine which she isn’t allowed to drink.

Campaign Summary | Session 2 →

Bodypaint Generator: Just draw some spots

Now that I have all the photos from the Solstice Cyclists organized, I can start looking at cyclists, breaking down their paintjobs, and creating generators for them.

There are many ways I could implement the generator. Do I make an interactive webpage (Javascript, SVG) so that the public can create and modify paintjobs?  Since people are complicated three-dimensional objects, maybe I should use a 3D program, like Unity. To keep it simple, I’m using SVG.js to draw patterns on a square in a web browser. I’ll teach it about human bodies later.

The first cyclist I photographed just had some spots. (Here’s a photo. content warning: nudity)  How hard can drawing some spots be, right?  Harder than I thought.

Spots are mostly the same size, but not exactly,  so the generator has baseSize and sizeVariation parameters. I defined some constants for colors and had the generator pick a few each time.  Now it just has to place those spots randomly on the canvas.

Figure 1. Randomly placed spots.
Figure 2. randomly placed spots

This is not how a human would draw spots.  Some spots overlap, and the distribution of spots is very uneven.  Humans tend to draw spots roughly the same distance from each other, but will avoid placing the spots in a grid.  How can I imitate that behavior?

  1. define a reasonable distance between spots, but vary it a bit (baseDistance, distanceVariation)
  2. When placing a new spot, start from the position of the previous spot & move some distance in a random direction.
  3. Make sure that position is still on the canvas (bounds checking)
  4. Make sure that position isn’t too close to any other previous spot
  5. If placement fails, repeat step 2 again.
  6. If placement fails too many times, give up.

After writing a few helper functions for polar coordinates, I tried again.  I added a small white square to indicate an attempted placement that was rejected.

Figure 3. Place next spot near previous spot.

The spots don’t overlap or crowd each other anymore, but the algorithm tends to make chains of spots instead of filling regions.

Figure 4. Having trouble placing spots.

In Figure 4, the algorithm tried hundreds of times to place the last two spots. It should have given up after 10 attempts, but this bug accidentally visualizes the band of acceptable distances between spots.

In order to make the spots form clumps instead of chains, I changed where I tried to place the next spot.  Instead of placing near the previous spot, I’d try to place it near the first spot. If that failed a number of times, the region around the first spot must be too crowded, so I’ll try near the second spot, then the third, and so on.

Figure 5. A clump of spots
Figure 6. Failed placements visualized

This looks more like what I would produce if I was drawing spots on a piece of paper, or someone’s skin. Figure 6 shows that the algorithm fails to place a spot quite often, but the execution is still fast enough to seem real-time to a human observer. I may have to worry about efficiency later, but for now it’s fine.

This algorithm has a maximum number of spots it attempts to place. If it can’t find an open space near any of the existing spots, it will give up early, but it also doesn’t keep going to fill the entire canvas. Fortunately, that’s an easy fix. Instead of looping a set number of times, use while(true), and then the emergency break statement becomes the normal way of exiting.

Figure 7. Enough spots to fill the canvas.
Figure 8. Enough spots to fill the canvas.

Now that I like the arrangement of spots, I can easily switch out the circles for anything with roughly the same dimensions: letters, stars, squares, or flowers.  This algorithm won’t work for objects that are significantly bigger in one dimension than another, like fish, words, or snakes.

In conclusion, I can fill a space with random spots in a way that imitates how a human would do it, which isn’t that random at all.

Solstice Cyclists part 2: data ingestion

Start with Part 1 to learn how I captured 4000 photographs of the mostly-naked, mostly-painted Solstice Cyclists.

Inadequate spreadsheet

My naive start was a spreadsheet with columns for what people wore, what they rode, and a description of their paint.  I used the row number of the spreadsheet as the cyclist ID & tagged images that contained a certain cyclist with that number.  I had columns for top, bottom, head, and face clothing.  That didn’t account for people wearing fairy wings, or sunglasses & a fake beard at the same time. Putting each piece of data in a separate column meant that I could search for “red” or for “sunglasses” but not for “red person wearing sunglasses”  So the spreadsheet was not expressive enough to capture the information & search was inefficient, so dupe-checking took a long time.

Database design

Instead of giving each cyclists clothing slots that could have either 0 or 1 items in each, I created a many-to-many relationship between clothes and cyclists. Each piece of clothing also had a “slot” attribute (top, bottom head, face, back, or other). So a cyclist could wear any number of items, and each item would keep track of where it was worn.  Cyclists & images also had a many-to-many relationship. Vehicle & Sex were simple enumerations.  Descriptions remained as plain text.

Spreadsheet to database.

Converting all the data in the spreadsheet to DB records let me remove any inconsistencies in how I entered the data in the spreadsheet, e.g. “wig, blue” or “blue wig”.  As I added clothes & vehicles to the DB, I searched & replaced those words in the spreadsheet with the DB IDs.  I had to be careful to replace only words in the appropriate columns, since the plain-text descriptions sometimes referenced clothing or vehicles. Sometimes I missed and found a description like, “Mostly red, wearing a green 73” which is quite confusing.

Once I’d replaced all the words with database IDs, I exported the spreadsheet as a CSV file and wrote a PHP script to ingest it into the database. I chose PHP because I’ve already done a lot of SQL with PHP for my Atlanta Fashion Police & convention gallery projects.  The script was pretty simple.  The line number was the cyclist ID. The first column contains an ID for Table X, the second column contains an ID for Table Y, and so on. My PHP server has a maximum execution time of 30 seconds, so I added parameters to the script to only ingest  100 lines at a time and ran the script multiple times. Since it’s a private PHP server that doesn’t have consumer traffic, I should have just increased the timeout, let the script run, then changed it back.

While building the spreadsheet, I had been tagging photographs in Lightroom with cyclist IDs. I exported the tagged photographs into a certain directory, then wrote another PHP script to iterate through all files in that directory, read the EXIF data, and fill in the images_show_cyclists table.

New frontend

This is my process for identifying cyclists going forward.  I look at an image in Lightroom and find a new cyclist who was not in the previous image.  I may scrub back and forth in the timeline to get a better view.  I fill in the search/create page to see if I have already seen a similar cyclist.

New “clothing” dropdowns are created as existing ones are filled in, so I can specify any number of clothing items. The “description” field checks for each word in order, so “blue yellow” matches both “blue & yellow stripes on arms” and “blue torso, red arms, black legs, goofy hat, yellow face”

Clicking “Find matching cyclists” will either show a list of cyclists with the features I’ve selected, or unlock the “CREATE” button if there are no matching cyclists.  Each matching cyclist is a link that takes me to a page that lists its features, what images it appears in, and previews one of those images.

Having a picture of the cyclist on the “view cyclist” page makes it much easier to confirm if I’ve actually found the cyclist I’m looking for, since I can just look between the two images.

The “EDIT this cyclist” page is almost identical to the search/create page, but instead of starting blank, it starts with data filled in from the DB.

Cyclists make multiple laps and groups tend to stick together, so if I see one cyclist back for a second lap, I can look at photographs from her first lap and identify some of the cyclists around her as well.

Preliminary data

I haven’t examined all the photographs yet, but here are some things I’ve discovered so far.

In 2012 photos taken over 50 minutes, I identified 1475 Solstice Cyclists.

Here’s a graph of how many passed over time.  Click to expand. 1 pixel vertically = 1 cyclist. 1 pixel horizontally = 1 second.  Red represents cyclists on their first lap. Green is the second lap. Blue is the third.  There are gaps when my view was blocked, the street was empty, I had to switch memory cards, and when traffic stopped & I paused the automatic camera.

The male/female split is 49/51, even closer than Dragon Con’s demographics, and very different from the split seen in most photographers’ galleries, in which images of women dominate.  Hmmmmmm. How curious.  HMMMMMM.

1300 people rode bicycles, which is to be expected from a group called the Solstice Cyclists, but I also saw:

  • 39 people on foot
  • 23 on inline skates
  • 6 on roller skates
  • 24 on scooters
  • 5 unicycles
  • 10 people on 5 tandem bikes
  • 7 skateboards
  • 2 pedicabs, with 2 drivers & 4 passengers

I also identified some groups & popular “costumes”

  • 11 giraffes
  • 45 mermaids
  • 8 Care Bears
  • 39 people wearing actual, normal clothes
  • 27 Wonder Women

I still have around 700 images to look through, so these numbers will change a bit, but as you can see from the graph, most of the cyclists in these later images are back for another lap, and there aren’t many new cyclists.

Once all that is done, I can start (START!) on the actual meat of this project: creating a grammar for bodypaint based on these thousands of examples & generating new paint patterns.

Solstice Cyclists part 1: data capture

The Solstice Cyclists, an intentionally-disorganized group of mostly-naked, mostly-painted cyclists who precede and overwhelm the Fremont Solstice Parade each year are one of my favorite groups to photograph.  They are colorful. creative, joyful, and high-energy.

Last year I decided I needed a photo of every single Solstice Cyclist.  (Does this seem familiar?)  I had two reasons:

  1. Statistics. Photographers’ galleries contain mostly women. Is this disparity caused by population imbalance or by selection bias? Solstice Cyclists are famous for being naked cyclists, but some people wear some clothing. How common is that?  What protective device is more common: bike helmet or sunglasses?
  2. Source data for grammar. I want to expand my bodypaint generator to use graphics, and I want the generator’s output to mirror actual paintjobs. Once I identify all the different cyclists, I can study their paintjobs, break them down into parts, and put those parts back together in novel but believable ways.

I got a tripod and a timer so one camera could automatically photograph everyone who passed while I did my normal photography beside it. I had to juggle a surprising number of factors to place that camera properly.

  • To avoid being blocked by spectators, the tripod needed to be either right next to the street, or high enough to shoot over their heads. I saw a few balconies, stout tree branches, even a bridge, that could get the needed height, but that brought new problems. Most paintjobs photograph best from the front, and bicycle riders tent to lean forward, so a camera that is too high has a bad angle. Also, accessing those high places is non-trivial, so I opted for a front-row seat.
  • Aiming down the street at approaching cyclists is my usual MO, but an automated camera will have trouble with that.  Since the camera is looking down the street, cyclists in the same image can be 10 feet or 100 yards away. How does the camera know which one to focus on, and which ones to leave blurry?  Cyclists in front will obstruct the camera’s view of cyclists behind them.
  • The route turns a few times. Maybe setting up at a corner will alleviate these issues. Setting up just after a corner sets a maximum distance at which cyclists will appear. Any further and they’d be in the crowd. There’s still the problem of cyclists approaching the camera and filling the frame, blocking other cyclists.
  • What about aiming across the street?  Cyclists will stay about the same distance from the camera as they cross the frame, and they are only 3 or 4 abreast, as opposed to unlimited ranks front-to-back, so obstruction is less of an issue.  Since I’m as far forward as possible (so spectators don’t stand in front of me) cyclists on the near side of the street will be very close. My lens might not be wide enough to capture their whole bodies, and they will cross the frame very quickly, maybe in between ticks of the automatic timer.
  • Thus, I decided to shoot across the street at the cyclists on the far side of the road. The frame is wide enough at that range that I’ll get several photos as each cyclist passes. Three-quarter to side view is not ideal, but still pretty good.  I had to accept cyclists on the near side sometimes blocking the shot, but it was the best I could do.
  • Oh, also! Position along the parade route matters as well.  The Cyclists circle back so they stay close to the parade (human-powered floats are much slower than bicycles). Near the end of the parade route, there are fewer spectators and no returning cyclists to block my view, but I only get one chance to see each cyclist, and some cyclists leave the route before then (mechanical failures, etc.) Closer the start of the route I get multiple chances to photograph each cyclist, but more obstructions.

The day before the parade I scouted the parade route, looking for places to set up.

I chose the spot on the right, which is near the “center of the universe” sign on the east side of Fremont Ave. The tree gave some protection to the tripod. It’s a lot easier to accidentally trip over a tripod than it is to walk into a tree.

During the parade I kept looking over at the “shots remaining” counter on the tripod-mounted camera like the marines watching the sentry guns in Aliens.  “That number is going down.  It, it keeps going down.  Are we going to run out before they stop coming?”  The automatic filled a 32GB memory card and I had to swap for another in the middle of the parade.  Whenever a traffic jam stopped the stream of cyclists passing me, I’d pause the automatic camera to save disk space.

In all the automatic camera captured 2644 images.  That’s equivalent to an entire day of Atlanta Fashion Police, except it took only 63 minutes, not 16 hours.  I took an additional 1400 photos with the camera I was holding.

I considered using computer vision to help me identify cyclists, but even nudity-detecting algorithms were bamboozled by the cyclists’ coloration. So I couldn’t even get “Yes, there is a person in this photo”, much less, “There are 6 people in this photo, and the guy with the red stripes and sunglasses has appeared in 3 other photos.” Time to use my eyes, the best pattern-recognizers I know! I thought I could store all the information in a CSV file. I’m only recording a few pieces of data for each cyclist, do I really have to make an SQL database with webforms to search and update it?

1064 rows later, I realized that, yes, I did need that DB.  Since cyclists could make several laps, and I was gathering data from both cameras, I needed to check for duplicate cyclists often.  Ctrl-F in a spreadsheet wasn’t cutting it.

Next time: building that database, and a few insights from the data.

PROCJAM 2017: Spaceship Wrecker

PROCJAM is a relaxed game (or anything) jam with the motto “Make Something That Makes Something”. It basically ran from 4 NOV 2017 to 13 NOV 2017, but the organizers emphasize not stressing out about deadlines.

Procedural generation is my jam, as you may have noticed from my Pathfinder Twitterbots and the little generators on my site.  I didn’t want to generate terrain, caves/dungeons, or planets, because so many generators like that already exist.  I had no shortage of ideas to choose from, though.  Deciding which one to pursue took quite some time!  Some potential projects:

  • Generate spaceships from subsystems that produce & consume various resources
  • Generate fantasy creatures with different senses & capabilities, and individuals of those races who may have disabilities or mutations
  • Generate buildings that accommodate multiple fantasy creatures with widely varying needs.
  • A MUD Twitterbot with emoji visualization
  • Generate footprints that players can follow, and a field guide that identifies the creatures that leave the footprints.

The generators I want to make have lots of constraints and dependencies. Many generators are stateless: the number of projectiles the gun fires can be chosen without regard for the projectiles damage, or fire rate, or elemental affinity.  Not so the fantasy creatures, who won’t use a spoken language if they can’t hear, or the spacecraft, who can’t use a science lab without a generator to power it.  I feel the added complexity in generation is worth it, because it forces the generated artifacts to make sense.

I chose “Spaceship Wrecker”, which generates a spaceship full of subsystems, then lets the player launch an asteroid or bullet to damage some of those systems and watch the failures cascade across the ship. In my mind I envision players boarding wrecked spaceships, prying open airlocks, re-routing cables, and getting the ship back online, but let’s start small, build up incrementally, and see how far I get in a week.

What parts do I need, and what do they depend on?

  • Engines (move the ship)
  • Fuel tanks (supply the engines)
  • Generators (supply electrical power to all kinds of parts)
  • Life support (supply air to rooms with people in them)
  • Crew quarters (supply crew to operate various parts)
  • Command/cockpit/bridge
  • mission systems (sensors, cargo, labs, etc.)

This gave me my list of resources:

  • Air
  • Crew
  • Fuel
  • Power
  • Thrust (technically that’s two resources: engines overcome inertia with thrust, but it’s simpler to create demand for engines by saying that parts consume thrust.)

I built some placeholder assets as Unity prefabs: 1-meter cubes, color-coded by function, with positive or negative resource values to indicate what they produced and consumed. At first I kept track of supplies at the ship level. If the need for power across all parts on the ship was X, I added enough generators to supply X power.  I didn’t care which generators supplied which components yet.  I would add some graph system later to distribute the resources.

I could specify a few components to start with, and the generator would add components until all components were satisfied.  Fun edge case: a ship with no components has no unmet needs, and thus is a valid ship.

Right after finishing that working algorithm that created sensible ships, I changed my data model & threw that algorithm out for a better one.  I made “ResourceProducer” and “ResourceConsumer” components to add to spaceship parts. Producers could form connections to Consumers, so each component knew how its resources were allocated.  When a component was damaged (remember the player-launched asteroid?) it could notify its consumers that the supplies were gone. Those parts would shut down, and their producer components would revoke resources from other components, spreading destruction across the ship.

Here a part has been hit by an asteroid (indicated by the green line) and it turns red to show its not working. Events propagate and three other components also shut down.  Success!

Let’s talk a bit about that asteroid. I imagine a tiny thing going extremely fast. Anything it hits is wrecked, and it penetrates to a significant depth. Multiple parts can go offline from the initial hit, if it’s lined up correctly.  I let the player orbit the ship with the camera, then click to launch the asteroid from the camera position to the cursor position. I RayCast to find the impact point, then spawn a trigger volume oriented in the same direction as the RayCast.  Spaceship parts know they are damaged when their colliders intersect with the trigger. I took a few tries to get the trigger volumes transform correct. I learned that some angle vectors contain Euler angles, so the 3 components degrees of rotation around each axis. Other angle vectors are unit vectors that point in the desired direction.

The ring structure was a placeholder for a more meaningful arrangement that I had been putting off because it was difficult. I wanted the parts clustered together because that’s how we envision cool spaceships and because the player could then line up asteroid impacts on multiple parts.  Parts should be connected by wires or corridors.  Parts that shared resources should be close together. Engines should be in the back. Fuel tanks should be far away from crew quarters. There were so many constraints I could place on the system!

I was also replacing my 1-meter cubes with low-poly models of differing sizes.  I tried spawning parts with space in between them, and using SpringJoints to pull them together, but SpringJoints maintain distance. I found a way to push parts away from each other, but that’s the opposite of what I wanted.

I thought about trying to place parts at the origin, seeing if they collided with anything, and pushing them to the edge of that hitbox if they did. I wasn’t sure what would happen once several parts were placed, and the first push might push the new part out of one part and into another.

I made a 2D Boolean array in which each cell represented a square meter that was either empty or occupied. As I spawned a new part, I’d get its size from its collider & try to fit a box of that size into the grid, starting at the center. If it didn’t fit, I pushed it in a random direction until it did. So my ships expanded from the center and all the parts touched each other.

But the parts only knew that other parts took up space. Related parts didn’t cluster together, and engines pointed their nozzles into bedrooms. Some algorithm research revealed that the “bin packing” problem was NP-hard, so I felt better about not immediately knowing how to proceed. I decided to sidestep the problem by rotating the engines so their nozzles were pointing down. All the parts were on the same 2D plane, so there would never be a part below an engine to get scorched.  I finished replacing all the placeholders with low-poly models and felt pretty good about my complex creations.

As a final step, I added another shader to differentiate between destroyed by the asteroid (bright pink) and shut down by system failure (dark red). I’m still looking to the future, when players go inside these ships to repair them.

So it’s done!  Basically. I should add some UI:instructions for how to interact with the ships. Of course, graphically indicating the connections between parts would be cool.  A spiral search is probably better than random walk for placing new components. A graph-based approach could improve co-location of related parts. It would be nice to have corridors for the crew to move through. Those could be hit by asteroids too, so each room would need airlocks. Are the airlocks dependent on main power to operate….?

Like I said, it’s basically done!