TRAE Wrote Every Line of This WeChat Mini Game Collection, Then WeChat Forced a Full Rewrite
From Empty Directory to Launch: I Used TRAE End-to-End to Build a WeChat Mini Game Collection (Including a Full Architecture Refactor)
This post is participating in #All-New TRAE Hands-On Experience
Track: Full-Link Development Hands-On
This is a hands-on retrospective of "AI as the lead developer": every line of code in the entire project, from 0 to 1, was written by TRAE (including the initial project scaffolding).
My role was product manager + acceptance tester: proposing ideas, setting acceptance criteria, playtesting and giving feedback, and capturing screenshots.
TRAE's role was full-stack engineer: technical selection, directory design, coding, debugging, refactoring, and test scaffolding, all the way through.
The project went through two generations of architecture, on two branches:
mainbranch: Taro + React + TypeScript WeChat Mini Program versionfeature/minigamebranch: refactored native WeChat Mini Game version
Across both generations, code changes were at the 50,000-line level, completed entirely inside TRAE.
First Generation Architecture (main branch): TRAE Scaffolds a Taro Mini Program from an Empty Directory
The first step was to have TRAE bootstrap the project directly. The raw requirement I gave TRAE was only a sentence-level idea:
Help me make a WeChat mini game that contains several different mini games. For now, build two:
- Make a QR-code maze mini game where the QR code is the maze, and scanning the QR code shows the correct path.
- Make a balance-ball mini game that uses the phone's gravity sensor: by tilting and rotating the phone, the ball rolls from a start point on one side to an end point on the other. There are obstacles and holes in between, and falling into a hole means immediate failure.
It made the complete technical selection decision: the goal was "low-cost, fast validation of gameplay," so it chose Taro (React + TypeScript + SCSS), and proactively explained why: componentized development makes UI pages fast, and TypeScript keeps game logic maintainable.
Then it produced the entire project skeleton in one shot:
src/
├── pages/ # index / qrmaze / balance / records / mine
├── utils/ # maze.ts / qr.ts / storage.ts / format.ts / haptic.ts
├── data/ # levels.ts deterministic level factory
└── types/ # game.ts global types
As shown in the figure:
Key point
TRAE directly built the project structure for me and pulled in the plugins I needed. Really powerful!
TRAE can basically understand every requirement I state, and can generate code that basically doesn't need manual review or modification. In many cases I just looked at the UI and described the problems I saw on screen, without looking at the code implementation at all!
Process
After that, I kept communicating with TRAE to reach the effect I wanted.
As shown:
Later, this rookie also asked it to add a game and optimize the older games:
Key point
When I encountered UI problems, I could also screenshot them and send them directly to TRAE, even draw a box around the area to tell TRAE, and TRAE fully understood the problem I described!
As shown:
Final Output
Anyway, TRAE optimized many times; I won't list them all. The final output contained three gameplay modes:
- QR-Code Maze: the walls of the maze are themselves a real, scannable QR code. After scanning it with a program, the optimal path appears as a line, and you can follow it directly.
- Full Fire: a vertical shooter with squad mechanics, a two-choice arithmetic gate, elite monsters, and BOSS battle effects.
- Gravity Balance Ball: tilt the phone to control the ball, avoid abyss holes (vortices), and roll into the teleport portal.
Plus three pages: home, battle records, and profile (tabBar).
Honest Assessment
To be fair, TRAE did very well on feature implementation, completeness, and code quality. But without an artist on hand, the game is indeed crude. (You could actually use TRAE Work, now the Agent mode, to help generate illustrations; use both approaches. Here this rookie is an enterprise user and couldn't do it, while regular users got to use it before enterprise users. (T ^ T))
Two: The Mini Program Wouldn't Publish, and I Discovered Games Can Only Use the Mini Game Category: TRAE Completed the Taro → Native Mini Game Migration
After all the gameplay was working and I was about to publish, I learned I had hit a platform-rule pitfall: the project itself was a game, but WeChat doesn't allow games to be published under the "Mini Program" category. The only way to get it out was to switch the account category to "Mini Game" and get a new Mini Game AppID.
As soon as the category changed, trouble appeared immediately: Taro-built Mini Program code simply won't run in the Mini Game runtime.
Mini Programs and Mini Games are two completely different runtimes. A Mini Program needs app.js + page routing + the WXML component system, while a Mini Game only recognizes a single game.js entry point. There are no pages, no components; all UI has to be drawn on a Canvas by yourself.
Not a single line of code was changed, but it would not run.
I asked TRAE to evaluate two paths: keep looking for a Mini Game adaptation solution on Taro, or migrate entirely to a native Mini Game?
TRAE's conclusion was direct: Taro's Mini Game support is of limited help for this kind of "single-Canvas, frame-by-frame loop" project; the component model doesn't help and just adds an extra bridge. Since all three gameplay modes rely on Canvas, native Mini Game + hand-drawn UI was the right path.
That's how the refactor decision was made. It wasn't for performance; it was a refactor forced by platform rules.
So the entire package was refactored: 141 files, +24,332 / −33,181 lines, which amounts to rewriting the whole project for the Mini Game runtime. TRAE produced the new architecture skeleton in Builder mode, then "translated" the old logic scene by scene:
minigame/
├── game.js / game.json
├── jsQR.js # exit scan validation
├── js/
│ ├── games/shooter/ # engine / timeline / render / layout / constants
│ ├── scenes/ # home / qrmaze / shooter / balance / records / mine
│ ├── qr.js # self-implemented QR encoder (v1~8)
│ ├── maze.js # maze generation (524 lines)
│ ├── levels.js # deterministic level factory
│ └── ui.js / scroll.js / tabbar.js / haptic.js / icons.js
Mini Game architecture:
Mini Program architecture:
Key point
The entire refactor went very smoothly and was a completely proportional port; only minor style details were adjusted, and the gameplay experience was essentially identical!
Three: TRAE's Actual Performance Across the Whole Pipeline
1. Requirements → Solution: It Asks Back and Gives Reasons
For a vague requirement like "make a mini game collection," TRAE didn't start writing immediately. It first confirmed the gameplay scope, offline requirements, and acceptance method, then gave a technical plan.
The selection reasoning could go down to "why this instead of that," rather than blindly piling on new tech.
2. Coding: Cross-File Structural Changes Are Its Strength
The migration phase was the most typical example: in one conversation, TRAE translated the 226-line TypeScript shooter timeline (timeline.ts) into CommonJS timeline.js, preserving the wave scheduling and "debuff protection" semantics completely.
For this kind of structural engineering across 20+ files, writing by hand takes days; TRAE did it in minutes.
3. Debugging: Give It the Symptom, Get the Root Cause
The two fixes that impressed me most were both cases where I described the symptom and TRAE located the root cause:
Edge bullet-hell leak: monsters leaked through the dead corners at the screen edge into the defense line. TRAE located a defect in the boundary sampling interval for bullet-hell generation.
Wall cells wrongly deleted when reinforcing the path (main branch
eff053c): the maze "reinforcement" feature destroyed already-generated walls. The root cause was a conflict between backfill order and deduplication logic. For this kind of cross-function state problem, TRAE's ability to read through the call chain was far more efficient than me adding breakpoints.
4. Testing: It Built Its Own Verification System
This is what I think TRAE did best.
Because I had mentioned that levels are deterministically generated and the logic layer doesn't depend on the WeChat runtime, TRAE also set up a set of Node smoke tests (scripts/minigame-smoke.cjs): by injecting a wx stub, it can run regression checks on the level factory, QR derivation, and shooter engine in Node.
Four: The Two Most Valuable Technical Points (Both Produced by TRAE)
1. Hand-Written QR Encoder + jsQR Loop Verification
The walls of the QR-code maze must be a real, scannable QR code, and the Mini Game environment has no ready-made Canvas QR library. TRAE implemented a QR Model 2 encoder from scratch (qr.js): Byte mode UTF-8, error correction level L, supporting versions 1 to 8 (21 to 49 modules). For v6 and above, it interleaves 2 RS blocks per the spec, with RS error correction on the GF(256) field based on the primitive polynomial 0x11D.
The verification method is hardcore: use jsQR for a decode loop. Rasterize the 0/1 matrix output by the encoder into RGBA, feed it to jsQR for decoding, and the decoded string must equal the input 8-character level code.
2. Deterministic Level Factory + Node Smoke Tests
All levels are derived from mulberry32(seed) based on the level number: the QR version, maze strategy, minimum step count (100+), and shooter waves for level N are all determined by N. Combined with the wx stub above:
node scripts/minigame-smoke.cjs
# ✓ level factory / QR maze derivation + jsQR scan loop / shooter timeline and engine
This pattern of "pure-function logic layer + wx stub + Node smoke tests" gave every change from TRAE an executable acceptance criterion. After it made changes, I only needed to playtest a few levels.
I only verify the feel; the machine verifies the logic!
Level Generation Logic
// ============================================
// Level data
// ============================================
/** QR maze level config */
export interface QRMazeLevel {
id: number;
name: string;
/** QR version (determines maze size: v4=33 cells, v5=37, v6=41, v7=45, v8=49) */
version: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
/** QR content (can be recognized by real scanning, includes salt suffix) */
content: string;
/** Path strategy: short easy / long hard */
strategy: "short" | "long";
/** Minimum optimal path steps (deterministic search tries to guarantee) */
minSteps: number;
}
/** Level theme names (first 10), then "Endless Maze N" */
const MAZE_NAME_POOL = [
"初识密文",
"回纹密林",
"双重迷阵",
"深渊方阵",
"时序回廊",
"校准星图",
"掩码幻境",
"光路折返",
"镜像解码",
"无尽回响"
];
/** Deterministic random (mulberry32): the same level number always generates the same secret phrase */
export function mulberry32(seed: number): () => number {
let a = seed >>> 0;
return () => {
a = (a + 0x6d2b79f5) | 0;
let t = Math.imul(a ^ (a >>> 15), 1 | a);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
const CODE_CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
/**
* Infinite level factory: level N is completely deterministically generated by N (replayable, offline)
* - Difficulty ladder: the maze must be a real scannable QR matrix, so the size can only be 21+4×version (illegal sizes like 30×30 / 35×35 cannot be scanned).
* Therefore it increases by 4 each level from v5(37×37) → v6(41×41) → v7(45×45) → v8(49×49), capped at v8;
* all levels use long strategy (winding corridors) and require optimal path ≥ minSteps (100 steps) — deterministic search guarantees it
* - Secret phrase random: QW{N}#XXXXXXXX (the fill area can be freely shaped; scanning always yields the original text)
*/
export function makeQRMazeLevel(n: number): QRMazeLevel {
const version = Math.min(4 + n, 8) as QRMazeLevel["version"];
const strategy: "short" | "long" = "long";
const minSteps = 100;
const codeLen = 8;
const rng = mulberry32(n * 7919 + 13);
let code = "";
for (let i = 0; i < codeLen; i++) {
code += CODE_CHARS[Math.floor(rng() * CODE_CHARS.length)];
}
const name = n <= MAZE_NAME_POOL.length ? MAZE_NAME_POOL[n - 1] : `无尽迷阵 ${n}`;
return { id: n, name, version, content: `QW${n}#${code}`, strategy, minSteps };
}
/** Balance ball obstacle wall (top-left corner + width/height, unit coordinates 0~100 × 0~137) */
export interface BalanceWall {
x: number;
y: number;
w: number;
h: number;
}
/** Balance ball abyss hole */
export interface BalanceHole {
x: number;
y: number;
r: number;
}
/** Balance ball level config */
export interface BalanceLevel {
id: number;
name: string;
start: { x: number; y: number };
goal: { x: number; y: number; r: number };
holes: BalanceHole[];
walls: BalanceWall[];
/** Hole suction strength multiplier: harder with higher levels (default 1) */
suction?: number;
}
/** Logic coordinate system: width 100, height 137 (proportional to canvas 686×940rpx) */
export const BALANCE_FIELD = { width: 100, height: 137 };
export const BALANCE_BALL_R = 3.2;
export const BALANCE_LEVELS: BalanceLevel[] = [
{
id: 1,
name: "热身滚道",
start: { x: 10, y: 10 },
goal: { x: 90, y: 126, r: 6 },
holes: [
{ x: 50, y: 66, r: 5 },
{ x: 80, y: 36, r: 4.5 },
{ x: 60, y: 3.2, r: 4.5 }, // hole at top edge: blocks shortcut along top edge
{ x: 96.8, y: 80, r: 4.5 }, // hole at right edge: blocks shortcut along right edge
{ x: 48, y: 133.8, r: 4.5 } // hole at bottom edge: blocks shortcut along left+bottom edge
],
walls: []
},
{
id: 2,
name: "回旋走廊",
start: { x: 10, y: 10 },
goal: { x: 91, y: 126, r: 5.5 },
holes: [
{ x: 44, y: 112, r: 4.5 },
{ x: 74, y: 24, r: 5 },
{ x: 86, y: 84, r: 4.5 },
{ x: 96.8, y: 64, r: 4.2 } // hole at right edge: blockade point of the right corridor
],
walls: [
{ x: 26, y: 0, w: 4, h: 88 },
{ x: 58, y: 49, w: 4, h: 88 }
]
},
{
id: 3,
name: "针尖方阵",
start: { x: 9, y: 9 },
goal: { x: 91, y: 126, r: 5.5 },
holes: [
{ x: 34, y: 34, r: 4 },
{ x: 66, y: 34, r: 4 },
{ x: 34, y: 103, r: 4 },
{ x: 66, y: 103, r: 4 },
{ x: 50, y: 68, r: 5.5 },
{ x: 96.8, y: 72, r: 4.2 }, // hole at right edge
{ x: 70, y: 133.8, r: 4.2 } // hole at bottom edge
],
walls: [
{ x: 18, y: 62, w: 24, h: 3 },
{ x: 58, y: 62, w: 24, h: 3 }
]
},
{
id: 4,
name: "深渊回廊",
start: { x: 9, y: 9 },
goal: { x: 91, y: 129, r: 5.5 },
holes: [
{ x: 86, y: 28, r: 5 },
{ x: 14, y: 70, r: 5 },
{ x: 50, y: 123, r: 5 },
{ x: 86, y: 108, r: 4.5 },
{ x: 58, y: 70, r: 4.5 },
{ x: 24, y: 18, r: 4 }
],
walls: [
{ x: 0, y: 44, w: 72, h: 3 },
{ x: 28, y: 97, w: 72, h: 3 }
]
}
];
/** Balance ball level theme names (levels 5~16), then "Endless Track N" */
const BALANCE_NAME_POOL = [
"碎星浅滩",
"引力回廊",
"迷雾缓坡",
"旋涡窄桥",
"微光裂谷",
"静默深渊",
"镜面斜坡",
"薄冰长阶",
"乱流航段",
"无垠坡道",
"坠星海峡",
"永夜回环"
];
/** Distance between two points (Math.sqrt is precisely defined by IEEE-754, consistent across devices) */
function balanceDist(ax: number, ay: number, bx: number, by: number): number {
const dx = ax - bx;
const dy = ay - by;
return Math.sqrt(dx * dx + dy * dy);
}
/** Distance from a point to a rectangle (wall) */
function pointRectDist(px: number, py: number, w: BalanceWall): number {
const cx = Math.min(Math.max(px, w.x), w.x + w.w);
const cy = Math.min(Math.max(py, w.y), w.y + w.h);
return balanceDist(px, py, cx, cy);
}
/** Distance between rectangle (wall) and rectangle (wall) */
function rectRectDist(a: BalanceWall, b: BalanceWall): number {
const dx = Math.max(b.x - (a.x + a.w), a.x - (b.x + b.w), 0);
const dy = Math.max(b.y - (a.y + a.h), a.y - (b.y + b.h), 0);
return Math.sqrt(dx * dx + dy * dy);
}
/**
* Infinite balance ball level factory: level N is completely deterministically generated by N
* - Integer seed + reproducible random numbers: the same level number gives identical layout on any phone (offline replayable)
* - First 4 levels are hand-tuned tutorial levels, level 5 onward is procedurally generated
* - Difficulty curve: abyss holes 5→10, obstacle walls 2→6, teleport radius 5.5→4.2
* - Solvability guarantee: floating walls are ≥9 from borders/one another (ball diameter 6.4, so there is always a path around);
* holes do not stick together (spacing ≥8), holes do not overlap walls, holes do not cover start/end
* - Anti-edge-sliding: edge fin walls (short walls protruding from the border) cut off border paths +
* edge holes (holes half-embedded in the border) make rolling all the way along the edge impossible
*/
export function makeBalanceLevel(n: number): BalanceLevel {
if (n <= BALANCE_LEVELS.length) return BALANCE_LEVELS[n - 1];
const d = n - BALANCE_LEVELS.length; // generated difficulty tier (level 5 = 1)
const rng = mulberry32(n * 104729 + 7);
const r1 = (lo: number, hi: number) => Math.round((lo + rng() * (hi - lo)) * 10) / 10;
const holeCount = Math.min(4 + Math.ceil(d / 2), 12);
const wallCount = Math.min(2 + Math.floor(d / 2), 7);
const finCount = Math.min(2 + Math.floor(d / 4), 4);
const edgeHoleCount = Math.min(1 + Math.floor(d / 3), 4);
const goalR = Math.max(3.8, 5.5 - d * 0.12);
// Hole suction strength: each difficulty tier +1 increases it by 8%, capped at 1.8× (late holes are noticeably more "attractive")
const suction = 1 + Math.min(0.8, d * 0.08);
const start = { x: r1(8, 14), y: r1(8, 14) };
const goal = { x: r1(85, 91), y: r1(122, 129), r: goalR };
// Wall set: main fins → floating walls → random fins; later placements keep clearance from earlier ones
const walls: BalanceWall[] = [];
// Edge fin walls: short walls protruding from the border to cut off "edge-sliding" shortcuts.
// The top and right main fins are placed first (the field is empty at this point, so the scan always hits, and the main edge path is always blocked):
// take candidate points along the edge, pick one randomly by seed; floating walls and random fins then avoid them; holes go last.
const scanFin = (edge: number): BalanceWall | null => {
const len = r1(11, 17 + Math.min(5, d * 0.8));
const N = 34;
const cands: BalanceWall[] = [];
for (let s = 0; s <= N; s++) {
const t =
edge === 0 || edge === 2
? 16 + ((BALANCE_FIELD.width - 32) * s) / N
: 16 + ((100 - 16) * s) / N;
const fin: BalanceWall =
edge === 0
? { x: t, y: 0, w: 3, h: len } // top edge hanging down
: edge === 1
? { x: BALANCE_FIELD.width - len, y: t, w: len, h: 3 } // right edge extending in
: edge === 2
? { x: t, y: BALANCE_FIELD.height - 3, w: 3, h: len } // bottom edge standing up
: { x: 0, y: t, w: len, h: 3 }; // left edge extending in
if (pointRectDist(start.x, start.y, fin) < 13) continue;
if (pointRectDist(goal.x, goal.y, fin) < goal.r + 10) continue;
cands.push(fin);
}
const clear = (f: BalanceWall) =>
walls.reduce((m, o) => Math.min(m, rectRectDist(f, o)), Infinity);
const pool = cands.filter((f) => clear(f) >= 10);
const fallback = pool.length ? pool : cands.filter((f) => clear(f) >= 9);
return fallback.length ? fallback[Math.floor(rng() * fallback.length)] : null;
};
const finTop = scanFin(0);
if (finTop) walls.push(finTop);
const finRight = scanFin(1);
if (finRight) walls.push(finRight);
// Then place floating horizontal/vertical bars: distance to borders and each other ≥9, guaranteeing a path around
for (let i = 0; i < wallCount; i++) {
const len = r1(22, Math.min(38, 26 + d * 1.2));
for (let tries = 0; tries < 50; tries++) {
const horiz = rng() < 0.5;
const wall: BalanceWall = horiz
? {
x: r1(9, BALANCE_FIELD.width - 9 - len),
y: r1(9, BALANCE_FIELD.height - 30),
w: len,
h: 3
}
: {
x: r1(9, BALANCE_FIELD.width - 12),
y: r1(9, BALANCE_FIELD.height - 9 - len),
w: 3,
h: len
};
if (pointRectDist(start.x, start.y, wall) < 12) continue;
if (pointRectDist(goal.x, goal.y, wall) < goal.r + 9) continue;
if (walls.some((o) => rectRectDist(wall, o) < 9)) continue;
walls.push(wall);
break;
}
}
// Other random fins (allowed to give up when clearance is insufficient; not forced)
for (let i = 2; i < finCount; i++) {
for (let tries = 0; tries < 40; tries++) {
const edge = Math.floor(rng() * 4);
const len = r1(11, 17 + Math.min(5, d * 0.8));
const fin: BalanceWall =
edge === 0
? { x: r1(16, BALANCE_FIELD.width - 16), y: 0, w: 3, h: len }
: edge === 1
? { x: BALANCE_FIELD.width - len, y: r1(16, 100), w: len, h: 3 }
: edge === 2
? { x: r1(16, BALANCE_FIELD.width - 16), y: BALANCE_FIELD.height - 3, w: 3, h: len }
: { x: 0, y: r1(16, 100), w: len, h: 3 };
if (pointRectDist(start.x, start.y, fin) < 13) continue;
if (pointRectDist(goal.x, goal.y, fin) < goal.r + 10) continue;
if (walls.some((o) => rectRectDist(fin, o) < 10)) continue;
walls.push(fin);
break;
}
}
// Then place holes: the first few are "edge holes" (half-embedded in the border, so hugging the edge risks falling in),
// the rest are generated inside the field; all stay away from start/end, do not press on walls, and do not stick together
const holes: BalanceHole[] = [];
for (let i = 0; i < holeCount; i++) {
const asEdge = i < edgeHoleCount;
for (let tries = 0; tries < 60; tries++) {
const r = r1(4, Math.min(6, 4.4 + d * 0.14));
let hole: BalanceHole;
if (asEdge) {
// Hole center 1.6~3.4 from border: half the hole is embedded in the border; avoid the four-corner start/end zones
const edge = Math.floor(rng() * 4);
const off = r1(1.6, 3.4);
if (edge === 0) hole = { x: r1(20, BALANCE_FIELD.width - 20), y: off, r };
else if (edge === 1) hole = { x: BALANCE_FIELD.width - off, y: r1(20, 112), r };
else if (edge === 2)
hole = { x: r1(20, BALANCE_FIELD.width - 20), y: BALANCE_FIELD.height - off, r };
else hole = { x: off, y: r1(20, 112), r };
} else {
hole = {
x: r1(7 + r, BALANCE_FIELD.width - 7 - r),
y: r1(7 + r, BALANCE_FIELD.height - 7 - r),
r
};
}
if (
balanceDist(hole.x, hole.y, start.x, start.y) <
hole.r + BALANCE_BALL_R + (asEdge ? 12 : 8)
)
continue;
if (balanceDist(hole.x, hole.y, goal.x, goal.y) < hole.r + goal.r + (asEdge ? 8 : 6))
continue;
// Distance between hole and wall leaves room for ball diameter + anti-suction margin: the hole's attraction zone won't seal the path against a wall
if (walls.some((w) => pointRectDist(hole.x, hole.y, w) < hole.r + 3 + BALANCE_BALL_R))
continue;
if (holes.some((o) => balanceDist(hole.x, hole.y, o.x, o.y) < hole.r + o.r + 8)) continue;
holes.push(hole);
break;
}
}
const name = d <= BALANCE_NAME_POOL.length ? BALANCE_NAME_POOL[d - 1] : `无尽滚道 ${n}`;
return { id: n, name, start, goal, holes, walls, suction };
}
// ============================================
// Full Fire (shooter)
// ============================================
/** Full Fire level config (numeric curve deterministically generated by level number) */
export interface ShooterLevel {
id: number;
name: string;
/** Initial squad member count */
initCount: number;
/** Fire interval (seconds) */
fireGap: number;
/** Monster base push-down speed (units/second) */
monsterSpeed: number;
/** Monster spawn interval (seconds) */
spawnGap: number;
/** Arithmetic gate spawn interval (seconds) */
gateGap: number;
/** Arithmetic gate difficulty tier: 0 only + ×; 1 adds −; 2 full + − × ÷ */
opsMode: 0 | 1 | 2;
}
/** Shooter logic coordinate system: same as balance ball (width 100, height 137, canvas 686×940rpx) */
export const SHOOTER_FIELD = { width: 100, height: 137 };
/** Full Fire level theme names (first 12), then "Endless Front N" */
const SHOOTER_NAME_POOL = [
"初试枪火",
"合金弹幕",
"蜂群来袭",
"裂口巨兽",
"钢铁洪流",
"烈焰走廊",
"深渊虫巢",
"雷鸣防线",
"末日壁垒",
"星陨要塞",
"湮灭风暴",
"无尽硝烟"
];
/**
* Full Fire level factory: level N is completely deterministically generated by N
* - Win condition = kill the BOSS (appearance timing and HP are estimated by the page timeline)
* - Monster spawn sequence / arithmetic gate sequence are expanded by the page using mulberry32(id*9973+5) (same level, same layout)
* - HP random tiers: small 1-3 / medium 5-15 / large 20-30 / extra large 40-100 (bigger number = bigger body, page tiers by HP)
* - Initial squad of 6; +−×÷ double gates side by side, each taking half the screen, forced to choose one (buffs compare who adds more, debuffs compare who subtracts less)
* - First 2 levels only show double buff gates (tutorial); level 3 onward mixes in debuff combinations; level 6 onward ÷ gates appear
*/
export function makeShooterLevel(n: number): ShooterLevel {
return {
id: n,
name: n <= SHOOTER_NAME_POOL.length ? SHOOTER_NAME_POOL[n - 1] : `无尽战线 ${n}`,
initCount: 6,
fireGap: 0.3,
// Monster overall speedup: both base and growth raised, capped at 8 on high levels
monsterSpeed: Math.min(3.4 + n * 0.3, 8),
// Denser spawns: more monsters on screen at once (with clustered appearance)
spawnGap: Math.max(1.3 - n * 0.06, 0.5),
// Arithmetic gate wave interval: ≈ one pair of half-screen gates crossing time (GATE_SPEED), ensuring the previous two-choice wave finishes before the next appears
gateGap: 9.5,
opsMode: n <= 2 ? 0 : n <= 5 ? 1 : 2
};
}
Five: Efficiency Gains
- Two generations of architecture cumulatively involved 57,000-line-level changes; Taro project bootstrap + three gameplay modes from scratch + full refactor, all coded by TRAE.
- The same workload using traditional methods (writing by hand + researching + debugging) was estimated at over a month; in reality it took only a few intermittent days.
- The areas where TRAE contributed most: scaffolding the project from zero, cross-file batch refactoring, root-cause localization, code development and fixes.
- My actual input: requirement clarification, playtest feedback, acceptance screenshots.
Six: Final Thoughts
The biggest takeaway from this hands-on experience: the ceiling of AI coding tools depends on the "acceptance criteria" you define, and on clear requirements! I designed deterministic levels and separated logic from rendering, so its output could be machine-accepted; I only needed to play the product manager role. Once "correct" is clearly defined, "fast" happens naturally.
If you also want to use TRAE to complete a full project from scratch, my advice is just one thing: don't treat AI as an autocomplete tool; treat it as a collaborator. Give it complete requirements, clear acceptance criteria, and a test system that can verify automatically, and it will give you an entire project back.