Tab Completion

I'm Tab Atkins Jr, and I wear many hats. I work for Google on the Chrome browser as a Web Standards Hacker. I'm also a member of the CSS Working Group, and am either a member or contributor to several other working groups in the W3C. You can contact me here.
Listing of All Posts

Want To Buy: a D&D 5e Rolling App

Last updated:

While pacing the living room this morning, I was thinking about how much 5th edition simplified D&D. Almost every single roll boils down to:

  • d20 (maybe with advantage or disadvantage)
  • stat bonus
  • maybe proficiency

That's it! Attacks, skills, saving throws, they're all the exact same set-up. This is great, as it makes both the players' and the DM's lives easier.

This got me to thinking about a simplified rolling app, taking advantage of this regular structure. Here's how I envision it:

Taking up the lower 2/3 of the screen or so, close to your thumb, is a hexagon with your six stats in each corner. Clicking on one of these does a straight stat roll - d20 + stat - and displays it.

In the center of the hexagon are a number of bonus bubbles, representing common bonuses. To use these, you fling a bubble towards one of the stats, and it does an appropriate roll.

One bonus bubble is always there, labeled "P" for proficiency bonus. You use this for most things: attacks with weapons or spells you're proficient in, and skills or saving throws you're proficient in. Several others can be turned on in settings to represent additional possible bonuses:

  • If you have players make defense rolls (rather than monsters doing attack rolls against the player's AC), a shield bubble, set up with their armor bonus.
  • If the player has the "half proficiency on all skills" bard bonus, a "1/2 P" bubble.
  • If the player has Expertise (double prof on some skills), an "E" bubble.
  • If the player has a magic weapon, or some other attack with a persistent bonus, a sword bubble, set up with their special bonus. (Proficiency is assumed here.)
  • Probably something customizable for misc bonuses the character commonly makes because of a magic item or something.

On the top of the screen are two representations of 2d20, one colored yellow/orange and happy, one purple/black and unhappy. If you click one of these before making a roll, it'll make it with advantage or disadvantage.

In the settings screen, you can input your six stats and your current level (required), and optionally set up the additional bonus bubbles as described above.

There's also a log of the last hundred rolls or so, so recent rolls can be checked if the player makes a mistake or accidentally dismisses the roll results too quickly.

Writing this would be a fun exercise in learning Pointer Events, I think. I might get around to it at some point. If anyone else decides to make it first, please let me know. ^_^

Fantasy World Racial Traits

Last updated:

I've previously riffed on "my elves and dwarves". Translating this over to mechanics, tho, makes me a little uneasy.

I've got a bit of a bug in my bonnet over people attaching too much "inherent flavor" to mechanics. For example, in my current D&D game I'm playing a Bard. The other players in my game have tried to refer to me as such in-game, and I had to correct them - my character isn't a bard by any stretch of the imagination. He's a noble son, raised in the Mondavi family tradition, which involves a mix of physical, social, and magic training, and uses a musical/lyrical focus for their magic to double-dip on those categories. There's no "Bard's College" that he's associated with, and he doesn't play music in taverns for coin or tell stories to crowds (tho he can certainly tickle the ivory in a more upscale party, if he wants to entertain his friends).

I spread this same philosophy to all the classes in the game, tho it does sometimes require one to be a little creative in interpreting things. The point is just that mechanics are nothing more than numbers and rules; they can admit a lot of interpretations, and doing so frees up character-gen in a lot of interesting ways.

I think the same should apply to races. I don't necessarily want to RP as a half-orc just because I want a tough person who's great with big weapons; maybe I just want my character to be a human, or a buff elf. And, overall, this actually works quite well - with a little bit of creative tweaking, all the "races" in the DMG can be interpreted as just traits you're born with / trained into earlier in life, before you started adventuring. Reusing the terminology from some other games, they become a "Background Feat" granted at first level. This has precedent in several systems I'm already familiar with: Iron Heroes had some feats marked as "Background Feats" which could only be taken at first level, and were a little more powerful than a normal feat; Numenera's character creation consists of completing the phrase "I'm an ADJ NOUN who VERBS", where the NOUN is your class and the ADJ and VERB are additional qualities that can represent your upbringing or race.

The only exceptions to this are the handful of "flavor" features that are definitely more biological, not thematic. The half-orc's traits suggest Strong but they also have darkvision, not because they're strong but because that's how half-orcs work. Same for elf trance, or halfling/gnome smallness. These carry little to no mechanical value - they're not used to balance the races - so it's okay to just attach these to the race itself, rather than the trait we extract from them.

So, here's the D&D 5e races, reinterpreted as background qualities that can apply to any race:

Tough

  • Con +2
  • Adv on saving throws against poison
  • Resistance to poison damage

and

  • Wis +1, HP +1/hit die, or
  • Str +2, proficient in light/med armor

Fae-Blooded

  • Dex +2
  • Proficiency with Perception
  • Adv on saving throws against charm

and

  • Int +1, cantrip, extra language, or
  • Wis +1, speed 35, hide when lightly obscured, or
  • Cha +1, learn dancing lights, faerie fire, *darkness*

Wily

  • Dex +2
  • Reroll 1s on attack/damage/skill/saving throw, must take second result
  • Advantage on saving throws against frightened
  • Move thru space of equal or larger creatures

and

  • Cha +1, can Hide behind an equal or larger creature, or
  • Con +1, advantage on saving throws against poison, resistance to poison damage

Skilled

  • +1 to all stats, or
  • +1 to two stats, proficiency in one skill, gain one feat

Elemental

  • Str +2, Cha +1
  • Magic blast (5'x30' line or 15' cone, choose acid, lightning, fire, poison, or cold)
  • Resistance to your blast element

Smart

  • Int +2
  • Advantage on Int/Wis/Cha saving throws against magic

and

  • Dex +1, gain minor illusion cantrip, simple talking with small animals, or
  • Con +1, Expertise in Int(History) checks about magic/alchemical/tech items, tinker to create small devices

Charming

  • Cha +2, +1 to two other stats
  • Advantage on saving throws against charm
  • Proficient in two skills

Strong

  • Str +2, Con +1
  • Proficient in Intimidate
  • Can drop to 1hp instead of 0, once per short rest
  • +1 die on crits

Demon-blooded

  • Cha +2, Int +1
  • Resistance to fire damage
  • Know thaumaturgy, hellish rebuke, and darkness, usable 1/day

Fantasy World Planes

Last updated:

Beyond the single world is the Ethereal Plane, where metaphysics dominates. It's filled with Ether, the raw stuff of creation, which accretes into distinct forms based on the metaphysical surroundings.

The makeup of the Ethereal is determined by your distance from three Poles, broad metaphysical tendencies that determine how the worlds work, and which are in complete opposition to each other. These are Stasis, Dynamism, and Entropy. (Yes, I played a lot of White Wolf as a teen.)

In Far Stasis, ether becomes a perfect crystal. Unchanging, without imperfections, it's an ideal form. In Far Dynamism, ether is raw potential, unable to take any specific form at all. Far Entropy is complete void, the ether there contradictorily representing its own non-existence. All three poles are inimicable to life; all the interesting stuff happens closer in.

As you move from Stasis to Dynamism, the etheric crystal fractures into the Elemental Pillars. These first represent pure forms of the five elemental atoms, but as you get closer to Dynamism, they become more heterogenous, gradually gaining form and shape and life.

The path from Stasis to Dynamism is instead the Energetic Rainbow, representing the breakdown of etheric form and energy. First is Light - pure and stable, but hard to contain. This shades into Lightning, still energetic but clearly more chaotic, but still controllable by proper application of Earth. Further on the energy loses direct physical coherence, becoming Kinetic - invisible and harder to grasp directly; doesn't travel as far but sheds its energy much more readily, making it more damaging within its useful range. A little further and we lose physical form entirely, reaching the more abstract form of Entropic energy, raw corrosion and loss of form. And finally, as we reach the pole it becomes pure Void.

Somewhere in the middle of the three Poles we get just the right mix to support the material world. Of course, we can't have just one - there are three, shadows of the stable fixed point cast by each of the Poles. The Static shadow is our world, the Prime Material plane, a world of physics and rules and things staying roughly the same from day to day. The Dynamic shadow is the Feywild - changing and fantastical, ruled by dreams and intuition. The Entropic shadow is the Shadowfell, a world in constant decay. The three worlds influence each other, tho our world, being the most Static, has the most stabilizing influence - things in the Feywild and Shadowfell tend to revert toward whatever they look like in the Prime. However, places of heavy magic use thin the barrier towards the Feywild and invite interesting effects, while places of death and pain draw the Shadowfell and induce monsters and ruin in the area. Insofar as the Body atoms "come from" anywhere, they originate here.

The Ethereal represents the "physical" half of "metaphysical", encompassing the physical worlds and the elements they're made from. Closer toward the Dynamic Pole, reality blurs and the Ethereal shades into the Astral, its meta/mental counterpart. This is where people's souls go when they dream, where reality is much more malleable. You can find pure forms of the Mental atoms here.

Souls travel to the Near Astral when people dream, where it's coincident with the Ethereal. In the Far Astral, out of reach of mortals in most circumstances, lie the Divine planes, where the gods make their domains. This layer is far enough out from the Ethereal that the Poles no longer hold any sway; instead, the divinities align along much more complex mandalas of metaphysics. We know that the five-fold symmetry of the Spheres holds at least some sway, due to the Divine atoms, but more is difficult to tell. Souls tend to drift this way after death, where they're collected by the gods.

CSS Function Syntaxes (color and otherwise)

Last updated:

Yesterday I committed a few changes to the CSS Color spec that are proving a little controversial to people without any background on the changes. Hopefully this will help!

Specifically, I made it so that the rgb() function (and others) can now use the syntax rgb(0 255 0 / 50%) for half-transparent green. Previously you'd write that as rgba(0, 255, 0, 50%). Why'd I make this change?

This is part of a general overhaul to how CSS defines functions that fantasai and I are pushing. The overall strategy is written up on our wiki, but the general idea is that CSS has some informal rules about how to organize things and use certain punctuation characters. In particular, in CSS properties we normally just separate values with spaces, relying on distinguishable syntax (like <string> vs <number> vs <length>) or just careful ordering to keep things unambiguously parseable. We use punctuation separators only for very specific purposes: commas are used to separate repetitions of a base value (like layers in the background property, or font names in the font-family property), and occasionally, when we can't do anything better, a slash is used to separate two otherwise-ambiguous values (like font-size vs line-height in the font shorthand, transition-delay vs transition-duration in the transition shorthand, or the multiple pieces of syntax in a border-image layer).

However, functions violated those rules. They used commas extensively and somewhat inconsistently, just to separate values from each other. On the one hand, this makes CSS functions look slightly more like functions in a traditional programming language; on the other hand, it meant you had to learn a different mental model of how CSS's syntax works, and type a bunch of comma characters that weren't actually necessary. fantasai and I have gradually come to the position that it's worthwhile to unify CSS's syntaxes, making functions more property-like. (Our position can be summed up aptly as "functions are just named chunks of CSS syntax".)

Color

So that brings us to the Color spec. Color 3 was already a function-full spec, and Color 4 more than doubles that number, adding hwb(), gray(), lab() and lch(), and color(). The first four of those look similar to the existing rgb()/etc functions, just taking a couple of numbers, so they were originally designed with the same syntax, separating every number with commas.

But color() was a bit more complex, more like a CSS property. It had to take a colorspace name, an arbitrary number of arguments to the colorspace, an alpha, then finally a fallback value in case the colorspace wasn't loaded or was invalid. Putting commas between every value there just got ridiculous, not to mention made it difficult to read; in particular, it was hard to tell where the colorspace arguments ended and the alpha began.

So, I opened Issue 266 about it, and discussion eventually led us to making it use CSS property syntax pretty much exactly: color() takes a comma-separated list of colors (each one serving as fallback for the previous), and within each color, everything is space-separated. Because colorspaces can take an arbitrary number of numeric parameters, the alpha value was ambiguous (hard to even tell whether or not there was an alpha at a casual glance), and so we employed the slash to separate it visually from the parameters.

At this point, tho, it was slightly weird to have this one color function use this particular syntax form, while none of the others used anything like it. Welp, all the color functions were on our wiki page's list of things to overhaul anyway, so bombs away! We went ahead and changed all the new functions to use the same syntax (all values space-separated, with an optional slash-separated alpha), and then added a new syntax variant to the old functions with the same form.

(I stress, this is a new variant syntax, not a replacement. All your old rgb(0, 255, 0) code is fine and will never be incorrect. We're just classifying it as a legacy syntax; we've got a handful of those cluttering up CSS specs.)

So, now all the color functions use the same syntax form, and they all agree with our general push to make functions resemble properties more closely. It may feel a little weird at first, but I think you'll appreciate it as you get used to it (a few characters less typing, at the very least). And we've been edging this way for quite a while - as far back as linear-gradient() we were trying to use commas reasonably, with the complex sizing/positioning part up front completely space-separated and commas used only to separate the color-stop repetitions.

Cleanly Handling a Fork on GitHub

Last updated:

One of the greatest things about GitHub is the ease with which you can fork a project and make your own changes.

One of the worst things about GitHub is the ease with which you can fork a project and then lose track of the changes upstream is making.

This is extra-true when you want to make occasional PRs for a project with regular commits; it's easy for your fork to get way behind in a way that makes it really annoying to write patches that'll merge cleanly later. This guide collects my personal experience in handling this situation with minimal pain.

One: Set an "upstream" remote

If you run git remote -v in your repo, you'll see the remote repositories that your local copy knows about. These are things that are easy to push to. By default, it'll only contain the GitHub URL for your fork, labeled "origin". You're gonna be interacting with the original repo you forked from a lot, so you want to give it an easy name, too:

git remote add upstream https://github.com/ORIGINAL_OWNER/ORIGINAL_REPOSITORY.git

Now you can easily refer to the original repo as upstream.

Two: Regularly re-sync with upstream

Whenever you're about to start some new work in your fork, re-sync it with the upstream first, so you have all the new commits and are less likely to conflict badly:

git fetch upstream
git checkout master
git merge upstream/master
git push

This should cleanly merge and just do a "fast-forward" on your repo, rather than actually making a merge commit, as long as you faithfully do what I suggest in the next step.

Three: NEVER COMMIT TO YOUR MASTER BRANCH

If you want a clean, easy workflow, you want to NEVER, EVER commit to your local master branch. The master branch's sole purpose is to track upstream exactly, so you can always cleanly work against the current version and write easy-to-merge PRs.

Instead, always make your changes in branches. When you push the branch to your remote, you can do a PR; when the PR is accepted, you can pull the newly-updated upstream and then delete your branch. master always remains a source of upstream truth, unpolluted by your personal code unless blessed by the upstream maintainers.

(This is good advice in general; always make changes in branches and only commit to master when you're done, but I'm usually lazy and just always commit to master for personal projects. But it's super-important to get this right for forks, or else you're in for a lot of pain.)

Four: Rebase your branches regularly

Say you're in the middle of writing a new proposed feature (in a branch, of course) and you notice that upstream has some new commits that touch code you're going to be modifying soon. You'd like to get that code into your branch now, before you start modifying things, so you don't have merge conflicts later. But how?

First, resync your master as stated in step 2. Then, rebase your topic branch on top of the new upstream code:

git checkout MY_COOL_BRANCH
git rebase master

This'll undo your commits, pull in the new stuff from upstream, then replay your commits on top of it, so when you eventually make a PR, the upstream maintainers will have a perfect clean merge, with your commits sitting on top of their latest code.

Five: Force-push commits that just fix PR nitpicks

When you eventually do submit your PR, the review will probably catch some small mistakes you need to fix. You can just make those fixes and push them to the PR branch, but then when it's eventually merged there will be a lot of silly little "fix typo" commits scattered around.

Instead, just fix the commit itself, using the git commit --amend option, then force-push that to your remote with git push --force. Force-pushing is normally a very bad idea, because git is usually rightfully trying to stop you from making a mistake when it rejects a push, but in this case changing the history of a short-lived PR branch is unimportant and makes things look cleaner overall.