From 6c68927c7466dfa988a52933a29d487222cf3211 Mon Sep 17 00:00:00 2001 From: "Somhairle H. Marisol" Date: Tue, 29 Sep 2026 10:05:58 +0800 Subject: p71: 中文 API/模块说明(docs/api-reference) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 docs/api-reference/:README + 6 模块(Sim/地图生成/桌面渲染/交互交易/职业/存读档) 每节含 职责·对外 API 概览·单位与量纲·不变量·失败分支·算法与性能取舍 - 口径以当前源码为准;性能引用实测 p70 基准(不含未测宣称) - P71ApiDocsTests(3 例,红先→绿):文件齐备/章节齐备/关键接口与单位锚点 - Desktop 341/341、Kernel 120/120;未改生产 .fs/.fsproj、未 push --- docs/api-reference/rendering-desktop.md | 65 +++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 docs/api-reference/rendering-desktop.md (limited to 'docs/api-reference/rendering-desktop.md') diff --git a/docs/api-reference/rendering-desktop.md b/docs/api-reference/rendering-desktop.md new file mode 100644 index 0000000..ca3a841 --- /dev/null +++ b/docs/api-reference/rendering-desktop.md @@ -0,0 +1,65 @@ +# 桌面渲染(Game / VillageArt / 字形图集) + +源码:`src/LivingVillage.Desktop/Game.fs`、`src/LivingVillage.Desktop/VillageArt.fs`、 +`src/LivingVillage.Desktop/CjkGlyphAtlas.fs`、`src/LivingVillage.Desktop/ChineseText.fs`、 +`src/LivingVillage.Desktop/HudLayout.fs`。 + +## 职责 + +MonoGame 渲染与主循环:把 `World` 画成江南水乡画面(地面/河道/建筑/人物/夜景光晕/室内), +并绘制 HUD、对话浮层与各类面板。不负责模拟推进(`Sim.step` 在 Kernel)、不负责地图生成算法。 + +## 对外 API 概览 + +- `LivingVillageGame`(`Game` 子类):`override Update(gameTime)` / `override Draw(gameTime)`。 + 默认 `IsFixedTimeStep = true`、`TargetElapsedTime = 1/60s`、垂直同步开启; + `LV_UNLOCK_FPS=1` 时放开两者以测渲染上限。 +- `VillageArt.loadTextures device` → `ArtTextures`;`VillageArt.drawWorld`、 + `VillageArt.drawMapViewport` / `drawMapFitted` / `drawMapRegion`、`VillageArt.drawCharacter`、 + `VillageArt.drawInterior` / `drawInteriorCharacter`、`VillageArt.drawNightGlows`、 + `VillageArt.drawCc0BoatOverlay`、`VillageArt.drawGroundShadow`。 +- 动画:`VillageArt.characterFrame isMoving tick`、`npcSpriteSpec*`、`avatarSpriteSpec`、 + `waterFrameTickAt tick x y`、`smokeFrameTick tick`。 +- 预乘与光晕:`VillageArt.premultiply r g b a`、`VillageArt.lanternGlowLayers`、 + `VillageArt.ellipseRuns`。 +- 中文字形:`CjkGlyphAtlas.columns = 16`、`cellPixels = 24`、`contains char`、 + `sourceRectangle char`、`load device`;文案清单 `ChineseText.requiredUiLabels`; + HUD 几何 `HudLayout.barHeight = 36`、`textScale = 2`。 + +## 单位与量纲 + +- 视口默认 1280×720;世界像素 = 瓦片 × `Sim.tilePixels`(32)。 +- 字形:每字一格 24×24 px,绘制时按 `PixelText` 的 `scale` 放大(HUD 常用 `scale = 2` → 每字 16px; + 图集源格固定 24px)。ASCII 走 `PixelText.glyphs` 位图,空格宽 `4*scale`,其余 ASCII 宽 `6*scale`。 +- 动画:字符格 32×48;`animationFrame = (tick/4) % 4`、`idleFrame = (tick/24) % 2`。 + +## 不变量 + +1. **预乘 alpha**:任何 `alpha < 255` 的前景绘制必须用 `VillageArt.premultiply`, + 否则预乘混合会把 rgb 直接加进帧缓冲导致纯白块(见 `docs/维护说明.md` §预乘 alpha)。 +2. **无缺字**:`PixelText.isRenderable` 要求每个字符 ∈ ASCII 位图 ∪ `CjkGlyphAtlas.contains`; + 不可渲染字符按设计**留空**,绝不画假字。 +3. **图集几何**:`CjkGlyphAtlas` 表保持有序稳定,`pixelWidth = 16*24 = 384`、 + `pixelHeight = rowCount*24`;`load` 校验尺寸否则 `invalidOp`。 +4. **帧选择纯函数**:动画/水波/炊烟只由 `tick` 与「是否移动」决定,同 tick 必得同帧。 +5. **绘制分层**:`drawWorld` 末尾单独绘制炊烟;夜间光晕只在真实灯笼实体处产生。 + +## 失败分支 + +- `CjkGlyphAtlas.load`:文件缺失 → `FileNotFoundException`;尺寸不符 → `invalidOp` 并释放纹理。 +- 文本:`PixelText.draw` 对不支持字符静默跳过(推进光标),不抛异常。 +- 纹理加载:`VillageArt.loadTexture` 校验期望宽高,资产缺失/尺寸不符会失败(构建期资产必在)。 + +## 算法与性能取舍 + +- 绘制分层(`Game.DrawWorldView`):地面 → 结构/民居 → 夜间光晕 → 水面波纹/船 → + 角色阴影与角色 → 黄金时刻暖幕 → 月光幕 → 面板/浮层(`DrawM5Overlay`)。 +- 只绘制可见瓦片(`isVisible` 视口裁剪);光晕用预乘同心圆 pixel run 近似径向衰减。 +- 帧循环结构:`Update`(键盘/菜单/证据钩子/`Sim` 推进)→ `Draw`(世界或菜单/证据帧); + 证据钩子(`LV_*_SHOT`)在 Playing 帧后落盘并退出。 +- 实测(`docs/evidence/p70-analysis.txt`,1280×720 / Xvfb 软件渲染 / 解锁 vsync): + 60 秒 8,788 帧、`measured_fps ≈ 139.3`;帧间隔 mean 7.179 / max 33.919 / p95 9.847 ms; + 掉帧 > 25ms = 4(0.05%)、> 33.3ms = 2(0.02%);每帧分配 mean ≈ 231 KB / p95 297 KB。 + 帧率随机器与软件渲染波动,只列本次实测,不外推。 +- 复跑:`P70_SECONDS=60 bash scripts/bench-p70.sh /tmp/opencode/lv-p70` + (逐帧日志经 `LV_FRAME_LOG=1`,仅日志、不改渲染逻辑)。 -- cgit v1.2.3