Anime Semantle

Devlog

← Back to Game

A chronological record of how Anime Semantle was designed, what broke, and how it got fixed, newest first. This includes the missteps and failed attempts, not just the parts that went smoothly. New entries get added to the top of this list as changes land. For a retrospective on the AdSense review saga specifically, see this write-up.

Published an article testing the content-uniformity hypothesis — three questions with no answer

To test the hypothesis that all seven existing analysis articles look uniform because they share the same "numbers plus commentary" shape, added one article in a different format. It's built from veins F, I, and K — the ones mine_content_data.py mined but that never became articles. For a while README listed this as "D/F/I/K," but D had already shipped as rating-myth, so it wasn't actually available to reuse; that typo is now fixed and only F/I/K were used. All three of these veins concluded "we measured it and got no usable answer," so instead of ending in a table, a callout box, and a gameplay tip like the other articles, this one keeps what wasn't known for each question, stated plainly.

Sitemap resubmitted; set a 14-day stability deadline of 09-20 before the 6th application

Right after recovering from the deployment incident below, the sitemap (35 URLs, all live) was resubmitted to Google Search Console. The gate for the sixth AdSense application is whether the International Targeting report holds at zero errors for 14 straight days; the check deadline was set to 2026-09-20, with margin built in for the fact that GSC reports can lag by up to 10 days.

Deployed from a stale local tree and rolled the live pool back

After committing the archive fix below locally, running the deploy went out with a work tree that, unbeknownst to this session, had been stuck at the 08-25 commit — fifteen commits behind origin, including the 08-26 follow-up cleanup, several days of daily archive auto-commits, and the 09-01 monthly data refresh (answer pool 3,110 → 3,112). git status had reported clean and up to date only because this session had never actually pulled from origin — it doesn’t warn you that local is behind remote. The game itself didn’t break (the two old pool versions were internally consistent with each other), but for the affected window (around 2026-09-06 20:24 KST) the answer a real player saw may not match what the archive eventually records against the new pool, so that date was added to SKIP_DATES. Merged origin and redeployed immediately to restore the correct pool. Lesson: always run git fetch && git log HEAD..origin/main --oneline before deploying, to check whether local is behind.

Found and removed a solve-rate leak on the indexed archive, then rebuilt its density

While auditing the site before deploying the URL/IA restructure, an unnoticed case of the exact same pattern as one of the fourth rejection’s causes turned up in archive.html, which is indexed. It had been rendering “N players · X% solve rate” on every entry since 07-18; the only fix applied back then was noindexing the aggregate stats.html page — individual archive entries were never part of that audit, and were most likely live throughout both the fourth (08-03) and fifth (08-19) rejection windows (confirmed present on 12 of 52 entries). That rendering was removed entirely. As a low-cost move against the still-unmeasured “density per page” hypothesis raised after the fifth rejection, the archive was also restructured into detailed entries for the last 14 days plus a single compressed summary row for everything older — raising the signal-to-noise ratio per page without adding more total volume.

September answer pool update, confirmed live

The monthly pipeline completed normally on 09-01. The answer pool grew slightly, from 3,110 entries (800 anime + 2,310 characters) to 3,112 (800 anime + 2,312 characters), and the similarity row files were regenerated for the new pool. We compared the live site’s data file directly against the repository and confirmed the deploy had already caught up — in the past, a delayed deploy after a pool change caused the archive to record answers players never actually saw; that gap didn’t happen this time. Translation completeness for the newly added entries wasn’t checked in this pass, since the source files aren’t present locally.

Where the menu actually pointed, and the first screen

Reopening the Game / AI Analysis / Data menu built the day before, all three links under “Data” turned out to be pages deliberately kept out of search. Two of them were the list-style pages that had been a rejection reason before — so they had ended up in the most visible slot on the site, while the five articles actually built on the data were not in the menu at all. Fixed by rearranging what was already there, without writing anything new. The language-selection screen got the same treatment. It was 263 characters, yet it is the first impression and the address search engines treat as canonical, and it said nothing about what the site computes. It also failed to remember the choice: picking “Continue in English” on a Korean-language browser landed you on a page that immediately offered to switch you to Korean.

Moving Korean off the root and rebuilding the home page

Until now the Korean edition occupied the site root while only English lived under /en/. An English speaker typing the domain directly got a Korean screen, and there was no neutral address to tell search engines what the default language was. Korean moved to /ko/ and the root became a language-selection page. No automatic redirect — a redirect once caused a duplicate-indexing incident here, and search engines advise against it anyway. Every old address is connected by a permanent redirect so existing links keep working. The home page was rebuilt at the same time. The game stays in first position, but below it Game, AI Analysis, and Data now sit at equal weight, alongside a diagram showing how the similarity score is actually computed.

6.7x more writing, identical verdict

All six causes diagnosed after the fourth rejection were addressed before reapplying. Reader-facing writing went from 5,385 to 35,832 characters, the boilerplate pages were pulled out of the index, and the archive commentary was rewritten to branch on the underlying data. The result was a rejection citing exactly the same reason as rounds one through four. That none of the six had recurred was re-measured and confirmed. Which leaves a different question: not “what newly broke” but “was this direction ever the kind of thing that could overturn this verdict.” So the approach changed — from adding more writing to reworking the structure of the site itself. The longer version is in the retrospective.

Genre guides rewritten from full data, plus two new ones

The romance and isekai guides were built around a single anchor title and its neighbours. Measuring each genre in full turned up three errors. The anchor used for the romance guide did not carry a Romance tag at all; the claim that “0.55–0.65 means a school romance” does not hold across all 132 romance titles (school romances sit at 0.771–0.855 with each other); and the table still carried old translated titles that had already been corrected. All three were fixed and each guide grew from 1.4k to 3.8k characters. Two new guides followed, for sports and supernatural. That sports is hard not because it clusters tightly but because everything outside it falls away only became visible after measuring ten genres the same way.

Mining the data into five analysis articles

Built a script that sweeps the computed similarity data from several angles, then wrote up only the axes where the results actually separated. Rating and similarity correlate at +0.004 across 599,852 pairs — effectively unrelated. For 780 of 794 titles, a character from the work scores closer to it than the nearest other work does. Theme turned out to split difficulty more than twice as widely as genre. Some axes were mined and discarded. The intuition that films are harder does not hold up numerically, and supporting characters scoring closer to their own work than leads appears to be an artefact of how the text is assembled, so it was not turned into a gameplay tip. This is also when six already-published passages turned out to be wrong — they claimed that a short synopsis produces lower similarity, and measurement showed the short ones actually score slightly higher. It had been written without measuring. All six were replaced with corrections.

The day the answer pool collapsed from 3,108 to 142

The monthly automated data refresh cut the answer pool from 3,108 to 142 entries, then ran to completion and committed the result. The cause was not the intermediate API but the source upstream of it: only requests already in cache returned, and everything else failed — deterministically rather than intermittently, so no amount of retrying would get through. The intermediate layer was removed in favour of the official API. Identifiers match, so the accumulated translation work carried over unchanged, and the usage terms are now cleanly satisfied. To keep the same failure from passing silently again, the collector now refuses to write anything and exits with an error when it gathers less than 90% of the expected volume.

From 95MB down to 0.18MB per visit

Opening the game made the browser download the entire similarity dataset — 95MB raw, around 34MB even compressed — and then parse and merge all of it, freezing the page for seconds. On mobile that meant tens of seconds and a real data cost. All that is actually needed is the one row for the day’s answer, so a row file is now pre-built for every possible answer and only that one gets fetched. 32KB. That the values come out identical was confirmed not by sampling but by comparing every possible answer-and-guess combination — all 9.3 million pairs matched.

Strategy guide, answer statistics, and solve-rate tracking

Published a strategy guide built from real similarity data — reference tables and a full walkthrough using measured values like "when the answer was One Piece, Naruto scored 0.552 and Haikyu!! scored 0.000." Also added an answer statistics page aggregating the daily answer archive (genre distribution, anime/character ratio), regenerated automatically every day. Finally, deployed a Cloudflare Worker + D1 API that counts daily players, solvers, and give-ups — once enough data accumulates, per-day solve rates will be published.

Paying down SEO tech debt — redirects and the sitemap

The third review came back with the same rejection, so this time the whole site got audited from a crawler's point of view. Two problems surfaced. First, internal links and canonicals pointed at .html URLs that the host 307-redirects, causing duplicate indexing and canonical errors — all internal URLs across 50 files were unified to the extensionless form. Second, Google hadn't re-read the sitemap in three weeks because an old version contained an invalid changefreq value (quarterly). A regex bug in the daily automation that silently stopped sitemap updates was found and fixed too, and the sitemap was resubmitted in Search Console along with indexing requests for the new pages.

Contact email address changed

Changed the contact inbox to hograywm@gmail.com. Updated the email on the main page footer note, the contact page, and the privacy policy page across both KO and EN.

robots.txt cleanup, a contact page, deploy config fix

Removed a robots.txt Disallow rule that was conflicting with the noindex meta tags, letting search engines actually read them. Added a standalone contact page, and fixed a Cloudflare Workers deploy failure caused by a missing wrangler.jsonc config file.

A fix that made things worse, then a change of direction

On top of the wordlist page, 36 genre pages were added — and the second review rejected the site for the same "low-value content" reason. The actual cause turned out to be that this had scaled up a pattern of repackaging MyAnimeList data 34x over. Those pages were set to noindex to remove them from search, and a daily-updating past-answers archive unique to this site was built instead. The about page was also updated with the actual measured data behind the similarity weighting choice. A redirect-loop SEO issue on the about/privacy/archive pages was discovered and fixed during this same investigation.

"Low-value content" rejection, and a list page in response

AdSense rejected the site for "content with little value." Assuming the standalone game itself was too thin, a full anime/character wordlist page was built in response. In hindsight, this specific fix set up the next rejection.

Monthly auto-refresh + Discord notifications

Locked in the monthly data refresh schedule and added a Discord notification summarizing new/removed titles after each update. The first version had a bug that misflagged existing entries as new, fixed the next day.

The fight against CLS (layout shift)

Spent several days tracking down layout shift during page load — locking the scrollbar, reordering the results section, and chasing down the remaining sub-pixel shifts. The similarity calculation logic was also redesigned from scratch around this time, and the "70% structure + 30% synopsis" weighting used today traces back to this rework.

Building the auto-update pipeline

Set up GitHub Actions to automate data collection, translation, and similarity computation for the first time. The pipeline built here is still the basis for the monthly automated data refresh running today.

Expanding the answer pool and splitting into chunks

The number of precomputed neighbors stored per item grew in stages from 100 to 3,000. As the data grew, we hit Cloudflare Pages' 25MB per-file limit, so the similarity JSON was split into 5 separate files. The English version and character translations were also added around this time.

First deploy

Anime Semantle went live. The initial setup included the meta tags and ads.txt required for ad review, along with basic SEO tags.