In many tabletop games, there are often gameplay mechanics that only require a success or failure result. Boardgames, roleplaying games, and miniatures wargaming often have the notion of a "target number." If you hear phrases like "hitting on threes" or "four +es" around your table, you are experiencing one of these mechanics.
This allows for a unique situation; to play the game, you only need to know if you have succeeded or failed when the dice is rolled. The actual value on the die only matters in the context of the success criteria. Once the players know if it is a success or failure, they can proceed with the game. The value that is rolled is not strictly relevant.
If we had a device that could determine the result of a roll in success/failure terms, we could withold the values on the dice from the user. This gives us a source of entropy that can be used, but not revealed. The game mechanics act as a filter or obfuscation layer.
Examples:
We hope to leverage this filter to contribute the same entropy that powered a game to the KZG Ceremony without revealing it to the players.
Here's how a dice tower could be constructed to collect the entropy from a tabletop game and only reveal the far smaller amount of information that the player needs.
A dice tower like this could likely be built out of comodity embedded systems and a touchscreen display.
To contribute to the ceremony, our entropy will need to be multiplied by the previous KZG ceremony output before it is destroyed. Instead of waiting in a queue of contributors, the participation will be scheduled ahead of time and the current KZG file will be provided out of band.
The first construction problem we encountered was making sure the camera framed the dice area correctly. The specs on the camera that estimate its field of view were close, but some trial and error was necessary to maximize how much of the camera sensor was available to look at dice. I printed out a ball and socket camera mount, which allowed me to find the optimal camera angle and distance. We wanted to maximize area covered, while avoiding views of side faces of the dice. This would later turn out to be overdesigned.
The next problem was the actuator to return the dice to the user after the entropy had been collected. The first draft of that looked like this.
Learn More →
This worked great until I started throwing lots of dice at it. The only thing holding that trapdoor level is the servo itself. Once you start dropping dice on it—which might land on either extreme from the fulcrum—you now have a decent amount of force pushing against your servo, corrupting its zero point and wearing on the gearing to the axle.
This is probably the biggest problem to be addressed in the next iteration.
I also experienced a couple of random restarts, likely due to powering the servo from the on-board gpio pins on the Raspberry Pi. Doing that is known to produce a pretty dirty power signal. For now we'll continue limping forward, but that issue needs to be addressed later.
Big shoutout to Quentin Golsteyn whose Yahtzee project was one of the first I discovered while doing initial research to see if this whole idea was even viable. Check out his project!
My application, however, needed to support many more than two dice, because there is nothing more satisfying than attacking your opponent with a fistful of 10 to 15 dice. It's soooo satisfying. We also need to operate in a smaller space; I am intending this dice tower to be portable so I can take it to a planned gaming event, and the area where dice land that is observed by the camera has specific needs. It must be concealed from the player and have consistent, steady lighting. It might be a little cramped in there.
It turns out, when dice land next to each other, the blobs-and-clusters technique Quentin developed runs into some problems. For example, the pips on a two can be closer to a neighboring die than they are to each other. Here we see one of the pips on the two was within clustering distance of the four pips on an adjacent die.
It's not always the twos—but it's usually the twos. Sometimes fours. Any pips that could be closer to the pips on a neighboring die than to their own pips presents a problem. It's a pretty big problem and, at this point, I got scared; the simplicity of the blobs-and-clusters technique was doing a lot of heavy lifting! I was having no problems running this on a Raspi4 on multiple frames for each roll and returning the dice to the user quickly.
I came up with two potential solutions.
Both of these options felt like staring down a deep rabbit-hole, and I still had three armies of miniatures to paint and a board full of terrain to build before gameday when the KZG commitment window is scheduled.
So, screw it. If the problem is the dice, make better dice. Bigger dice with more distance from their edge to the pip pattern would make sure the pips always cluster within the die face. Turns out, I have a friend with a laser cutter who has experience making custom dice.
He was kind enough to make me a set of custom dice on which we compressed the pip pattern toward the center of each face. Problem solved; Dankshard be praised!
Learn More →
I would later discover that I was quite wrong about my assumption that dice landing on the edge of the platform is ok. It is not, and we are eventually going to have to redesign that whole mechanism.
There were a few. As you can see in the video above, there is still a lot of uncertainty around pip detection. I had heard that CV is all about controlling your environment and stripping out as much noise as possible. The rumors are true!
CV issues I had to learn about and solve:
Yeah, we're not done with dice issues yet. But these are mechanical, not visual! So, thats fun. In rare cases, as dice tumble down the tower, they might land on top of each other. If that happened while using a dice tray, it would be obvious and everyone would go, "Whoa, neat!" and just re-roll the die that landed on top, preserving the rest of the roll. Our concealment from the user prevents that.
But what we can do is give the user some feedback on how many dice were detected. Anything other than the number of dice expected would simply warrant a re-roll, and we'd go on with the game.
Endurance testing was not successful. If we collect two bits of entropy per die (three if we don't care about half a bit of bias), we're gonna need 512 dice tossed in there over four hours, so I would just stand there and toss dice in and watch the debug output. A smart friend questioned why I put the axle in the middle of the platform, and I didn't have a good answer. He suggested that if the axle was on one end, then you could use a latch to hold up the other end. Then you'd have a perfectly stable plane to land on and transfer no force backward through the gearing and servo. Something like this:
Learn More →
When you have a 3D printer, though, it's easy to go a little crazy. I actually did a revision of the entire device which was printed in FDM and … it wasn't great. The process of thinking in CAD is helpful, but doesn't really lend iteself to improvisation.
The introduction of the solenoid was going to require more current than I could provide via the GPIO pins on the RasPi, so I added a motor driver board to the design. This would give us a dedicated, clean current source to drive both the solenoid and a larger servo at the expense of added software complexity.
Learn More →
The final version would be masonite and a dowel with a small, simple geared cam on the shaft and some glue. Here is a view from the rear access panel, showing all aspects of actuation.
Learn More →
Here you can see the resin printed cams that drive the axle and a solenoid latch in the front for the opposite edge to rest on. A few more tweaks to this basic design, and we're in business. I printed a few custom housings and brackets for the solenoid and the servo to make sure they were locked in position but could still be repaired or replaced in a mid-ceremony emergency.
The last hardware issue that was causing failures over time was drifting of the camera ball joint. Vibration from dice hitting the ramp that it was mounted to eventually caused the camera position in the ball joint to shift. I tried adding a set screw to fix it in position, but the resin is just too brittle, and it loosens over time. Now that we know the proper position, though, we can replace the ball joint with a free-floating, fixed position mount. Something like this:
Now we have a fixed position, the camera is isolated from vibration, and integrated, dimmable lighting is coplanar to the camera sensor.
Look, I've been a backend dev for 20+ years. My day job is working on an Ethereum client (support client diversity, run Besu!). The closest I ever got to user interfaces is website hackery back in the day. Now, this project won't need anything fancy, but we do need to accept input and display feedback to our users. I'm already using Python for the CV, the Raspberry Pi is running Raspbian so we have a full-fledged desktop. We should be able to mock up a touchscreen UI pretty easily in QT.
So, lets be honest—I'm just gonna hack this together with ChatGPT:
That's an early version of the UI running in debug mode. Still not perfect, but the image filtering is MUCH improved from where we were before. I gotta say, ChatGPT completely changed the way I'm going to learn new programming languages. It's like having a private tutor. Not only can I feed it the code and ask it how to make changes, I can ask it why things work a certain way and even ask it to make a comparison to other languages I'm more familiar with.
Ok, I think we're ready to get this out of the lab and use this in a game. At this point, I had about a week before the convention we were playing at. A week before a four-hour window during which we would use this gadget to permanently enshrine our big game of Konflikt '47 in the foundation of Ethereum's scaling strategy.
To do a proper dress rehearsal, I set up a local instance of the KZG ceremony sequencer and a copy of SCAR so that I could be absolutely sure we were executing correctly when our window opened. I went through all of the same motions that I would need to during the actual contribution window; the only difference was specifying a few alternate url options to the offline mode of the go-kzg client.
Big shoutout to all the contributors of these tools! It is remarkable how little time I had to spend on the actual crypto part of this problem, leaving me more time to play with computer vision and make terrible mechanical engineering decisions.
Here's what a game of Konflikt '47 looks like. It's pushing around army men and tanks on a cute little battlefield. Tape measures and dice. Pew pew!
Things went very smoothly as far as device function was concerned. No misfunctions; responsive performance. After about two hours of play, however, we had only collected about 300 bits of entropy. Our goal was 1024 bits, so… we're gonna have a problem at show time.
Option one was easy to execute, but the real missed opportunity was supporting morale rolls. A morale roll in Konflikt '47 is always a roll of two dice, with the goal being to hit or get below a target number. In the beginning of the game, every unit needs to pass a morale roll to enter the battle. If they fail, they keep trying. Later in the game, as units get pinned by enemy fire, they need to make morale checks before every order they receive. If we could support morale rolls on the tower, we could add more entropy, especially at the beginning of the game.
Fire up ChatGPT, explain the changes we want to make to the UI, and we're good to go. Now the user needs to change the mode when selecting their target number. So, we increase the range of selectable target numbers and disable any which would be invalid.
With that last-minute feature added, we might be done! I'll just spend the rest of this last week painting minis, building terrain, and doing more endurance testing…
It's amazing how something as innocuous as turning the heat off overnight in your workspace can totally jeopardize weeks of work. I should just let Exhausted Past Justin explain the chain of events that struck less than 72 hours before gametime:
Learn More →
So that sucked.
We set up a camera pointed at the tower to make sure nobody tampered with it. It's a riveting four hours of dice going into a tower. I live-streamed it at the time on Twitch and also saved it to disk. The video is 19GB, though, so I'm not posting it on YouTube—but can provide it if someone is masochistic enough to want it.
I explained what was going on, how this was a fun and goofy way to contribute to a shared secret that would power the future of Ethereum scaling and everyone was happy to participate. Nobody had any issues with playing a game where they could not see their die rolls.
That's the game board after everything is fully deployed. We are underway and collecting entropy! The only real question remaining is: Will we hit the full 1024 bits of entropy?
By turn three, we've already collected 415 bits of entropy, a big improvement from the dress rehearsal. Troops are still moving into position and crossing the board, so we expect the rate of entropy collection to increase a lot as infantry units engage in the fight.
And that's when the Werewolves came out. Only thing worse than Werewolves is Nazi Werewolves.
A bolt of red, white and blue darts across the battlefield—the Star-Spangled-Man-With-A-Plan!
The game was a smashing success, and 15 minutes before our contribution closed we crossed the 1024 bit mark. The tower said "CEASE HOSTILITIES - DANKSHARD BE PRAISED" and began the commitment process. Once complete, I recovered the commitment and sent it to the sequencer.
0xF51D203536Ea8b5BFBc06b3a1c21514766b22BB1
is the address we submitted under, you can search for it at ceremony.ethereum.org to confirm its inclusion.
Powers of Tau Pubkeys:
(2^12):0x9157bfd6d53e6f9cd427aa84acfe47e7f1224ab99ac2c606ccea8dec3ba85a73d3b29c74422461697e9d86e36baa7f8d02add99798a40e2f32c2cd7185b13ad86f57fb7df418c708727d9ff2f85cc220f66353408151075785caf78f3b6c4dbe
(2^13):0x92ae7793b9ba21e8dcfa88e4722acd8b5282d0ad9f9407db13b966797d8a98474986e3beb0d6271fb4fe68ea485dd8ff05144addcf1af56f69a2c26ad5e6009b481e77f431247a03c22e458da0f5264e1d0db7e3e5aa6f1fc93cf539271b77fc
(2^14):0x908b21c002bb0a336e8c22da6c5f3ae688c7319f72045eff8817233fbb3c29a652f04a435e4392507462b4530f80db5c102844255ec594c1b15b3b2ae9f3a3797436846f754e8769f4c5450902d2f3d704971fb998881aeea998c64d37198391
(2^15):0xa64a105c21f2d9cff3799ef38203ae6da23b12be6949ab8dd136244de4af918b6bdb4d69537af464c3b3a156f89655720b827a331ad71d3a43ce9420b7b81be6e81b69009824de01b618ea65d2512da82215d75c99fbe9e010df5c51279416e6
Victory came down to the very last die on the very last turn. Germany barely eked out a victory, recovering the second objective it needed at the last minute!
Huzzah! All that was left to do was destroy the media that KZGamer runs on. It had already deleted the entropy storage after completing the commitment, but just to be sure…
Learn More →
Here's a post-event tour of the tower:
Learn More →
I think it is super cool that the Ethereum dev community arranged such a neat opportunity and that I could turn it into a really fun event for me and my friends. Shoutout Trent Van Epps and Carl Beekhuizen for managing the whole KZGCeremony. Shoutout
Ignacio Hagopian who quickly responded to feedback on offline features for the go-kzg client.
Shoutout Quentin Golsteyn whose experiments in CV reading dice convinced me this stupid idea was only stupid, not crazy.
Shoutout to all my degen gamer friends who played the game in testing and production, and listened to me rubber-duck debug alllllll the issues I ran into along the way.
Most importantly, shoutout to my family who put up with me being isolated in the shed, grinding on this project for the weeks that it took.