# Codebase Concerns **Analysis Date:** 2026-09-07 > Project: `mota-ts` (魔塔/Magic Tower game engine monorepo). Scope: full repo (`packages`, `packages-user`, `src`, `script`, plus root config files). `public`, `template`, `graphify-out`, `_bundle`, `node_modules` are gitignored/generated and were treated as out of scope for source-level findings. --- ## Tech Debt **Legacy bridge layer (`legacy-*` packages):** - Issue: The old engine surface is bridged to the new data-side via global `core` object + `patch.add(...)` shims, producing pervasive `any` casts and `@ts-expect-error` markers. Comments literally say `// @ts-expect-error todo` and `// 为了防止逆天样板出问题` (to guard against pathological third-party plugins). - Files: `packages-user/legacy-plugin-data/src/fallback.ts` (630 lines), `packages-user/data-state/src/legacy/item.ts`, `packages-user/data-state/src/legacy/hero.ts`, `packages-user/data-state/src/legacy/tile.ts`, `packages-user/data-fallback/src/hero.ts` - Impact: Legacy compatibility is maintained through untyped, hard-to-reason-about shims; type-safety guarantees of the new layer are bypassed wherever the old API is touched. - Fix approach: Continue the planned Layer-0/1/2 data-side refactor (see `dev.md` §"双端分离"); replace `core.*` global access with typed `IStateBase`/`IStateSystem` calls, deleting `patch.add` shims as each legacy surface is migrated. **`@ts-expect-error` / `@ts-ignore` debt:** - Issue: ~43 suppression markers across `packages` and `packages-user`, several explicitly deferred (`// @ts-expect-error 之后修` = "fix later", `// @ts-expect-error 遗留问题` = "legacy issue", `// @ts-expect-error todo`, `// @ts-ignore`). - Files (notable): `packages/legacy-ui/src/tools/fixed.ts:32,35`, `packages/legacy-ui/src/preset/ui.ts:94`, `packages/legacy-ui/src/ui/equipbox.vue:248,258`, `packages/legacy-common/src/eventEmitter.ts:122`, `packages/render/src/core/gl2.ts:703,706`, `packages-user/legacy-plugin-data/src/fallback.ts:146,344,346,399,430`, `packages-user/client-modules/src/render/components/textbox.tsx:620` - Impact: Suppressions mask real type gaps and drift; new code written against these modules has no reliable type contract. - Fix approach: Resolve each suppression (not blanket-disable); where a third-party declaration is wrong, contribute a local `.d.ts` augmentation instead of `@ts-expect-error`. **Widespread `any` types (explicitly allowed):** - Issue: `@typescript-eslint/no-explicit-any` is set to `'off'` in `eslint.config.js:59`, so `any` is unchecked across the codebase (86 matches in `packages`, 74 in `packages-user`). The WebGL layer and the module registry are the heaviest users. - Files: `packages/render/src/core/gl2.ts` (`buffer`/`sub` take `any`), `packages/render/src/core/graphics.ts` (`prevValue: any, nextValue: any`), `packages-user/entry-data/src/mota.ts:87` (`Record` registry), `packages/legacy-ui/src/controller.ts:129` (`[x: string]: any`) - Impact: `dev.md` §"类型规范" mandates avoiding unnecessary `any`, yet the registry and WebGL bindings are entirely untyped; regressions slip past `vue-tsc`. - Fix approach: Re-enable `no-explicit-any` selectively (per-directory overrides) once `Mota.require`/`register` and WebGL wrappers are typed; start with `entry-data/src/mota.ts` and `render/src/core/gl2.ts`. **Singleton/global-state patterns flagged for refactor:** - Issue: Three module-level singletons are explicitly marked `// TODO: 逐渐弱化 … 单例概念` (gradually weaken singleton concept, pass instances via parameters). - Files: `packages-user/data-state/src/ins.ts:3` (`state = new CoreState()`), `packages-user/client-modules/src/core.ts:4` (`client = new ClientCore(state)`), plus the global `window.Mota` registry in `packages-user/entry-data/src/mota.ts:152` and the static `GameStorage.list` registry in `packages/legacy-system/src/storage.ts:4`. - Impact: Global mutable singletons make the data-side non-reentrant, hard to test, and couple the render/data split the project is trying to enforce. - Fix approach: Inject `ICoreState`/`IClientCore` through constructors/parameters per the TODOs; remove `GameStorage.list` static accumulation or scope it. **Oversized source files:** - Issue: Multiple files exceed ~800 lines with no `#region` segmentation despite `dev.md` recommending it for long files. - Files: `packages-user/client-modules/src/render/map/renderer.ts` (1642 lines), `packages/render/src/core/types.ts` (1526), `packages-user/client-modules/src/render/components/textboxTyper.ts` (1295), `packages/render/src/core/gl2.ts` (1289), `packages-user/client-modules/src/render/map/vertex.ts` (1090), `packages/render/src/core/item.ts` (1043), `packages/render/src/core/graphics.ts` (950), `packages-user/data-system/src/combat/context.ts` (818) - Impact: High cognitive load, difficult review; the renderer/vertex/gl2 trio is the core hot path and also the hardest to change safely. - Fix approach: Split by responsibility (e.g. `renderer.ts` into layer/draw/camera modules); use `#region` as an intermediate step. **Planned deprecations/refactors (from `task.md`):** - Issue: `task.md` lists an explicit backlog: deprecate `getMappedName`, `getNextLvUpNeed`, `getLvName`, `getHeroLoc`, `setHeroLoc`, `getNakedStatus`, `getStatusLabel`, `setBuff`, `addBuff`, `getBuff`, `setStatus`, `addStatus`, `getStatus`, `getStatusOrDefault`, `getRealStatus`, `getRealStatusOrDefault`; refactor 存档系统 (save system), 寻路系统 (pathfinding), `core.status.hero`, `core.status.hero.flags`. - Impact: The save system and pathfinding are singled out as needing rework while still in active use; changes in these areas are risk-prone until the refactor lands. - Fix approach: Treat `task.md` as the authoritative debt backlog; sequence save-system and pathfinding refactors before adding features that depend on them. --- ## Known Bugs **Unguarded `JSON.parse` on persisted storage (startup crash risk):** - Symptoms: A corrupted/malformed `localStorage` entry throws during `GameStorage.read()`, which runs in the constructor (`storage.ts:11`), breaking module initialization and the whole game load. - Files: `packages/legacy-system/src/storage.ts:18-21` - Trigger: Any prior crash mid-write, manual tampering, or a schema change leaves invalid JSON under a `HumanBreak_*` / `{author}@{key}` key. - Workaround: Manually clear the offending `localStorage` key via devtools. **Unguarded decompress+parse on save/swap data:** - Symptoms: `JSON.parse(decompressFromBase64(...))` can throw on truncated/invalid save blobs with no recovery UI. - Files: `packages/legacy-ui/src/utils.ts:284` (`swapChapter`), `packages-user/client-modules/src/render/utils/saves.ts:82,88` - Trigger: Loading a corrupt `.h5save` file or a failed network response during chapter swap. - Workaround: None in-app; error surfaces as an unhandled rejection. **Duplicate module registration silently overwrites:** - Symptoms: `Mota.register(key, data)` logs `console.warn('模块注册重复: …')` and overwrites the previous module, which can mask load-order bugs (the last registrant wins). - Files: `packages-user/entry-data/src/mota.ts:100-105` - Trigger: Two entry bundles or a plugin re-registering the same `@user/...` / `@motajs/...` key. - Workaround: None; the overwrite is silent beyond the console warning. --- ## Security Considerations **Dynamic code execution from game data (`eval` / `new Function`):** - Risk: Untrusted game data (item effect scripts, typewriter strings) is executed as JavaScript, allowing arbitrary code execution if a game archive/plugin is malicious or compromised. - Files: `packages/legacy-ui/src/utils.ts:155` (`eval('`' + str + '`')` in `type()`), `packages-user/data-state/src/legacy/item.ts:40,48,55` (`new Function('state','item', legacy.itemEffect)` etc.), and the gitignored `index.cjs` (root) which uses Node's `vm` module to run game/replay code for headless replay validation. - Current mitigation: None. This is inherent to the "魔塔" plugin model where plugins supply raw JS strings. `index.cjs` is gitignored and local-only (replay validation), but `item.ts`/`utils.ts` run in the player's browser on live game data. - Recommendations: Sandbox `new Function` bodies (e.g. `vm`/WebWorker/`with`-scoped whitelist), or migrate legacy effect strings to a declarative effect DSL. At minimum, document that loading a project = executing its code. **Unsafe HTML injection (`v-html` / `innerHTML`):** - Risk: XSS if any interpolated string originates from game data, plugin output, or player-provided text. - Files: `packages/legacy-ui/src/ui/settings.vue:52`, `packages/legacy-ui/src/ui/shop.vue:12`, `packages/legacy-ui/src/ui/toolbox.vue:101`, `packages/legacy-ui/src/ui/equipbox.vue:155`, `packages/legacy-ui/src/tools/book.tsx:43` (``), `packages/legacy-system/src/keyboard.vue:17` - Current mitigation: Content largely originates from first-party game data (`descText`, item descriptions), but the sink is unguarded. - Recommendations: Sanitize or escape before rendering; replace `v-html` with text interpolation where markup isn't required. **Hardcoded credentials in working tree:** - Risk: `user.ts` at the repo root contains `export const id = 2691; export const password = '';` — a static credential pattern for game upload/auth. - Files: `E:\github\template\user.ts` - Current mitigation: `user.ts` is listed in `.gitignore`, so it is not committed — but it exists in the working tree and is easy to accidentally force-add or copy. - Recommendations: Move to an untracked `.env`/config loaded at runtime; never hardcode credentials in source; rotate the credential. **CodeQL configured but minimal:** - Risk: `.github/workflows/codeql.yml` runs only the default `javascript` query pack (`queries: security-extended,security-and-quality` is commented out) and uses deprecated `actions/checkout@v3` / `codeql-action@v2`. - Files: `.github/workflows/codeql.yml:17,41,45,53,59,72` - Current mitigation: Weekly + push-to-master CodeQL scan exists. - Recommendations: Pin `@v4` actions, enable `security-extended,security-and-quality`, and ensure `eval`/`new Function` findings are triaged. --- ## Performance Bottlenecks **`beforeunload`/`blur` writes every storage instance:** - Problem: On every window blur (tab switch, devtools focus, dialog), all `GameStorage` instances are serialized and written to `localStorage` synchronously. - Files: `packages/legacy-system/src/storage.ts:111-116` - Cause: Global `GameStorage.list` registry iterated without debounce; writes are synchronous `localStorage.setItem` calls on the main thread. - Improvement path: Debounce/coalesce writes; only persist dirty storages (track a dirty flag in `setValue`); avoid `blur` as a write trigger or use `requestIdleCallback`. **Large in-memory replay buffers:** - Problem: `ReplayArray` maintains `commandBuffer`, `paramBuffer`, `indexBuffer` as `ArrayBuffer`s grown by a multiplier; long sessions accumulate large buffers that are copied on resize. - Files: `packages-user/data-common/src/replay/array.ts` (823 lines) - Cause: Resize likely re-allocates and copies the whole buffer; full replay is held in memory for step/seek. - Improvement path: Use growable ring/segmented buffers; consider streaming to IndexedDB (`dexie` is already a dependency) for long replays. --- ## Fragile Areas **`packages-user/legacy-plugin-data/src/fallback.ts` (legacy → new bridge):** - Files: `packages-user/legacy-plugin-data/src/fallback.ts` (630 lines), `packages-user/legacy-plugin-data/src/shop.ts`, `packages-user/legacy-plugin-data/src/hook.ts` - Why fragile: Every shim reaches into both the old `core.*` global and the new `state` object simultaneously; a change to either side silently breaks the other. Heavily decorated with `@ts-expect-error todo`. - Safe modification: Add regression coverage for each `patch.add` handler before touching; keep old/new writes atomic (mirror `core.status.hero.loc` and `state.hero.mover` together, as in `setHeroLoc`). - Test coverage: None (see Test Coverage Gaps). **`packages/render/src/core/gl2.ts` + `graphics.ts` (WebGL core):** - Files: `packages/render/src/core/gl2.ts` (1289 lines), `packages/render/src/core/graphics.ts` (950 lines), `packages/render/src/core/render.ts` (822 lines) - Why fragile: Untyped `any` WebGL bindings, manual buffer/sub-offset arithmetic, and shader compilation error paths (`logger.json` codes 9/10/13/17/18/28/29). Rendering regressions are visually subtle. - Safe modification: Keep shader/layout changes isolated; validate against `logger.json` error codes; add render smoke tests if a headless GL context becomes feasible. - Test coverage: None. **Save/load round-trip (`ISaveableContent` system):** - Files: `packages-user/data-state/src/core.ts` (`saveables`/`addedSaveables` maps), `packages-user/data-base/src/flag/field.ts` (`toStructured`/`fromStructured` return `any`), `packages-user/client-modules/src/render/utils/saves.ts` - Why fragile: Save schema is spread across `toStructured`/`fromStructured` methods returning `any`; a field rename or type change silently corrupts saves. This is the "存档系统" refactor target in `task.md`. - Safe modification: Version the save format; add a save round-trip test before modifying `toStructured`/`fromStructured`. - Test coverage: None. --- ## Scaling Limits **Singleton architecture limits reentrancy/multi-instance:** - Current capacity: One `CoreState` (`data-state/src/ins.ts`), one `ClientCore` (`client-modules/src/core.ts`), one global `window.Mota` registry. - Limit: Cannot host two independent game sessions (e.g. editor + preview, or side-by-side replay) in one page; the static `GameStorage.list` also grows unbounded across instances. - Scaling path: Convert singletons to injected instances (the already-filed TODOs), and scope `GameStorage.list` per game context. **Replay buffer memory growth:** - Current capacity: In-memory `ReplayArray` for the whole session. - Limit: Multi-hour sessions produce large buffers; growth-by-multiplier causes repeated copies. - Scaling path: Segmented/streaming storage backed by IndexedDB (`dexie` dependency available). --- ## Dependencies at Risk **`anon-tokyo` (version `0.0.0-alpha.0`):** - Risk: Pinned to a pre-release alpha version in `package.json:28`; API may change without notice. - Impact: Whatever it powers (likely a font/typeface or UI preset) could break on upgrade. - Migration plan: Pin to a stable release or vendor the needed subset. **Legacy engine dependency on global `core` (not npm):** - Risk: The `legacy-*` packages rely on a runtime-injected global `core` object rather than typed imports; it is not represented as a dependency and cannot be type-checked. - Impact: Refactors of the data-side risk breaking an invisible contract with third-party 魔塔 plugins. - Migration plan: Continue the Layer migration; expose a typed `ICoreState`/`IStateBase` and deprecate raw `core` access via `patch.add`. **TypeScript `6.0.3` (bleeding edge):** - Risk: `typescript: 6.0.3` (`package.json:90`) is a very new major; `vue-tsc` (`^2.2.12`) and `typescript-eslint` (`^8.58.2`) may lag on full compatibility. - Impact: Type-check results may differ between editor and CI; possible false positives/negatives. - Migration plan: Pin to the latest stable that `vue-tsc` and `typescript-eslint` officially support. --- ## Missing Critical Features **Automated test suite (blocking):** - Problem: `vitest` is configured (`package.json:8`, `"test": "vitest"`) but zero test files exist anywhere in `packages`, `packages-user`, `src`, or `script` (no `*.test.*`/`*.spec.*`/`__tests__`), and there is no `vitest.config.*`. - Blocks: Safe refactoring of the save system, pathfinding, and legacy bridge; regression prevention for render/data split; the `pnpm test` script currently does nothing useful. **CI for lint/type/build/tests:** - Problem: `.github/workflows/` only contains `codeql.yml` (security scan) and `page.yml` (docs deploy). No workflow runs `pnpm lint:packages`, `pnpm lint:user`, `pnpm check:type`, `pnpm check:circular`, or `pnpm test`. - Blocks: Automated gating of the quality rules documented in `dev.md`; nothing prevents a bad commit from reaching `master`. --- ## Test Coverage Gaps **Entire codebase is untested:** - What's not tested: Save/load round-trip (`ISaveableContent`), replay array encode/decode, combat calculation (`packages-user/data-system/src/combat/*`), the legacy→new bridge (`fallback.ts`), map layer/vertex generation, storage persistence. - Files: No test files present. `pnpm test` (`vitest`) has nothing to run. - Risk: Every refactor (esp. the `task.md` save/pathfinding items and the in-flight map interface refactor on branch `refactor/data`) is unguarded against regressions; the data/render split's "data side must run headlessly in Node" guarantee (see `dev.md` §"双端分离") is untested and can silently regress. - Priority: **High** — add at minimum unit tests for `data-common` (replay, save) and `data-system` (combat), which are the headless, deterministic, high-value layers. --- *Concerns audit: 2026-09-07*