diff --git a/specs/numerical_design_system.md b/specs/numerical_design_system.md index 6707cee..ccfb544 100644 --- a/specs/numerical_design_system.md +++ b/specs/numerical_design_system.md @@ -1,7 +1,7 @@ # Numerical Design & Balancing System (NDBS) - Implementation Guide -**Version:** 2.0 (AI-Actionable) -**Target Stack:** Python (FastAPI), Vue.js (Vite), C# (Godot .NET) +**Version:** 2.0 +**Target Stack:** Python 3.14 (FastAPI, uv), Vue.js (Vite), C# (Godot .NET) **Role:** AI Developer Guide ## 1. System Overview @@ -13,32 +13,40 @@ This guide is structured as a sequence of tasks for an AI agent to implement the --- -## 2. Directory Structure Plan -The tool will reside in a new `tools/ndbs/` directory, keeping it separate from the game assets but with access to them. +## 2. Architecture -```text -D:\code\super-mentor\ -├── resources\definitions\ <-- Target Data Source -├── scripts\Rules\Generated\ <-- Target Script Output -├── tools\ -│ └── ndbs\ -│ ├── server\ <-- Python Backend -│ │ ├── main.py -│ │ └── ... -│ └── client\ <-- Vue Frontend -│ ├── src\ -│ └── ... +The system is designed as a **Unified Web Service**. A single Python process handles both the API logic and serving the frontend interface. + +1. **NDBS Service (Python/FastAPI):** + * **API Layer:** Handles data reading/writing and script generation (`/api/*`). + * **Static Layer:** Serves the compiled Vue.js application (`index.html`, `js/`, `css/`) from the `client/dist` directory. +2. **NDBS Web Client (Vue.js):** + * Developed as a standard SPA. + * Built into static files (`npm run build`) which are consumed by the Python service. + +**Runtime Flow:** +User runs `python main.py` -> Browser opens `http://localhost:8000` -> Frontend loads -> Frontend calls `/api/...` -> Python modifies files. + +```mermaid +graph TD + User[Designer] -->|Browser| Service[NDBS Python Service] + Service -->|Serve Static| Frontend[Vue.js UI] + Service -->|API| Logic[Business Logic] + Logic -->|Read/Write| JSON[JSON/TRES Definitions] + Logic -->|Generate| CSharp[C# Rule Scripts] ``` --- -## 3. Implementation Phases (AI Prompts) +## 3. Implementation Phases ### Phase 1: Python Backend Foundation **Goal:** Establish a FastAPI server that can read/write the game's JSON files. **Step 1.1: Environment Setup** -* **Instruction:** Create `tools/ndbs/server/`. Initialize a Python environment. Install `fastapi`, `uvicorn`, `pydantic`. +* **Instruction:** Create `tools/ndbs/server/`. Initialize a Python environment using `uv`. + * Run `uv init --python 3.14` to set up the project. + * Run `uv add fastapi uvicorn pydantic` to install dependencies. * **Code Requirement:** Create `tools/ndbs/server/main.py`. * **Key Functionality:** * Enable CORS (allow all origins for dev). @@ -58,6 +66,11 @@ D:\code\super-mentor\ **Step 1.3: Schema Inference (Dynamic Models)** * **Instruction:** Since we don't have hardcoded Pydantic models for every file, create a utility that reads a file (JSON or TRES) and generates a generic "Schema" description (listing keys and value types) to help the frontend build forms dynamically. +**Step 1.4: Static Asset Serving (SPA Support)** +* **Instruction:** Configure FastAPI to serve the frontend. + * Mount the `client/dist` directory to `/` as static files. + * **Critical:** Implement a "Catch-All" route (after API routes) that returns `client/dist/index.html`. This ensures that Vue Router paths (e.g., `http://localhost:8000/editor/archetypes`) work when refreshed. + --- @@ -65,7 +78,7 @@ D:\code\super-mentor\ **Goal:** A clean UI to browse files and edit JSON data. **Step 2.1: Project Scaffolding** -* **Instruction:** Create `tools/ndbs/client` using Vite + Vue 3 (TypeScript). Install `axios`, `pinia` (state management), and `naive-ui` (or `element-plus`) for UI components. +* **Instruction:** Create `tools/ndbs/client` using Vite + Vue 3 (TypeScript). Install `axios`, `pinia`, and `naive-ui`. **Step 2.2: File Explorer & Layout** * **Instruction:** Create a standard "Sidebar + Main Content" layout. @@ -75,9 +88,13 @@ D:\code\super-mentor\ **Step 2.3: Generic JSON Editor** * **Instruction:** Create a component `JsonEditor.vue`. * Fetch content using `GET /api/files/content`. - * Display the raw JSON in a textarea (Monaco Editor preferred later, start simple). + * Display the raw JSON in a textarea (Monaco Editor preferred later). * Add a "Save" button that calls `POST /api/files/content`. - * **Enhancement:** Use a library like `jsoneditor` or `v-jsoneditor` to provide a tree view/form view. + +**Step 2.4: Build Integration** +* **Instruction:** Configure `vite.config.ts` to output to `../server/client_dist` (or just `dist` inside client, and server reads from there). + * Ensure the "Build" command produces the artifacts expected by Step 1.4. + --- @@ -143,11 +160,10 @@ D:\code\super-mentor\ ### 4.1 File Paths * **Server Root:** `tools/ndbs/server` -* **Client Root:** `tools/ndbs/client` * **Game Root:** `../../` (Relative to server) ### 4.2 Coding Style -* **Python:** Type hints (Python 3.10+), Pydantic models for request/response bodies. +* **Python:** Type hints (Python 3.14), Pydantic models for request/response bodies. Use `uv` for dependency management. * **Vue:** Composition API (` + + diff --git a/tools/ndbs/server/web/styles.css b/tools/ndbs/server/web/styles.css new file mode 100644 index 0000000..a9f1c52 --- /dev/null +++ b/tools/ndbs/server/web/styles.css @@ -0,0 +1,394 @@ +@font-face { + font-family: "AlibabaPuHuiTi"; + src: url("/static/fonts/AlibabaPuHuiTi-3-65-Medium.ttf") format("truetype"); + font-weight: 500; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: "AlibabaPuHuiTi"; + src: url("/static/fonts/AlibabaPuHuiTi-3-85-Bold.ttf") format("truetype"); + font-weight: 700; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: "KenneyFuture"; + src: url("/static/fonts/KenneyFuture.ttf") format("truetype"); + font-weight: 700; + font-style: normal; + font-display: swap; +} + +:root { + color-scheme: light; + --bg-0: #f5f1ea; + --bg-1: #fff0d6; + --bg-2: #e3f4ec; + --ink-0: #182126; + --ink-1: #2b353a; + --ink-2: #556068; + --accent-0: #f59e0b; + --accent-1: #0f766e; + --accent-2: #d1495b; + --panel: rgba(255, 255, 255, 0.86); + --panel-border: rgba(18, 33, 38, 0.12); + --shadow: 0 24px 50px rgba(12, 20, 28, 0.15); +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; + min-height: 100vh; + font-family: "AlibabaPuHuiTi", "Noto Serif", serif; + color: var(--ink-0); + background: radial-gradient(circle at 12% 18%, var(--bg-1) 0%, var(--bg-0) 50%, var(--bg-2) 100%); +} + +button, +input, +textarea { + font-family: inherit; +} + +button { + border: 1px solid rgba(24, 33, 38, 0.18); + background: rgba(245, 158, 11, 0.86); + color: var(--ink-0); + border-radius: 10px; + padding: 8px 14px; + cursor: pointer; + font-size: 13px; + transition: transform 0.2s ease, box-shadow 0.2s ease; +} + +button:hover { + transform: translateY(-1px); + box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12); +} + +button.ghost { + background: transparent; +} + +input.text-input, +input.filter-input { + width: 100%; + border: 1px solid rgba(18, 33, 38, 0.16); + border-radius: 12px; + padding: 8px 10px; + background: rgba(255, 255, 255, 0.8); + font-size: 13px; +} + +.app-shell { + position: relative; + display: grid; + grid-template-columns: 320px 1fr; + gap: 24px; + min-height: 100vh; + padding: 24px; +} + +.app-shell::before, +.app-shell::after { + content: ""; + position: absolute; + z-index: 0; + border-radius: 999px; + filter: blur(2px); +} + +.app-shell::before { + width: 420px; + height: 420px; + top: -120px; + left: -120px; + background: radial-gradient(circle, rgba(245, 158, 11, 0.28) 0%, rgba(245, 158, 11, 0) 70%); +} + +.app-shell::after { + width: 520px; + height: 520px; + bottom: -200px; + right: -180px; + background: radial-gradient(circle, rgba(15, 118, 110, 0.25) 0%, rgba(15, 118, 110, 0) 72%); +} + +.sidebar, +.main { + position: relative; + z-index: 1; +} + +.sidebar { + background: var(--panel); + border: 1px solid var(--panel-border); + border-radius: 20px; + box-shadow: var(--shadow); + overflow: hidden; + padding: 20px 18px; + display: flex; + flex-direction: column; + gap: 16px; +} + +.brand-title { + font-family: "KenneyFuture", "AlibabaPuHuiTi", serif; + font-size: 20px; + letter-spacing: 0.05em; +} + +.brand-subtitle { + font-size: 12px; + color: var(--ink-2); + margin-top: 4px; +} + +.quick-actions { + display: flex; + gap: 10px; + flex-wrap: wrap; +} + +.list-section { + display: flex; + flex-direction: column; + gap: 8px; +} + +.section-header { + display: flex; + justify-content: space-between; + align-items: center; + font-size: 12px; + text-transform: uppercase; + letter-spacing: 0.12em; + color: var(--ink-2); +} + +.section-count { + font-size: 11px; + background: rgba(15, 118, 110, 0.16); + padding: 2px 8px; + border-radius: 999px; +} + +.nav-list { + display: flex; + flex-direction: column; + gap: 8px; + max-height: 280px; + overflow: auto; + padding-right: 6px; +} + +.nav-item { + border: 1px solid rgba(18, 33, 38, 0.12); + background: rgba(255, 255, 255, 0.82); + border-radius: 12px; + padding: 8px 10px; + text-align: left; + cursor: pointer; + transition: transform 0.2s ease, border-color 0.2s ease; + animation: list-in 420ms ease-out both; +} + +.nav-item.active { + border-color: rgba(245, 158, 11, 0.8); + background: rgba(255, 240, 214, 0.95); +} + +.nav-item:hover { + transform: translateY(-1px); + border-color: rgba(245, 158, 11, 0.5); +} + +.nav-item span { + font-size: 13px; + color: var(--ink-1); + word-break: break-word; +} + +.main { + display: flex; + flex-direction: column; + gap: 20px; +} + +.panel { + background: var(--panel); + border-radius: 18px; + border: 1px solid var(--panel-border); + box-shadow: var(--shadow); + padding: 20px 22px; + animation: float-in 420ms ease-out both; +} + +.panel-title { + font-family: "KenneyFuture", "AlibabaPuHuiTi", serif; + font-size: 18px; + letter-spacing: 0.04em; +} + +.panel-subtitle { + color: var(--ink-2); + font-size: 13px; + margin-top: 4px; +} + +.hero { + background: linear-gradient(135deg, rgba(255, 240, 214, 0.95) 0%, rgba(255, 255, 255, 0.9) 40%, rgba(227, 244, 236, 0.9) 100%); +} + +.hero-top { + text-transform: uppercase; + letter-spacing: 0.18em; + font-size: 11px; + color: var(--ink-2); +} + +.hero h1 { + font-family: "KenneyFuture", "AlibabaPuHuiTi", serif; + font-size: 34px; + margin: 10px 0 8px; +} + +.hero p { + margin: 0; + color: var(--ink-1); + max-width: 540px; +} + +.controls { + display: flex; + flex-direction: column; + gap: 12px; +} + +.control-row { + display: flex; + gap: 12px; + flex-wrap: wrap; + align-items: center; +} + +.editor-panel { + display: flex; + flex-direction: column; + gap: 14px; +} + +.editor-header { + display: flex; + justify-content: space-between; + align-items: center; + gap: 16px; + flex-wrap: wrap; +} + +.editor-actions { + display: flex; + gap: 10px; +} + +.code-area { + width: 100%; + min-height: 420px; + resize: vertical; + padding: 16px; + border-radius: 14px; + border: 1px solid rgba(24, 33, 38, 0.2); + background: rgba(247, 247, 244, 0.9); + color: var(--ink-0); + font-family: "Courier New", monospace; + font-size: 13px; + line-height: 1.5; + outline: none; +} + +.schema-summary { + display: flex; + gap: 10px; + align-items: center; + flex-wrap: wrap; +} + +.schema-summary span { + padding: 3px 8px; + border-radius: 999px; + background: rgba(15, 118, 110, 0.15); + color: var(--ink-1); + font-size: 12px; +} + +.schema-summary.hidden { + display: none; +} + +.status-message { + font-size: 13px; + color: var(--ink-2); +} + +.status-message.error { + color: var(--accent-2); +} + +.build-output { + padding: 12px; + border-radius: 12px; + background: rgba(23, 37, 44, 0.06); + max-height: 200px; + overflow: auto; + font-size: 12px; + white-space: pre-wrap; +} + +.hidden { + display: none; +} + +@keyframes float-in { + from { + opacity: 0; + transform: translateY(12px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes list-in { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@media (max-width: 960px) { + .app-shell { + grid-template-columns: 1fr; + } + + .nav-list { + max-height: 220px; + } +} + +@media (prefers-reduced-motion: reduce) { + * { + animation: none !important; + transition: none !important; + } +}