Chapter 7: Two Days to Build the Infrastructure

Game: xiuxian
Author: Paper City
Published: Mar 19, 2026, 20:45 (Shanghai)

Y moves the game into Cloudflare and builds Worker routing, D1 storage, accounts, leaderboards, telemetry, CI/CD, and logging. L arrives the next evening to rearrange buttons, rename rankings, and shape the modal.

The following plots are purely fictitious and any similarity is purely coincidental.

Opening

As of March 7, the Immortal Cultivation Simulator is still a purely static page.

There is no backend. There is no database. There is no user system. The player's archive is in the browser's localStorage and will be lost when the device is changed. Ranking list? Doesn't exist. The page is hung on GitHub Pages, change the code once and wait five minutes for deployment. L's end is getting better and better with each change, with Easter eggs, Nascent Soul, ascension, and Tiandao dialogue all stuffed in. However, the foundation of the entire project is still one piece of HTML plus a few JS files. There is no structural difference from the version she created with Kimi on the first day.

Y has been watching it for a while. He knew this thing would have to be moved sooner or later. It's not because of any urgent problem. It's because he can see the next step - if you want a ranking, you have to have a database; if you want a database, you have to have a backend; if you want a backend, you can't continue to hang on GitHub Pages.

At ten o'clock in the morning on March 7, he opened the Codex and started working on it.

Moving

The solution chosen by Y is Cloudflare. Pages hosts static files, Worker does routing and API, D1 does database, and KV does caching. There is no need to worry about servers or operation and maintenance, everything runs on edge nodes. He had never used Cloudflare Family Bucket before. But he thought clearly about the architecture, and all that was left was to let Codex help him build it piece by piece.

The first commit is huge. GitHub Actions workflow, Worker routing entry, D1 initial migration, Wrangler configuration file, environment variable template - all are pushed up in one go. The Worker's entry file `index.mjs` is responsible for distributing all requests: the home page uses SSR, `/xiuxian/*` uses static proxy, and `/api/*` uses API routing. The upstream redirection of Pages needs to retain the `/xiuxian` prefix, so a layer of `rewriteUpstreamRedirect()` must be added.

Then there is the deployment script. `stage.sh` is pushed to staging, `release.sh` is pushed to production, ff-only, no rollback is allowed. He also wrote a WSL startup script to install the apt package, NVM, Node, and wrangler with one click, in case he needs to develop on another machine one day.

The homepage has also been rewritten. The original English navigation page was changed to Chinese: "Today, which life should I practice?". The labels read "A single game lasts 3-8 minutes", "No download required, click to play" and "Mobile supported". A corner mark called "HOT" hangs on the card of the Immortal Cultivation Simulator.

By 11 a.m., the move was basically completed. The entire project was moved from GitHub Pages to Cloudflare, and both staging and production lines were connected.

Y didn't stop.

He built a user system in the afternoon

In the afternoon of the same day, Y continued to talk to Codex and began to set up user accounts.There are two more tables in the database. `users` saves users, `user_game_assets` saves files. Each user has a `vibe_uid`, UUID generated, globally unique. Login does not require a password - send an email verification code, eight digits, valid for ten minutes, and the expiration time is stored in KV. Send away Resend API. Anti-flash KV counter plus TTL window.

There are a few more buttons on the front-end page. The start page says "Seal of the Soul: Unbound (Local Save Only)". Next to it are "Retrieval Progress" and "Mailbox Binding Soul Mark". The checkout page now has "Leave your name on the list".

Then there's telemetry. New `telemetry_events` table with game dimensions, funnel name, step index, status flags. Each event has whitelist field filtering, assigned by game. The old `events_log` is used as a compatible image. `telemetry-client.js` is added to the front end, which is loaded before the engine is initialized and automatically pushes events to `/api/telemetry/collect`.

By six o'clock in the evening, the user system and telemetry pipelines were all in place.

Then he started making rankings.

Three lists

The design of the leaderboard is very interesting. Not a list. It's three.

"Juan Wang List" - Who can cultivate the Nascent Soul in the shortest time. Take the minimum value. The description reads "This kid is so scary!"

"Gou Dao Bang" - who lives the longest. Take the maximum value. The description reads, "Undecided in times of trouble, seclusion for thousands of years..."

"Liver Emperor List" - Who has been reincarnated the most times. Take the sum. The description reads, "You will almost develop antibodies after drinking Meng Po Soup!"

These three names are all derived from L at first glance. Y had talked to her before about how many dimensions the ranking should be made. L reported three names at once, and Y used them directly. The definition of the list is stored in D1 and is preset with the seed script. Submit JWT authentication, HS256 signature, and sign and verify it yourself in the Worker. The front end has modal, three tabs to switch, and each line displays ranking, name, and score. When settling, there will also be a percentile at the bottom: "You exceed 87% of the players in the server."

By 9 p.m., the leaderboard is online. In one day: Cloudflare migration, user system, telemetry pipeline, three leaderboards. Y turned off the computer.

The next day he started to unpack what he had written.

On the morning of March 8, Y came back and took a look at Worker.

`index.mjs` is already over 2200 lines long. Routing, authentication, passport, rankings, telemetry, and home page rendering are all squeezed into one file. It was fine if it worked yesterday when I was rushing to work, but it doesn’t work today.

He discussed the splitting strategy with Codex and spent a whole morning splitting it into eight modules: `helpers.mjs` for response and verification, `auth.mjs` for JWT parsing, `passport.mjs` for user information and OTP, `leaderboard.mjs` for ranking and submission, and `telemetry.mjs` For event ingestion, `homepage.mjs`, `proxy.mjs`, and `db.mjs` each take care of their own business.

After taking it apart, I did a few things.Security hardening: The archive interface must carry JWT, and the `vibe_uid` passed by the client is no longer trusted. The leaderboard rejects submissions with `yuan_ying_age < 10` or greater than lifetime age. The telemetry health interface is supplemented with CORS headers. Nickname verification filters control characters, which are consistent between the front and back ends.

Error log: Create a new `error_log` table, the `logger.mjs` module is responsible for writing, fire-and-forget, and will never block requests. The top-level `fetch()` wraps an error boundary, and all nine silent catch blocks are connected to the log. Records from seven days ago are regularly cleared at three o'clock in the morning every day.

CI/CD: D1 migration changed to remote execution. A schema gate is added to check whether all tables exist and whether the seed data is in place before deployment. passport smoke test. Pages deployment branch dynamic resolution. wrangler version pegged 4.71.0.

An OTP pop-up window was also made to replace the browser's native `prompt()`. Enter your email address, send the verification code, and enter the verification code, all on the page.

Then he deleted the email binding entry from the UI.

He built it and demolished it

This operation is quite interesting.

Y built a complete email binding and retrieval process the day before: sending a verification code, binding the email, changing devices and using the email to restore the archive. The front end has buttons, pop-up windows, and status prompts. The backend has OTP generation, KV current limiting, and Resend delivery. The entire link is open.

Then he chopped the front entrance all the same night. The "Mailbox Retrieve Archive" button on the start page, the "Mailbox Binding" button on the settlement page, OTP pop-up windows and related styles - all are removed. The backend interface is still there, but users can't see it.

Why? Probably because after finishing it, I found that for a cultivation simulator, forced email binding is too heavy. Players want to play as soon as they come and don’t want to fill in their email address first. Anonymous tokens are enough. I’ll talk about the email later.

This kind of "build first and then cut down" is not uncommon in engineering. The building process helps you figure out where the boundaries are. I know what I'm doing when I'm cutting.

At eight o'clock in the evening, L is here

March 8, 8:03 pm. L's first commit comes in.

`Tiandao Golden List Name Adjustment`.

She asked Kimi to unify the names of the rankings on the UI into "Tiandao Golden Ranking". What Y previously wrote was a technical "ranking list", but L felt it was not enough to practice cultivation. Tiandao Golden List, four words, it was right at once.

Then she started asking Kimi to turn the page.

Start page: Put "Reincarnation and Reconstruction" in the largest and most conspicuous position, "Reincarnation Book" and "Tiandao Golden List" are placed side by side below, and the button size and spacing are rearranged.

Checkout page: Prioritization has been rearranged. At the top is the screenshot to share, then "Re-Enter Reincarnation", and at the bottom are "Submit Results" and "Tiandao Gold List". Her logic is: after playing a game, the first thing is to take a screenshot and send it to Moments, the second thing is to play another game, and the third thing is to check the ranking.

Speed ​​button: The original "1x" text button has been replaced by a set of radio-style switches, with one grid each for "1x" and "10x", and the selected one is highlighted. The pause symbol was changed from `II` to the Unicode `⏸`.Property bar: The hover prompt has been deleted and replaced with a line of small text description written directly under each property. The stat lines are arranged up and down, with the name larger and the description smaller. The flex layout adds `min-width: 0` to prevent long names from squeezing buttons.

Button style: Added `.btn-flat` class, with transparent background and border, used for browsing buttons such as "Book of Reincarnation" and ranking entry. It is separated from the main operation button by solid color.

Finally, she asked Kimi to build a universal popup component. `#game-modal`, supports message text, input box, confirm and cancel button, `modalSystem.show()` can be done in one method. Replaced `prompt()` and `safeAlert()` that were scattered everywhere before.

These submissions last from 8 to 9 p.m. L After correcting one item, mention the next one. After mentioning it, continue to change the next one. Y was still working on her PR and updating documents at that time. The two people submitted alternately like this for more than an hour.

End of the third day

On March 9, Y asked Codex to help him put the finishing touches.

Three small icon links are added at the bottom of the start page: Xiaohongshu, Feedback, and Buy Coffee. Opacity 0.52, visually very light and not stealing the show. The URL is written in config and can be changed at any time by the operation.

Automatic submission has been added to the ranking list: after the game is over, there is no need to manually click the button, the system will automatically try to submit the results. When you are listed for the first time, a celebration pop-up window will pop up with a golden title and status prompt.

Feedback function: a pop-up window, the user fills in the content and uses the Resend API to send it to the operation email. No login required. IP for current limiting. Maximum 1000 words.

The last commit is the development logging system. Two new tables, `devlog_authors` and `devlog_posts`, were created in D1. A 651-line `devlog.mjs` module is added to the Worker, which is responsible for Markdown parsing, list page SSR, and detail page rendering. slug routing, paging query, filtering by game and author. The style is a light parchment base, serif font.

This development log system is the one used later to publish this article.

A building was built in two days

Three days from March 7th to March 9th, about fifty submissions.

Y built it all by himself: Cloudflare Pages static hosting, Worker routing and API, four core tables of D1 database plus four migration scripts, KV cache and current limiting, user account system, JWT authentication, OTP email verification, three rankings, telemetry pipeline, error log system, request tracking, CI/CD pipeline, smoke test, local development tools, deployment scripts, WSL startup scripts, health checks, operation and maintenance documents, feedback collection, and development log system.

L showed up for forty minutes the next night. Changed the name, arranged the buttons, created pop-ups, and set priorities.

The difference in rhythm between the two people is already obvious here. Y will hold it in for a few days and then turn over the entire infrastructure in one go. L is changing every day, but it’s all about changing the game itself—copywriting, values, feel, and UI. When two people meet, Y usually sets up the frame and L decides where to hang the curtains.What is the name of the leaderboard, what order are the buttons in, what should be the first thing to do after playing - these are things that Y will not think about. He will ask Codex to write a default value that works, and then wait for L to change it. L doesn’t think about how many modules the Worker should disassemble, what algorithm to use for JWT, or how to do D1 migration—she only needs the things on the last page to look and feel right, and leave the middle work to Kimi.

Neither one waited for the other. Y directs Codex and L directs Kimi. The two people use different AIs and do different jobs, but when put together they become a complete thing.