Skip to content

Repository files navigation

TeamMap

多人协作增量地图上报系统:每个玩家的 Minecraft 客户端把自己看到的地图增量上报给中央服务器,服务器为每个玩家维护独立地图会话、裁决合成一张完整地图,并通过 squaremap 兼容的 Web 界面展示。

TeamMap Mod (Fabric 26.1.2)          Rust 后端 (Axum)                    Web 前端 (squaremap)
┌──────────────────────┐  WS+protobuf ┌──────────────────────────┐  HTTP  ┌──────────────────┐
│ 区块着色 -> canonical  │ ───────────▶ │ 会话隔离 -> Merkle 对账    │ ◀───── │ Leaflet 瓦片地图   │
│ XXH3 哈希 -> 脏队列    │  zstd 压缩    │ 内容寻址 blob -> 裁决合成  │  SSE   │ 玩家标记/名牌      │
│ 令牌桶 -> 增量上传     │ ◀─────────── │ PNG 金字塔 -> SQLite      │ ─────▶ │ 多视图切换        │
└──────────────────────┘   ACK/差量    └──────────────────────────┘        └──────────────────┘

目录结构

目录 内容
protocol/ teammap/v1/map.proto —— 客户端/服务端共用协议(XXH3 双种子 128 位内容哈希、region Merkle 两级树、组级差量)
mod/ Fabric 26.1.2 客户端 mod(Java 25,官方命名,loom 1.17.17 no-mappings 模式)
backend/ Rust 中央服务器:axum + prost + sqlx(sqlite) + zstd + xxhash-rust
web/ 从 squaremap(MIT)vendor 的前端 + TeamMapBridge(SSE 即时刷新)
squaremap/ 上游 squaremap 源码,仅作实现参考(渲染管线/瓦片布局/前端基座)

核心设计

带宽与性能(防打爆)

  1. 内容寻址去重:每个区块的 canonical 记录(256×ARGB + 256×高度 = 1536B)以 XXH3-64(seed0) ++ XXH3-64(seedGOLDEN) 做 128 位内容哈希。相同内容全局只存一份; 重复上报在服务端按哈希直接去重(DEDUP),零存储、零重绘、零下发。
  2. Tile Merkle tree 对账:region(32×32 区块 = 512×512 像素,squaremap 同款瓦片) 内两级摘要(32 组 × 32 叶)。客户端每 30s 推一次世界摘要(每 region 仅几百字节), 服务端逐组比对,只请求真正不同的组;重连恢复时未变化区域零重传。
  3. 三层带宽护栏(客户端+服务端各一把令牌桶):内容去重 → flush 周期 + 令牌桶 (默认 64 KiB/s,突发 256 KiB)→ 未确认积压上限(默认 4 MB),超限暂停采集。
  4. 主线程保护:mod 端每 tick 限预算重着色(默认 2 个区块/tick);服务端合成重绘 按 1s 批量 flush,仅脏瓦片重画。

每玩家地图会话与裁决

  • 会话 = (player_uuid, world),各自独立存储,互不交叉;交叉只发生在合成图。
  • 裁决按最新 capture_ts 优先(客户端时钟偏差在服务端钳制到 ±24h/+5min)。
  • 防抖:
    • 内容相同(同哈希)不触发任何裁决/重绘;
    • 滞回:挑战者须比现任胜者新 5s(翻转越多滞回越长,上限 30s);
    • 冷却:60s 内翻转 3 次 → 锁死现任胜者并标记 disputed(Web 上黄框提示),冷却期 60s;
    • 离线来源的地图保留,在线来源内容更新即可接管(无需滞回)。
  • 探索流式 e2e(tests/exploration_e2e.rs)专门覆盖"一个人朝一个方向 持续跑图、那片视野只有他有"的场景:合成图随探索生长、独有视野成为胜者、 未探索者个人视图为空、探索者离线后成果保留。

存储

  • 内存:blob 以 zstd 压缩形态缓存的 FIFO 池(默认 256 MiB 上限),解码只发生在合成瞬间。
  • SQLite(WAL):blobs(内容寻址+GC)/ chunk_state(会话区块)/ region_state (Merkle 根)/ composite(裁决结果)/ sessions。重启后全量恢复。
  • 派生 PNG 瓦片写磁盘缓存(可再生),HTTP ETag/304 协商缓存。

快速开始

1. 后端

cd backend
cargo run --release
# 首次运行会生成 config/teammap.toml(含全部参数注释)

2. 前端

cd web
pnpm install && pnpm build   # 产物在 web/dist,由后端托管

3. Mod

cd mod
./gradlew build              # 产物 mod/build/libs/teammap-0.1.0.jar
# 放入 26.1.2-Fabric 实例的 mods/ 目录

游戏内入口:Mods 列表(Mod Menu)-> TeamMap -> 设置。可配置服务器地址、 房间号、上传间隔/速率、着色预算,保存后立即生效(无需重启游戏)。 也可直接编辑实例目录 config/teammap.json。

连不上服务器? 设置页勾选"禁用系统代理"。游戏启动器常带 -Djava.net.useSystemProxies=true,系统代理会把 127.0.0.1/内网直连 也拦下来;客户端日志(logs/latest.log 搜 teammap)会显示每次 连接与上传的详细状态。

地图缩放

默认原生 4 级(z0~z3 全分辨率)+ 额外 2 级放大([map].extra_zoom,最大可视 64px/区块,像素锐化渲染)。调节 config/teammap.toml 的 extra_zoom 后重启生效。

房间号模型(TeamViewRelay room_code 同款)

  • 无口令:房间号即身份——把服务器地址 + 房间号发给朋友,即可共同查看/上报同一张地图。
  • 所有数据(会话、合成图、瓦片、玩家标记)按房间完全隔离;房间随首次加入自动创建。
  • Web 端视图命名:合成图 {房间}~{世界}、个人图 {房间}~{世界}~{玩家名}, 侧栏自动列出全部房间。

4.(可选)无 Minecraft 演示

cd backend
cargo run --example sim_client -- --addr 127.0.0.1:8790 --players Alice,Bob --room game1 --stay-secs 600
# 打开 http://127.0.0.1:8790/?world=game1~overworld&zoom=3&x=0&z=0

Web 视图

  • overworld —— 全体玩家裁决后的合成视图(disputed 区块带黄框描边)
  • overworld~Alice —— Alice 的个人视图(她所见地图的原始状态)
  • 视图列表 = 顶层 tiles/settings.json 的 worlds;squaremap 前端零改动兼容。

测试

cd backend
cargo test                 # 21 单测 + 4 端到端(对账/差量/防抖/持久化/房间隔离/探索流式)
cargo clippy --all-targets # 0 警告

许可

  • backend/、protocol/、mod/:MIT
  • web/ 基于 squaremap web(MIT,© jpenilla 及贡献者);squaremap/ 为上游参考副本。

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages