2024-07-22 - [53] 5:4

I have gotten back into making and eating more salads and drinking less alcohol. My motivation was getting fitted for a tux 2 months ago for a wedding in middle of this October... After eating a lot of prepared processed foods and drinking quite a few cans of busch light a day, I can guarantee those measurements have gotten a little snug. My solution, at least for a bit, is to eat more whole foods and mostly stop drinking wheat beers!

My grocery bills are cheaper now, which is nice. I can make very big salads that fill my stomach and satiate my hunger and figured out that yellow mustard and a bit of vinegar makes a tasty very low calorie salad dressing. Also had quite a few cans of beans I haven't had the motivation to eat, so adding those to salads to add protein. It's also an excuse to eat through the fermented cauliflower I made a few months ago.

While I'm not a vegetarian currently, I was a vegetarian for about 7-ish years back about a decade and a half ago. Making tasty veggie dishes was fun, nice, and easy for me, didn't take too much time, and in my opinion was tastier than most of what I was eating before. Along with that, after I would eat a meal, I actually had energy rather than feeling like I needed to take a nap.

While my grocery bills are cheaper this way, the spendiest part ends up being tomatoes. Thankfully though, there are a lot of veggie gardeners around me that grow tomatoes. The thing I have noticed in particular with tomato gardeners is that they go from 0 tomatoes, curious as to why they didn't grow, to wayyyyyy too many tomatoes and having to pawn them off to everyone they can. I'll gladly take some of those tomatoes!

With any luck, this diet will inspire me to add more recipes to my site. Maybe.

]]>2024-07-14 - [53] 4:26

A few days ago, I opened up my RSS feed reader and noticed that the YouTube videos in the YouTube RSS feeds I'm following wouldn't load in my video player. I used a combination of mpv and yt-dlp to load and play YouTube videos for a while now.

The issue seems to be that YouTube has changed API things to make YouTube video downloaders stop working.

Before I continue, I'll note a few points quick:

- YouTube does NOT owe me access to their videos
- YouTube does NOT owe me the ability to see their videos without using their website or app
- YouTube does NOT owe me the ability to see their videos without ads

I used to watch a lot of YouTube before, especially as entertainment when I was bored. Eventually, the site started getting slower for me, likely for multiple reasons. The site also was CPU and RAM intensive, would reset my speed preferences, and would constantly reset autoplay. Eventually, I got rid of my YouTube account and set my web browser to clear cache and cookies after closing. This made the already unstable settings even more unstable, which I knew would happen.

While I used to use the recommendations and search functions of YouTube to watch videos as a form of entertainment, eventually I just followed a few YouTube channels over the built in RSS feeds (that I think YouTube forgot exist...). This still allowed me to watch YouTube videos as a form of mostly mindless entertainment.

Following yt-dlp and a few other programs breaking for YouTube, I have decided to stop watching YouTube videos as a form of entertainment. My RSS feed reader no longer checks the YouTube channel RSS feeds, which now means I can check my RSS feed once a week or month instead of multiple times a day.

I have come to the conclusion that YouTube is simply a video hosting platform. Some resources I may run into will be videos that are hosted on YouTube. If I need to see those resources, I will do so on YouTube's terms using their website and possibly even turning off my adblocker if I have to. With that said, YouTube being demoted to only being a video hosting platform means I won't go out of my way to search for videos or watch videos for the purpose of entertainment.

YouTube won... They want me to use their site in a very particular way, so as a result I will just use their site as little as I possibly can. This means I won't be seeing the cool projects friends of mine are working on if they only post about it on YouTube.

There will likely be a continuing game of cat and mouse for tools like yt-dlp to work with YouTube, but I just don't feel like playing that game.

]]>2024-06-27 - [53] 4:8

After about 12 years of wanting to get my amateur radio license, I have decided to finally just go and get it.

The only issue I had before was the legal name and address needing to be registered, and I didn't feel like having my deadname registered to do a hobby I would enjoy. While everyone knows me as Vi and have known me as Vi since at least 2012/13 (depending on when I came out to the specific people), I didn't have my name changed legally, due to costs to apply for a name change, having to have the name change announced in a local paper, and having to go to court and **hope** that the judge wasn't having a bad day. If after all of that work, the judge decides "no" for whatever reason, the multiple hundreds of dollars for the filing fee is just gone. After my state finally decided to implement the ID requirements for the REAL ID law, it also became a LOT more stressful to get an ID. Anyone who has had a name change, even in the case of marriage, suddenly needed to bring the full paper trail proving name changes.

Over the last 7 or so years though, I have become very apathetic in terms of the subject of gender and name in relation to myself in particular. I'm not sure how much of it is out of it has been trained and how much of it is me discovering myself more. I just don't experience gender and don't care to experience gender. I don't use third person pronouns to talk about myself, and as long as someone isn't being malicious, I have gotten to the point where I really don't care what third person pronouns people use for me. I also don't really care what name is used for me as long as it's not malicious and it's consistent enough for me to understand that I'm being addressed. There is a friend of mine who calls me "Nick" because that's the name that pops into their head every time they see me. They're incredibly consistent about it, so for them, I **AM** Nick. There are even some folks in my town that call me "Ruby" or "Reuben" because I solve Rubik's cubes and that name came to them in a non-malicious way.

Because of my general apathy towards my gender/name, I have gotten to the point of not really caring if my deadname is registered in an FCC database in relation to a hobby I wish to pick up anymore.

For the next in-person test session in August near me, I plan on going for my Technician and General licenses. While I could also study for the Amateur Extra license, I feel it's more personally appropriate for me to get my General license for a few months and understand the artform before getting my Amateur Extra license. It's one thing to memorize a test and the answers... it's a completely different thing to understand WHY the answers are what they are. I'll probably go for Amateur Extra in the November test or sometime next year.

I was incredibly amused as soon as I learned that the FCC gives you all of the test questions and answers IN ORDER. If you get question T1A01, which is "Which of the following is part of the Basis and Purpose of the Amateur Radio Service?", the 4 multiple choice answers are always "A: Providing personal radio communications for as many citizens as possible", "B: Providing communications for international non-profit organizations", "C: Advancing skills in the technical and communication phases of the radio art", and "D: All these choices are correct" and the answer is always "C". This feels like getting the teacher's verison of a highschool textbook.

Because the questions for the 3 tests are handled this way and my solution for things is usually "I'll build a tool to handle that!", it was just a matter of grabbing the questions and answers and building a simple test program. I'll probably release a git repo of my test program soon-ish. I can specify question pools, like "T1-T3,T4A-T4B,G1A" to choose all of the subelement 1 through 3 along with subelement 4 sections A and B of the Technician test and subelement 1 section A of the General test. I can also randomize the questions of the selected question pools. An option to choose N questions from the selected question pools (first N questions if not randomized, otherwise a random N questions with no repeats if the questions are randomized) is also incorperated. The last important input of the program lets me decide if I want to do a test or not. In the case of a test, only 1 question is asked at a time and I have to input A, B, C, or D. If it's not a test, then all of the questions are printed out from the specificed question pools and an asterisk is placed next to the correct answer.

My study process has just been to print out a section (for example: T5B), learn the answers, then do a randomized test with all of the questions of that section, then if the section wasn't "A", I will do a test with the entire subelement. After I get through a subelement, I will do a randomized test with every subelement I have learned through.

Because of the **in order** nature of these answers for each question, I'm running into a strange issue where I'm answering a little too quickly based on the order of the answers rather than reading the questions... That's technically fine, because a correct answer is a correct answer, but I will probably force myself to read each question before answering soon. This issue is a big reason why I want to wait on the Amateur Extra license, so I don't just memorize my way into having a shorter call sign without having much if any real knowledge.

Honestly, due to costs of equipment, likely not much for a while. I might ask around about handhelds that are on a relatively shoestring budget that don't make other operators want to murder me because of inadequate RF filtering. It's just important to me that I have the licenses in case I want or need to use these radio bands for whatever reason in the future.

]]>2024-06-12 - [53] 3:24

In my journal post "Hopes (Resolutions?) for 2024" from back in the end of December of last year, I wrote about how I was trying to find a human calculatable hash function to create a secure pencil and paper authentication scheme. Below is that journal post.

Hopes (Resolutions?) for 2024After searching for quite a while, I found a system that should work well for me in a paper titled "Towards Human Computable Passwords" by Jeremia Blocki, Manuel Blum, Anupam Datta, and Santosh Vempala and published in 2017. The paper is linked below.

[HTTPS] Towards Human Computable Passwords[HTTPS]I want to be able to update my gemini capsule and web server from another computer, for instance, a computer at a public library or a friend's computer if I don't have access to my own computers. At some point, I will probably add Titan support back to my gemini capsule server software "Bergelmir", which is fine if I'm using my own computers that have a gemini client like Lagrange, but I can't just download a "Small Web" browser on other people's computers. I also can't trust that there isn't some sort of malware or keylogger on other people's computers, so I don't want to be put in the situation where some malicious actor now has the ability to edit my website as they please.

Once my gemini client / web server has Titan support added to it, I could easily and pretty securely edit my site using a client TLS certificate to prove my identity, but I already have ssh access to my server, so I don't really need titan support.

I really would like to edit or add pages to my site from the public library computers though. This means I need to add some sort of authentication scheme to my website that only I can answer correctly. A shared secret a client TLS certificate to prove my identity, but I already have ssh access to my server, so I don't really need titan support.

I really would like to edit or add pages to my site from the public library computers though. This means I need to add some sort of authentication scheme to my website that only I can answer correctly. A shared password isn't enough for a secure authentication scheme though, as someone could look over my shoulder while I type in the password or the computer I am using could have a keylogger.

What I need is a human calculatable hash function. My server and I would both have a shared secret, then my server would give me random data (a challenge) that I would be able to manipulate in my head to produce a hash. The server would produce the same hash, and if my result and the server's result match, the server will know that I have the shared secret. The end result is that I have proven to my server that I am in fact me, and anyone snooping on the computer I use to edit my website won't know the shared secret or be able to successfully solve other random challenges.

In the "Towards Human Computable Passwords" paper, they use base 10 numbers and a list of n images, all of which are associated with a random number and memorized by the human. It's not required to use base 10 or images though, so I used base 6 and a shuffled mapping of all 36 characters in my "bonewords" password scheme, so a random mapping of "abcdefghikmnoprswzAEFHLY234678+,-/?^" where each character is used one time each and each character is used, for example "i/c8-AEebgLsz+H2YFr6ph73^mdfk4,a?own".

{bonewords}The details of the method I will be giving will be my particular method, as opposed to the base 10 method with images shown in the paper.

I personally want to be able to generate that random mapping without needing to use a computer. This mapping is equivalent to shuffling the 36 characters, so I am able to just use a deck of cards. Because there are 36 characters, I use the Ace through 10 of Clubs, Hearts, and Spades, along with the Ace through 6 of Diamonds. I then map Clubs to 1-10, Hearts to 11-20, Spades to 21-30, and Diamonds to 31-36. After I have those 36 cards, I thoroughly shuffle them by doing a bunch of riffle shuffled intersperced with overhand shuffles to change up the top and bottom cards, as riffle shuffles have a tendency of keeping the top and bottom cards near the top and bottom. How many shuffles should you do? Well, you could smush the cards around for a minute or a few minutes and square up the cards, or you could do like I do and do 2-4 riffle shuffles, followed by an overhand shuffle, followed by 2-4 riffle shuffles again, followed by another overhand shuffle, repeating that pattern until you have riffle shuffled 7-10 or more times. The goal is to just make sure the cards are good and shuffled without you being able to know where any of the 36 cards might be. Once you have the pack shuffled, map the cards so the Ace of Clubs = "a", 2 of Clubs = "b", 3 of Clubs = "c", ..., 4 of Diamonds = "/", 5 of Diamonds = "?", and 6 of Diamonds = "^", so the 36 bonewords characters in order. Now take the shuffled deck and write the characters in the order of the shuffled deck, for instance, in our example of "i/c8-AEebgLsz+H2YFr6ph73^mdfk4,a?own", the deck order would be 9 of Clubs, 4 of Diamonds, 3 of Clubs, ... 3 of Hearts, 7 of Hearts, 2 of Hearts. Write down that character order somewhere and memorize it. Give your server that information as well in a trusted computing setting.

Now that you and your server have a shared secret, whenever you want to authenticate, the server will give you a randomly generated challenge. The challenge will be a random string of a particular size of characters that are found in the shared secret. This string is fully random, as in, any of the characters are allowed in any position of the string and repeats are also allowed. That means a challenge of all "a" characters is valid, although incredibly unlikely.

The random challenge string has 3 parts to it, 6 (or whatever base is being used) random characters as an index, then a set of random characters called k1, then a set of random characters called k2. The length of characters in k1 and k2 are important for security. The "Towards Human Computable Passwords" paper specifies that it will take approximately n^(min(len(k1)+1, (len(k2)+1)/2) correctly answered challenges before a polynomial time attacker would determine your shared secret. For a case where the length of k1 is 3 characters long and k2 is 7 characters long, it would take approximately 36^(min(3+1, (7+1)/2)) correctly answered challenges, which is equivalent to 36^(min(4,4)), which is equivalent to 36^4, which is 1679616. These numbers of course assume that this problem is an NP problem and that P != NP.

Let's use the challenge "36EmFpfpdp2-8fY4", where the 6 index characters are "36EmFp", k1 is "fpd", and k2 is "p2-8fY4". As a reminder, the shared secret is "i/c8-AEebgLsz+H2YFr6ph73^mdfk4,a?own", which we can split into groups of 6 to make our lives as people a little easier, so "i/c8-A", "EebgLs", "z+H2YF", "r6ph73", "^mdfk4", and ",a?own"

Step 1: The first step of solving the random challenge is to look at the values of k1 ("fpd") one at a time and find their position in the shared secret, then add those positions together and take the modulo 6 of the sum (use the answer 6 instead of 0 if the answer modulo 6 is 0). It's this modulo 6 that is the reason why we can split the shared secret into groups of 6 to make life easier. In this example, from k1, "f" is 4, because it's the 4th symbol in its group in the shared secret, "p" is 3, and "d" is 3. (4 + 3 + 3) mod 6 is 4, so our current value in solving the random challenge is 4.

Step 2: The second step of solving the random challenge is to take the result we got from the first step, and find that number character in the index characters ("36EmFp"). Our result from the first step is 4, so we take the 4th character of the 6 index characters, which is "m", and find its position in the shared secret modulo 6 (use the answer 6 instead of 0 if the value modulo 6 is 0). "m" is the 2nd character in its group in the shared secret, so our current value in solving the random challenge is 2.

Step 3: The third step of solving the random challenge is to look at the values of k2 ("p2-8fY4")one at a time and find their positions in the shared secret, then add those positions together along with the value of step 2 and take the modulo 6 of the sum (use the answer 6 instead of 0 if the answer modulo 6 is 0). The value of step 2 was 2. "p" is 3, "2" is 4, "-" is 5, "8" is 4, "f" is 4, "Y" is 5, and "4" is 6. (2 + 3 + 4 + 5 + 4 + 4 + 5 + 6) mod 6 is 3, so the final result is 3.

The answer to the random challenge is the result of step 3, so the answer is 3.

All of this work provides a single value from 1-6. That's trivial to brute force (just try 1, then 2, then 3, then 4, then 5, then 6... you're guaranteed to get one of those guesses right and by default have a 1/6 chance of guessing correctly). To prevent brute forcing, multiple random challenges are needed. The strength against brute forcing can be calculates as d^(challenges), where d is the base, and challenges are the number of challenges. For example, using base 6, as I have so far, and a 12 character authentication code (so 12 challenges) means a polynomial time attacker would need to guess 6^12, or 2176782336, authentication codes to guarantee they authenticate. There would be a 50% chance of guessing correctly within 1088391168 guesses in this case.

As long as the server changes up the challenges often enough, it would not be practical for a polynomial time attacker to guess the full authentication code.

Below is an example of 12 random challenges and their answers to produce a 12 character authentication code with the shared secret of "i/c8-AEebgLsz+H2YFr6ph73^mdfk4,a?ow":

- 36EmFpfpdp2-8fY4 = 3
- pHp4Y2-4a6/,4/e, = 1
- -E/kg8o7g2FLLH3z = 5
- 7niFH,Ygo^Ez4rz6 = 6
- dYFdhd-sw6dnk-w^ = 6
- /gkLshkzb,Hbpm+h = 5
- 4b4g-6egH+/4/-Fd = 2
- p4cdk3eiFEYHYo6g = 3
- ^End^wiLi/Hrm2gh = 3
- ?HiFdararg7mnb^L = 2
- gw,7L7zb,mY-pY/3 = 3
- 684^h-ezn-4H6bFd = 4

For this example, the authentication code would be "315665233234". I could either use "315665233234" or convert it via bonewords to "o7?ips", although converting it would likely take more mental capacity for me.

While I can't guarantee there isn't some weakness in this scheme, I can say that while running a few million random simulations, I get each result from 1 to 6 at virtually equal rates. The results seem heavily dependent on the randomness of the challenges.

As long as the paper is correct in the scheme's security and as long as I have a randomly generated 36 character shared secret, the amount of correct challenge answers needed to derive the shared secret will be dependent on the lengths of k1 and k2. In the case of the length of k1 being 3 and k2 being 7, I would need to correctly answer 1679616 challenges before my shared secret is essentially spent. With 12 character authentication codes, it would take approximately 1088391168 guesses to brute force the 12 character authentication code. Also with 12 character authentication codes, I divide the 167916 challenges value by 12, which means in my case I would be able to edit or add to my site 139968 times before my shared secret is essentially spent.

These numbers are plenty big enough for my needs.

Here is a quick python script I wrote to generate 12 random challenges with the shared secret "i/c8-AEebgLsz+H2YFr6ph73^mdfk4,a?own":

```
import secrets
shared_secret = "i/c8-AEebgLsz+H2YFr6ph73^mdfk4,a?own"
auth_len = 12
auth = ""
k1_len = 3
k2_len = 7
for _ in range(auth_len):
challenge = ""
# Create challenge
for _ in range(6 + k1_len + k2_len):
challenge += shared_secret[secrets.randbelow(36)]
step_1_val = 0
for k1_val in challenge[6: 6 + k1_len]:
# We add 1 to make value 1-indexed
step_1_val += shared_secret.index(k1_val) + 1
# Step 1 answer mod 6
step_1_val %= 6
# if step 1 value is 0, use 6 instead
if step_1_val == 0:
step_1_val = 6
# Python is 0-indexed, so we subtract 1 to not overflow index
step_2_char = challenge[step_1_val - 1]
# We add 1 to make value 1-indexed
step_2_val = shared_secret.index(step_2_char) + 1
# Step 2 answer mod 6
step_2_val %= 6
# if step 2 value is 0, use 6 instead
if step_2_val == 0:
step_2_val = 6
step_3_val = step_2_val
for k2_val in challenge[6 + k1_len:]:
# We add 1 to make value 1-indexed
step_3_val += shared_secret.index(k2_val) + 1
# Step 1 answer mod 6
step_3_val %= 6
# if step 3 value is 0, use 6 instead
if step_3_val == 0:
step_3_val = 6
answer = step_3_val
# Print challenge and answer
print("%s = %d" % (challenge, step_3_val))
auth += str(step_3_val)
# Print final authentication code
print("Auth Code: %s" % auth)
```

]]>2024-06-08 - [53] 3:20

Recently, "Garage: Bad Dream Adventure" came out on the Nintendo Switch eShop for $24.99 USD, as opposed to its $19.90 USD price on steam or $4.99 USD price on the Apple app store and Google Play. Considering how generally niche this game is, I suspect it will go on sale at some point on the Nintendo eShop. I wanted to write a quick program that can scrape the page on nintendo.com to see if a sale is happening and how long the sale is going on for. Not just for this game, but potentially for other games I might have my eye on as well. Building that web page scraper was relatively straight forward for me, but I needed to solve another problem... How do I get that information in front of me so I am made aware of it?

There are a few things I check on my computer nearly every day, and sometimes multiple times a day. One of those things would be email. Initially I wanted to have my program email me when it detects specific sales, but I didn't really feel like dealing with spam filters to make sure the emails come through for me to see. Another thing I check often enough is my RSS feed reader. That's what I decided to build these notifications for.

Turns out it's really easy for the RSS feed reader I use, newsboat, to load a local RSS feed file. Newsboat also has good RSS feed item caching, where it uses the value of guid to determine if a new RSS feed item is available so newsboat can tell me that there's a new RSS feed item. I use the SHA256 hash of the RSS feed item description (the content) as for the guid value, which means newsboat will only show me that there's a new feed item when it hasn't seen that exact description before.

There are only 2 cases where the guid will match a different RSS feed item in the newsboat cache:

- There are no deals
- The current deal is the same as the last time newsboat checked the RSS feed

In both of these cases, I'm not alerted more than once by newsboat, as long as I don't clear the newsboat cache. This means I can rewrite the RSS feed file as many times as I want. I also only need to use a single RSS feed item for the feed file.

I'm pretty sure I will end up using this idea again of having a local RSS feed file for notifications I should see in the future.

Below is an example RSS feed file created by my Nintendo eShop scraping program:

```
<?xml version="1.0" encoding="utf-8" ?>
<rss version="2.0">
<channel>
<title>Nintendo eShop Sales</title>
<link>http://localhost</link>
<description>Self-generated RSS feed to show Nintendo eShop sales</description>
<item>
<title>Current Nintendo eShop Sales</title>
<link>http://localhost</link>
<guid>4d06bc884c45a764cafe019706d9a509aab59fa1dc79d51aee4e07e0fa7e1a22</guid>
<pubDate>Sat, 08 Jun 2024 02:28:47 +0000</pubDate>
<description><![CDATA[<h1>Current Nintendo eShop Sales</h1>
<h2>Mysterious Retro Games Bundle</h2>
<p>Current Price: $1.99 USD (80% Off)
<br>
Normal Price: $9.99 USD
<br>
Sale Ends: Jun 24, 2024 06:59 UTC
<br>
<a href="https://www.nintendo.com/us/store/products/mysterious-retro-games-bundle-switch/" target="_blank">eShop Page</a></p>
<h2>The Last Campfire</h2>
<p>Current Price: $1.99 USD (86% Off)
<br>
Normal Price: $14.99 USD
<br>
Sale Ends: Jun 11, 2024 06:59 UTC
<br>
<a href="https://www.nintendo.com/us/store/products/the-last-campfire-switch/" target="_blank">eShop Page</a></p>
]]></description>
</item>
</channel>
</rss>
```

]]>2024-04-29 - [53] 2:10

I changed up Git, Gopher, and Finger support on my site to heavily simplify my personal use code base. These changes are:

- Removed HTTPS support from Git server
- Removed Gopher server support from Bergelmir
- Removed Finger server support from Bergelmir
- Removed text.vigrey.com

This is for 2 reasons. The first reason is because I would prefer not to use nginx anymore (I might make a replacement for my needs or I may just decide that I'm fine without a proxy). The second reason is because I'd prefer to not have to rely on the git-http-backend cgi script. At the moment, I don't understand fastcgi or its parameters or cgi in general. As such, I'd prefer not to use it.

This means the way to fetch/clone/pull/etc my git repositories is to use git://vigrey.com instead of https://vigrey.com as the source.

There are going to be some negative consequences to this change. I understand that these issues will happen and apologize for them happening. Some of these consequences will be:

- Git fetching from go toolchain will break for vigrey.com imports
- All git connections will be unencrypted

Even though those consequences will happen, I have made peace with the fact that those consequences will happen.

I feel a bit bad about this in particular. I'm a Minnesotan and want to generally support the Gopher protocol because it was made in Minnesota. With that said, removing support for it in Bergelmir will simplify Bergelmir quite a bit. It will also simplify a few other programs a bit to be able to remove gopher support on them. I was only serving static content from the gopher server. Getting rid of the server will save quite a bit of storage space.

My site is first and foremost a gemini capsule. Writing gemtext files has been much nicer for me than writing html. I'm fine with being able to serve over HTTP as well, cosidering the web is what people know.

The simplicity of the finger protocol interested me quite a bit. It was also nice to have a "miniblog" system instead of needing to write a full-sized blog post or microblog post on a platform like Mastodon or Twitter. I will still keep my finger log archive up.

Removing finger support will help simplify Bergelmir.

I quite like the idea of my site not loading any other resources for any page requested. My base site might end up adopting that concept, but no promises one way or the other. Due to how I want the http server to only load static content, I need to generate a lot of html files for the text.vigrey.com and vigrey.com versions of my site. I'd prefer to lower my storage requirements by not having to generate the text.vigrey.com content.

]]>2024-04-05 - [53] 1:16

Back in 2017, I learned how to convert coin flips from a biased coin into fair coin flips using a process created by John Von Neumann to debias random binary data. For about 7 years, I tried to figure out if there was a way to expand that to biased dice of sizes larger than 2. I didn't end up figuring out a direct method, but while trying to figure out how to program a shuffler for a deck of cards on the Nintendo Entertainment System in 6502 Assembly, I learned how to convert random bits to random numbers within arbitrary ranges. This allowed me to find a solution to convert rolls of a biased dice of one base to an unbiased dice of another base. This probably isn't the optimal solution, but it is one that is simple enough for me to calculate with pencil and paper or even often times in my head.

You are given a weirdly shaped and weighted 6-sided dice. All rolls from that dice are independent from each other, as you would expect from a dice, but the results are incredibly biased towards particular values and against other values. You don't know exactly how biased the dice is, but you can roll the dice as many times as you want. Using only results of the biased 6-sided dice, simulate a roll from a fair 20-sided dice.

We have an N-sided dice that is able to roll at least 2 results, although we don't know what at what rate. These results MUST be independent of each other. The same N-sided dice MUST be rolled each time. Dice rolls MUST NOT influence future dice rolls. We need to emulate an X-sided dice where X is at least 2.

First we take the input dice and convert it into a potentially biased coin. How do we do that? Roll the dice until you get 2 different results, then pick one of the results you got. That can be your "Heads" result. The reason we wait until you get 2 different results is so you know more than 1 result is possible on the input dice. You now have a likely biased coin. For instance, if you rolled the 6-sided dice 4 times and got the results of 1, 1, 1, 4, then you can pick either 1 or 4 as your "Heads" result. For this example, let's pick 1 as the "Heads" result. Now any time you roll a 1, think "Heads" and any time you roll something else, think "Tails".

For this step, we use the Von Neumann debiasing step to "coin flips". Do 2 "coin flips" with the dice as explained above. You now have 4 possible results, which are "Heads Heads", "Heads Tails", "Tails Heads", and "Tails Tails". If your 2 results are the same, throw out the results and do 2 more "coin flips". Keep doing the 2 "coin flips" until you get 2 different results. At this point, you'll have either "Heads Tails" or "Tails Heads". The result for the debiased coin flip will be whatever flip happened first of the 2 flip set. If you got "Heads Tails", then "Heads" is the debiased result, otherwise if you got "Tails Heads", then "Tails" is the debiased result. The important thing is these 2 "coin flips" have to be seperate from every other 2 "coin flips", so no sharing any flip results among the sets of 2 flips. If you don't get 2 different flip results you MUST discard the 2 flip results and do another 2 flip results.

Before explaining this step, I wanted to mention that I am currently unable to find the source document with this algorithm, but I believe it was published by Donald Knuth and Andrew Yao in 1976. Some variables will be different than the source algorithm, but the logic will be functionally the same.

For this algorithm, you will need 2 variables, A and B. You will also need to know the amount of sides for your output dice roll, which in the scenario near the top of this post is 20. Set A to 1 and B to 0.

```
A = 1
B = 0
```

We will also have a function called "flip()" that will perform a debiased coin flip from the biased input dice, as explained in steps 1 and 2. If flip() results in "Heads", then flip() will equal 0, otherwise if flip() results in "Tails", then flip() will equal 1. In summary, a fair heads = 0 and a fair tails = 1.

```
while true {
A = 2 * A;
B = 2 * B + flip();
if (A >= sides) {
if (B < sides) {
break;
}
A = A - sides;
B = B - sides;
}
}
return B;
```

B will ALWAYS be a smaller value than A throughout this loop. B will also be a random value that has equal chances of being any value from 0 to (but not including) A throughout this loop. When A becomes the same size or larger than the amount of sides of the output dice, B will either be smaller than the amount of sides or it won't be. If B is smaller than the amount of sides, then it has an equal chance of being any value from 0 to (but not including) the amount of sides. If B is not smaller than the amount of sides, then B overshot the amount of sides by a value from 0 to (but not including) A minus the amount of sides, where each value of that is equally as likely. That overshoot is applied to B by subtracting the amount of sides from B and also by subtracting the amount of sides from A. This loop ends when A is greater than or equal to the aount of sides AND B is less than the amount of sides. The end result is the value B, which as explained a few sentences ago, has an equal chance of being any value from 0 to (but not including) the amount of sides. This algorithm is most efficient with the amount of sides is a power of 2, like 2, 4, 8, 16, 32, 64, etc...

This algorithm will generate a uniform number from 0 up to (but not including) the number of sides of the fair output dice, so to get a dice roll from 1 to (and including) the number of sides, just add 1 to the result. That means if you get a 0 from the algorithm, the 20-sided dice roll would be 1, and if you get 17 from the algorithm, the 20-sided dice roll would be 18.

Put together, the dice rolling algorithm is as follows:

```
function roll(sides int) {
A = 1;
B = 0;
while true {
A = 2 * A;
B = 2 * B + flip();
if (A >= sides) {
if (B < sides) {
break;
}
A = A - sides;
B = B - sides;
}
}
return B;
}
```

There are likely better ways to convert a biased dice into an unbiased dice than what I wrote here. Part of a better solution is to pick the "Heads" and "Tails" values of step 2 more equally by finding combinations of dice rolls that are just as likely as each other, for instance, deciding that 1 or 5 is "Heads" and 2, 3, 4, and 6 is "Tails" for a biased dice that rolls either 1 or 5 about half of the time compared to any other number. The important part is that you can convert a dice of size N to a simple "True" or "False" statement that is independent from previous or future rolls and is mutually exclusive. In this example, a dice roll can't be "1 or 5" AND "2, 3, 4, or 6" at the same time and due to the rules of the input dice needing to not be influenced by previous rolls, we are able to use this "True" or "False" statement as a means to get a biased coin flip result.

If the input dice is fair (as in the results are not influenced by previous rolls and all values are just as likely as all other values for any roll), you can just skip step 2 entirely.

I appreciate that this method is simple enough computationally do to with pencil and paper and without a computer. When I go out in general, I tend to bring a coin with me to use as a 2-sided dice for whenever I need to generate random numbers for whatever reason.

Something important to mention is that my definition for the input dice is flawed. I mention that the same input dice MUST be rolled each time but each roll MUST NOT be influenced by previous rolls. Unfortunately we live in the physical world where matter and chemistry impact other matter over time, so each dice roll will end up shaping the dice (flattening, chipping, etc...) in incredibly small ways that will change the result rates of the dice over time. We just ignore that and assume the dice is consistent every time. We basically handwave away the complications and assume the input dice is a "sphearicle cow in a vacuum".

[HTTPS] Sphearical Cow - Wikipedia[HTTPS]Because the dice changes in the physical world over time, I have decided that a little bit of wiggle room is fine in the case where I need to generate a random number but I don't have a coin or dice. The method I use if I don't have a coin or dice is looking at a poster or thing with words on it, then starting from the first word and counting the amount of vowels in a word as a coin flip. If the word has an even number of vowels, the result is "Heads", and if the result has an odd number of vowels, the result is "Tails". I assume the even/odd parity of vowels is correlated to the length of the word and that the length of the word is probably correlated to the length of the previous word or words, but I also assume it doesn't make enough of an impact to matter much, especially when I do the Von Neumann debiasing step. I'm essentially doing a manual hashing algorithm to the text and using the resulting hash as a random number within a given range.

]]>Znal bs lbh jub jrer fghpx ernqvat "Gur Gentrql bs Whyvhf Pnrfne" ol Jvyyvnz Funxrfcrner va fpubby jvyy erzrzore gur cuenfr "Orjner gur Vqrf bs Znepu", juvpu vf gur svsgrragu qnl bs Znepu. Fbzr sbyxf bhg gurer jvyy pbzzrzbengr/pryroengr gur qnl ol cbfgvat cvpgherf bs n ohapu bs xavirf fgnoorq guebhtu n Pnrfne fnynq qerffvat obggyr be n objy bs Pnrfne fnynq (nccneragyl Pnrfne fnynq vf anzrq nsgre fbzrbar jvgu gur svefg anzr "Pnrfne", engure guna Whyvhf Pnrfne), be cvpgherf bs n crapvy ubyqre gung ybbxf yvxr n ohfg bs Whyvhf Pnrfne jvgu gur ubyrf sbe gur crapvyf va gur ohfg'f onpx, jurer Whyvhf Pnrfne tbg fgnoorq gb qrngu (gung fragrapr fher ena ba sbe n juvyr).

Lbh zvtug abgvpr gung V'z pbzzrzbengvat gur Vqrf bs Znepu ba gur gjragl-rvtugu bs Znepu vafgrnq bs gur svsgrragu bs Znepu. Lbh nyfb zvtug abgvpr guvf cbfg vf hfvat abg hfvat gur pbeerpg yrggref (ubcrshyyl lbh pna ernq guvf fbzrubj). Jryy, guvf cbfg bs Pnrfnef pbzrf va guerr cnegf!

- Pnrfne Pvcure
- Pbzzrzbengvba bs gur Vqrf bs Znepu (gur qnl Whyvhf Pnrfne jnf nffnffvangrq)
- Gur Whyvna Pnyraqne

Vs lbh'er ernqvat guvf abj, lbh nyernql xabj ubj gur Pnrfne Pvcure jbexf. Whyvhf Pnrfne jbhyq jevgr frpergf ol genafcbfvat yrggref sbejneq n pregnva nzbhag bs yrggref, fb vs gur ahzore jnf guerr, gura N -> Q, O -> R, P -> S, ..., K -> N, L -> O, M -> P. Gb qrpelcg gur zrffntr, lbh jbhyq whfg genafpevor gur yrggref onpxjneqf gung fnzr nzbhag bs yrggref, fb N -> K, O -> L, M -> P, Q -> N, R -> O, S -> P. Guvf cbfg vf rapelcgrq hfvat EBG13. Orvat rknpgyl unys bs gur gjragl-fvk pbhag bs gur nycunorg, lbh pna hfr EBG13 gjb gvzrf va n ebj gb trg n zrffntr onpx gb jung vg bevtvanyyl jnf. Gur rapelcgvba naq qrpelcgvba zrgubq vf rknpgyl gur fnzr va EBG13.

Pbafvqrevat yrggref jvyy NYJNLF genafsbez vagb gur fnzr yrggref jvgu gur fnzr xrl (rknzcyr: N jvyy NYJNLF genafsbez gb O jvgu n xrl bs 1) naq gur genafsbezrq yrggref ergnva gur fnzr yrggre serdhrapl nf gur bevtvany yrggre, vg'f gevivny gb qrpelcg zrffntrf gung hfr gur Pnrfne Pvcure. Lbh nyfb bayl arrq gb nggrzcg qrpelcgvat gur zrffntr hc gb 26 gvzrf gb oehgr sbepr gur xrl naq trg gur bevtvany zrffntr. Nyfb orpnhfr rirel yrggre hfrf gur fnzr xrl, ybat zrffntrf ner whfg nf rnfl gb oehgr sbepr gur bevtvany zrffntr nf fubeg zrffntrf ner. QB ABG HFR GUR PNRFNE PVCURE SBE NALGUVAT BGURE GUNA SHA! JR BAYL NPPRCG SHA SBE GUVF PBBY PVCURE!

Gb xrrc guvatf cerggl fubeg, Whyvhf Pnrfne jnf nffnffvangrq ba gur svsgrragu qnl bs Znepu, n qnl xabja nf gur Vqrf bs Znepu. Ur jnf nffnffvangrq ol gur Ebzna Frangr ivn fgnoovat gung qnl va gur lrne 44 OP, fubegyl nsgre tnvavat gur gvgyr "qvpgngbe crecrghn" (qvfpgngbe va crecrghvgl). Juvyr V qba'g xabj ubj zhpu guvf vzcnpgrq gur Frangbef jub nffnffvangrq Pnrfne, Pnrfne nccrnerq nf gubhtu ur jnf syvegvat jvgu gur vqrn bs orvat Xvat (jurgure va anzr nf jryy be va nyy ohg anzr). Whyvhf Pnrfne jnf fgnoorq 23 gvzrf, nygubhtu znal bs gur fgno jbhaqf jrer vasyvpgrq nsgre Pnrfne jnf qrnq, vs abg zbfg bs gur fgno jbhaqf.

V unir jevggra nobhg gur Whyvna Pnyraqne n srj gvzrf orsber ba zl fvgr. Engure guna arrqvat gb or cerfrag va Ebzr gb fnl vs na gjragl-vfu qnlf arrqrq gb or nqqrq gb gur pnyraqne gb nyvta gur pnyraqne gb gur frnfbaf, Pnrfne raqrq hc univat na nhgbzngrq flfgrz perngrq. Gur pnyraqne jbhyq or yratgurarq sebz 355 qnlf gb 365 qnlf naq vafgrnq bs arrqvat gur rkgen cnegvny zbagu nqqrq (juvpu arrqrq gb or naabhaprq naq jnfa'g frg nf n cnggrea), na rkgen qnl jbhyq or nqqrq gb gur lrne rirel sbhe lrnef. Gung znqr na nirentr lrne 365.25 qnlf ybat. Gung flfgrz nyfb nyybjrq Pnrfne gb abg unir gb or culfvpnyyl ng Ebzr gb naabhapr gur rkgen qnlf gb gur lrne, fb ur pbhyq fcraq uvf gvzr va onggyr yvxr ur jnf qbvat sbe nobhg n qrpnqr orsber.

Gur pnyraqne flfgrz jr hfr abj fyvtugyl zbqvsvrf gur Whyvna Pnyraqne, ol erzbivat guerr yrnc lrnef qhevat 400 lrne crevbqf bs gvzr gb zber pybfryl nyvta gur lrne gb gur frnfbaf. Gung pnyraqne flfgrz vf gur Tertbevna Pnyraqne naq vf pheeragyl guvegrra qnlf nurnq bs gur Whyvna Pnyraqne. Pbafvqrevat Whyvhf Pnrfne vzcyrzragrq gur Whyvna Pnyraqne naq vg jnf gur pnyraqne flfgrz gung jnf va rssrpg ng gur gvzr bs uvf nffnffvangvba, V pbzzrzbengr gur Vqrf bs Znepu ba gur gjragl-rvtug bs Znepu, nf vg jbhyq or gur svsgrragu bs Znepu vs jr jrer fgvyy hfvat gur Whyvna Pnyraqne.

Bu, naq na vagrerfgvat snpg: Qhr gb Rnfgre orvat qrsvarq nf gur svefg Fhaqnl nsgre gur svefg shyy zbba ba be nsgre Znepu gjragl-svefg, naq orpnhfr gur shyy zbba jnf ba Znepu gjragu-sbhegu/svsgu ba gur Tertbevna Pnyraqne, Rnfgre vf na ragver zbba plpyr rneyvre sbe gubfr jub hfr gur Tertbevna Pnyraqne gb qrgrezvar Rnfgre guna vg vf sbe gubfr jub hfr gur Whyvna Pnyraqne gb qrgrezvar Rnfgre. Sbe gubfr jub hfr gur Tertbevna Pnyraqne sbe qrgrezvavat Puevfgvna ubyvqnlf, Rnfgre vf guvf Fhaqnl (Znepu guvegl-svefg) guvf lrne, naq sbe gubfr jub hfr gur Whyvna Pnyraqne sbe qrgrezvavat Puevfgvna ubyvqnlf, Rnfgre vf ba gurve Fhaqnl, Ncevy gjragl-frpbaq, juvpu vf gur Tertbevna Fhaqnl, Znl svsgu.

]]>2024-03-21 - [53] 1:1

In the end of February 2022, I decided to start working on a Game Boy homebrew game project. That project was a tarot card reader for the Game Boy. The complicated part of that project though is that I need the tarot card designs to be converted to something the Game Boy can use, so I have slowly been working on those designs since.

At the beginning of 2024, I only had 8 designs done out of the 78 tarot cards. One of my 2024 new year resolutions was to try and do 1 card design each week in 2024, which would get my total up to 60 or 61 at the end of the year out of 78. I have been blowing past that rate though! This is the 14th week of 2024 and I have done 31 designs this year. That means I'm at 39 out of 78, which is halfway through! I can now comfortably do only 1 a week if I wanted to and be done by the Winter Solstice.

These Game Boy tarot card designs are the Rider Waite cards designed by Pamela Colman Smith. The Rider Waite deck designs were first published around 1909 and are in the public domain, so I decided to use those designs. Creative liberties had to be used to shrink the card designs down to 67 by 116 pixels of only 4 colors. The gameboy medium of 2-bits of color (4 shades of green) and a screen resolution of 160 by 144 pixels certainly limits what I can do with these designs.

If you are following my Mastodon account (@vi@mastodon.social, otherwise you can search @vi@vigrey.com on your fediverse instance), you will likely have already seen me post the cards as they are finished.

All of the cards I have finished so far can be found at the following link. All 22 of the Major Arcana cards have been finished and now I'm just working on the Minor Arcana cards. I hope you like what I have finished so far.

Game Boy Tarot CardsMy overall plan is to get physical Game Boy tarot card reading cartridges made and sold along with some physical print cards with these designs. The Game Boy cartridge mapper I plan to use is the same Wisdom Tree mapper, which was used for quite a few unlicensed Game Boy games that were usually religious in nature. The mapper has already been implemented in quite a few Game Boy emulators and is considerably easier to get all of the chips for compared to finding clones of the official Nintendo mapper chips for the Game Boy.

]]>2024-03-20 - [52] 12:36

A little bit more than a year ago I came up with a calendar system called the "Limerick Calendar" which has 5 day weeks instead of 7 day weeks. It's called the Limerick Calendar because limericks are poems that have 5 lines to them. Considering the year is usually 365 days long, this means we can split the year into 73 weeks. 73 is just 1 week shy of a multiple of 12, which means we can have months that are almost exactly the same length with the exception of 1 extra week that needs to be inserted at some point.

This system turns out to be pretty similar to the Paratheo-Anametamystikhood of Eris Esoteric (POEE) Calendar, which is described on page 00034 of the "Principia Discordia". My calendar has a few different rules, especially for leap year and the start of the year.

I never ended up writing about this system in a journal post like I wanted to on March 21st of 2023, so I'm trying to write one up quickly now in the last few moments of the day. This has been the first full year of the Limerick Calendar system since implementation to complete, so I want to celebrate! Today's date is [52] 12:36, which can also be represented as [52] 366. The day can also be represented with 2 Jokers side to side, which will be explained a little bit in the next section but also in greater detail in the wiki page I link below. The rules for the Limerick Calendar system are in the section after.

I did write about this system as a wiki page though, which is linked directly below:

{limerick calendar} Wiki pageThe cards of a French-suited deck of playing cards are Ace, 2 through 10 (including 10), Jack, Queen, and King of 4 different suits each, which are Clubs, Hearts, Spades, and Diamonds. This totals 4 suits of 13 cards each, or 52 cards in total. If you take the value of Ace as the number 1, the value of Jack as 11, Queen as 12, and King as 13, then add up the values of all of the cards in the deck, you will get 364 as the sum. Decks tend to come with 2 Jokers as well. Counting each Joker as 1, you can use a single Joker to bring the value to 365, the length of a non-leap year, or both Jokers to bring the value to 366, the length of a leap year.

I use this fact to my advantage to be able to represent the day of the year as 2 playing cards.

The following rules describe the calendar system.

- Weeks have 5 days each

- Months 1 through 11 (including 11) have 6 weeks each, totalling 30 days for each month
- Month 12 has 7 weeks each, totalling 35 days in that month
- If the year is a leap year, the 12th month gets 1 more day at the end of week 7, making a total of 6 days that week and 36 days total in that month

- Leap years occur on every Limerick Calendar year that is divisible by 4 but not by 128
- The first day of the first month of year [1] in the Limerick Calendar (written as "[1] 1:1" or "[1] 1") occurred on March 21st, 1972 in the Gregorian Calendar
- The year before year [1] is year [0] and negative years precede year [0]

I set year [1] in the Limerick Calendar as year 1972 in the Gregorian Calendar so February 28th, 2100 in the Gregorian Calendar would be in within year [128] in the Limerick Calendar. This provides the longest period into the future of the 2 calendar systems having the same leap years, even if it does misalign the calendar systems before that point, considering February 29th, 1972 in the Gregorian calendar would be within year [0] in the Limerick Calendar, but year [0] in the Limerick Calendar only has 365 days in it.

I'm a calendar nerd. Of course I would make my own calendar system! If you look at all of my journal posts, you will notice the Limerick Calendar date under the post titles right next to the Gregorian Calendar date, so I do actually use this system.

The new year quickly approaches, [53] 1:1. The 53rd card in a deck of French-suited playing cards is the first joker, so I am naming year [53] the "Year of the First Joker". March 21st, 2024 is also 1:1 [53], which is also 1 [53], which is also the 2 cards "[53] Joker + Ace of Clubs" in the Northern Hemisphere or "[53] Ace of Spades + Joker" in the Southern Hemisphere.

]]>