Notes
Development logs.
Most of them are about measuring before fixing.
Written from real work records (commits and numbers measured at hand) while building browser games and an iPhone app. Each article is the English version of a Japanese original; the Japanese one is linked at the end. 43 articles. RSS feed.
Read by theme
Collecting and matching data
Crawling for trading-card lottery information and linking it to stores and products: misses, mix-ups, and things that broke without an error.
- Seven records of cutting misses in crawling and monitoring: pagination, X monitoring, measuring coverage and waiting rooms
- Four store-matching mistakes in a TCG lottery app: spelling variants, vague map lookups, shared entry pages, and one page with many products
- Eight product-matching failures in a TCG lottery app: English vs katakana, splitting sets, official catalogs and spreading typos
- Seven silent failures: real lotteries discarded, pages never read, and errors swallowed without a trace
Operations and costs
Daily jobs and servers, AI and database costs, and repairing data: measuring and fixing systems that keep running.
- Nine things that happened running scheduled jobs and a server: timeouts, time budgets, re-runs on deploy and a VPS migration
- Five records of measuring, then cutting, AI and Firestore costs: a broken Gemini meter, 91% of reads in one place, and crawl costs
- Seven lessons from repairing bad data: existing records, bulk-job accidents, counting duplicates and the notification gate
iOS apps and the App Store
Review, builds, freezes, crashes and buttons that would not respond: what actually happened to an iPhone app.
- Where App Store review stopped me: “Guideline 2.1 Information Needed” on the first submission, and four rejections
- ITMS-91064 “Invalid Binary”: NSPrivacyTracking was true but NSPrivacyTrackingDomains was empty
- Firebase 12 does not work with Xcode 15.2 (Swift 6.0 required): why I pinned Firebase 11.4.0 and AdMob 11.5.0
- iOS app freezes and is killed (0x8BADF00D): a SwiftUI computed property ran on every redraw
- Firebase Auth: force-quitting the app created anonymous accounts in bulk (waiting for currentUser to restore)
- The same report for the third time: a SwiftUI button responded only on its text, and my two earlier “fixes” did nothing
- A function named “load from iCloud if newer” compared nothing: a 20-record history could become 1
- With no ads, 25 real-device checks were blocked: test ads in developer mode only, and no hard-coded prize names
- No builds arriving in TestFlight: I had used up the 25 free Xcode Cloud hours (my first diagnosis was wrong)
Game design, measured
Balancing decided by AI-vs-AI matches and automated play, and how bullet-hell patterns are made.
- How bullet-hell patterns work: ring, aimed, rain, spiral and homing, with formulas and live demos
- Setting the rules of Negaeri Shogi by playing AI against AI: one change that worked, six that broke
- Online play that trusts nothing the client says: the server decides moves, clocks and results
- The conger eel was too big: finding why a merge puzzle’s last piece was out of reach
- “The pile settles at 89%” was wrong: my measurement was what was wrong
- Making a 10-second stopwatch ranking fair: 3 decimals, ties and write counts
- What changes when an anime quiz goes from 50 questions to 12?
Compared from official sources
Prices, terms and limits compared using only official information, with what could not be confirmed.
- Japanese Pokémon card & TCG lottery apps compared: Toreca Dash, Toreget, NowPokeca, Pokeka Tracker and Toreca no Chizu
- Comparing static site hosts by their official terms: Cloudflare Pages, Netlify, Vercel, GitHub Pages and Firebase Hosting
- Claude API vs Gemini API by official numbers: small-model prices, web search, spend limits and deprecation
- Comparing iOS ad SDKs and Firebase by their official information: AdMob, AppLovin MAX, LevelPlay and Supabase
- Comparing comment systems for static sites by their official information: giscus, utterances, Disqus, Remark42 and more
All articles, newest first
-
How bullet-hell patterns work: ring, aimed, rain, spiral and homing, with formulas and live demos
The exact math behind ring, aimed (odd vs even), rain, spiral and homing bullet-hell patterns, from a real danmaku game, with live demos.
-
Sushi Stack scores stopped reaching the leaderboard: I changed the game’s numbers and forgot the server’s re-check (twice)
Honest runs of a browser game stopped reaching the online leaderboard: two game values changed without updating the server that re-checks scores. The fix, and a test that compares both sides with the same seeds.
-
Japanese Pokémon card & TCG lottery apps compared: Toreca Dash, Toreget, NowPokeca, Pokeka Tracker and Toreca no Chizu
Five apps and sites that track Pokémon card and TCG lotteries, pre-orders and restocks at stores in Japan, compared on games, alerts, maps and price — by the developer of one of them, weak points included.
-
The same report for the third time: a SwiftUI button responded only on its text, and my two earlier “fixes” did nothing
The “Won” and “Lost” buttons responded only on the text. Two earlier fixes (modifier order) did not work; I built the look inside a ButtonStyle and replaced six places. A mail-order article with coordinates had the same kind of root.
-
A function named “load from iCloud if newer” compared nothing: a 20-record history could become 1
The iCloud sync function never checked whether data was newer and replaced everything. A fresh device with an empty state could overwrite another device’s 20 records with 1. I added update-time comparison and never overwrite with empty.
-
With no ads, 25 real-device checks were blocked: test ads in developer mode only, and no hard-coded prize names
Before ads were served, 25 of 163 real-device checklist items could not be verified. In developer mode only, the app switches to Google’s test ad units. Prize names are read from one setting and never hard-coded, including in the checklist.
-
Online play that trusts nothing the client says: the server decides moves, clocks and results
Online play without login. Moves are validated by the rules engine, the clock lives only on the server, a disconnect gets 30 seconds, tokens are signed with HMAC, and ratings use pre-match numbers. With real values from the server code.
-
Setting the rules of Negaeri Shogi by playing AI against AI: one change that worked, six that broke
A 6×6 board game where sandwiched pieces defect. Instead of guessing the numbers, I ran 40 to 100 AI-vs-AI games per rule. Flips per move went from 2% to 81%, removing the king guard ended games in 17 moves, and the first-move edge is still unsolved.
-
Four store-matching mistakes in a TCG lottery app: spelling variants, vague map lookups, shared entry pages, and one page with many products
Four real cases where matching store names went wrong in a trading-card lottery app: spelling variants, a map lookup that returned Shizuoka, one entry page used by different stores, and one form for six products.
-
Seven records of cutting misses in crawling and monitoring: pagination, X monitoring, measuring coverage and waiting rooms
Seven records of counting and cutting misses while crawling and monitoring lottery and first-come sales: aggregator pagination, gaps in X monitoring, how coverage was measured, intervals vs delivery, and waiting-room pages.
-
Nine things that happened running scheduled jobs and a server: timeouts, time budgets, re-runs on deploy and a VPS migration
Nine records from the jobs that run hourly and daily, and the server under them: moving from Cloud Functions to a VPS, a timeout fixed three times, checks that never ran, re-runs on every deploy, and secrets in logs.
-
Pokémon card sleeves and loaders: sizes and what “pt” means
What to check before buying Pokémon card sleeves, toploaders and deck boxes: standard vs small size, what 35pt means, and a buying checklist. Not a hands-on review; unverified points are listed.
-
ITMS-91064 “Invalid Binary”: NSPrivacyTracking was true but NSPrivacyTrackingDomains was empty
An iOS build was rejected by Apple's automated review with ITMS-91064. NSPrivacyTracking was true while NSPrivacyTrackingDomains was an empty array. How it was fixed, and what was not verified.
-
Firebase 12 does not work with Xcode 15.2 (Swift 6.0 required): why I pinned Firebase 11.4.0 and AdMob 11.5.0
On an old Mac stuck on Xcode 15.2, newer Firebase and AdMob SDKs fail to build. Which versions break, which I pinned (Firebase 11.4.0, AdMob 11.5.0), and why.
-
iOS app freezes and is killed (0x8BADF00D): a SwiftUI computed property ran on every redraw
An iOS app was force-quit by the watchdog (0x8BADF00D). A SwiftUI computed property re-filtered and re-sorted 951 articles on the main thread every body evaluation.
-
No builds arriving in TestFlight: I had used up the 25 free Xcode Cloud hours (my first diagnosis was wrong)
New builds stopped appearing in TestFlight. My first diagnosis (“[ci skip] is blocking builds”) was wrong; the workflow built the iOS app on every commit.
-
Firebase Auth: force-quitting the app created anonymous accounts in bulk (waiting for currentUser to restore)
Force-quitting the app made Firebase create a new anonymous account each launch: 17 of 19 users were throwaway. Cause: reading currentUser too early.
-
A canvas becomes twice as large on Retina: inset:0 does not stretch it (a bug in a logo that bursts when you tap it)
Burst particles appeared in the wrong place on high-DPI screens. A canvas with only inset:0 keeps its pixel size, so it was 2x larger in CSS on Retina.
-
Making a 10-second stopwatch ranking fair: 3 decimals, ties and write counts
Why I truncate to three decimals so coarse clocks get no advantage, how ties (a 1/333 chance) are broken, and how I counted KV writes on the free tier.
-
From 16,177 titles to 6,091: how the anime quiz database was built by dropping over 60%
An anime quiz picks from about 6,000 titles. Where they came from, the criterion that cut 62.3% of 16,177, what remains, and the biases of the method.
-
“80% was chosen over 82%”: the quiz’s match percentage disagreed with its ranking
An anime quiz’s match percentage used a different yardstick from its ranking. With the old display, 35 to 40% of 1,000 synthetic respondents saw a reversed top 3.
-
The streaming buttons were shown even for titles you could not watch: making the quiz result screen true
The anime quiz showed the same two streaming buttons for every title; about 29% were wrong. How I narrowed buttons by availability and fixed image problems.
-
Cutting six buttons to one: first-time players did not know what to press
A browser game title screen had six things to press. I made the first step one button, and fixed saves vanishing inside an iframe and play continuing in a hidden tab.
-
Measuring Qiita’s “posting too frequently” limit: a new account could not create another article for about 30 minutes
After publishing, a new Qiita account could not create the next article. Checking the editor every 5 minutes, two measurements gave about 30 minutes; one was lost.
-
Rejected by CrazyGames with no reason given: I recorded the game and found four problems
A game portal rejected my game with only “overall quality below expectations.” I recorded what the reviewer saw, found four causes and fixed them by measuring.
-
Japanese text garbled in the CrazyGames preview: the file was correct, the cause was never found, so I escaped to ASCII
One character broke in a game portal’s preview though the file bytes were correct. How I made the game immune without finding the cause.
-
Works on my machine, breaks when deployed: four incidents shipping a static site to Cloudflare Pages
Stale cache, breadcrumbs that multiplied each build, a symlink into my own environment, and local-only images: four deploy incidents on Cloudflare Pages.
-
Comparing static site hosts by their official terms: Cloudflare Pages, Netlify, Vercel, GitHub Pages and Firebase Hosting
Cloudflare Pages, Netlify, Vercel, GitHub Pages and Firebase Hosting compared by official terms (Oct 2026): ads allowed, free limits, file limits, config.
-
Comparing comment systems for static sites by their official information: giscus, utterances, Disqus, Remark42 and more
giscus, utterances, Disqus, Remark42, Cusdis, Staticman and Commento compared by official pages (Oct 2026): storage, ads and tracking, maintenance status.
-
Comparing iOS ad SDKs and Firebase by their official information: AdMob, AppLovin MAX, LevelPlay and Supabase
AdMob, AppLovin MAX, LevelPlay, Firebase and Supabase compared by official pages (Oct 2026): payouts, privacy manifests, minimum Xcode, free tiers.
-
Claude API vs Gemini API by official numbers: small-model prices, web search, spend limits and deprecation
Claude and Gemini API compared by official pages (Oct 2026): small-model prices, web search cost and caps, spend limits, deprecation notice.
-
I could not listen, so I measured the waveform: building Japanese instruments with WebAudio for Sushi Stack
A browser game with no audio files: koto, taiko and gong synthesized with WebAudio. I could not listen, so I set volume by measuring waveform peaks.
-
Making a ranking server use only half of the free tier: from 5 calls to 2
A game ranking on Workers and D1: calls per play from 5 to 2, rows read from 421 to 221. Server-side rank counting and removing the preflight call.
-
Eight product-matching failures in a TCG lottery app: English vs katakana, splitting sets, official catalogs and spreading typos
Eight real failures in matching trading-card lottery articles to products: English vs katakana names, splitting 9-pack sets, products seeded from official catalogs, spreading typos, and leaked values.
-
Seven silent failures: real lotteries discarded, pages never read, and errors swallowed without a trace
Seven real cases where nothing raised an error yet real data was being dropped or never read: regexes, dates without a year, truncated AI answers, t.co relays, notice pages, swallowed try? failures, and fail-open.
-
Seven lessons from repairing bad data: existing records, bulk-job accidents, counting duplicates and the notification gate
Seven real lessons from repairing bad data: fixing the gate does not fix existing data, cleanup can hide entry bugs, a bulk job that hit everything, how duplicates were counted, and notifications that cannot be undone.
-
Where App Store review stopped me: “Guideline 2.1 Information Needed” on the first submission, and four rejections
Records of an indie iOS app stuck in App Store review: the six items asked for in Guideline 2.1 Information Needed on the first submission, and four rejections including iPad screenshots.
-
Python splitlines() breaks JSONL: the culprit was U+2028, an invisible line separator
Python splitlines() broke a JSONL file because 2 of 7,247 records contained U+2028 (LINE SEPARATOR). Why it happens and the one-line fix: split on the newline only.
-
What changes when an anime quiz goes from 50 questions to 12?
A 50-question anime quiz now gives a result after 12. Measured with 1,000 synthetic respondents: the type mostly matches, but the single recommended title changes a lot.
-
“The pile settles at 89%” was wrong: my measurement was what was wrong
Rebalancing a merge puzzle, I found my measurement had counted falling pieces. Three causes: a backward size ladder, a pit-shaped tub, and pieces tunneling the floor.
-
Measuring the gap between ads in time, not in plays
Why I replaced “one ad every 5 plays” with “7 minutes since the last ad” in a browser game, how it behaves, and what I still do not know.
-
Five records of measuring, then cutting, AI and Firestore costs: a broken Gemini meter, 91% of reads in one place, and crawl costs
Five records of cutting AI (Claude, Gemini), Firestore and crawling costs in an indie app: doubting the meter first, converting to yen, and counting where the money actually went.
-
The conger eel was too big: finding why a merge puzzle’s last piece was out of reach
I could never reach my puzzle’s last piece. Statistics from hundreds of auto-played games showed the real cause: everything was too big for the tub.